diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 82d62b92..f06c6ea6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -20,15 +20,100 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: cargo test run: cargo test --workspace --all-features + - name: LSP latency budgets + # Wall-clock budgets, so they run alone and single-threaded. Inside + # `cargo test --workspace` they share the machine with every other test + # binary and fail on contention: `analyze(complex program)` measures + # ~1.2ms and once failed a workspace run at 11.99ms against its 10ms + # budget. They are `#[ignore]`d for that reason — see the module doc. + run: cargo test -p lk-lsp --test perf_latency_test -- --ignored --test-threads=1 + - name: Compiler scaling budget + # `compiling_many_functions_stays_linear` asserts a *ratio* of two + # wall-clock measurements, which contention breaks in both directions — + # min-of-5 per side was not enough. Same treatment, same reason. + run: cargo test -p lk-core --lib -- --ignored --test-threads=1 + - name: no build artifacts in the example trees + # `lk compile foo.lk` writes `foo` — extensionless, so no suffix + # pattern in `.gitignore` reaches it and `git add -A` after a compile + # takes it. Two 20MB binaries reached `main` that way, inside commits + # about unrelated things. `.gitignore` now excludes extensionless files + # under these trees; this catches a `git add -f` past it. + run: | + found=$(git ls-files examples bench | grep -v '\.' || true) + if [ -n "$found" ]; then + echo "::error::tracked files with no extension under examples/ or bench/ — build artifacts?" + echo "$found" + exit 1 + fi - name: cargo fmt run: cargo fmt --all -- --check - name: cargo clippy run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: cargo clippy (no_std) + # `--all-features` never compiles the `no_std` side, so the code that + # only exists there — `io_bare`, the interrupt stubs, every `#[cfg(not( + # feature = "std"))]` branch, and *all* of the test code in that profile + # — was never linted. It was not clean when this step was added: a raw + # pointer dereferenced in a safe `extern "C" fn`, a doc comment on a + # macro invocation, and four test files that had never compiled without + # `std` at all. The bare-metal *build* steps below do not catch these: + # they build the lib, not `--all-targets`, and without the lint. + run: | + cargo clippy -p lk-core --no-default-features --all-targets -- -D warnings + cargo clippy -p lkrt --no-default-features --all-targets -- -D warnings + - name: zed extension check + # `ecosystem/zed-ext` is excluded from the workspace (it targets + # wasm32-wasip1), so `cargo test --workspace` never sees it. The check + # existed as a Makefile target and was never wired up here, which means + # the extension could stop compiling and nothing would say so. + run: | + rustup target add wasm32-wasip1 + make zed-ext-check - name: wasm32 smoke (L0 no_std crates + browser playground) run: | rustup target add wasm32-unknown-unknown cargo build -p lk-values --target wasm32-unknown-unknown cargo build -p lk-wasm --target wasm32-unknown-unknown + - name: every bare-metal LK program type-checks + # The two bare-metal crates are excluded from the workspace, and their + # build steps only compile the *one* program each embeds — so a `.lk` + # beside it drifted out of the type system unnoticed. Two real bugs were + # found this way on the same day: a `Float` reaching `&` in seven PCI + # driver functions, and a `u32` mask assigned to an `Int` flag in + # `uart.lk`, which the embedded path never type-checked at all. + run: | + cargo build -p lk-cli --no-default-features --features stdlib + fail=0 + # Matched by prefix, not by a hand-written list: `bare-metal-native` + # was added later and `find bare-metal bare-metal-x86` does not reach it, + # so the one program in it was outside the very gate this step is. + for f in $(find . -path './bare-metal*' -name '*.lk' -not -path '*/target/*'); do + if ! ./target/debug/lk check "$f"; then + echo "::error file=$f::does not type-check" + fail=1 + fi + done + exit $fail + - name: VM vs native sweep (every example and bench program) + # Between the coverage gate ("does it lower?") and the differential + # suites ("does this pinned case agree?") sits the case that has bitten + # twice: lowers fine, answers differently, and no case in the corpus has + # that shape. This runs every program in the repo through both. + # + # It lived in a `/tmp` shell loop for months, which meant it was retyped + # from memory after every reboot and its expected counts lived in a + # commit message. + run: | + cargo build -p lk-cli --features aot + SWEEP_REQUIRE="identical=78 diverged=1" bash scripts/vm_native_sweep.sh + - name: every LK source is `lk fmt` shape + # `lk fmt --check` shipped as a CI feature and no workflow ran it, so + # 36 of the repo's 97 `.lk` files were not in the shape the tool + # produces — including the ones the formatter is demonstrated on. A + # formatter nobody can run is not one. + # + # This reuses the `lk` the step above builds, so it must stay after it. + run: ./target/debug/lk fmt --check - name: thumbv7em MCU smoke (bare + alloc L0 profiles on bare-metal ARM) # Proves the L0 foundations cross-compile to a real Cortex-M MCU target # (no OS, no allocator provided): lk-values is the `alloc` profile @@ -42,7 +127,7 @@ jobs: # The computation-only stdlib modules must cross-compile too, each # on its own: a crate that only builds because a sibling happened to # enable `std` for it is not actually no_std. - for m in bytes encoding hash iter math slice string; do + for m in bytes encoding hash iter math string; do cargo build -p "lk-stdlib-$m" --no-default-features --target thumbv7em-none-eabi done cargo build -p lk-stdlib-bare --target thumbv7em-none-eabi @@ -199,7 +284,13 @@ jobs: # says the font renderer drew. The '...' are timer interrupts handled # by an LK function — three in a row, where one could be a fluke. for expected in \ - "display at pci slot" \ + # `SS.F`, not a slot number: enumeration walks functions as well as + # devices, so what identifies the display is its (bus, device, + # function) address. This line said "slot" for as long as the x86 + # step could not get past *building* the kernel — an assertion that + # never runs is not a gate, and this one went stale unnoticed + # because upstream failures kept it from ever being reached. + "display at pci 2.0" \ "framebuffer 0xfd000000" \ "half 44" \ "pixels 00001428 00ffc040" \ @@ -224,13 +315,15 @@ jobs: echo "::error::the exception reporter did not report the faulting address" exit 1 } + # Each `check_*.py` builds and objcopies its own image (`kernel.py`) — + # this used to do it for all of them, which meant the fault-probe + # build above had to be undone here before anything else ran, and + # nothing said so. A check that boots whatever is on disk is not a + # check: see `kernel.py` for the two ways that lied. + # # Reading the framebuffer back proves the writes reached the device's # memory; it does not prove the mode was set, because an unconfigured # card still accepts them. Only what QEMU scans out shows that. - LK_BIN=../target/debug/lk cargo build --release - llvm-objcopy -O elf32-i386 \ - target/x86_64-unknown-none/release/lk-bare-metal-x86 \ - target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot timeout 120 python3 check_screen.py # The input half, end to end: `sendkey` puts real scancodes into the # emulated PS/2 controller, the LK handler decodes them into a line, @@ -275,6 +368,10 @@ jobs: # looked up by name. The board supplies stacks and the switch; which # code runs on them is no longer in `src/tasks.rs` at all. timeout 300 python3 check_spawn.py + # Ring 3. The program prints through a syscall and is then refused a + # write to kernel memory — the boundary becoming a property of the + # machine rather than of the program's good manners. + timeout 300 python3 check_user.py env: LK_BIN: ../target/debug/lk - name: AOT native-lowering coverage gate @@ -284,18 +381,57 @@ jobs: # pinned off, no fallback) is the only thing that catches it. Add a # file to AOT_COVERAGE_ALLOW if it legitimately cannot lower. run: | - cargo build -p lk-cli --features aot - # 48/51. The three allowed files are the try/catch corpus: `try`/`catch` - # became a real statement (`Stmt::Try` → `TryBegin`/`TryEnd`) to fix - # three silent wrong answers on the VM path, and the MIR lowering has no - # handler-region support yet, so they degrade to the Tier 0 VM bundle. - # Output stays VM-identical (see `try_catch_differential` in - # cli/tests/clif_differential_test.rs); what lapses is *native* - # lowering. Drop these entries when the region outlining lands — - # see todos.md. + # The script builds the compiler it scans with — a stale binary would + # report full coverage for code it does not contain, and unlike a + # missing one that failure is silent. + # + # 76/76, and no allow list. The try/catch corpus was the last thing + # on it; each of the three came off by a different fix, and each was + # checked by *running* it against the VM rather than by the fact that + # it compiled — see `aot/lower/src/try_region.rs`, where the two + # answers that compiled and computed the wrong thing are also + # recorded. + # + # An example that legitimately cannot lower goes back on this list + # with a rationale. It is empty now, which means a regression has + # nowhere to hide. AOT_COVERAGE_REQUIRE_FULL=1 \ - AOT_COVERAGE_ALLOW=examples/syntax/try_catch.lk,examples/syntax/error_unwrap.lk,examples/syntax/error_model_edges.lk \ bash scripts/aot_coverage.sh + - name: the same gate against an *optimized* compiler + # Every gate above builds `lk` unoptimized, and the `lk` people install + # is not that one. The difference is not academic: `debug_assert_eq!` + # discards its whole expression in a release build, and one of them held + # the call that grows the signature tables for a `try` body — so an + # optimized `lk` panicked on *every* program containing `try`, in every + # build ever shipped, while the debug build this file exercised was + # fine. Overflow checks and `debug_assert` are the two things a profile + # switch changes silently; this step is what notices. + # + # Release rather than dist: dist adds full LTO for a perf number nothing + # here measures, and costs several minutes for it. The profile + # difference that matters (`debug_assertions` off, optimizations on) is + # the same either way. + run: | + cargo build --release -p lk-cli --features aot + LK_BIN=./target/release/lk \ + AOT_COVERAGE_REQUIRE_FULL=1 \ + bash scripts/aot_coverage.sh + # And that it *runs*: compiling is not the property that broke the + # second time. An optimized `lk` links the lkrt archive sitting beside + # it, while the refresh that keeps that archive current used to build + # the debug one unconditionally — so a release `lk` linked whatever + # was last left there. The first `try` program it compiled died on + # `SIGILL` against a trampoline from a different day. + for f in examples/syntax/try_catch.lk examples/general/concurrency_demo.lk; do + LK_FORCE_VM=1 ./target/release/lk "$f" > /tmp/vm.txt + LK_AOT_HYBRID=0 LK_AOT_NO_FALLBACK=1 \ + ./target/release/lk compile "$f" --output /tmp/opt_native + /tmp/opt_native > /tmp/native.txt + diff /tmp/vm.txt /tmp/native.txt || { + echo "::error::$f differs between the VM and an optimized native build" + exit 1 + } + done - name: strict native-only differential (no Tier 0 fallback) # Complements the scan above: the corpora must not just *compile* # native, they must produce VM-identical output while pinned to the diff --git a/.github/workflows/correctness.yml b/.github/workflows/correctness.yml index 7a003e28..b89f8805 100644 --- a/.github/workflows/correctness.yml +++ b/.github/workflows/correctness.yml @@ -42,7 +42,7 @@ jobs: # `lk compile` builds this on demand inside a test's per-case timeout — # prebuild it so timeouts measure the compile itself, not a cold cargo. - name: prebuild the lk-api staticlib - run: cargo build -p lk-api --features ffi --release + run: cargo build -p lk-api-cabi --release - name: cargo test under LK_GC_STRESS run: LK_GC_STRESS=1 cargo test -p lk-core -p lk-stdlib -p lk-cli @@ -60,7 +60,7 @@ jobs: with: cache-on-failure: true - name: prebuild the lk-api staticlib - run: cargo build -p lk-api --features ffi --release + run: cargo build -p lk-api-cabi --release - name: hand-written differential cases run: LK_NATIVE_SANITIZE=address,undefined cargo test -p lk-cli --test aot_differential_test - name: examples corpus differential @@ -89,7 +89,7 @@ jobs: with: cache-on-failure: true - name: prebuild the lk-api staticlib - run: cargo build -p lk-api --features ffi --release + run: cargo build -p lk-api-cabi --release - name: build ASan lkrt run: bash scripts/build_lkrt_asan.sh - name: differential suites against the instrumented lkrt @@ -111,7 +111,7 @@ jobs: with: cache-on-failure: true - name: prebuild the lk-api staticlib - run: cargo build -p lk-api --features ffi --release + run: cargo build -p lk-api-cabi --release - name: fuzz with the run id as seed (printed for reproduction) run: LK_FUZZ_SEED=${{ github.run_id }} LK_FUZZ_CASES=500 cargo test -p lk-cli --test aot_fuzz_differential_test -- --nocapture - name: artifact decoder/verifier fuzz with the run id as seed diff --git a/.gitignore b/.gitignore index 3ded6348..a4736470 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,24 @@ website/dist/ website/src/wasm/pkg/.gitignore lk-lsp-debug.log + +# `lk compile foo.lk` writes the executable as `foo` — no extension, so no +# suffix pattern reaches it. Two 20MB binaries got committed this way +# (`examples/syntax/{closure,struct_trait}`, in commits about unrelated +# things), because running the compiler on an example dirties the tree and +# `git add -A` then takes it. +# +# Every file the repository actually tracks under these two trees has an +# extension, so "no dot" *is* the compiled-artifact shape here. The +# directories have to be re-included first: git cannot re-include a file +# whose parent directory is excluded. +examples/**/* +!examples/**/ +!examples/**/*.* +bench/**/* +!bench/**/ +!bench/**/*.* main lua-5.5.0/ +__pycache__/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e26dfee..96520cd2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "lk.lsp.inlayHints.parameters.enabled": false +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5aa792ac..281042dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1205,6 +1205,13 @@ dependencies = [ "lkrt", ] +[[package]] +name = "lk-api-cabi" +version = "0.1.3" +dependencies = [ + "lk-api", +] + [[package]] name = "lk-cli" version = "0.1.3" @@ -1214,6 +1221,7 @@ dependencies = [ "clap", "libc", "lk-aot", + "lk-aot-lower", "lk-completion", "lk-core", "lk-stdlib", @@ -1248,6 +1256,7 @@ dependencies = [ "dashmap 6.1.0", "futures", "hashbrown 0.15.5", + "indexmap", "itoa", "libm", "lk-values", @@ -1294,6 +1303,7 @@ dependencies = [ "chrono", "dashmap 6.1.0", "lk-core", + "lk-stdlib-bare", "lk-stdlib-bytes", "lk-stdlib-chan", "lk-stdlib-common", @@ -1312,12 +1322,12 @@ dependencies = [ "lk-stdlib-process", "lk-stdlib-random", "lk-stdlib-regex", - "lk-stdlib-slice", "lk-stdlib-stream", "lk-stdlib-string", "lk-stdlib-task", "lk-stdlib-time", "lk-stdlib-uuid", + "lk-stdlib-web", "once_cell", "serde_json", "serde_yaml", @@ -1338,7 +1348,6 @@ dependencies = [ "lk-stdlib-hash", "lk-stdlib-iter", "lk-stdlib-math", - "lk-stdlib-slice", "lk-stdlib-string", ] @@ -1536,15 +1545,6 @@ dependencies = [ "regex", ] -[[package]] -name = "lk-stdlib-slice" -version = "0.1.3" -dependencies = [ - "anyhow", - "lk-core", - "lk-stdlib-common", -] - [[package]] name = "lk-stdlib-stream" version = "0.1.3" @@ -1610,7 +1610,6 @@ dependencies = [ "lk-stdlib-math", "lk-stdlib-path", "lk-stdlib-regex", - "lk-stdlib-slice", "lk-stdlib-string", ] @@ -1642,16 +1641,26 @@ dependencies = [ name = "lkrt" version = "0.1.3" dependencies = [ + "base64", "cc", "chrono", + "crc32fast", "hashbrown 0.15.5", + "hex", + "indexmap", "lk-aot-abi", "lk-core", + "rand", + "regex", "rustc-hash", + "serde", "serde_json", "serde_yaml", + "sha1", + "sha2", "spin", "toml", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1fc9a8aa..5dd3c3da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "core", "values", "api", + "api-cabi", "cli", "completion", "aot/abi", @@ -32,7 +33,6 @@ members = [ "stdlib/crates/process", "stdlib/crates/random", "stdlib/crates/regex", - "stdlib/crates/slice", "stdlib/crates/stream", "stdlib/crates/string", "stdlib/crates/task", @@ -62,7 +62,12 @@ anyhow = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "1" serde_yaml = "0" -toml = "1" +# `preserve_order`: a TOML document's tables come back in document order, +# because an LK map's order is a contract (docs/semantics.md). Without it +# `toml::Value` is a `BTreeMap` and a parsed table arrives alphabetised. +# Unlike serde_json's same-named feature this costs nothing here — TOML +# decoding is already `std`-only. +toml = { version = "1", features = ["preserve_order"] } base64 = "0.22" crc32fast = "1" ed25519-dalek = "2" @@ -97,6 +102,21 @@ proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["full", "extra-traits"] } +# Debuginfo dominates `target/debug`: with the default `debug = 2` every one of +# the workspace's ~30 test binaries carries full DWARF for the entire graph +# (measured: 944 binaries, 70GB). `line-tables-only` keeps what a backtrace +# actually reads — file, line, function — and drops the variable/type tables +# that only an interactive debugger uses. Third-party dependencies get nothing +# at all; stepping into `serde` is not something this workspace does. +# +# Raise to `debug = 2` locally (or `CARGO_PROFILE_DEV_DEBUG=2`) when a session +# genuinely needs gdb-level inspection of a dependency. +[profile.dev] +debug = "line-tables-only" + +[profile.dev.package."*"] +debug = false + [profile.release] panic = "abort" opt-level = 2 diff --git a/Makefile b/Makefile index 6a4903f4..9afd4faa 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,23 @@ NPM ?= npm -INSTALL_VSIX ?= +CARGO ?= cargo +# Extra flags for the `cargo install` steps, e.g. +# make install CARGO_INSTALL_FLAGS="--no-default-features --features stdlib" +# on a machine that cannot build the AOT backend. +CARGO_INSTALL_FLAGS ?= +# Set to a CLI path to install the VSIX into one specific editor instead of +# every VS Code-family editor found (see scripts/lib/vscode_cli.sh). VSCODE_CLI ?= VSC_EXT_DIR := ecosystem/vsc-ext VSC_EXTENSIONS := lsp ZED_EXT_DIR := ecosystem/zed-ext -.PHONY: vsix $(VSC_EXTENSIONS:%=vsix-%) clean-vsix debug-lsp-ext zed-ext-check install +.PHONY: vsix $(VSC_EXTENSIONS:%=vsix-%) clean-vsix debug-lsp-ext zed-ext-check zed-ext-release-check \ + install install-cli install-lsp install-vsix install-zed prune vsix: $(VSC_EXTENSIONS:%=vsix-%) +# Packaging only. Installing is `make install-vsix`. $(VSC_EXTENSIONS:%=vsix-%): vsix-%: $(NPM) install --prefix $(VSC_EXT_DIR)/$* $(NPM) --prefix $(VSC_EXT_DIR)/$* run package @@ -18,61 +26,70 @@ $(VSC_EXTENSIONS:%=vsix-%): vsix-%: echo "No VSIX package found under $(VSC_EXT_DIR)/$*"; \ exit 1; \ fi; \ - if [ "$(INSTALL_VSIX)" = "1" ] || [ "$(INSTALL_VSIX)" = "yes" ]; then \ - answer=yes; \ - elif [ -t 0 ]; then \ - printf "Install $$vsix_file into VS Code now? [y/N] "; \ - read answer || answer=; \ - else \ - echo "VSIX built: $$vsix_file"; \ - echo "Install it with: code --install-extension $$vsix_file"; \ - answer=no; \ - fi; \ - case "$$answer" in \ - [Yy]|[Yy][Ee][Ss]) \ - vscode_cli="$(VSCODE_CLI)"; \ - if [ -z "$$vscode_cli" ] && command -v code >/dev/null 2>&1; then \ - vscode_cli=code; \ - fi; \ - if [ -z "$$vscode_cli" ] && [ -x "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" ]; then \ - vscode_cli="/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"; \ - fi; \ - if [ -z "$$vscode_cli" ] && [ -x "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders" ]; then \ - vscode_cli="/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders"; \ - fi; \ - if [ -z "$$vscode_cli" ] && [ -x "/Applications/VSCodium.app/Contents/Resources/app/bin/codium" ]; then \ - vscode_cli="/Applications/VSCodium.app/Contents/Resources/app/bin/codium"; \ - fi; \ - if [ -n "$$vscode_cli" ]; then \ - "$$vscode_cli" --install-extension "$$vsix_file" || { \ - echo "VSIX install failed. The package was still built at: $$vsix_file"; \ - echo "If VS Code asks for a restart before reinstalling, restart VS Code and run: \"$$vscode_cli\" --install-extension $$vsix_file"; \ - exit 1; \ - }; \ - else \ - echo "VS Code CLI was not found. Install manually from VS Code: Extensions > ... > Install from VSIX... > $$vsix_file"; \ - echo "Or run: make vsix INSTALL_VSIX=1 VSCODE_CLI=/path/to/code"; \ - exit 1; \ - fi; \ - ;; \ - *) \ - echo "Skipped VSIX install: $$vsix_file"; \ - ;; \ - esac + echo "VSIX built: $$vsix_file" clean-vsix: rm -f $(VSC_EXT_DIR)/*/*.vsix +# Reclaim `target/`. Cargo never removes the artifacts of a fingerprint it has +# stopped using, so the directory only grows: this workspace reached 190GB of +# `target/debug` before anyone looked. `cargo clean --gc` is still nightly-only. +prune: + bash scripts/prune_target.sh + debug-lsp-ext: ./scripts/debug-vscode-lsp.sh zed-ext-check: cargo check --manifest-path $(ZED_EXT_DIR)/Cargo.toml --target wasm32-wasip1 -install: - cargo install --path cli --force - cargo install --path lsp --force - $(MAKE) vsix INSTALL_VSIX=1 +# Run before publishing the Zed extension. `extension.toml` pins the grammar to +# a commit, and it ships with a placeholder — Zed clones that commit to build +# the grammar, so publishing with the placeholder in place produces an +# extension whose syntax highlighting cannot be built. A comment asking someone +# to remember is not a check; this is. +zed-ext-release-check: zed-ext-check + @commit=$$(grep -E '^commit = ' $(ZED_EXT_DIR)/extension.toml | head -1 | sed 's/.*"\(.*\)"/\1/'); \ + if ! printf '%s' "$$commit" | grep -qE '^[0-9a-f]{40}$$'; then \ + echo "zed extension.toml: grammar commit is '$$commit', not a 40-char SHA."; \ + echo "Set it to the commit that contains ecosystem/tree-sitter-lk before publishing."; \ + exit 1; \ + fi; \ + if ! git cat-file -e "$$commit^{commit}" 2>/dev/null; then \ + echo "zed extension.toml: grammar commit $$commit is not in this repository."; \ + exit 1; \ + fi; \ + echo "zed extension.toml: grammar pinned to $$commit" + +# Everything a workstation needs: both binaries, the VS Code extension in every +# VS Code-family editor found (remote windows included), and the Zed step. +# The editor steps are best-effort by design — a machine without node or +# without an editor still gets a working `lk` and `lk-lsp`, and says so. +# The editor steps run under `||` so that a machine with no editor (or a failed +# VSIX install) still ends with `lk` and `lk-lsp` installed and a clear message, +# instead of aborting the target halfway. `make install-vsix` on its own keeps +# its non-zero exit for scripting. +install: install-cli install-lsp + @$(MAKE) install-vsix || echo "install: the VS Code extension step failed (see above); lk and lk-lsp are installed." + @$(MAKE) install-zed || true + @echo "install: done. Reload your editor window to pick up the new extension and lk-lsp." + +install-cli: + $(CARGO) install --path cli --force $(CARGO_INSTALL_FLAGS) + +install-lsp: + $(CARGO) install --path lsp --force $(CARGO_INSTALL_FLAGS) + +install-vsix: + @if ! command -v $(NPM) >/dev/null 2>&1; then \ + echo "install-vsix: '$(NPM)' not found, skipping the VS Code extension."; \ + echo "install-vsix: install Node.js and rerun 'make install-vsix'."; \ + exit 0; \ + fi; \ + $(MAKE) vsix && VSCODE_CLI="$(VSCODE_CLI)" bash scripts/install_vsix.sh + +install-zed: + @bash scripts/install_zed_ext.sh # Correctness harnesses (see plan.md). Miri needs `rustup component add miri # --toolchain nightly`. Leaks are ignored because lkrt's arena ownership frees diff --git a/README.md b/README.md index 368c0814..05712cfb 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,28 @@ See `docs/concurrency.md` and `docs/semantics.md` for the full semantics. Details: [lang.lollipopkit.com](https://lang.lollipopkit.com). +## On bare metal + +LK compiles to a kernel. `bare-metal-x86/` boots on QEMU with no OS underneath +and no `std` in the graph: long mode, interrupts, preemptive tasks whose +*scheduling policy is LK*, PCI, a framebuffer with a font this repository wrote, +PS/2 keyboard and mouse, an ATA disk, a read-only tar filesystem, a free-list +allocator, a window manager with dragging and stacking — and ring 3, with +checked syscalls and an address space per user task. + +The drivers are LK modules (`drivers/*.lk`), and so is the interrupt table +itself — LK builds all 256 gates and loads them with `lidt`. What is Rust is the +part a language should not own: the linker script, the boot path, and the +interrupt trampolines, because an interrupt is not a call and the code it lands +in has to have every register spilled before a compiled handler can run. + +Eleven QEMU checks run in CI, and each asserts what the machine *scanned out* or +what the disk image holds afterwards — not what the program believes it did. +`bare-metal-x86/README.md` is the long version, including the mistakes: a +soft-float ABI that computed wrong numbers in silence, a shared-page constant +that overlapped a window descriptor, a mouse packet misframed into permanent +stillness. + ## Installation Install the latest GitHub release: @@ -122,21 +144,25 @@ assert_eq!(result.display_first_return(), "true"); - Run REPL: `lk` - Execute a source file or module artifact: `lk FILE` (supports `.lk` and `.lkm`) -- Type-check without executing: `lk check FILE` (reports compile-time diagnostics) +- Type-check without executing: `lk check FILE` (the same check the executors run; `--strict` also requires every signature to resolve to something other than `Any`) - Format sources in place: `lk fmt [PATH...]` (no path = the whole project; `--check` reports instead of writing, for CI) - Compile to a native executable: `lk compile [FILE]` (Cranelift backend; omitting `FILE` uses `./main.lk`, package `./src/main.lk`, or a single workspace app entry; shapes outside the native slice fall back to the Tier 0 VM bundle) - Compile to a bytecode module artifact: `lk compile bytecode [FILE]` → `FILE.lkm` +- Bundle a self-contained executable that embeds the program *and* the VM: `lk bundle FILE` (AOT Tier 0 — every program bundles, at VM speed) +- Report which instructions a file exercises: `lk coverage FILE` (`--disassemble` prints the bytecode) +- Inspect macro expansion: `lk macro expand FILE` (`--trace`, `--deps`, `--origins`; see [docs/macros.md](docs/macros.md)) - Create packages and manage decentralized git + lockfile dependencies (no central registry): `lk pkg init`, `lk pkg add`, `lk pkg fetch`, `lk pkg update`, `lk pkg check`, `lk pkg tree` (see [docs/packages.md](docs/packages.md)) -Note: command-line argument paths must be sanitized relative paths. ### Editor Support Editor integrations live under `ecosystem/`. -- VS Code support is a single merged extension under `ecosystem/vsc-ext/lsp`. It includes `.lk` language registration, TextMate highlighting, snippets, and the LK LSP client with smart completion for stdlib modules, imported aliases, local symbols, named arguments, repeated string argument values, and common receiver methods. Use `make debug-lsp-ext` for a local Extension Development Host, or `make vsix` to build the VSIX. +- VS Code support is a single merged extension under `ecosystem/vsc-ext/lsp`. It includes `.lk` language registration, TextMate highlighting, snippets, and the LK LSP client with smart completion for stdlib modules, imported aliases, local symbols, named arguments, repeated string argument values, and common receiver methods. Use `make install` to install the CLI, `lk-lsp` and the extension into every VS Code-family editor found (VS Code / Insiders / VSCodium / Cursor / Windsurf, remote windows included), `make debug-lsp-ext` for a local Extension Development Host, or `make vsix` to only build the VSIX. - Zed support lives under `ecosystem/zed-ext`. It uses `ecosystem/tree-sitter-lk` for Tree-sitter highlighting and starts `lk-lsp` for diagnostics, completion, hover, goto definition, document symbols, semantic tokens, and inlay hints. Use `make zed-ext-check` to validate the extension crate. +Working on LK itself: [docs/testing.md](docs/testing.md) lists the gates and what each one is the only thing that catches — several are outside `cargo test --workspace`. + ## License ```plaintext diff --git a/README.zh-CN.md b/README.zh-CN.md index 67178403..b98d9af2 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -60,6 +60,24 @@ println("{} (total: {})", status, m["total"]!); // ok (total: 30) 细节: [lang.lollipopkit.com](https://lang.lollipopkit.com) +## 在裸机上 + +LK 能编译成内核。`bare-metal-x86/` 在 QEMU 上启动,底下没有操作系统,依赖图里 +没有 `std`:长模式、中断、抢占式任务(*调度策略是 LK 写的*)、PCI、帧缓冲与本仓库 +自己写的字体、PS/2 键盘与鼠标、ATA 磁盘、只读 tar 文件系统、空闲链表分配器、 +可拖动可层叠的窗口管理器 —— 以及 ring 3、受检的系统调用、每个用户任务自己的 +地址空间。 + +驱动是 LK 模块(`drivers/*.lk`),中断表本身也是 —— 256 个门由 LK 构造,再由它 +`lidt` 装上。用 Rust 写的是语言不该拥有的那部分:链接脚本、启动路径,以及中断 +蹦床 —— 中断不是调用,被打断的代码从没同意交出自己的寄存器,所以进入一个编译出来 +的处理程序之前,必须先有一段汇编把它们全部压下去。 + +CI 里跑十一个 QEMU 检查,每一个断言的都是机器*扫描输出*了什么、或者磁盘镜像事后 +留下了什么 —— 而不是程序自以为做了什么。`bare-metal-x86/README.md` 是长 +版本,包括那些错误:静默算错数的 soft-float ABI、与窗口描述符重叠的共享页常量、 +一个错帧之后再也不动的鼠标。 + ## 安装 安装 GitHub 最新 release: @@ -126,6 +144,9 @@ assert_eq!(result.display_first_return(), "true"); - 原地格式化源码:`lk fmt [PATH...]`(不给路径则格式化整个项目;`--check` 只报告不改写,供 CI 使用) - 编译为 native 可执行文件:`lk compile [FILE]`(Cranelift 后端;省略 `FILE` 时使用当前目录的 `main.lk`、package 的 `src/main.lk`,或单一 workspace app 入口;超出原生切片的形状回退到 Tier 0 VM bundle) - 编译为 bytecode 模块产物:`lk compile bytecode [FILE]` → `FILE.lkm` +- 打包成自带 VM 的独立可执行文件:`lk bundle FILE`(AOT Tier 0 —— 任何程序都能打包,速度是 VM 的) +- 报告一个文件用到哪些指令:`lk coverage FILE`(`--disassemble` 打印字节码) +- 查看宏展开:`lk macro expand FILE`(`--trace`、`--deps`、`--origins`,详见 [docs/macros.md](docs/macros.md)) - 创建包并管理去中心化 git + lockfile 依赖(无中心 registry):`lk pkg init`、`lk pkg add`、`lk pkg fetch`、`lk pkg update`、`lk pkg check`、`lk pkg tree`(详见 [docs/packages.md](docs/packages.md)) 注意:命令行参数路径必须为经净化的相对路径。 @@ -134,9 +155,11 @@ assert_eq!(result.display_first_return(), "true"); 编辑器集成统一放在 `ecosystem/` 下。 -- VS Code 支持已合并为 `ecosystem/vsc-ext/lsp` 下的单个扩展,包含 `.lk` 语言注册、TextMate 高亮、代码片段,以及带智能补全的 LK LSP 客户端;补全覆盖 stdlib 模块、导入别名、本地符号、named arguments、重复出现的字符串参数值和常见 receiver 方法。使用 `make debug-lsp-ext` 启动本地 Extension Development Host,或使用 `make vsix` 构建 VSIX。 +- VS Code 支持已合并为 `ecosystem/vsc-ext/lsp` 下的单个扩展,包含 `.lk` 语言注册、TextMate 高亮、代码片段,以及带智能补全的 LK LSP 客户端;补全覆盖 stdlib 模块、导入别名、本地符号、named arguments、重复出现的字符串参数值和常见 receiver 方法。使用 `make install` 安装 CLI、`lk-lsp` 以及扩展(会装进本机探测到的所有 VS Code 系编辑器:VS Code / Insiders / VSCodium / Cursor / Windsurf,含 remote 窗口),`make debug-lsp-ext` 启动本地 Extension Development Host,或 `make vsix` 只构建 VSIX。 - Zed 支持位于 `ecosystem/zed-ext`,使用 `ecosystem/tree-sitter-lk` 提供 Tree-sitter 高亮,并启动 `lk-lsp` 提供 diagnostics、completion、hover、goto definition、document symbols、semantic tokens 和 inlay hints。使用 `make zed-ext-check` 验证扩展 crate。 +参与 LK 本身的开发:[docs/testing.md](docs/testing.md) 列了全部门禁,以及每一条**只有它**抓得住什么 —— 有几条不在 `cargo test --workspace` 里。 + ## 许可证 ```plaintext diff --git a/aot/abi/src/lib.rs b/aot/abi/src/lib.rs index eb0c9d1a..ad6b8312 100644 --- a/aot/abi/src/lib.rs +++ b/aot/abi/src/lib.rs @@ -66,17 +66,28 @@ pub enum Receiver { /// Borrows the receiver (or takes none) *and* returns a freshly allocated /// arena container handle — the constructors the pass looks for. Constructs, + /// Returns a freshly allocated handle that **points back into the + /// receiver** — `xs.slice(a, b)`, whose window reads through to `xs` on + /// every access (`lkrt::lkslice`). + /// + /// Both halves matter and neither of the other two says both: the result is + /// releasable at the end of its scope like any other fresh handle, while + /// the receiver is not, because releasing a list that a live window still + /// addresses is a use-after-free. Spelling this as `Constructs` would have + /// freed the source; spelling it `Retained` would have kept every window + /// alive to process exit. + ConstructsView, } impl Receiver { /// Whether the runtime may hold on to the receiver after the call. pub fn retains(self) -> bool { - matches!(self, Receiver::Retained) + matches!(self, Receiver::Retained | Receiver::ConstructsView) } /// Whether the call's result is a fresh arena container handle. pub fn constructs(self) -> bool { - matches!(self, Receiver::Constructs) + matches!(self, Receiver::Constructs | Receiver::ConstructsView) } } @@ -96,6 +107,13 @@ pub struct AbiFn { } /// Invokes the given callback macro with every ABI table entry, in order. This is +/// An entry with no emitter is not "available", it is **unverified**: nothing +/// exercises its argument marshalling or its receiver class, so the first caller +/// is the one that finds out whether the row is right. Three such rows +/// (`dyn.as_typed_map`, `dyn.as_set`, `dyn.as_bytes`) were added for symmetry +/// with their `from_*` counterparts and deleted unused — add the row with the +/// call site, not before it. +/// /// the single source of truth (RFC aot-redesign §3.3): the [`ABI_FUNCTIONS`] const /// table below and `lkrt`'s compile-time signature-conformance checks both expand /// from it, so a signature can no longer drift between the schema, the codegen @@ -121,19 +139,26 @@ macro_rules! for_each_abi_fn { ("cpu", "irq_restore", lkrt_cpu_irq_restore, WritesHost, [I64], Nil); ("cpu", "timestamp", lkrt_cpu_timestamp, WritesHost, [], I64); ("cpu", "wait_for_interrupt", lkrt_cpu_wait_for_interrupt, WritesHost, [], Nil); - // Volatile MMIO. `WritesHost` even for the reads: the effect - // annotation is what drives CSE, and a device read that can change - // state or return a different value each time is not pure. These - // are calls rather than inline loads because Cranelift has no - // volatile flag — see lkrt/src/mmio.rs. - ("mmio", "read_u8", lkrt_mmio_read_u8, WritesHost, [I64], I64); - ("mmio", "read_u16", lkrt_mmio_read_u16, WritesHost, [I64], I64); - ("mmio", "read_u32", lkrt_mmio_read_u32, WritesHost, [I64], I64); - ("mmio", "read_u64", lkrt_mmio_read_u64, WritesHost, [I64], I64); - ("mmio", "write_u8", lkrt_mmio_write_u8, WritesHost, [I64, I64], Nil); - ("mmio", "write_u16", lkrt_mmio_write_u16, WritesHost, [I64, I64], Nil); - ("mmio", "write_u32", lkrt_mmio_write_u32, WritesHost, [I64, I64], Nil); - ("mmio", "write_u64", lkrt_mmio_write_u64, WritesHost, [I64, I64], Nil); + // System control: descriptor tables, CR2/CR3, the TLB. x86 only, + // and `WritesHost` including the reads — CR2 changes behind the + // code's back on every fault, which is its entire purpose, so two + // reads of it must not be collapsed into one. See lkrt/src/system.rs. + ("cpu", "load_idt", lkrt_cpu_load_idt, WritesHost, [I64, I64], Nil); + ("cpu", "load_gdt", lkrt_cpu_load_gdt, WritesHost, [I64, I64], Nil); + ("cpu", "reload_segments", lkrt_cpu_reload_segments, WritesHost, [I64, I64], Nil); + ("cpu", "load_task_register", lkrt_cpu_load_task_register, WritesHost, [I64], Nil); + ("cpu", "read_cr2", lkrt_cpu_read_cr2, WritesHost, [], I64); + ("cpu", "read_cr3", lkrt_cpu_read_cr3, WritesHost, [], I64); + ("cpu", "write_cr3", lkrt_cpu_write_cr3, WritesHost, [I64], Nil); + ("cpu", "raise_interrupt", lkrt_cpu_raise_interrupt, WritesHost, [I64], Nil); + ("cpu", "invalidate_page", lkrt_cpu_invalidate_page, WritesHost, [I64], Nil); + // Volatile MMIO has no entries here any more, and that absence is + // the point: `volatile_read_uN`/`volatile_write_uN` lower to a real + // machine load and store (`Inst::VolatileLoad`), not to a call. + // What made them calls was that Cranelift has no volatile flag and + // its alias analysis collapses two accesses to one address; what + // replaced them is a `sequence_point` before each access, which + // emits nothing and moves the key that analysis works from. // Port I/O — `WritesHost` for the same reason the MMIO reads are: // reading a device port can change its state, so it must not be // collapsed with another read of the same port. @@ -144,7 +169,7 @@ macro_rules! for_each_abi_fn { ("port", "out_u16", lkrt_port_out_u16, WritesHost, [I64, I64], Nil); ("port", "out_u32", lkrt_port_out_u32, WritesHost, [I64, I64], Nil); ("lkrt", "abi_version", lkrt_abi_version, Pure, [], I64); - ("lkrt", "abi_check", lkrt_abi_check, WritesHost, [I64], Nil); + ("lkrt", "rt_begin", lkrt_rt_begin, WritesHost, [I64], Nil); ("lkrt", "cleanup", lkrt_cleanup, WritesHost, [], Nil); ("lkrt", "error_clear", lkrt_error_clear, WritesHost, [], Nil); ("lkrt", "last_error", lkrt_last_error, ReadsHost, [], StrPtr); @@ -157,7 +182,7 @@ macro_rules! for_each_abi_fn { ("rt", "assert", lkrt_assert, WritesHost, [I64], Nil); ("rt", "assert_msg", lkrt_assert_msg, WritesHost, [I64, StrPtr], Nil); ("rt", "panic", lkrt_panic, WritesHost, [StrPtr], Nil); - // Native protected calls (`try$call`, plan G): handler-stack + // Native protected regions (plan G): handler-stack // frames around a `_setjmp` in the generated code, the raised // value, and the raise entry points (no live handler → the // existing loud abort). Cells are the VM's `UpvalCell` — shared @@ -167,7 +192,18 @@ macro_rules! for_each_abi_fn { ("rt", "current_error", lkrt_rt_current_error, ReadsHost, [], DynVal); ("rt", "raise_dyn", lkrt_rt_raise_dyn, WritesHost, [DynVal], Nil); ("rt", "raise_msg", lkrt_rt_raise_msg, WritesHost, [StrPtr], Nil); + // The present-bit of a nullable carrier, checked with the sentence + // to raise if it is not set. The sentence is a *compile-time* + // constant: the lowering knows the operator and both operand types, + // which is exactly what the VM's message names, so there is no + // table of messages here to drift from the one in the executor. + ("rt", "maybe_guard", lkrt_rt_maybe_guard, WritesHost, [I64, StrPtr], Nil); ("rt", "cell_new", lkrt_rt_cell_new, WritesHost, [DynVal], Ptr); + // The raw-handle family: a typed container parked as-is, because + // boxing one is an element-wise copy. Tag-checked at both ends. + ("rt", "cell_new_raw", lkrt_rt_cell_new_raw, WritesHost, [I64], Ptr); + ("rt", "cell_get_raw", lkrt_rt_cell_get_raw, ReadsHost, [Ptr], I64); + ("rt", "cell_set_raw", lkrt_rt_cell_set_raw, WritesHost, [Ptr, I64], Nil); ("rt", "cell_get", lkrt_rt_cell_get, ReadsHost, [Ptr], DynVal); ("rt", "cell_set", lkrt_rt_cell_set, WritesHost, [Ptr, DynVal], Nil); // Early release of an arena container proven dead (scope drop). @@ -181,19 +217,143 @@ macro_rules! for_each_abi_fn { // Go close semantics (buffer drains, then raises), snapshot // argument blocks for spawn, join-once task await. ("chan", "new", lkrt_chan_new, WritesHost, [I64], I64); + ("time", "timeout", lkrt_time_timeout, WritesHost, [I64], I64); + ("time", "after", lkrt_time_after, WritesHost, [I64], I64); ("chan", "send", lkrt_chan_send, WritesHost, [I64, DynVal], Nil); ("chan", "recv", lkrt_chan_recv, WritesHost, [I64], DynVal); ("chan", "close", lkrt_chan_close, WritesHost, [I64], Nil); ("chan", "try_send", lkrt_chan_try_send, WritesHost, [I64, DynVal], I64); ("chan", "try_recv", lkrt_chan_try_recv, WritesHost, [I64], DynVal); ("chan", "len", lkrt_chan_len, ReadsHost, [I64], I64); + ("chan", "capacity", lkrt_chan_capacity, ReadsHost, [I64], I64); ("chan", "is_closed", lkrt_chan_is_closed, ReadsHost, [I64], I64); ("chan", "select", lkrt_chan_select, WritesHost, [Ptr, Ptr, Ptr, Ptr, I64], Ptr); // `encoding` submodules: the VM's exact crates + conversion rules // (`core/src/val/de.rs`); object key order mirrors two-stage. ("json", "parse", lkrt_json_parse, WritesHost, [StrPtr], DynVal); + ("json", "stringify", lkrt_json_stringify, WritesHost, [DynVal], StrPtr); ("yaml", "parse", lkrt_yaml_parse, WritesHost, [StrPtr], DynVal); + ("yaml", "stringify", lkrt_yaml_stringify, WritesHost, [DynVal], StrPtr); ("toml", "parse", lkrt_toml_parse, WritesHost, [StrPtr], DynVal); + ("toml", "stringify", lkrt_toml_stringify, WritesHost, [DynVal], StrPtr); + // `base64`/`hex`/`url`: the same crates the stdlib module uses, so + // the text is byte-identical. `WritesHost` like every other + // arena-allocating string producer. `url.decode_component` raises on + // a malformed escape. + // `Bytes` handles: an arena-owned `Vec`, the same shape a list + // handle has. Content equality and `Bytes([…])` display, both the + // VM's rules. + ("bytes_h", "from_str", lkrt_lkbytes_from_str, WritesHost, [StrPtr], Ptr); + ("bytes_h", "len", lkrt_lkbytes_len, Pure, [Ptr], I64); + ("bytes_h", "is_empty", lkrt_lkbytes_is_empty, Pure, [Ptr], I64); + ("bytes_h", "eq", lkrt_lkbytes_eq, Pure, [Ptr, Ptr], I64); + ("bytes_h", "get", lkrt_lkbytes_get, Pure, [Ptr, I64], DynVal); + ("bytes_h", "concat", lkrt_lkbytes_concat, WritesHost, [Ptr, Ptr], Ptr); + ("bytes_h", "slice", lkrt_lkbytes_slice, WritesHost, [Ptr, I64, I64], Ptr); + // `Bytes` had a carrier and four methods; ten of its fourteen fell + // back. A count is not a position, so take/skip get their own guard + // rather than borrowing `slice`'s. + ("bytes_h", "take", lkrt_lkbytes_take, WritesHost, [Ptr, I64], Ptr); + ("bytes_h", "skip", lkrt_lkbytes_skip, WritesHost, [Ptr, I64], Ptr); + ("bytes_h", "index_of", lkrt_lkbytes_index_of, ReadsHost, [Ptr, I64], DynVal); + // `index_of`'s sibling, and `reverse` — the two pure sequence + // operations `Bytes` was missing while it had every other read. + ("bytes_h", "count", lkrt_lkbytes_count, ReadsHost, [Ptr, I64], I64); + ("bytes_h", "reverse", lkrt_lkbytes_reverse, WritesHost, [Ptr], Ptr, Constructs); + ("bytes_h", "sort", lkrt_lkbytes_sort, WritesHost, [Ptr], Ptr, Constructs); + ("bytes_h", "unique", lkrt_lkbytes_unique, WritesHost, [Ptr], Ptr, Constructs); + ("bytes_h", "contains", lkrt_lkbytes_contains, ReadsHost, [Ptr, I64], I64); + ("bytes_h", "from_i64_list", lkrt_lkbytes_from_i64_list, WritesHost, [Ptr], Ptr); + ("bytes_h", "from_dyn_list", lkrt_lkbytes_from_dyn_list, WritesHost, [Ptr], Ptr); + ("bytes_h", "to_i64_list", lkrt_lkbytes_to_i64_list, WritesHost, [Ptr], Ptr); + // The three reductions. `min`/`max` answer nil on an empty + // sequence, so they box; `sum` answers `0` and does not. + ("bytes_h", "sum", lkrt_lkbytes_sum, ReadsHost, [Ptr], I64); + ("bytes_h", "min", lkrt_lkbytes_min, ReadsHost, [Ptr], DynVal); + ("bytes_h", "max", lkrt_lkbytes_max, ReadsHost, [Ptr], DynVal); + ("list_h", "i64_sum", lkrt_lklist_i64_sum, ReadsHost, [Ptr], I64); + ("list_h", "f64_sum", lkrt_lklist_f64_sum, ReadsHost, [Ptr], F64); + ("list_h", "i64_min", lkrt_lklist_i64_min, ReadsHost, [Ptr], DynVal); + ("list_h", "i64_max", lkrt_lklist_i64_max, ReadsHost, [Ptr], DynVal); + ("list_h", "dyn_sum", lkrt_lklist_dyn_sum, ReadsHost, [Ptr], DynVal); + ("list_h", "dyn_min", lkrt_lklist_dyn_min, ReadsHost, [Ptr], DynVal); + ("list_h", "dyn_max", lkrt_lklist_dyn_max, ReadsHost, [Ptr], DynVal); + ("list_h", "f64_min", lkrt_lklist_f64_min, ReadsHost, [Ptr], DynVal); + ("list_h", "f64_max", lkrt_lklist_f64_max, ReadsHost, [Ptr], DynVal); + ("list_h", "str_min", lkrt_lklist_str_min, ReadsHost, [Ptr], DynVal); + ("list_h", "str_max", lkrt_lklist_str_max, ReadsHost, [Ptr], DynVal); + ("bytes_h", "utf8", lkrt_lkbytes_utf8, WritesHost, [Ptr], StrPtr); + ("bytes_h", "utf8_lossy", lkrt_lkbytes_utf8_lossy, WritesHost, [Ptr], StrPtr); + ("bytes_h", "to_str", lkrt_lkbytes_to_str, WritesHost, [Ptr], StrPtr); + ("base64", "decode", lkrt_base64_decode, WritesHost, [StrPtr], Ptr); + ("hex", "decode", lkrt_hex_decode, WritesHost, [StrPtr], Ptr); + ("base64", "encode", lkrt_base64_encode, WritesHost, [StrPtr], StrPtr); + ("hex", "encode", lkrt_hex_encode, WritesHost, [StrPtr], StrPtr); + // `uuid.v4` is deliberately not `Pure`: two calls are two UUIDs, and + // CSE merges equal `Pure` calls in a dominance scope. + // `regex` compiles through a shared bounded cache, so a call is + // `ReadsHost`, not `Pure` — two identical calls are still cheap, but + // the cache is process state. + ("regex", "is_match", lkrt_regex_is_match, ReadsHost, [StrPtr, StrPtr], I64); + ("regex", "split", lkrt_regex_split, WritesHost, [StrPtr, StrPtr], Ptr); + ("regex", "find", lkrt_regex_find, WritesHost, [StrPtr, StrPtr], DynVal); + ("regex", "find_all", lkrt_regex_find_all, WritesHost, [StrPtr, StrPtr], Ptr); + ("regex", "captures", lkrt_regex_captures, WritesHost, [StrPtr, StrPtr], DynVal); + ("regex", "replace", lkrt_regex_replace, WritesHost, [StrPtr, StrPtr, StrPtr], StrPtr); + // `random`: nondeterministic to a value, so never `Pure` (CSE would + // merge two rolls into one). + ("process", "id", lkrt_process_id, ReadsHost, [], I64); + ("process", "set_cwd", lkrt_process_set_cwd, WritesHost, [StrPtr], I64); + ("process", "exit", lkrt_process_exit, WritesHost, [I64], Nil); + ("process", "status", lkrt_process_status, WritesHost, [StrPtr, Ptr], I64); + ("process", "output_string", lkrt_process_output_string, WritesHost, [StrPtr, Ptr], StrPtr); + ("process", "output", lkrt_process_output, WritesHost, [StrPtr, Ptr], Ptr); + ("process", "status_noargs", lkrt_process_status_noargs, WritesHost, [StrPtr], I64); + ("process", "output_string_noargs", lkrt_process_output_string_noargs, WritesHost, [StrPtr], StrPtr); + ("process", "output_noargs", lkrt_process_output_noargs, WritesHost, [StrPtr], Ptr); + ("random", "int", lkrt_random_int, WritesHost, [I64, I64], I64); + ("random", "float", lkrt_random_float, WritesHost, [], F64); + ("random", "bool", lkrt_random_bool, WritesHost, [], I64); + ("random", "bool_p", lkrt_random_bool_p, WritesHost, [F64], I64); + ("random", "bytes", lkrt_random_bytes, WritesHost, [I64], Ptr); + ("random", "choice_i64", lkrt_random_choice_i64, WritesHost, [Ptr], DynVal); + ("random", "choice_f64", lkrt_random_choice_f64, WritesHost, [Ptr], DynVal); + ("random", "choice_str", lkrt_random_choice_str, WritesHost, [Ptr], DynVal); + ("random", "choice_dyn", lkrt_random_choice_dyn, WritesHost, [Ptr], DynVal); + ("random", "shuffle_i64", lkrt_random_shuffle_i64, WritesHost, [Ptr], Ptr); + ("random", "shuffle_f64", lkrt_random_shuffle_f64, WritesHost, [Ptr], Ptr); + ("random", "shuffle_str", lkrt_random_shuffle_str, WritesHost, [Ptr], Ptr); + ("random", "shuffle_dyn", lkrt_random_shuffle_dyn, WritesHost, [Ptr], Ptr); + ("uuid", "v4", lkrt_uuid_v4, WritesHost, [], StrPtr); + ("uuid", "parse", lkrt_uuid_parse, WritesHost, [StrPtr], StrPtr); + ("uuid", "is_valid", lkrt_uuid_is_valid, Pure, [StrPtr], I64); + ("base64", "encode_bytes", lkrt_base64_encode_bytes, WritesHost, [Ptr], StrPtr); + ("hex", "encode_bytes", lkrt_hex_encode_bytes, WritesHost, [Ptr], StrPtr); + // `hash`, both carriers of each member (`Bytes | String`). + ("hash", "sha256_str", lkrt_hash_sha256_str, Pure, [StrPtr], StrPtr); + ("hash", "sha1_str", lkrt_hash_sha1_str, Pure, [StrPtr], StrPtr); + ("hash", "crc32_str", lkrt_hash_crc32_str, Pure, [StrPtr], I64); + ("hash", "fnv64_str", lkrt_hash_fnv64_str, Pure, [StrPtr], I64); + ("hash", "sha256_bytes", lkrt_hash_sha256_bytes, ReadsHost, [Ptr], StrPtr); + ("hash", "sha1_bytes", lkrt_hash_sha1_bytes, ReadsHost, [Ptr], StrPtr); + ("hash", "crc32_bytes", lkrt_hash_crc32_bytes, ReadsHost, [Ptr], I64); + ("hash", "fnv64_bytes", lkrt_hash_fnv64_bytes, ReadsHost, [Ptr], I64); + ("url", "encode_component", lkrt_url_encode_component, WritesHost, [StrPtr], StrPtr); + ("url", "decode_component", lkrt_url_decode_component, WritesHost, [StrPtr], StrPtr); + // A closure as a runtime *value* (`lkrt::lkclosure`). Built from a + // function address and the same argument block a `spawn` uses for + // its captures; called by appending that block to the arguments, + // which is the order the native signature already declares. + // + // Not `Constructs`: that annotation is the scope-drop pass's + // contract that a call answers a *bare arena handle*, which + // `rt.handle_release` can be handed. This one answers an `LkDyn`. + ("rt", "closure_new", lkrt_closure_new, WritesHost, [Ptr, Ptr, I64, I64], DynVal); + ("rt", "closure_call", lkrt_closure_call, WritesHost, [DynVal, Ptr], DynVal); + ("rt", "closure_arity", lkrt_closure_arity, ReadsHost, [DynVal], I64); + // The callable-property call (`m.thing()`), which needs the name so + // a miss can say what the interpreter says. + ("rt", "closure_call_property", lkrt_closure_call_property, WritesHost, [DynVal, Ptr, StrPtr], DynVal); ("rt", "spawn_args_new", lkrt_spawn_args_new, WritesHost, [], Ptr); ("rt", "spawn_args_push", lkrt_spawn_args_push, WritesHost, [Ptr, DynVal], Nil); ("rt", "spawn_arg", lkrt_spawn_arg, ReadsHost, [Ptr, I64], DynVal); @@ -205,15 +365,13 @@ macro_rules! for_each_abi_fn { ("rt", "task_await", lkrt_task_await, WritesHost, [I64], DynVal); ("socket", "addr", lkrt_socket_addr, Pure, [StrPtr, I64], StrPtr); ("tcp", "connect", lkrt_tcp_connect, WritesHost, [StrPtr], I64); - ("tcp", "read", lkrt_tcp_read, WritesHost, [I64, I64], I64); + ("tcp", "read", lkrt_tcp_read, WritesHost, [I64, I64], Ptr); ("tcp", "write_str", lkrt_tcp_write_str, WritesHost, [I64, StrPtr], I64); - ("tcp", "write_bytes", lkrt_tcp_write_bytes, WritesHost, [I64, I64], I64); + ("tcp", "write_bytes", lkrt_tcp_write_bytes, WritesHost, [I64, Ptr], I64); ("tcp", "close", lkrt_tcp_close, WritesHost, [I64], I64); // Not `Pure`: it `take_bytes` — the handle is *consumed*, so a // second call with the same handle fails where the first one // succeeded. Mislabeling it would let a CSE pass collapse the two. - ("bytes", "to_string_utf8", lkrt_bytes_to_string_utf8, WritesHost, [I64], StrPtr); - ("bytes", "free", lkrt_bytes_free, WritesHost, [I64], I64); ("lkrt", "handle_close", lkrt_handle_close, WritesHost, [I64], I64); ("io.std", "write", lkrt_io_std_write, WritesHost, [I64, StrPtr, I64], I64); ("io.std", "flush", lkrt_io_std_flush, WritesHost, [I64], I64); @@ -221,23 +379,35 @@ macro_rules! for_each_abi_fn { ("env", "get", lkrt_env_get, ReadsHost, [StrPtr, Ptr], I64); ("env", "get_or", lkrt_env_get_or, ReadsHost, [StrPtr, StrPtr], StrPtr); ("env", "has", lkrt_env_has, ReadsHost, [StrPtr], I64); - ("env", "set", lkrt_env_set, WritesHost, [StrPtr, StrPtr], I64); - ("env", "remove", lkrt_env_remove, WritesHost, [StrPtr], I64); - ("fs", "read", lkrt_fs_read, ReadsHost, [StrPtr], I64); + ("fs", "read", lkrt_fs_read, ReadsHost, [StrPtr], Ptr); ("fs", "read_to_string", lkrt_fs_read_to_string, ReadsHost, [StrPtr], StrPtr); ("fs", "write_str", lkrt_fs_write_str, WritesHost, [StrPtr, StrPtr], I64); - ("fs", "write_bytes", lkrt_fs_write_bytes, WritesHost, [StrPtr, I64], I64); + ("fs", "write_bytes", lkrt_fs_write_bytes, WritesHost, [StrPtr, Ptr], I64); ("fs", "exists", lkrt_fs_exists, ReadsHost, [StrPtr], I64); ("fs", "metadata_len", lkrt_fs_metadata_len, ReadsHost, [StrPtr], I64); ("fs", "metadata_is_file", lkrt_fs_metadata_is_file, ReadsHost, [StrPtr], I64); ("fs", "metadata_is_dir", lkrt_fs_metadata_is_dir, ReadsHost, [StrPtr], I64); ("fs", "metadata_readonly", lkrt_fs_metadata_readonly, ReadsHost, [StrPtr], I64); - ("fs", "canonicalize", lkrt_fs_canonicalize, ReadsHost, [StrPtr], StrPtr); + ("fs", "canonicalize", lkrt_fs_canonicalize, ReadsHost, [StrPtr], DynVal); + ("fs", "metadata_map", lkrt_fs_metadata_map, WritesHost, [StrPtr], Ptr); + ("env", "vars_map", lkrt_env_vars_map, WritesHost, [], Ptr); + ("fs", "is_file", lkrt_fs_is_file, ReadsHost, [StrPtr], I64); + ("fs", "is_dir", lkrt_fs_is_dir, ReadsHost, [StrPtr], I64); + ("fs", "append_str", lkrt_fs_append_str, WritesHost, [StrPtr, StrPtr], I64); + ("fs", "append_bytes", lkrt_fs_append_bytes, WritesHost, [StrPtr, Ptr], I64); + ("fs", "create_dir", lkrt_fs_create_dir, WritesHost, [StrPtr], I64); + ("fs", "create_dir_all", lkrt_fs_create_dir_all, WritesHost, [StrPtr], I64); + ("fs", "remove_file", lkrt_fs_remove_file, WritesHost, [StrPtr], I64); + ("fs", "remove_dir", lkrt_fs_remove_dir, WritesHost, [StrPtr], I64); + ("fs", "remove_dir_all", lkrt_fs_remove_dir_all, WritesHost, [StrPtr], I64); + ("fs", "rename", lkrt_fs_rename, WritesHost, [StrPtr, StrPtr], I64); + ("fs", "copy", lkrt_fs_copy, WritesHost, [StrPtr, StrPtr], I64); ("fs", "temp_dir", lkrt_fs_temp_dir, ReadsHost, [], StrPtr); ("path", "temp_dir", lkrt_path_temp_dir, ReadsHost, [], StrPtr); ("process", "cwd", lkrt_process_cwd, ReadsHost, [], StrPtr); ("os", "clock", lkrt_os_clock, ReadsHost, [], F64); ("os", "epoch", lkrt_os_epoch, ReadsHost, [], I64); + ("os", "time", lkrt_os_time, ReadsHost, [], I64); ("os", "hostname", lkrt_os_hostname, ReadsHost, [], StrPtr); ("os", "arch", lkrt_os_arch, ReadsHost, [], StrPtr); // The module member is `os.os` (renamed: the schema name pairs with @@ -247,6 +417,7 @@ macro_rules! for_each_abi_fn { ("fs", "read_dir_list", lkrt_fs_read_dir_list, ReadsHost, [StrPtr], Ptr); // `math.floor(Float) -> Int` with the VM's exact rounding (`floor() // as i64`, saturating); an `Int` argument short-circuits in the lowering. + ("math", "f64_to_machine_int", lkrt_f64_to_machine_int, Pure, [F64, I64, I64], I64); ("math", "floor", lkrt_math_floor, Pure, [F64], I64); ("math", "ceil", lkrt_math_ceil, Pure, [F64], I64); ("math", "round", lkrt_math_round, Pure, [F64], I64); @@ -255,16 +426,47 @@ macro_rules! for_each_abi_fn { ("math", "sqrt", lkrt_math_sqrt, ReadsHost, [F64], F64); ("math", "sin", lkrt_math_sin, Pure, [F64], F64); ("math", "cos", lkrt_math_cos, Pure, [F64], F64); + // `sin`/`cos` were native and `tan` was not; the inverse and log + // families were absent entirely. Their domain guards raise the + // stdlib module's own words, because a caught error's text is the + // program's output. + ("math", "tan", lkrt_math_tan, Pure, [F64], F64); + ("math", "asin", lkrt_math_asin, Pure, [F64], F64); + ("math", "acos", lkrt_math_acos, Pure, [F64], F64); + ("math", "atan", lkrt_math_atan, Pure, [F64], F64); + ("math", "atan2", lkrt_math_atan2, Pure, [F64, F64], F64); + ("math", "log", lkrt_math_log, Pure, [F64], F64); + ("math", "log10", lkrt_math_log10, Pure, [F64], F64); + ("math", "log2", lkrt_math_log2, Pure, [F64], F64); + ("math", "clamp_i64", lkrt_math_clamp_i64, Pure, [I64, I64, I64], I64); ("math", "exp", lkrt_math_exp, Pure, [F64], F64); ("math", "pow", lkrt_math_pow, Pure, [F64, F64], F64); ("math", "hypot", lkrt_math_hypot, Pure, [F64, F64], F64); ("math", "cbrt", lkrt_math_cbrt, Pure, [F64], F64); ("math", "is_nan", lkrt_math_is_nan, Pure, [F64], I64); + ("math", "is_inf", lkrt_math_is_inf, Pure, [F64], I64); + ("math", "sinh", lkrt_math_sinh, Pure, [F64], F64); + ("math", "cosh", lkrt_math_cosh, Pure, [F64], F64); + ("math", "tanh", lkrt_math_tanh, Pure, [F64], F64); + ("math", "trunc_f64", lkrt_math_trunc_f64, Pure, [F64], F64); + ("math", "fract_f64", lkrt_math_fract_f64, Pure, [F64], F64); + ("math", "to_int_f64", lkrt_math_to_int_f64, Pure, [F64], I64); // `math.sign` keeps its argument's numeric flavor (Int → signum, // Float → ±1.0/0.0); the lowering dispatches on the static type. ("math", "sign_i64", lkrt_math_sign_i64, Pure, [I64], I64); ("math", "sign_f64", lkrt_math_sign_f64, Pure, [F64], F64); + // The `path` module's fixed-arity members. `String?` results arrive + // boxed, the same convention `string.strip_prefix` uses. + ("path", "normalize", lkrt_path_normalize, Pure, [StrPtr], StrPtr); + ("path", "parent", lkrt_path_parent, Pure, [StrPtr], DynVal); + ("path", "file_name", lkrt_path_file_name, Pure, [StrPtr], DynVal); + ("path", "file_stem", lkrt_path_file_stem, Pure, [StrPtr], DynVal); + ("path", "extension", lkrt_path_extension, Pure, [StrPtr], DynVal); + ("path", "with_extension", lkrt_path_with_extension, WritesHost, [StrPtr, StrPtr], StrPtr); + ("path", "is_absolute", lkrt_path_is_absolute, Pure, [StrPtr], I64); + ("path", "components", lkrt_path_components, WritesHost, [StrPtr], Ptr); ("path", "sep", lkrt_path_sep, ReadsHost, [], StrPtr); + ("path", "delimiter", lkrt_path_delimiter, ReadsHost, [], StrPtr); // chrono-backed datetime (same crate as the stdlib module, so // formatting/weekday output is byte-identical). `format`/`parse`/ // ordinal helpers abort on invalid input like the VM's loud error. @@ -283,10 +485,37 @@ macro_rules! for_each_abi_fn { ("list_h", "i64_from_range", lkrt_lklist_i64_from_range, WritesHost, [I64, I64, I64, I64], Ptr, Constructs); ("list_h", "i64_take", lkrt_lklist_i64_take, WritesHost, [Ptr, I64], Ptr, Constructs); ("list_h", "i64_skip", lkrt_lklist_i64_skip, WritesHost, [Ptr, I64], Ptr, Constructs); + ("list_h", "f64_take", lkrt_lklist_f64_take, WritesHost, [Ptr, I64], Ptr, Constructs); + ("list_h", "f64_skip", lkrt_lklist_f64_skip, WritesHost, [Ptr, I64], Ptr, Constructs); + ("list_h", "str_take", lkrt_lklist_str_take, WritesHost, [Ptr, I64], Ptr, Constructs); + ("list_h", "str_skip", lkrt_lklist_str_skip, WritesHost, [Ptr, I64], Ptr, Constructs); ("list_h", "i64_chain", lkrt_lklist_i64_chain, WritesHost, [Ptr, Ptr], Ptr, Constructs); ("list_h", "f64_chain", lkrt_lklist_f64_chain, WritesHost, [Ptr, Ptr], Ptr, Constructs); ("list_h", "str_chain", lkrt_lklist_str_chain, WritesHost, [Ptr, Ptr], Ptr, Constructs); ("list_h", "i64_push", lkrt_lklist_i64_push, WritesHost, [Ptr, I64], Nil, Borrowed); + // `clear()` on every carrier: the operation does not depend on the + // element type, so all four rows land together. + ("list_h", "i64_clear", lkrt_lklist_i64_clear, WritesHost, [Ptr], Nil, Borrowed); + // `pop` / `insert` / `remove_at`: none of the three had a lowering on + // any carrier, so a single `xs.pop()` dropped its whole module to the + // VM. `drop_last` is `pop`'s mutation half — the read reuses the + // carrier's `Maybe` machinery (see `list_drop_last!` for why a + // `*_pop` returning `Maybe` by value is not portable). `insert` + // answers nothing for the same reason `clear` does: the VM evaluates + // it to the receiver, which the lowering already holds, and a + // `Borrowed` pointer return would hand back an unowned handle. + ("list_h", "i64_drop_last", lkrt_lklist_i64_drop_last, WritesHost, [Ptr], Nil, Borrowed); + ("list_h", "f64_drop_last", lkrt_lklist_f64_drop_last, WritesHost, [Ptr], Nil, Borrowed); + ("list_h", "str_drop_last", lkrt_lklist_str_drop_last, WritesHost, [Ptr], Nil, Borrowed); + ("list_h", "i64_insert", lkrt_lklist_i64_insert, WritesHost, [Ptr, I64, I64], Nil, Borrowed); + ("list_h", "f64_insert", lkrt_lklist_f64_insert, WritesHost, [Ptr, I64, F64], Nil, Borrowed); + ("list_h", "str_insert", lkrt_lklist_str_insert, WritesHost, [Ptr, I64, StrPtr], Nil, Borrowed); + ("list_h", "i64_remove_at", lkrt_lklist_i64_remove_at, WritesHost, [Ptr, I64], I64, Borrowed); + ("list_h", "f64_remove_at", lkrt_lklist_f64_remove_at, WritesHost, [Ptr, I64], F64, Borrowed); + ("list_h", "str_remove_at", lkrt_lklist_str_remove_at, WritesHost, [Ptr, I64], StrPtr, Borrowed); + ("list_h", "f64_clear", lkrt_lklist_f64_clear, WritesHost, [Ptr], Nil, Borrowed); + ("list_h", "str_clear", lkrt_lklist_str_clear, WritesHost, [Ptr], Nil, Borrowed); + ("list_h", "dyn_clear", lkrt_lklist_dyn_clear, WritesHost, [Ptr], Nil, Borrowed); // List HOF over compiled zero-capture lambdas (`ptr @lk_fn_N` // callbacks). The callback may abort (div/0 inside the lambda), so // none of these are Pure. @@ -312,6 +541,10 @@ macro_rules! for_each_abi_fn { ("list_h", "i64_set", lkrt_lklist_i64_set, WritesHost, [Ptr, I64, I64], Nil, Borrowed); // Linear membership test; returns 0/1 (the caller narrows to `i1`). ("list_h", "i64_contains", lkrt_lklist_i64_contains, ReadsHost, [Ptr, I64], I64, Borrowed); + // Cross-type numeric membership: `1 in [1.0]` and `1.0 in [1, 2]` + // follow `==`, not the list's internal representation. + ("list_h", "i64_contains_f64", lkrt_lklist_i64_contains_f64, ReadsHost, [Ptr, F64], I64, Borrowed); + ("list_h", "f64_contains_i64", lkrt_lklist_f64_contains_i64, ReadsHost, [Ptr, I64], I64, Borrowed); // `xs[start..]`: a fresh handle with the elements from `start` on // (negative `start` aborts, matching the VM's fatal slice error). ("list_h", "i64_slice_from", lkrt_lklist_i64_slice_from, WritesHost, [Ptr, I64], Ptr, Constructs); @@ -322,6 +555,11 @@ macro_rules! for_each_abi_fn { ("list_h", "f64_len", lkrt_lklist_f64_len, ReadsHost, [Ptr], I64, Borrowed); ("list_h", "f64_at", lkrt_lklist_f64_at, ReadsHost, [Ptr, I64], F64, Borrowed); ("list_h", "f64_set", lkrt_lklist_f64_set, WritesHost, [Ptr, I64, F64], Nil, Borrowed); + // `str_set` completes the carrier set: `xs[i] = v` lowered on `Int` + // and `Float` only, so the same two lines stayed native or did not + // depending on the list's representation. (`dyn_set` was already + // declared further down — it had a row and no lowering using it.) + ("list_h", "str_set", lkrt_lklist_str_set, WritesHost, [Ptr, I64, StrPtr], Nil, Borrowed); ("list_h", "f64_contains", lkrt_lklist_f64_contains, ReadsHost, [Ptr, F64], I64, Borrowed); // String-element list handle (elements are interned string-constant pointers). ("list_h", "str_new", lkrt_lklist_str_new, WritesHost, [], Ptr, Constructs); @@ -329,18 +567,92 @@ macro_rules! for_each_abi_fn { ("list_h", "str_len", lkrt_lklist_str_len, ReadsHost, [Ptr], I64, Borrowed); ("list_h", "str_at", lkrt_lklist_str_at, ReadsHost, [Ptr, I64], StrPtr, Borrowed); ("list_h", "str_join", lkrt_lklist_str_join, WritesHost, [Ptr, StrPtr], StrPtr, Borrowed); + // `join` on the numeric carriers. It was absent because the VM + // refused a non-string list — one arbitrary rule reproduced as a + // second one here. The VM renders every element now, and these + // render them the same way the display helpers do. + ("list_h", "i64_join", lkrt_lklist_i64_join, WritesHost, [Ptr, StrPtr], StrPtr, Borrowed); + ("list_h", "f64_join", lkrt_lklist_f64_join, WritesHost, [Ptr, StrPtr], StrPtr, Borrowed); + ("list_h", "dyn_join", lkrt_lklist_dyn_join, WritesHost, [Ptr, StrPtr], StrPtr, Borrowed); + // `index_of` is on every sequence in the VM; the lowering had it + // only on `Str`. + ("list_h", "i64_index_of", lkrt_lklist_i64_index_of, ReadsHost, [Ptr, I64], DynVal, Borrowed); + ("list_h", "i64_count", lkrt_lklist_i64_count, ReadsHost, [Ptr, I64], I64, Borrowed); + ("list_h", "f64_count", lkrt_lklist_f64_count, ReadsHost, [Ptr, F64], I64, Borrowed); + ("list_h", "f64_index_of", lkrt_lklist_f64_index_of, ReadsHost, [Ptr, F64], DynVal, Borrowed); + ("list_h", "str_index_of", lkrt_lklist_str_index_of, ReadsHost, [Ptr, StrPtr], DynVal, Borrowed); + ("list_h", "str_count", lkrt_lklist_str_count, ReadsHost, [Ptr, StrPtr], I64, Borrowed); + ("list_h", "dyn_index_of", lkrt_lklist_dyn_index_of, ReadsHost, [Ptr, DynVal], DynVal, Borrowed); + ("list_h", "dyn_count", lkrt_lklist_dyn_count, ReadsHost, [Ptr, DynVal], I64, Borrowed); ("list_h", "str_contains", lkrt_lklist_str_contains, ReadsHost, [Ptr, StrPtr], I64, Borrowed); ("list_h", "i64_slice", lkrt_lklist_i64_slice, WritesHost, [Ptr, I64, I64], Ptr, Constructs); - // `.slice(start[, end])` method semantics: negative aborts (the - // VM's loud non-negative-index error), `end` clamps to len. - ("list_h", "i64_slice_method", lkrt_lklist_i64_slice_method, WritesHost, [Ptr, I64, I64], Ptr, Constructs); + // The other carriers, sharing `slice_bounds` with the one above: + // two-argument `slice` lowered only on `Int`, so `xs.slice(1, 3)` + // dropped a module to the VM for a reason no program can see. + ("list_h", "f64_slice", lkrt_lklist_f64_slice, WritesHost, [Ptr, I64, I64], Ptr, Constructs); + ("list_h", "str_slice", lkrt_lklist_str_slice, WritesHost, [Ptr, I64, I64], Ptr, Constructs); + ("list_h", "dyn_slice", lkrt_lklist_dyn_slice, WritesHost, [Ptr, I64, I64], Ptr, Constructs); + // `.slice(start[, end])` is a **window**, not a copy — see the + // `slice_h` block below. (`i64_slice` above stays a copy: `xs[1..5]` + // is a range index, which the VM materializes.) ("list_h", "i64_sort", lkrt_lklist_i64_sort, WritesHost, [Ptr], Ptr, Constructs); + ("list_h", "f64_sort", lkrt_lklist_f64_sort, WritesHost, [Ptr], Ptr, Constructs); + ("list_h", "str_sort", lkrt_lklist_str_sort, WritesHost, [Ptr], Ptr, Constructs); + // The boxed carrier, whose order is `dyn_compare` — the VM's + // cross-kind comparison, mirrored with a conformance test rather + // than copied (see `vm_mirror`). + ("list_h", "dyn_sort", lkrt_lklist_dyn_sort, WritesHost, [Ptr], Ptr, Constructs); ("list_h", "i64_reverse", lkrt_lklist_i64_reverse, WritesHost, [Ptr], Ptr, Constructs); + ("list_h", "f64_reverse", lkrt_lklist_f64_reverse, WritesHost, [Ptr], Ptr, Constructs); + ("list_h", "str_reverse", lkrt_lklist_str_reverse, WritesHost, [Ptr], Ptr, Constructs); + ("list_h", "dyn_reverse", lkrt_lklist_dyn_reverse, WritesHost, [Ptr], Ptr, Constructs); + // List windows (`lkrt::lkslice`): `xs.slice(a, b)` reads through to + // `xs` instead of copying it, matching `HeapValue::Slice` in the VM. + // `ConstructsView` is what keeps the source alive for as long as the + // window can address it. `get_pair` (by-value `Maybe`) is + // declared in codegen, like the list and map variants. + ("slice_h", "i64_new", lkrt_lkslice_i64_new, WritesHost, [Ptr, I64, I64], Ptr, ConstructsView); + ("slice_h", "i64_sub", lkrt_lkslice_i64_sub, WritesHost, [Ptr, I64, I64], Ptr, ConstructsView); + ("slice_h", "i64_len", lkrt_lkslice_i64_len, ReadsHost, [Ptr], I64, Borrowed); + // The copy, asked for by name. Its result windows nothing, so it is + // an ordinary `Constructs`. + ("slice_h", "i64_to_list", lkrt_lkslice_i64_to_list, WritesHost, [Ptr], Ptr, Constructs); + // Reads *through* the window rather than materializing it: a + // window exists so that asking for a sum does not build a list. + ("slice_h", "i64_sum", lkrt_lkslice_i64_sum, ReadsHost, [Ptr], I64, Borrowed); + ("slice_h", "i64_min", lkrt_lkslice_i64_min, ReadsHost, [Ptr], DynVal, Borrowed); + ("slice_h", "i64_max", lkrt_lkslice_i64_max, ReadsHost, [Ptr], DynVal, Borrowed); + ("slice_h", "i64_contains", lkrt_lkslice_i64_contains, ReadsHost, [Ptr, I64], I64, Borrowed); + ("slice_h", "i64_index_of", lkrt_lkslice_i64_index_of, ReadsHost, [Ptr, I64], DynVal, Borrowed); + ("slice_h", "i64_count", lkrt_lkslice_i64_count, ReadsHost, [Ptr, I64], I64, Borrowed); + // Sub-windows, and `WritesHost` because a negative count raises. + ("slice_h", "i64_take", lkrt_lkslice_i64_take, WritesHost, [Ptr, I64], Ptr, ConstructsView); + ("slice_h", "i64_skip", lkrt_lkslice_i64_skip, WritesHost, [Ptr, I64], Ptr, ConstructsView); + ("slice_h", "i64_display", lkrt_lkslice_i64_display, WritesHost, [Ptr], StrPtr, Borrowed); // String-keyed map handle. `get_pair` (returning a by-value `Maybe`) is // declared directly in codegen, like the list variant. ("map_h", "str_i64_new", lkrt_lkmap_str_i64_new, WritesHost, [], Ptr, Constructs); + ("map_h", "str_i64_new_sized", lkrt_lkmap_str_i64_new_sized, WritesHost, [I64], Ptr, Constructs); ("map_h", "str_i64_set", lkrt_lkmap_str_i64_set, WritesHost, [Ptr, StrPtr, I64], Nil, Borrowed); + ("map_h", "str_i64_set_const", lkrt_lkmap_str_i64_set_const, WritesHost, [Ptr, StrPtr, I64], Nil, Borrowed); ("map_h", "str_i64_len", lkrt_lkmap_str_i64_len, ReadsHost, [Ptr], I64, Borrowed); + // Typed-map display. The order is the carrier's own iteration order, + // which `vm_mirror` pins to the VM's. + ("map_h", "str_i64_display", lkrt_lkmap_str_i64_display, WritesHost, [Ptr], StrPtr, Borrowed); + ("map_h", "str_f64_display", lkrt_lkmap_str_f64_display, WritesHost, [Ptr], StrPtr, Borrowed); + ("map_h", "str_bool_display", lkrt_lkmap_str_bool_display, WritesHost, [Ptr], StrPtr, Borrowed); + ("map_h", "i64_i64_display", lkrt_lkmap_i64_i64_display, WritesHost, [Ptr], StrPtr, Borrowed); + ("map_h", "i64_i64_iter_pairs", lkrt_lkmap_i64_i64_iter_pairs, WritesHost, [Ptr], Ptr, Constructs); + // `.keys()` / `.values()` on an *int*-keyed map. The string-keyed + // carriers have had these all along; without them `{1: 2}.keys()` + // was the one container question the native build could not answer, + // and it dropped the whole program to the VM. + ("map_h", "i64_i64_keys", lkrt_lkmap_i64_i64_keys, WritesHost, [Ptr], Ptr, Constructs); + ("map_h", "i64_i64_values", lkrt_lkmap_i64_i64_values, WritesHost, [Ptr], Ptr, Constructs); + ("map_h", "i64_f64_keys", lkrt_lkmap_i64_f64_keys, WritesHost, [Ptr], Ptr, Constructs); + ("map_h", "i64_f64_values", lkrt_lkmap_i64_f64_values, WritesHost, [Ptr], Ptr, Constructs); + ("map_h", "i64_f64_iter_pairs", lkrt_lkmap_i64_f64_iter_pairs, WritesHost, [Ptr], Ptr, Constructs); + ("map_h", "i64_f64_display", lkrt_lkmap_i64_f64_display, WritesHost, [Ptr], StrPtr, Borrowed); // `{ ..rest }`: a fresh handle with one key removed (chained per key). ("map_h", "str_i64_without", lkrt_lkmap_str_i64_without, WritesHost, [Ptr, StrPtr], Ptr, Constructs); ("map_h", "str_f64_without", lkrt_lkmap_str_f64_without, WritesHost, [Ptr, StrPtr], Ptr, Constructs); @@ -348,9 +660,12 @@ macro_rules! for_each_abi_fn { ("map_h", "i64_i64_new", lkrt_lkmap_i64_i64_new, WritesHost, [], Ptr, Constructs); ("map_h", "i64_i64_set", lkrt_lkmap_i64_i64_set, WritesHost, [Ptr, I64, I64], Nil, Borrowed); ("map_h", "i64_i64_len", lkrt_lkmap_i64_i64_len, ReadsHost, [Ptr], I64, Borrowed); + ("map_h", "i64_i64_delete", lkrt_lkmap_i64_i64_delete, WritesHost, [Ptr, I64], DynVal, Borrowed); // String-keyed, f64-valued map. `get_pair` (by-value `Maybe`) → codegen. ("map_h", "str_f64_new", lkrt_lkmap_str_f64_new, WritesHost, [], Ptr, Constructs); + ("map_h", "str_f64_new_sized", lkrt_lkmap_str_f64_new_sized, WritesHost, [I64], Ptr, Constructs); ("map_h", "str_f64_set", lkrt_lkmap_str_f64_set, WritesHost, [Ptr, StrPtr, F64], Nil, Borrowed); + ("map_h", "str_f64_set_const", lkrt_lkmap_str_f64_set_const, WritesHost, [Ptr, StrPtr, F64], Nil, Borrowed); ("map_h", "str_f64_len", lkrt_lkmap_str_f64_len, ReadsHost, [Ptr], I64, Borrowed); // Int-keyed, f64-valued map. `get_pair` (by-value `Maybe`) → codegen. // Composite string-int key store (`m["n${i}"] = v`): the key is built @@ -360,6 +675,7 @@ macro_rules! for_each_abi_fn { ("map_h", "i64_f64_new", lkrt_lkmap_i64_f64_new, WritesHost, [], Ptr, Constructs); ("map_h", "i64_f64_set", lkrt_lkmap_i64_f64_set, WritesHost, [Ptr, I64, F64], Nil, Borrowed); ("map_h", "i64_f64_len", lkrt_lkmap_i64_f64_len, ReadsHost, [Ptr], I64, Borrowed); + ("map_h", "i64_f64_delete", lkrt_lkmap_i64_f64_delete, WritesHost, [Ptr, I64], DynVal, Borrowed); // Byte-wise string comparison, returning -1/0/1 (the caller compares to 0). ("str", "cmp", lkrt_str_cmp, Pure, [StrPtr, StrPtr], I64); // `a ++ b` → a freshly allocated C string (`WritesHost`: allocates/leaks). @@ -378,19 +694,32 @@ macro_rules! for_each_abi_fn { ("str", "lower", lkrt_str_lower, WritesHost, [StrPtr], StrPtr); ("str", "upper", lkrt_str_upper, WritesHost, [StrPtr], StrPtr); ("str", "trim", lkrt_str_trim, WritesHost, [StrPtr], StrPtr); - ("str", "find", lkrt_str_find, Pure, [StrPtr, StrPtr], I64); - ("str", "substring", lkrt_str_substring, WritesHost, [StrPtr, I64, I64], StrPtr); + ("str", "index_of", lkrt_str_index_of, WritesHost, [StrPtr, StrPtr], DynVal); ("str", "reverse", lkrt_str_reverse, WritesHost, [StrPtr], StrPtr); ("str", "repeat", lkrt_str_repeat, WritesHost, [StrPtr, I64], StrPtr); ("str", "replace", lkrt_str_replace, WritesHost, [StrPtr, StrPtr, StrPtr], StrPtr); + ("str", "replace_limited", lkrt_str_replace_limited, WritesHost, [StrPtr, StrPtr, StrPtr, I64], StrPtr); ("str", "chars", lkrt_str_chars, WritesHost, [StrPtr], Ptr, Constructs); // `string.strip_prefix/suffix` return String-or-nil (boxed Dyn); - // `count` counts non-overlapping matches (empty needle → byte - // len + 1, the stdlib module's exact rule); `capitalize`/`title` - // are Unicode-aware, byte-identical to the stdlib module. + // `count` counts non-overlapping matches, the empty needle included + // (one between every pair of *characters*, which is what + // `str::matches` answers); `capitalize`/`title`/`strip`/`pad_*` are + // Unicode-aware and character-counted, byte-identical to the VM's + // `core_methods`. ("str", "strip_prefix", lkrt_str_strip_prefix, WritesHost, [StrPtr, StrPtr], DynVal); ("str", "strip_suffix", lkrt_str_strip_suffix, WritesHost, [StrPtr, StrPtr], DynVal); + ("str", "strip", lkrt_str_strip, WritesHost, [StrPtr, StrPtr], StrPtr); + ("str", "pad_left", lkrt_str_pad_left, WritesHost, [StrPtr, I64, StrPtr], StrPtr); + ("str", "pad_right", lkrt_str_pad_right, WritesHost, [StrPtr, I64, StrPtr], StrPtr); + // Text → number, the only path there is; the answer is boxed + // because the module returns `Int?`/`Float?`. + ("str", "to_int", lkrt_str_to_int, Pure, [StrPtr, I64], DynVal); + ("str", "to_float", lkrt_str_to_float, Pure, [StrPtr], DynVal); ("str", "count", lkrt_str_count, Pure, [StrPtr, StrPtr], I64); + // Guarded counts: `WritesHost` because a negative one raises, which + // is an observable effect codegen must not optimize away. + ("str", "take", lkrt_str_take, WritesHost, [StrPtr, I64], StrPtr); + ("str", "skip", lkrt_str_skip, WritesHost, [StrPtr, I64], StrPtr); ("str", "capitalize", lkrt_str_capitalize, WritesHost, [StrPtr], StrPtr); ("str", "title", lkrt_str_title, WritesHost, [StrPtr], StrPtr); ("str", "char_at", lkrt_str_char_at, WritesHost, [StrPtr, I64], DynVal); @@ -399,6 +728,8 @@ macro_rules! for_each_abi_fn { ("str", "split", lkrt_str_split, WritesHost, [StrPtr, StrPtr], Ptr, Constructs); // Scalar → display string (the VM's `ToString`), allocating/leaking a C string. ("str", "from_i64", lkrt_i64_to_str, WritesHost, [I64], StrPtr); + // The unsigned reading of the carrier — see `lkrt_u64_to_str`. + ("str", "from_u64", lkrt_u64_to_str, WritesHost, [I64], StrPtr); ("str", "from_f64", lkrt_f64_to_str, WritesHost, [F64], StrPtr); ("str", "from_bool", lkrt_bool_to_str, WritesHost, [I64], StrPtr); // Divisor-guarded arithmetic: abort on a zero divisor (matching the VM's fatal @@ -427,10 +758,20 @@ macro_rules! for_each_abi_fn { // `!x` on a boxed value: Bool negates, Nil is true, anything else // is the VM's loud type error. ("dyn", "not", lkrt_dyn_not, ReadsHost, [DynVal], I64); + // `-x` on a boxed value: Int and Float negate, anything else is + // the VM's loud type error. + ("dyn", "neg", lkrt_dyn_neg, ReadsHost, [DynVal], DynVal); ("dyn", "as_i64", lkrt_dyn_as_i64, ReadsHost, [DynVal], I64); ("dyn", "cast_to_i64", lkrt_dyn_cast_to_i64, ReadsHost, [DynVal], I64); ("dyn", "as_f64", lkrt_dyn_as_f64, ReadsHost, [DynVal], F64); ("dyn", "as_str", lkrt_dyn_as_str, ReadsHost, [DynVal], StrPtr); + // The same two conversions for a *map key*, which refuse a type no + // map can key by name instead of with the generic type error. + // The shape tests, which are five tags and six rather than one each. + ("dyn", "is_list", lkrt_dyn_is_list, Pure, [DynVal], I64); + ("dyn", "is_map", lkrt_dyn_is_map, Pure, [DynVal], I64); + ("dyn", "as_key_i64", lkrt_dyn_as_key_i64, ReadsHost, [DynVal], I64); + ("dyn", "as_key_str", lkrt_dyn_as_key_str, ReadsHost, [DynVal], StrPtr); // Deliberately `Retained`: this returns the *existing* handle held // inside the boxed value (`v.payload`), not a fresh one — treating // it as a constructor would let the pass free someone else's list. @@ -449,8 +790,60 @@ macro_rules! for_each_abi_fn { ("dyn", "ge", lkrt_dyn_ge, ReadsHost, [DynVal, DynVal], I64); ("dyn", "index", lkrt_dyn_index, ReadsHost, [DynVal, I64], DynVal); ("dyn", "get", lkrt_dyn_get, ReadsHost, [DynVal, DynVal], DynVal); + ("dyn", "map_get_or", lkrt_dyn_map_get_or, ReadsHost, [DynVal, DynVal, DynVal], DynVal); + ("dyn", "clear", lkrt_dyn_clear, WritesHost, [DynVal], Nil); + ("dyn", "from_chan", lkrt_dyn_from_chan, Pure, [I64], DynVal); + ("dyn", "from_task", lkrt_dyn_from_task, Pure, [I64], DynVal); + ("dyn", "from_stream", lkrt_dyn_from_stream, Pure, [Ptr], DynVal); + ("dyn", "stream_list", lkrt_dyn_stream_list, Pure, [DynVal], Ptr); + ("dyn", "as_handle", lkrt_dyn_as_handle, Pure, [DynVal], I64); + ("dyn", "list_insert", lkrt_dyn_list_insert, WritesHost, [DynVal, I64, DynVal], Nil); + ("dyn", "list_remove_at", lkrt_dyn_list_remove_at, WritesHost, [DynVal, I64], DynVal); + ("dyn", "list_drop_last", lkrt_dyn_list_drop_last, WritesHost, [DynVal], Nil); ("dyn", "from_map", lkrt_dyn_from_map, Pure, [Ptr], DynVal); + // `Set`/`Bytes` in the boxed universe: without these two tags they + // could not enter a mixed container, a struct field, or a bridged + // return at all. + ("dyn", "from_typed_map", lkrt_dyn_from_typed_map, Pure, [Ptr, I64], DynVal); + // A typed list boxes in place too. `list_h.*_to_dyn` still exists — + // it is the element-wise *conversion* a mixed-list method result + // needs — but boxing must not go through it: the copy is a + // different list, and both directions of aliasing died on it. + ("dyn", "from_typed_list", lkrt_dyn_from_typed_list, Pure, [Ptr, I64], DynVal); + // `push` through a boxed receiver reaches the carrier itself; see + // `dyn.as_list`, which is read-only for exactly this reason. + ("dyn", "list_push", lkrt_dyn_list_push, WritesHost, [DynVal, DynVal], Nil, Borrowed); + ("dyn", "from_set", lkrt_dyn_from_set, Pure, [Ptr], DynVal); + ("dyn", "from_bytes", lkrt_dyn_from_bytes, Pure, [Ptr], DynVal); + // A window boxes in place, like `from_set`/`from_bytes`; `as_slice` + // is the read-back the `try` cell path needs, and both have call + // sites (a row without one is unverified, not available). + ("dyn", "from_slice", lkrt_dyn_from_slice, Pure, [Ptr], DynVal); + ("dyn", "as_slice", lkrt_dyn_as_slice, ReadsHost, [DynVal], Ptr); ("dyn", "field", lkrt_dyn_field, ReadsHost, [DynVal, StrPtr], DynVal); + // The same read by position — see `lkrt_dyn_field_at`. + ("dyn", "field_at", lkrt_dyn_field_at, ReadsHost, [DynVal, I64, StrPtr, I64], DynVal); + // Map methods on a *boxed* receiver. `as_map` cannot serve them: + // it hands back a `str_dyn` handle, and a typed map boxed in place + // is still its own carrier. Dispatched per operation rather than + // per unbox because `delete` writes — a materialized copy would + // answer the reads and drop the write. + ("dyn", "to_iter", lkrt_dyn_to_iter, WritesHost, [DynVal], Ptr); + // `needle in v` on a boxed haystack. A map answers key membership + // and every other container element membership, which is a + // run-time choice — the lowering had no `Dyn` arm at all, so the + // whole program fell back. + ("dyn", "contains", lkrt_dyn_contains, ReadsHost, [DynVal, DynVal], I64); + ("dyn", "seq_contains", lkrt_dyn_seq_contains, ReadsHost, [DynVal, DynVal], I64); + ("dyn", "map_pairs", lkrt_dyn_map_pairs, WritesHost, [DynVal], Ptr, Constructs); + ("dyn", "map_keys", lkrt_dyn_map_keys, WritesHost, [DynVal], Ptr, Constructs); + ("dyn", "map_values", lkrt_dyn_map_values, WritesHost, [DynVal], Ptr, Constructs); + ("dyn", "map_has", lkrt_dyn_map_has, ReadsHost, [DynVal, StrPtr], I64); + ("dyn", "map_delete", lkrt_dyn_map_delete, WritesHost, [DynVal, StrPtr], DynVal); + // `c[k] = v` through a boxed receiver, for either container: the + // key is boxed so one row can carry both spellings, since which of + // them a tag accepts is what the callee decides. + ("dyn", "index_set", lkrt_dyn_index_set, WritesHost, [DynVal, DynVal, DynVal], Nil, Borrowed); ("dyn", "len_of", lkrt_dyn_len_of, ReadsHost, [DynVal], I64); ("dyn", "display", lkrt_dyn_display, WritesHost, [DynVal], StrPtr); ("dyn", "display_quoted", lkrt_dyn_display_quoted, WritesHost, [DynVal], StrPtr); @@ -462,17 +855,41 @@ macro_rules! for_each_abi_fn { // marked map would leave a stale entry that a later allocation at // the same address would inherit. ("map_h", "obj_mark", lkrt_lkmap_obj_mark, WritesHost, [Ptr, I64], Nil); + ("map_h", "obj_mark_checked", lkrt_lkmap_obj_mark_checked, WritesHost, [Ptr, I64], Nil); + // A struct type's name and field order, described once at startup + // so `display` can render a marked instance the way the VM does + // (declaration order, nested values quoted). Two calls rather than + // a static table: these are shapes the ABI already has. + ("obj_ty", "begin", lkrt_struct_type_begin, WritesHost, [I64, StrPtr], Nil); + ("obj_ty", "field", lkrt_struct_type_field, WritesHost, [I64, StrPtr, I64], Nil); + // A store the lowering could not rule out statically. The declared + // code is a constant here, so this is a tag compare — no table. + ("obj_ty", "check", lkrt_check_declared_field, ReadsHost, [StrPtr, StrPtr, I64, DynVal], Nil); + // The same check when only the *mark* knows the struct type. + ("obj_ty", "check_marked", lkrt_check_marked_field, ReadsHost, [Ptr, StrPtr, DynVal], Nil); + ("obj_ty", "check_marked_dyn", lkrt_check_marked_field_dyn, ReadsHost, [Ptr, DynVal, DynVal], Nil); ("dyn", "obj_type_id", lkrt_dyn_obj_type_id, ReadsHost, [DynVal], I64); + ("dyn", "dispatch_type_id", lkrt_dyn_dispatch_type_id, ReadsHost, [DynVal], I64); + // `typeof(x)` where the carrier could be a struct instance at run + // time (`Dyn`, `MapStrDyn`): the answer is the declared name, which + // only the runtime's type table has. + ("dyn", "type_name", lkrt_dyn_type_name, ReadsHost, [DynVal], StrPtr); ("dyn", "method_missing", lkrt_dyn_method_missing, WritesHost, [], Nil); ("map_h", "str_dyn_new", lkrt_lkmap_str_dyn_new, WritesHost, [], Ptr, Constructs); + ("map_h", "str_dyn_new_sized", lkrt_lkmap_str_dyn_new_sized, WritesHost, [I64], Ptr, Constructs); ("map_h", "str_dyn_set", lkrt_lkmap_str_dyn_set, WritesHost, [Ptr, StrPtr, DynVal], Nil, Borrowed); + ("map_h", "str_dyn_set_const", lkrt_lkmap_str_dyn_set_const, WritesHost, [Ptr, StrPtr, DynVal], Nil, Borrowed); ("map_h", "str_dyn_get", lkrt_lkmap_str_dyn_get, ReadsHost, [Ptr, StrPtr], DynVal, Borrowed); + // The same read by position, with the key as the check — see + // `lkrt_lkmap_str_dyn_get_at`. + ("map_h", "str_dyn_get_at", lkrt_lkmap_str_dyn_get_at, ReadsHost, [Ptr, I64, StrPtr, I64], DynVal); ("map_h", "str_dyn_len", lkrt_lkmap_str_dyn_len, ReadsHost, [Ptr], I64, Borrowed); ("map_h", "str_dyn_has", lkrt_lkmap_str_dyn_has, ReadsHost, [Ptr, StrPtr], I64, Borrowed); ("map_h", "str_dyn_without", lkrt_lkmap_str_dyn_without, WritesHost, [Ptr, StrPtr], Ptr, Constructs); // Struct update (`P { ..base, k: v }`): the VM's merge_field_maps // two-step insertion + make_struct's fresh field copy. ("map_h", "str_dyn_merge", lkrt_lkmap_str_dyn_merge, WritesHost, [Ptr, Ptr], Ptr, Constructs); + ("map_h", "str_dyn_merge_typed", lkrt_lkmap_str_dyn_merge_typed, WritesHost, [Ptr, Ptr, I64], Ptr, Constructs); ("map_h", "str_dyn_rebuild", lkrt_lkmap_str_dyn_rebuild, WritesHost, [Ptr], Ptr, Constructs); // Map-literal protocol (VM-order mirror, plan D1): stage-1 build // in source order, then finish into the typed carrier — the @@ -491,6 +908,15 @@ macro_rules! for_each_abi_fn { ("map_h", "str_i64_iter_pairs", lkrt_lkmap_str_i64_iter_pairs, WritesHost, [Ptr], Ptr, Constructs); ("map_h", "str_i64_keys", lkrt_lkmap_str_i64_keys, WritesHost, [Ptr], Ptr, Constructs); ("map_h", "str_i64_values", lkrt_lkmap_str_i64_values, WritesHost, [Ptr], Ptr, Constructs); + // `clear` was the one container method the map lacked while the + // list and the set both had it, so `m.clear()` dropped its module to + // the VM. `Map` rides the `str_i64` carrier, so five + // helpers cover the six map types the MIR distinguishes. + ("map_h", "str_i64_clear", lkrt_lkmap_str_i64_clear, WritesHost, [Ptr], Nil, Borrowed); + ("map_h", "i64_i64_clear", lkrt_lkmap_i64_i64_clear, WritesHost, [Ptr], Nil, Borrowed); + ("map_h", "str_f64_clear", lkrt_lkmap_str_f64_clear, WritesHost, [Ptr], Nil, Borrowed); + ("map_h", "i64_f64_clear", lkrt_lkmap_i64_f64_clear, WritesHost, [Ptr], Nil, Borrowed); + ("map_h", "str_dyn_clear", lkrt_lkmap_str_dyn_clear, WritesHost, [Ptr], Nil, Borrowed); ("map_h", "str_i64_delete", lkrt_lkmap_str_i64_delete, WritesHost, [Ptr, StrPtr], DynVal, Borrowed); ("map_h", "str_f64_iter_pairs", lkrt_lkmap_str_f64_iter_pairs, WritesHost, [Ptr], Ptr, Constructs); ("map_h", "str_f64_keys", lkrt_lkmap_str_f64_keys, WritesHost, [Ptr], Ptr, Constructs); @@ -508,9 +934,6 @@ macro_rules! for_each_abi_fn { // Typed map → `Map` conversion (cold: a typed map // crossing a `try$call` cell boundary boxes). Replayed inserts in // iteration order keep the layout — same keys, same order. - ("map_h", "str_i64_to_dyn", lkrt_lkmap_str_i64_to_dyn, WritesHost, [Ptr], Ptr, Constructs); - ("map_h", "str_f64_to_dyn", lkrt_lkmap_str_f64_to_dyn, WritesHost, [Ptr], Ptr, Constructs); - ("map_h", "str_bool_to_dyn", lkrt_lkmap_str_bool_to_dyn, WritesHost, [Ptr], Ptr, Constructs); ("list_h", "f64_to_dyn", lkrt_lklist_f64_to_dyn, WritesHost, [Ptr], Ptr, Constructs); ("list_h", "str_to_dyn", lkrt_lklist_str_to_dyn, WritesHost, [Ptr], Ptr, Constructs); ("list_h", "dyn_new", lkrt_lklist_dyn_new, WritesHost, [], Ptr, Constructs); @@ -526,6 +949,9 @@ macro_rules! for_each_abi_fn { ("list_h", "dyn_flatten", lkrt_lklist_dyn_flatten, WritesHost, [Ptr], Ptr, Constructs); ("list_h", "dyn_slice_from", lkrt_lklist_dyn_slice_from, WritesHost, [Ptr, I64], Ptr, Constructs); ("list_h", "dyn_contains", lkrt_lklist_dyn_contains, ReadsHost, [Ptr, DynVal], I64, Borrowed); + ("list_h", "dyn_drop_last", lkrt_lklist_dyn_drop_last, WritesHost, [Ptr], Nil, Borrowed); + ("list_h", "dyn_insert", lkrt_lklist_dyn_insert, WritesHost, [Ptr, I64, DynVal], Nil, Borrowed); + ("list_h", "dyn_remove_at", lkrt_lklist_dyn_remove_at, WritesHost, [Ptr, I64], DynVal, Borrowed); ("list_h", "dyn_take", lkrt_lklist_dyn_take, WritesHost, [Ptr, I64], Ptr, Constructs); ("list_h", "dyn_skip", lkrt_lklist_dyn_skip, WritesHost, [Ptr, I64], Ptr, Constructs); ("list_h", "dyn_chain", lkrt_lklist_dyn_chain, WritesHost, [Ptr, Ptr], Ptr, Constructs); @@ -535,6 +961,12 @@ macro_rules! for_each_abi_fn { ("list_h", "dyn_map_fn", lkrt_lklist_dyn_map_fn, WritesHost, [Ptr, Ptr], Ptr, Constructs); ("list_h", "dyn_filter_fn", lkrt_lklist_dyn_filter_fn, WritesHost, [Ptr, Ptr], Ptr, Constructs); ("list_h", "dyn_reduce_fn", lkrt_lklist_dyn_reduce_fn, WritesHost, [Ptr, DynVal, Ptr], DynVal, Borrowed); + // The same three folds with the callback as a *closure value* — a + // callback the lowering cannot name, because it came out of a + // container or a parameter. + ("list_h", "dyn_map_closure", lkrt_lklist_dyn_map_closure, WritesHost, [Ptr, DynVal], Ptr, Constructs); + ("list_h", "dyn_filter_closure", lkrt_lklist_dyn_filter_closure, WritesHost, [Ptr, DynVal], Ptr, Constructs); + ("list_h", "dyn_reduce_closure", lkrt_lklist_dyn_reduce_closure, WritesHost, [Ptr, DynVal, DynVal], DynVal, Borrowed); ("list_h", "str_map_fn", lkrt_lklist_str_map_fn, WritesHost, [Ptr, Ptr], Ptr, Constructs); ("list_h", "str_filter_fn", lkrt_lklist_str_filter_fn, WritesHost, [Ptr, Ptr], Ptr, Constructs); ("list_h", "i64_unique", lkrt_lklist_i64_unique, WritesHost, [Ptr], Ptr, Constructs); @@ -544,17 +976,32 @@ macro_rules! for_each_abi_fn { ("set", "new", lkrt_lkset_new, WritesHost, [], Ptr, Constructs); ("set", "from_str_list", lkrt_lkset_from_str_list, WritesHost, [Ptr], Ptr, Constructs); ("set", "from_i64_list", lkrt_lkset_from_i64_list, WritesHost, [Ptr], Ptr, Constructs); + ("set", "from_dyn_list", lkrt_lkset_from_dyn_list, WritesHost, [Ptr], Ptr, Constructs); ("set", "has", lkrt_lkset_has, ReadsHost, [Ptr, DynVal], I64, Borrowed); ("set", "add", lkrt_lkset_add, WritesHost, [Ptr, DynVal], I64, Borrowed); ("set", "delete", lkrt_lkset_delete, WritesHost, [Ptr, DynVal], I64, Borrowed); ("set", "len", lkrt_lkset_len, ReadsHost, [Ptr], I64, Borrowed); ("set", "clear", lkrt_lkset_clear, WritesHost, [Ptr], Nil, Borrowed); + ("set", "display", lkrt_lkset_display, WritesHost, [Ptr], StrPtr, Borrowed); + // The set operations. `kind` selects which — one row per *shape* + // rather than seven rows, because the four combining operations + // differ only in which members they keep and all four must fill the + // answer in the same stated sequence. + ("set", "combine", lkrt_lkset_combine, WritesHost, [Ptr, Ptr, I64], Ptr, Constructs); + ("set", "relate", lkrt_lkset_relate, ReadsHost, [Ptr, Ptr, I64], I64, Borrowed); + ("set", "eq", lkrt_lkset_eq, ReadsHost, [Ptr, Ptr], I64, Borrowed); + ("set", "iter", lkrt_lkset_iter, WritesHost, [Ptr], Ptr, Constructs); ("arith", "i64_div", lkrt_i64_div_checked, ReadsHost, [I64, I64], I64); ("arith", "i64_mod", lkrt_i64_mod_checked, ReadsHost, [I64, I64], I64); ("arith", "f64_div", lkrt_f64_div_checked, ReadsHost, [F64, F64], F64); ("arith", "f64_mod", lkrt_f64_mod_checked, ReadsHost, [F64, F64], F64); ("arith", "i64_shl", lkrt_i64_shl_checked, ReadsHost, [I64, I64], I64); ("arith", "i64_shr", lkrt_i64_shr_checked, ReadsHost, [I64, I64], I64); + ("arith", "u64_shr", lkrt_u64_shr_checked, ReadsHost, [I64, I64], I64); + ("arith", "u64_lt", lkrt_u64_lt, Pure, [I64, I64], I64); + ("arith", "u64_div", lkrt_u64_div, ReadsHost, [I64, I64], I64); + ("arith", "u64_rem", lkrt_u64_rem, ReadsHost, [I64, I64], I64); + ("arith", "u64_to_f64", lkrt_u64_to_f64, Pure, [I64], F64); } }; } @@ -603,6 +1050,37 @@ pub fn find(module: &str, name: &str) -> Option<&'static AbiFn> { mod tests { use super::*; + /// A call that answers something different each time may not be `Pure`. + /// + /// `Pure` is what the MIR CSE pass keys on: two `Pure` calls with equal + /// arguments in one dominance scope become one. For a clock or a UUID that + /// is a wrong answer, not a slow one — `let a = uuid.v4(); let b = + /// uuid.v4();` would bind the same string twice. These entries take no + /// arguments, which is exactly the case CSE collapses most eagerly, so the + /// classification is pinned here rather than left to whoever adds the next + /// one by copying a neighbouring row. + #[test] + fn nondeterministic_entries_are_not_pure() { + for (module, name) in [ + ("uuid", "v4"), + ("random", "int"), + ("random", "float"), + ("random", "bool"), + ("random", "choice_i64"), + ("random", "shuffle_i64"), + ("os", "clock"), + ("os", "epoch"), + ("time", "now"), + ("datetime", "now"), + ] { + let entry = find(module, name).expect("entry exists"); + assert!( + !matches!(entry.effect, AbiEffect::Pure), + "{module}.{name} is Pure, so CSE may merge two calls that must answer differently" + ); + } + } + #[test] fn symbols_are_unique() { let mut seen = std::collections::HashSet::new(); diff --git a/aot/codegen/src/clif.rs b/aot/codegen/src/clif.rs index 02f271de..cfa2c9cc 100644 --- a/aot/codegen/src/clif.rs +++ b/aot/codegen/src/clif.rs @@ -13,12 +13,12 @@ //! the `{i64,i64}` carriers (`Dyn`/`Maybe*` flow as register pairs, [`Slot`]) so //! maps, dynamic container reads and unwraps lower — the `{double,i64}` float //! carrier goes through the `lkrt_*_f64_get_out` out-pointer shims (its by-value -//! return is not portably modelable); `TraitDispatch`; `TryCall` (via the lkrt +//! return is not portably modelable); `TraitDispatch`; the `try`-region call (via the lkrt //! `setjmp` trampoline); and the Tier 1 hybrid bridge `CallVm`. //! //! [`ClifError::Unsupported`] is now returned only from *within* arms for //! shapes at a capability boundary — e.g. a non-scalar value type in a scalar -//! slot, a `CallVm`/`TryCall` with a float/carrier operand or arity past the +//! slot, a `CallVm` with a float/carrier operand or arity past the //! trampoline cap, or a bridge target absent from `vm_functions`. use std::collections::HashMap; @@ -31,7 +31,9 @@ use cranelift_codegen::isa::{CallConv, TargetIsa}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_module::{DataDescription, DataId, FuncId as ClifFuncId, Linkage, Module, ModuleError}; use cranelift_object::{ObjectBuilder, ObjectModule}; -use lk_aot_mir::{CmpOp, Const, FloatBinOp, FuncId, Inst, IntBinOp, MirFunction, MirModule, Term, Ty, ValueId}; +use lk_aot_mir::{ + CarrierHalf, CmpOp, Const, FloatBinOp, FuncId, Inst, IntBinOp, MirFunction, MirModule, Term, Ty, ValueId, +}; /// Size of one `LkHybridArg` (`#[repr(C)] { i8 tag, i64 value }`): the `i64` /// forces 8-byte alignment, so the tag byte at offset 0 is padded and the value @@ -100,6 +102,8 @@ struct ModuleCtx<'a> { /// Intra-module function return types, for binding a [`Inst::CallFn`] result /// as one value or a `{i64,i64}` pair. fn_rets: &'a HashMap, + /// Machine-word count of each function's signature (see `fn_arity`). + fn_arity: &'a HashMap, helpers: &'a Helpers, /// Declared ABI runtime symbols, cached by C symbol name (declared lazily so /// a program that never calls one does not force its — possibly `DynVal` — @@ -243,6 +247,23 @@ fn ty_clif_parts(ty: Ty) -> Result, ClifError> { }) } +/// The CLIF type for a volatile access `bits` wide, and whether that is already +/// the full machine word. +/// +/// The width belongs to the access, not to the value: a value is always the +/// `i64` the VM carries every machine integer in, and an 8-bit device register +/// is still eight bits. The bool spares both call sites an `if bits == 64` +/// spelled next to a `match` that has already decided the same thing. +fn access_type(bits: u8) -> Result<(cranelift_codegen::ir::Type, bool), ClifError> { + Ok(match bits { + 8 => (types::I8, false), + 16 => (types::I16, false), + 32 => (types::I32, false), + 64 => (types::I64, true), + _ => return Err(ClifError::Unsupported("volatile access width must be 8, 16, 32 or 64")), + }) +} + /// Whether a MIR type is a two-register `{i64,i64}` carrier (see [`Slot`]). fn ty_is_pair(ty: Ty) -> bool { matches!(ty, Ty::Dyn | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeBool | Ty::MaybeStr) @@ -271,6 +292,42 @@ pub fn compile_module(mir: &MirModule, isa: std::sync::Arc) -> Re // `lk_fn_N` with their MIR signatures. let mut fn_ids = HashMap::new(); let mut fn_rets = HashMap::new(); + // How many machine words each function's parameters take, so a try-region + // call can be checked against the body it names. Nothing else checks it: the + // trampoline takes the body's *address* and casts it, so a caller passing + // one word too few leaves the body reading a slot nobody wrote — a wild + // pointer, not a link error. + let mut fn_arity: HashMap = HashMap::new(); + // The bodies of `try` regions, recognized by the only thing that makes a + // function one: something calls it as a region body. Derived rather than + // recorded, so there is no second place that could disagree. + // + // They take their parameters through a pointer to the caller's word buffer + // instead of one machine parameter each (see `body_signature`), which is + // what lets a region cross any number of values: the alternative was a + // hand-written arity switch in `lkrt/src/try_trampoline.c` and a cap the + // lowering had to budget against. + let try_bodies: std::collections::HashSet = mir + .functions + .iter() + .flat_map(|func| func.blocks.iter().flat_map(|block| block.insts.iter())) + .filter_map(|inst| match inst { + Inst::TryRegionCall { func, .. } => Some(*func), + _ => None, + }) + .collect(); + // A body is *only* ever called through the trampoline. If something also + // calls it directly, the two call sites disagree about the signature and one + // of them is wrong — say so rather than emit both. + for func in &mir.functions { + for inst in func.blocks.iter().flat_map(|block| block.insts.iter()) { + if let Inst::CallFn { func: callee, .. } = inst + && try_bodies.contains(callee) + { + return Err(ClifError::Unsupported("a try body is also called directly")); + } + } + } // Exported LK functions by the name the source gave them, so // `symbol_address("name")` can take the address of *this* function rather // than declaring an import that would have to guess a signature — and @@ -284,6 +341,12 @@ pub fn compile_module(mir: &MirModule, isa: std::sync::Arc) -> Re // with the function's own signature. This is how a board names LK // code — an interrupt vector or a C caller cannot reach `lk_fn_7`. (exported.clone(), Linkage::Export, signature_of(func, cc)?) + } else if try_bodies.contains(&func.id) { + ( + format!("lk_fn_{}", func.id.0), + Linkage::Local, + body_signature(func, cc)?, + ) } else { (format!("lk_fn_{}", func.id.0), Linkage::Local, signature_of(func, cc)?) }; @@ -293,6 +356,7 @@ pub fn compile_module(mir: &MirModule, isa: std::sync::Arc) -> Re } fn_ids.insert(func.id, id); fn_rets.insert(func.id, func.ret); + fn_arity.insert(func.id, param_words(func)?); } let helpers = Helpers::declare(&mut module)?; @@ -354,6 +418,8 @@ pub fn compile_module(mir: &MirModule, isa: std::sync::Arc) -> Re let mut ctx = module.make_context(); ctx.func.signature = if is_entry { main_signature(cc) + } else if try_bodies.contains(&func.id) { + body_signature(func, cc)? } else { signature_of(func, cc)? }; @@ -362,6 +428,7 @@ pub fn compile_module(mir: &MirModule, isa: std::sync::Arc) -> Re let mut mctx = ModuleCtx { module: &mut module, fn_ids: &fn_ids, + fn_arity: &fn_arity, fn_rets: &fn_rets, helpers: &helpers, abi_ids: &mut abi_ids, @@ -372,7 +439,15 @@ pub fn compile_module(mir: &MirModule, isa: std::sync::Arc) -> Re hybrid_argbuf, vm_functions: &mir.vm_functions, }; - build_function(func, &mut ctx.func, &mut fb_ctx, &mut mctx, is_entry, mir.abi_version)?; + build_function( + func, + &mut ctx.func, + &mut fb_ctx, + &mut mctx, + is_entry, + try_bodies.contains(&func.id), + mir.abi_version, + )?; } module.define_function(fn_ids[&func.id], &mut ctx)?; module.clear_context(&mut ctx); @@ -443,6 +518,7 @@ pub fn ty_to_clif(ty: Ty) -> Result { // Opaque handles / C-string pointers are pointer-sized. Ty::Str | Ty::ListI64 + | Ty::SliceI64 | Ty::ListF64 | Ty::ListStr | Ty::MapStrI64 @@ -452,6 +528,7 @@ pub fn ty_to_clif(ty: Ty) -> Result { | Ty::MapStrBool | Ty::Cell | Ty::Set + | Ty::Bytes | Ty::ListDyn | Ty::MapStrDyn => types::I64, Ty::Nil => return Err(ClifError::Unsupported("nil value type")), @@ -483,16 +560,62 @@ pub fn signature_of(func: &MirFunction, call_conv: CallConv) -> Result Result { + let mut words = 0; + for (_, ty) in &func.params { + words += ty_clif_parts(*ty)?.len(); + } + Ok(words) +} + +/// The signature of a `try` region's body: one pointer to the caller's word +/// buffer, and nothing back. +/// +/// The caller spills every crossing value into a stack buffer already — that is +/// what `lkrt_rt_try_region` is handed — so the words are in memory before the +/// call whichever way the body reads them. Taking them one Cranelift parameter +/// each meant a C trampoline reloading the buffer into registers through a +/// hand-written arity switch, which cost a round trip and put a **cap** on how +/// many values a region could cross: past eight the switch trapped, so the +/// lowering refused. Reading them out of the buffer directly removes both. +/// +/// Every parameter is exactly one word by construction (`function.rs` splits a +/// carrier into two `I64` and declares an `F64` as `I64`), which is checked here +/// rather than assumed — the buffer has no way to say that a slot was two. +fn body_signature(func: &MirFunction, call_conv: CallConv) -> Result { + for (_, ty) in &func.params { + if ty_clif_parts(*ty)?.len() != 1 { + return Err(ClifError::Unsupported( + "a try body parameter is wider than a machine word", + )); + } + } + if func.ret != Ty::Nil { + return Err(ClifError::Unsupported("a try body returns a value")); + } + let mut sig = Signature::new(call_conv); + sig.params.push(AbiParam::new(types::I64)); + Ok(sig) +} + /// Lower a MIR function body into `clif_func`. When `is_entry`, `clif_func` must -/// be the program `main` (`() -> i32`): its entry block gets an `abi_check` +/// be the program `main` (`() -> i32`): its entry block gets an `rt_begin` /// prologue and its returns print the top-level result before `ret 0`, matching -/// the string-IR backend. +/// the string-IR backend. When `via_argv`, the function is a `try` region's body +/// and its parameters are read out of the pointer it is handed +/// (see [`body_signature`]). +#[allow(clippy::too_many_arguments)] fn build_function( func: &MirFunction, clif_func: &mut Function, fb_ctx: &mut FunctionBuilderContext, mctx: &mut ModuleCtx, is_entry: bool, + via_argv: bool, abi_version: i64, ) -> Result<(), ClifError> { let mut builder = FunctionBuilder::new(clif_func, fb_ctx); @@ -523,11 +646,24 @@ fn build_function( builder.append_block_params_for_function_params(entry); // Bind the function-signature params to the entry block's params, consuming // one or two Cranelift params per MIR value (carriers are a pair). + // + // A try body's single parameter is the *address* of its words instead, so + // the binding is a load per parameter and has to wait until the builder is + // positioned in a block — it happens at the top of the loop below. let entry_params: Vec = builder.block_params(entry).to_vec(); - let mut cursor = 0; - for (vid, ty) in &func.params { - lower.bind_params(*vid, *ty, &entry_params, &mut cursor)?; - } + let argv = if via_argv { + Some( + *entry_params + .first() + .ok_or(ClifError::Unsupported("try body has no argv"))?, + ) + } else { + let mut cursor = 0; + for (vid, ty) in &func.params { + lower.bind_params(*vid, *ty, &entry_params, &mut cursor)?; + } + None + }; // Non-entry blocks carry the SSA phi params as block params (each carrier phi // is two Cranelift block params). for block in &func.blocks { @@ -547,9 +683,30 @@ fn build_function( for block in &func.blocks { let cb = lower.blocks[&block.id]; builder.switch_to_block(cb); + // A try body reads its parameters out of the caller's word buffer. Each + // slot is a full machine word, so the load is `i64` and a narrower + // declared type (a `Bool`, which Cranelift compares as `i8`) is reduced + // from it — rather than loading at the declared width, which would be + // reading whichever end of the slot the machine happens to put first. + if block.id == func.entry + && let Some(argv) = argv + { + for (index, (vid, ty)) in func.params.iter().enumerate() { + let word = builder + .ins() + .load(types::I64, MemFlagsData::trusted(), argv, (index * 8) as i32); + let want = ty_to_clif(*ty)?; + let value = match want { + types::I64 => word, + types::F64 => builder.ins().bitcast(types::F64, MemFlagsData::new(), word), + narrower => builder.ins().ireduce(narrower, word), + }; + lower.set1(*vid, value); + } + } // The entry/`main` guards against ABI drift before any user code runs. if is_entry && block.id == func.entry { - let check = mctx.abi_func(resolve_abi("lkrt", "abi_check")?)?; + let check = mctx.abi_func(resolve_abi("lkrt", "rt_begin")?)?; let version = builder.ins().iconst(types::I64, abi_version); lower.call(&mut builder, mctx, check, None, &[version])?; } @@ -677,11 +834,12 @@ impl Lower { dst: Option, args: &[Value], ) -> Result<(), ClifError> { - self.call_raw(b, mctx, callee, dst, false, args) + self.call_raw(b, mctx, callee, dst, false, args, "a runtime helper") } /// Import `callee` and emit a call with pre-flattened `args`. When `dst` is /// set, bind the result as a `{i64,i64}` pair (`dst_pair`) or a single value. + #[allow(clippy::too_many_arguments)] fn call_raw( &mut self, b: &mut FunctionBuilder, @@ -690,6 +848,10 @@ impl Lower { dst: Option, dst_pair: bool, args: &[Value], + // What to call the callee in an error. Cranelift's own name for it is + // `userextname7`, which is no help at all; the call site knows the + // symbol, so it hands it down. + callee_name: &str, ) -> Result<(), ClifError> { let func_ref = mctx.module.declare_func_in_func(callee, b.func); // Checked here rather than left to the Cranelift verifier: the verifier @@ -701,9 +863,8 @@ impl Lower { .params .len(); if expected != args.len() { - let name = b.func.dfg.ext_funcs[func_ref].name.display(None).to_string(); return Err(ClifError::Module(format!( - "call to {name} passes {} machine argument(s), declared with {expected}", + "call to {callee_name} passes {} machine argument(s), declared with {expected}", args.len() ))); } @@ -721,9 +882,13 @@ impl Lower { ); self.set2(dst, a, c); } else { - let v = *results - .first() - .ok_or(ClifError::Unsupported("call has no result for dst"))?; + // Naming the callee: without it this says only that *some* call + // produced nothing for a destination, and the whole point of + // checking here rather than leaving it to the Cranelift verifier + // is to have a name to start from. + let v = *results.first().ok_or_else(|| { + ClifError::Module(format!("call to {callee_name} produces no result, but one is wanted")) + })?; self.set1(dst, v); } } @@ -848,6 +1013,48 @@ impl Lower { }; self.set1(*dst, v); } + Inst::TryRegionCall { dst, func, args } => { + // `lkrt_rt_try_region(body)`: the trampoline pushes a handler, + // `setjmp`s in its own C frame, calls the body, and answers 1 + // for "returned" / 0 for "raised". The body's address is taken + // rather than called here, because what must not appear in this + // function is the `setjmp` — a call that returns twice. + let callee = *mctx + .fn_ids + .get(func) + .ok_or(ClifError::Unsupported("try body is not a declared function"))?; + let reference = mctx.module.declare_func_in_func(callee, b.func); + let body_addr = b.ins().func_addr(types::I64, reference); + // The inputs travel as machine words in a stack buffer, which is + // what the body reads them back out of. The count has to agree + // with what the body expects: the call goes through an address + // and a cast, so a disagreement is not a link error — the body + // loads a slot nobody wrote. One `try` whose region was found at + // the wrong pc built exactly that: a five-word body called with + // four, which ran and dereferenced whatever the fifth slot held. + if mctx.fn_arity.get(func) != Some(&args.len()) { + return Err(ClifError::Unsupported( + "try-region call disagrees with the body's arity", + )); + } + let slot_bytes = (args.len().max(1) * 8) as u32; + let args_slot = + b.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, slot_bytes, 3)); + for (i, arg) in args.iter().enumerate() { + let word = self.v(*arg)?; + b.ins().stack_store(word, args_slot, (i * 8) as i32); + } + let argv = b.ins().stack_addr(types::I64, args_slot, 0); + let tramp = mctx.raw_func("lkrt_rt_try_region", &[types::I64; 2], &[types::I64])?; + let tramp_ref = mctx.module.declare_func_in_func(tramp, b.func); + let call = b.ins().call(tramp_ref, &[body_addr, argv]); + let ok = *b + .inst_results(call) + .first() + .ok_or(ClifError::Unsupported("try region returned nothing"))?; + self.set1(*dst, ok); + return Ok(()); + } Inst::SymbolAddr { dst, symbol } => { // An `#[export]`ed function of this module is taken by its own // id: declaring it again under a made-up signature is what @@ -864,6 +1071,46 @@ impl Lower { self.set1(*dst, address); return Ok(()); } + Inst::VolatileLoad { dst, addr, bits } => { + let address = self.v(*addr)?; + // The `sequence_point` is the whole mechanism, and it emits no + // machine code at all — the x64 backend's `Inst::SequencePoint` + // assembles to nothing. What it does is count as a fence to + // Cranelift's alias analysis, which keys every access by the + // last store before it. Two reads of one device register + // therefore never share a key, and are never collapsed into + // one. That is what makes an ordinary `load` volatile here. + // + // Measured, and the counterfactual measured too: without this + // line, `volatile_read_u32(p) + volatile_read_u32(p)` compiles + // to a single `mov` followed by `lea (%rsi,%rsi,1)` — one read, + // and the sum folded to a doubling. + b.ins().sequence_point(); + let (ty, wide) = access_type(*bits)?; + // `notrap` but *not* `aligned`: on a board a device register is + // mapped and a load of it cannot fault, so trap metadata is + // dead weight. Alignment is the caller's business, and claiming + // it would be a promise this side cannot keep. + let raw = b.ins().load(ty, MemFlagsData::new().with_notrap(), address, 0); + let value = if wide { raw } else { b.ins().uextend(types::I64, raw) }; + self.set1(*dst, value); + return Ok(()); + } + Inst::VolatileStore { addr, value, bits } => { + let address = self.v(*addr)?; + let word = self.v(*value)?; + // As the load: without this, two identical writes to one port + // become one — measured, `movb $0x7,(%rdi)` emitted once for a + // source that says it twice. + b.ins().sequence_point(); + let (ty, wide) = access_type(*bits)?; + // The narrowing is the access width doing its job: the value + // arrives as the `i64` every machine integer is carried in, and + // an 8-bit register wants the low byte of it, not a rejection. + let narrowed = if wide { word } else { b.ins().ireduce(ty, word) }; + b.ins().store(MemFlagsData::new().with_notrap(), narrowed, address, 0); + return Ok(()); + } Inst::CallIndirect { dst, callee, args } => { let target = self.v(*callee)?; let a = self.args_v(args)?; @@ -893,7 +1140,7 @@ impl Lower { } => { let a = self.args_v(args)?; let callee = mctx.extern_func(symbol, arg_tys, *ret)?; - return self.call_raw(b, mctx, callee, *dst, ty_is_pair(*ret), &a); + return self.call_raw(b, mctx, callee, *dst, ty_is_pair(*ret), &a, symbol); } Inst::CallFn { dst, func, args } => { let a = self.args_v(args)?; @@ -902,7 +1149,7 @@ impl Lower { .get(func) .ok_or(ClifError::Unsupported("call to undeclared function"))?; let dst_pair = mctx.fn_rets.get(func).is_some_and(|t| ty_is_pair(*t)); - return self.call_raw(b, mctx, callee, *dst, dst_pair, &a); + return self.call_raw(b, mctx, callee, *dst, dst_pair, &a, &format!("lk_fn_{}", func.0)); } Inst::Call { dst, callee, args } => { let abi = callee.resolve().ok_or(ClifError::Unsupported("unknown ABI function"))?; @@ -925,7 +1172,15 @@ impl Lower { } let dst_pair = matches!(abi.result, lk_aot_abi::AbiType::DynVal); let clif_id = mctx.abi_func(abi)?; - return self.call_raw(b, mctx, clif_id, *dst, dst_pair, &a); + return self.call_raw( + b, + mctx, + clif_id, + *dst, + dst_pair, + &a, + &format!("{}.{}", abi.module, abi.name), + ); } Inst::Cmp { dst, @@ -942,6 +1197,14 @@ impl Lower { }; self.set1(*dst, v); } + Inst::BitsToFloat { dst, src } => { + // The same bits, read as a float. `bitcast` rather than a + // conversion: the value came out of an integer register because + // the `try` trampoline's signature is all `long long`. + let s = self.v(*src)?; + let v = b.ins().bitcast(types::F64, MemFlagsData::new(), s); + self.set1(*dst, v); + } Inst::IntToFloat { dst, src } => { let s = self.v(*src)?; let v = b.ins().fcvt_from_sint(types::F64, s); @@ -986,6 +1249,11 @@ impl Lower { let v = b.ins().bxor_imm(s, 1); self.set1(*dst, v); } + Inst::FloatNeg { dst, src } => { + let s = self.v(*src)?; + let v = b.ins().fneg(s); + self.set1(*dst, v); + } Inst::BoolAnd { dst, lhs, rhs } => { let (l, r) = (self.v(*lhs)?, self.v(*rhs)?); let v = b.ins().band(l, r); @@ -1031,6 +1299,33 @@ impl Lower { }; self.set1(*dst, value); } + // One raw half of a two-register carrier, as an `i64` word. A + // `MaybeF64`'s value half is an `f64`; its *bits* are what has to + // cross, so it is bitcast rather than converted. + Inst::CarrierWord { dst, src, half } => { + let (lo, hi) = self.two(*src)?; + let word = match half { + CarrierHalf::Lo => lo, + CarrierHalf::Hi => hi, + }; + let word = if b.func.dfg.value_type(word) == types::F64 { + b.ins().bitcast(types::I64, MemFlagsData::new(), word) + } else { + word + }; + self.set1(*dst, word); + } + // And back: the two words in the order they were taken. + Inst::CarrierFromParts { dst, lo, hi, ty } => { + let lo = self.v(*lo)?; + let hi = self.v(*hi)?; + let lo = if matches!(ty, Ty::MaybeF64) { + b.ins().bitcast(types::F64, MemFlagsData::new(), lo) + } else { + lo + }; + self.set2(*dst, lo, hi); + } // Wrap a plain scalar into a present carrier `{value, 1}`. A `Bool` // source is `I8`; widen it to the carrier's `I64` value component. Inst::MaybeWrap { dst, src, maybe_ty } => { @@ -1049,6 +1344,12 @@ impl Lower { Inst::ListGetMaybe { dst, handle, index } => { return self.pair_call(b, mctx, "lkrt_lklist_i64_get_pair", *dst, &[*handle, *index]); } + Inst::SliceGetMaybe { dst, handle, index } => { + return self.pair_call(b, mctx, "lkrt_lkslice_i64_get_pair", *dst, &[*handle, *index]); + } + Inst::StrByteAtMaybe { dst, handle, index } => { + return self.pair_call(b, mctx, "lkrt_str_byte_at", *dst, &[*handle, *index]); + } Inst::ListGetMaybeStr { dst, handle, index } => { return self.pair_call(b, mctx, "lkrt_lklist_str_get_pair", *dst, &[*handle, *index]); } @@ -1084,13 +1385,23 @@ impl Lower { Inst::UnwrapMaybeF64 { dst, src } => { let (value, present) = self.two(*src)?; let clif_id = mctx.raw_func("lkrt_maybe_f64_unwrap", &[types::F64, types::I64], &[types::F64])?; - return self.call_raw(b, mctx, clif_id, Some(*dst), false, &[value, present]); - } - Inst::TraitDispatch { dst, self_arg, arms } => { - return self.trait_dispatch(b, mctx, *dst, *self_arg, arms); + return self.call_raw( + b, + mctx, + clif_id, + Some(*dst), + false, + &[value, present], + "lkrt_maybe_f64_unwrap", + ); } - Inst::TryCall { dst, func, args } => { - return self.try_call(b, mctx, *dst, *func, args); + Inst::TraitDispatch { + dst, + self_arg, + args, + arms, + } => { + return self.trait_dispatch(b, mctx, *dst, *self_arg, args, arms); } Inst::CallVm { dst, @@ -1104,78 +1415,6 @@ impl Lower { Ok(()) } - /// Native protected call (`try$call`, plan G): drive the lkrt `setjmp` - /// trampoline (`lkrt_rt_try_call`) — Cranelift cannot emit `setjmp` itself — - /// and join its outcome into the `[ok, value]` dyn-list the desugared - /// destructuring consumes. Only integer/pointer try-body params are - /// supported: each argument is marshaled as one `i64` word into a stack - /// buffer (float/carrier args, or arity above the trampoline cap, reject). - fn try_call( - &mut self, - b: &mut FunctionBuilder, - mctx: &mut ModuleCtx, - dst: ValueId, - func: FuncId, - args: &[ValueId], - ) -> Result<(), ClifError> { - // Keep in step with the trampoline's arity switch (`try_trampoline.c`). - const MAX_ARGS: usize = 8; - if args.len() > MAX_ARGS { - return Err(ClifError::Unsupported("try-call arity over trampoline cap")); - } - // Marshal each argument to an `i64` word in a stack buffer. - let slot_bytes = (args.len().max(1) * 8) as u32; - let args_slot = b.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, slot_bytes, 3)); - for (i, arg) in args.iter().enumerate() { - let word = match self.slot(*arg)? { - Slot::Two(..) => return Err(ClifError::Unsupported("try-call carrier argument")), - Slot::One(v) => { - let t = b.func.dfg.value_type(v); - if t == types::I64 { - v - } else if t == types::I8 { - b.ins().uextend(types::I64, v) - } else { - return Err(ClifError::Unsupported("try-call non-integer argument")); - } - } - }; - b.ins().stack_store(word, args_slot, (i * 8) as i32); - } - let argv = b.ins().stack_addr(types::I64, args_slot, 0); - let ok_slot = b.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8, 3)); - let out_ok = b.ins().stack_addr(types::I64, ok_slot, 0); - // The try-body's address (`ptr @lk_fn_N`) and argument count. - let callee = *mctx - .fn_ids - .get(&func) - .ok_or(ClifError::Unsupported("try-call to undeclared function"))?; - let body_ref = mctx.module.declare_func_in_func(callee, b.func); - let body_addr = b.ins().func_addr(types::I64, body_ref); - let argc = b.ins().iconst(types::I64, args.len() as i64); - // Call the trampoline: returns the body result / caught error as a `Dyn` - // pair, and writes the ok flag through `out_ok`. - let tramp = mctx.raw_func("lkrt_rt_try_call", &[types::I64; 4], &[types::I64, types::I64])?; - let tramp_ref = mctx.module.declare_func_in_func(tramp, b.func); - let call = b.ins().call(tramp_ref, &[body_addr, argc, argv, out_ok]); - let (val_t, val_p) = { - let r = b.inst_results(call); - ( - *r.first().ok_or(ClifError::Unsupported("try-call result missing lo"))?, - *r.get(1).ok_or(ClifError::Unsupported("try-call result missing hi"))?, - ) - }; - let ok = b.ins().stack_load(types::I64, ok_slot, 0); - // Join into the `[ok, value]` dyn list the desugaring destructures. - let list = self.abi_call(b, mctx, "list_h", "dyn_new", &[])?; - let (ok_t, ok_p) = self.abi_call_pair(b, mctx, "dyn", "from_bool", &[ok])?; - let push = mctx.abi_func(resolve_abi("list_h", "dyn_push")?)?; - self.call(b, mctx, push, None, &[list, ok_t, ok_p])?; - self.call(b, mctx, push, None, &[list, val_t, val_p])?; - self.set1(dst, list); - Ok(()) - } - /// Tier 1 hybrid bridge call (`docs/aot/tier1-hybrid.md`): marshal each /// scalar argument into its tagged slot in `lk_hybrid_argbuf`, flush C stdio /// (the bridge VM prints through Rust's line-buffered stdout — unflushed C @@ -1252,35 +1491,15 @@ impl Lower { &[types::I32, types::I64, types::I64], &[types::I64, types::I64], )?; - self.call_raw(b, mctx, call_r, Some(d), true, &[fid, bufarg, argc]) + self.call_raw(b, mctx, call_r, Some(d), true, &[fid, bufarg, argc], "lk_hybrid_call_r") } None => { let call_v = mctx.raw_func("lk_hybrid_call_v", &[types::I32, types::I64, types::I64], &[])?; - self.call_raw(b, mctx, call_v, None, false, &[fid, bufarg, argc]) + self.call_raw(b, mctx, call_v, None, false, &[fid, bufarg, argc], "lk_hybrid_call_v") } } } - /// Call an ABI fn with pre-flattened scalar `args`, returning its `{i64,i64}` - /// carrier result as a `(component0, component1)` pair. - fn abi_call_pair( - &mut self, - b: &mut FunctionBuilder, - mctx: &mut ModuleCtx, - module: &str, - name: &str, - args: &[Value], - ) -> Result<(Value, Value), ClifError> { - let id = mctx.abi_func(resolve_abi(module, name)?)?; - let func_ref = mctx.module.declare_func_in_func(id, b.func); - let call = b.ins().call(func_ref, args); - let r = b.inst_results(call); - Ok(( - *r.first().ok_or(ClifError::Unsupported("carrier ABI call missing lo"))?, - *r.get(1).ok_or(ClifError::Unsupported("carrier ABI call missing hi"))?, - )) - } - /// Runtime trait-method dispatch (plan J1): read the boxed receiver's arena /// type mark (`dyn.obj_type_id`) and walk an `icmp` chain, calling the impl /// whose registered type id matches. Every arm takes the `Dyn` receiver and @@ -1292,10 +1511,19 @@ impl Lower { mctx: &mut ModuleCtx, dst: ValueId, self_arg: ValueId, + args: &[ValueId], arms: &[(i64, FuncId)], ) -> Result<(), ClifError> { let (s0, s1) = self.two(self_arg)?; - let type_id = self.abi_call(b, mctx, "dyn", "obj_type_id", &[s0, s1])?; + let type_id = self.abi_call(b, mctx, "dyn", "dispatch_type_id", &[s0, s1])?; + // `self` then the method's own arguments, each a boxed `Dyn` pair — the + // one call shape every arm is rendered with. + let mut call_args = vec![s0, s1]; + for arg in args { + let (a0, a1) = self.two(*arg)?; + call_args.push(a0); + call_args.push(a1); + } // The join block carries the dispatched `Dyn` result as two block params. let join = b.create_block(); let j0 = b.append_block_param(join, types::I64); @@ -1311,7 +1539,7 @@ impl Lower { .get(func) .ok_or(ClifError::Unsupported("trait arm to undeclared function"))?; let func_ref = mctx.module.declare_func_in_func(callee, b.func); - let call = b.ins().call(func_ref, &[s0, s1]); + let call = b.ins().call(func_ref, &call_args); let (r0, r1) = { let r = b.inst_results(call); ( @@ -1349,7 +1577,7 @@ impl Lower { let args = self.args_v(arg_ids)?; let params = vec![types::I64; args.len()]; let clif_id = mctx.raw_func(symbol, ¶ms, &[types::I64, types::I64])?; - self.call_raw(b, mctx, clif_id, Some(dst), true, &args) + self.call_raw(b, mctx, clif_id, Some(dst), true, &args, symbol) } /// Emit an `lkrt_*_get_out(args…, out_value: *f64, out_present: *i64)` call and @@ -1394,7 +1622,7 @@ impl Lower { ) -> Result<(), ClifError> { let (value, present) = self.two(src)?; let clif_id = mctx.raw_func(symbol, &[types::I64, types::I64], &[types::I64])?; - self.call_raw(b, mctx, clif_id, Some(dst), false, &[value, present]) + self.call_raw(b, mctx, clif_id, Some(dst), false, &[value, present], symbol) } fn term(&mut self, b: &mut FunctionBuilder, mctx: &mut ModuleCtx, term: &Term) -> Result<(), ClifError> { @@ -1478,9 +1706,52 @@ impl Lower { self.entry_write(b, mctx, sp)?; b.ins().jump(exit, &[]); } + // Container returns use the same VM-exact formatters as + // interpolation/println. Entry values are observable output too; + // rejecting them here made otherwise fully native programs fall + // back only because their final expression was a container. + Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::ListDyn + | Ty::SliceI64 + | Ty::MapStrI64 + | Ty::MapStrF64 + | Ty::MapStrBool + | Ty::MapI64I64 + | Ty::MapI64F64 + | Ty::Set + | Ty::Bytes => { + let (module, display_fn) = match self.ret_ty { + Ty::ListI64 => ("list_h", "i64_display"), + Ty::ListF64 => ("list_h", "f64_display"), + Ty::ListStr => ("list_h", "str_display"), + Ty::ListDyn => ("list_h", "dyn_display"), + Ty::SliceI64 => ("slice_h", "i64_display"), + Ty::MapStrI64 => ("map_h", "str_i64_display"), + Ty::MapStrF64 => ("map_h", "str_f64_display"), + Ty::MapStrBool => ("map_h", "str_bool_display"), + Ty::MapI64I64 => ("map_h", "i64_i64_display"), + Ty::MapI64F64 => ("map_h", "i64_f64_display"), + Ty::Set => ("set", "display"), + Ty::Bytes => ("bytes_h", "to_str"), + _ => unreachable!("guarded by the outer match"), + }; + let handle = self.v(v)?; + let rendered = self.abi_call(b, mctx, module, display_fn, &[handle])?; + self.entry_write(b, mctx, rendered)?; + b.ins().jump(exit, &[]); + } // A boxed `Dyn`: print its display unless nil-tagged (tag == 0). - Ty::Dyn => { - let (tag, payload) = self.two(v)?; + // `MapStrDyn` is the same runtime map handle tagged for the + // renderer; the marker also preserves struct display. + Ty::Dyn | Ty::MapStrDyn => { + let (tag, payload) = if self.ret_ty == Ty::Dyn { + self.two(v)? + } else { + let handle = self.v(v)?; + self.abi_call_pair(b, mctx, "dyn", "from_map", &[handle])? + }; let present = b.ins().icmp_imm(IntCC::NotEqual, tag, 0); let some = b.create_block(); b.ins().brif(present, some, &[], exit, &[]); @@ -1544,6 +1815,24 @@ impl Lower { .copied() .ok_or(ClifError::Unsupported("ABI call produced no result")) } + + /// Call an ABI fn whose flattened result occupies two machine values. + fn abi_call_pair( + &mut self, + b: &mut FunctionBuilder, + mctx: &mut ModuleCtx, + module: &str, + name: &str, + args: &[Value], + ) -> Result<(Value, Value), ClifError> { + let id = mctx.abi_func(resolve_abi(module, name)?)?; + let func_ref = mctx.module.declare_func_in_func(id, b.func); + let call = b.ins().call(func_ref, args); + let [first, second] = b.inst_results(call) else { + return Err(ClifError::Unsupported("ABI call did not produce two results")); + }; + Ok((*first, *second)) + } } fn int_cc(op: CmpOp) -> IntCC { @@ -1603,6 +1892,44 @@ mod tests { assert_eq!(elf_machine(&x64), EM_X86_64, "the triple must select the backend"); } + /// No 32-bit target can be compiled for — and three places quietly depend + /// on that. + /// + /// `isize`/`usize` are pointer width *by definition*, and each of these + /// treats them as 64 bits: + /// + /// - `IntKind::accepts_literal` (lk-values) range-checks them as `i64`/`u64` + /// - `Compiler::lower_cast` (lk-core) makes a cast to a pointer a no-op + /// rather than a truncation + /// - `truncate_to_width` (lk-core) masks them to the *running* machine's + /// width, which is the one of the three that already follows the target + /// + /// All three are correct while every reachable backend is 64-bit, and this + /// is what says so out loud. Cranelift's backend set here is x86-64, + /// aarch64, riscv64 and s390x; a 32-bit triple is refused at `isa::lookup` + /// before any of it matters. + /// + /// When a 32-bit backend arrives — a Cranelift bump, or a new one — this + /// test fails, and the sites above are what to fix. That is the point: the + /// decision arrives when it becomes real instead of waiting to be + /// remembered. + #[test] + fn no_32_bit_target_is_reachable_yet() { + let mir = cross_target_module(); + for triple in [ + "thumbv7em-none-eabi", + "armv7-unknown-linux-gnueabihf", + "i686-unknown-linux-gnu", + "riscv32imac-unknown-none-elf", + ] { + assert!( + compile_object_for(&mir, triple).is_err(), + "`{triple}` compiles now — `isize`/`usize` must stop meaning 64 bits; \ + see this test's doc comment for the places that assume it" + ); + } + } + #[test] fn an_unknown_triple_is_an_error_not_a_silent_host_build() { let err = compile_object_for(&cross_target_module(), "definitely-not-a-target").expect_err("must reject"); @@ -1917,7 +2244,7 @@ mod tests { compile_module(&mir, host_isa()).expect("print str must compile"); } - // The entry function compiles to C `main`: `abi_check` prologue + top-level + // The entry function compiles to C `main`: `rt_begin` prologue + top-level // result print + `ret 0`. #[test] fn lowers_entry_main() { @@ -1986,6 +2313,39 @@ mod tests { compile_module(&mir, host_isa()).expect("entry scalar return must compile"); } + // Container return values are top-level expressions too. Their handles go + // through the same display ABI that println uses; lowering may not reject a + // program merely because its final expression is a list. + #[test] + fn lowers_entry_container_return() { + let list = MirFunction { + id: FuncId(0), + params: vec![], + blocks: vec![MirBlock { + id: BlockId(0), + params: vec![], + insts: vec![Inst::Call { + dst: Some(vid(0)), + callee: lk_aot_mir::AbiRef::new("list_h", "f64_new"), + args: vec![], + }], + term: Term::Ret(Some(vid(0))), + }], + entry: BlockId(0), + ret: Ty::ListF64, + export_name: None, + }; + let mir = MirModule { + abi_version: 1, + globals: vec![], + mutable_globals: vec![], + vm_functions: vec![], + entry: FuncId(0), + functions: vec![list], + }; + compile_module(&mir, host_isa()).expect("entry container return must compile"); + } + // The `inst` match is exhaustive, so the capability boundary lives *inside* // arms. A `Nil`-typed function parameter (zero register components) is one // such rejected shape — it must fail with `Unsupported`, not an unrelated diff --git a/aot/driver/src/native_executable.rs b/aot/driver/src/native_executable.rs index b4fe16de..42d029f3 100644 --- a/aot/driver/src/native_executable.rs +++ b/aot/driver/src/native_executable.rs @@ -93,6 +93,27 @@ pub fn compile_native_executable_from_object_hybrid( // wrapper references `lk_hybrid_register`, the object references // `lk_hybrid_call_*`, which pull the objects in. command.arg(hybrid.lk_api_staticlib); + // Two archives, one set of crates underneath. + // + // A hybrid binary links `lkrt` and `lk-api` side by side, and they share + // dependencies that neither can drop: `unsafe_libyaml` arrives in `lkrt` + // through its YAML parser — which the *pure native* path needs, since + // `encoding.yaml_parse` lowers to `lkrt_yaml_parse` — and in `lk-api` + // through the stdlib's `encoding` module. Both archives therefore carry the + // same crate's objects, and a link that reads both dies on four hundred + // multiple definitions. + // + // `lk-api` already declares `lkrt` a *dev*-dependency so the two do not + // collide directly. What collides is the layer under both, which no + // dependency edge separates: the objects are the same crate at the same + // version out of the same workspace build, so the definitions are identical + // and whichever the linker keeps is the same program. + // + // Only here. The pure-native link reads one archive, where a duplicate + // symbol would mean something is actually wrong. + if !cfg!(target_os = "macos") { + command.arg("-Wl,--allow-multiple-definition"); + } // `pthread`/`dl` are Unix libraries; on Windows they are part of the CRT. if !cfg!(target_os = "windows") { command.args(["-lpthread", "-ldl"]); @@ -134,18 +155,20 @@ fn hybrid_wrapper_c(module_artifact_json: &str) -> String { void (*list_dyn_push)(void *, LkDyn),\n\ void *(*map_str_dyn_new)(void),\n\ void (*map_str_dyn_set)(void *, const char *, LkDyn),\n\ + long long (*obj_mark_by_name)(void *, const char *),\n\ void (*raise_dyn)(LkDyn));\n\ extern void *lkrt_lklist_dyn_new(void);\n\ extern void lkrt_lklist_dyn_push(void *, LkDyn);\n\ extern void *lkrt_lkmap_str_dyn_new(void);\n\ extern void lkrt_lkmap_str_dyn_set(void *, const char *, LkDyn);\n\ + extern long long lkrt_lkmap_obj_mark_by_name(void *, const char *);\n\ extern void lkrt_rt_raise_dyn(LkDyn);\n\ static const char *LK_HYBRID_ARTIFACT = \"{escaped}\";\n\ __attribute__((constructor)) static void lk_hybrid_setup(void) {{\n\ lk_hybrid_register(LK_HYBRID_ARTIFACT);\n\ lk_hybrid_register_rt(lkrt_lklist_dyn_new, lkrt_lklist_dyn_push,\n\ lkrt_lkmap_str_dyn_new, lkrt_lkmap_str_dyn_set,\n\ - lkrt_rt_raise_dyn);\n\ + lkrt_lkmap_obj_mark_by_name, lkrt_rt_raise_dyn);\n\ }}\n" ) } @@ -191,15 +214,24 @@ fn lkrt_staticlib_path() -> Option { } else { "liblkrt_cabi.a" }; - // Refresh before searching. A *stale* archive is worse than a missing one: - // it links partially, or — as happened the day the toolchain moved — brings - // a second copy of libstd built by another rustc and collides on - // `rust_eh_personality`, with a message that names neither archive as the - // old one. Under cargo's fingerprinting a rebuild is a sub-second no-op; - // where there is no workspace to build in (an installed `lk`), it fails and - // the search below still finds whatever was shipped. Same rule the CLI - // already follows for `lk-api`. - let _ = build_lkrt_staticlib(); + // Refresh before searching, *in the profile this binary will link from*. A + // stale archive is worse than a missing one: it links partially, or — as + // happened the day the toolchain moved — brings a second copy of libstd + // built by another rustc and collides on `rust_eh_personality`, with a + // message that names neither archive as the old one. Under cargo's + // fingerprinting a rebuild is a sub-second no-op; where there is no + // workspace to build in (an installed `lk`), it fails and the search below + // still finds whatever was shipped. Same rule the CLI already follows for + // `lk-api`. + // + // The profile is the point. This used to refresh the *debug* archive + // unconditionally while the search below picks the one sitting beside this + // binary — so for a `--release` or `--profile dist` `lk`, the one thing the + // refresh exists to prevent was exactly what happened: the archive it linked + // was never rebuilt. A `dist` build linked an lkrt from a day earlier, whose + // `lkrt_rt_try_region` still had the old signature, and every `try` program + // it compiled died on `SIGILL`. + let _ = build_lkrt_staticlib(cargo_profile_of(dir)); let mut candidates = vec![dir.join(file)]; // The `lk` CLI runs from `target//`, whose `deps` subdir holds the // hashed `liblkrt_cabi-.a`; a `cargo test` binary runs from @@ -210,7 +242,28 @@ fn lkrt_staticlib_path() -> Option { candidates.push(path); } } - newest_existing_path(candidates).or_else(build_lkrt_staticlib) + newest_existing_path(candidates).or_else(|| build_lkrt_staticlib(cargo_profile_of(dir))) +} + +/// The cargo profile whose output directory is `dir` — what a rebuild has to +/// name for its archive to land where the link will look. +/// +/// Cargo's one irregularity: the `dev` profile writes to `target/debug`. A test +/// binary runs from `target//deps`, so that one step up is taken here +/// too. Anything else (an installed `lk` in `~/.cargo/bin`) yields a name cargo +/// will reject, and the rebuild fails the same way it already does when there is +/// no workspace to build in — silently, leaving the search to find whatever was +/// shipped. +fn cargo_profile_of(dir: &Path) -> String { + let name = |path: &Path| path.file_name().and_then(|n| n.to_str()).map(str::to_owned); + let directory = match name(dir).as_deref() { + Some("deps") => dir.parent().and_then(name), + other => other.map(str::to_owned), + }; + match directory.as_deref() { + Some("debug") | None => "dev".to_string(), + Some(profile) => profile.to_string(), + } } /// Builds `lkrt-cabi`, whether or not an archive is already on disk. @@ -228,12 +281,12 @@ fn lkrt_staticlib_path() -> Option { /// /// Both are the rule the CLI already follows for `lk-api`: an archive the link /// needs is the link's business to produce, every time. -fn build_lkrt_staticlib() -> Option { +fn build_lkrt_staticlib(profile: String) -> Option { let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).parent()?.parent()?; - eprintln!("building lkrt staticlib (one-time)…"); + eprintln!("building lkrt staticlib ({profile})…"); let status = std::process::Command::new("cargo") .current_dir(workspace) - .args(["build", "-p", "lkrt-cabi"]) + .args(["build", "-p", "lkrt-cabi", "--profile", &profile]) .status() .ok()?; if !status.success() { @@ -244,7 +297,10 @@ fn build_lkrt_staticlib() -> Option { } else { "liblkrt_cabi.a" }; - let built = workspace.join("target/debug").join(file); + // `dev` is the profile whose directory is not its name; every other profile + // writes to a directory called after itself. + let directory = if profile == "dev" { "debug" } else { profile.as_str() }; + let built = workspace.join("target").join(directory).join(file); built.exists().then_some(built) } diff --git a/aot/lower/src/capture.rs b/aot/lower/src/capture.rs new file mode 100644 index 00000000..b7e37acb --- /dev/null +++ b/aot/lower/src/capture.rs @@ -0,0 +1,174 @@ +//! Resolving a closure's environment at a call site. +//! +//! Three sites hand a closure its captures — the ordinary closure call, +//! `spawn`, and the erased-lambda environment [`lower_user_call`] appends as +//! hidden trailing arguments. They differ in what they do with a *cell* the +//! enclosing frame owns, and in nothing else, so the three arms that do not +//! differ ([`ClosureCapture::Value`], [`ClosureCapture::StaticRef`], +//! [`ClosureCapture::CellParam`]) live here once and each site keeps only its +//! own `Cell` arm. +//! +//! [`ClosureCapture::CellParam`] is the arm that was missing at two of the +//! three. A closure nested in a closure captures what its parent captured, and +//! the parent holds that as a capture *parameter* rather than as a cell of its +//! own — so resolving it means reading the parent's parameter, not a slot. Only +//! the ordinary call did that; the rest refused, and a program as plain as +//! +//! ```lk +//! let running = 0; +//! let post = |amount| { +//! let entry = || { running = running + amount; return running; }; +//! return call_it(entry); +//! }; +//! ``` +//! +//! fell back to the VM for it (`examples/syntax/closure.lk`, section 13). + +use crate::*; + +/// What a call site does with a capture the enclosing frame owns. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum CaptureMode { + /// Caller and callee name the same cell, so a write by either is visible to + /// the other. This is the VM's semantics for every ordinary call. + Share, + /// The callee gets a private copy taken at the call. `spawn` is the only + /// site with this shape: a goroutine's mutation never leaks back. + Snapshot, +} + +/// Where a capture is being resolved *from*: the function currently being +/// lowered, whose capture parameters an onward capture reads. +#[derive(Clone, Copy)] +pub(crate) struct CaptureCtx<'a> { + /// The enclosing function's hidden trailing parameters. + pub(crate) params: &'a [(ValueId, Ty)], + /// Its index — what [`SigInfer::require_cell_capture`] keys a demand on. + pub(crate) index: u32, + /// Its visible parameter count, which is where its capture slots begin. + pub(crate) param_count: usize, +} + +/// One call site handing one closure its whole environment: everything that is +/// the same for every capture in the loop, so [`CaptureSite::resolve`] takes +/// only what varies. +#[derive(Clone, Copy)] +pub(crate) struct CaptureSite<'a> { + ctx: CaptureCtx<'a>, + callee: u32, + mode: CaptureMode, + block: usize, + pc: usize, +} + +impl<'a> CaptureSite<'a> { + pub(crate) fn new(ctx: CaptureCtx<'a>, callee: u32, mode: CaptureMode, block: usize, pc: usize) -> Self { + Self { + ctx, + callee, + mode, + block, + pc, + } + } + + /// Resolves capture `k` to the value this call site passes, or `None` when + /// the capture is a [`ClosureCapture::Cell`] and the site has to decide. + /// + /// `Cell` is deliberately not answered here: the three sites genuinely + /// disagree about it (seed a fresh runtime cell and read it back, snapshot + /// its content, or pass the content by value), and that disagreement is the + /// only real difference between them. + pub(crate) fn resolve( + &self, + ssa: &mut Ssa, + insts: &mut Vec, + sig: &mut SigInfer, + capture: &ClosureCapture, + k: usize, + ) -> Result, Unsupported> { + Ok(Some(match capture { + ClosureCapture::Cell(_) => return Ok(None), + ClosureCapture::Value(v, ty) => (*v, *ty), + // A static reference carries no runtime value; the slot exists only + // to keep the ABI arity, so it carries a dead `0`. + ClosureCapture::StaticRef => { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + (zero, Ty::I64) + } + ClosureCapture::CellParam(outer) => self.resolve_cell_param(ssa, insts, sig, *outer, k)?, + })) + } + + /// Capture `outer` of the enclosing function, handed onward to the callee's + /// capture `k`. + fn resolve_cell_param( + &self, + ssa: &mut Ssa, + insts: &mut Vec, + sig: &mut SigInfer, + outer: usize, + k: usize, + ) -> Result<(ValueId, Ty), Unsupported> { + let pc = self.pc; + let &(v, ty) = self.ctx.params.get(outer).ok_or(Unsupported::BadConst { pc })?; + // The enclosing frame already holds a runtime cell for this capture. + if ty == Ty::Cell { + return Ok(match self.mode { + // The pointer passes through, so parent and child name one cell + // — which is what the VM does. + CaptureMode::Share => (v, Ty::Cell), + // The goroutine reads the content once, at the spawn, and never + // sees a later write to it. + CaptureMode::Snapshot => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("rt", "cell_get"), + args: vec![v], + }); + (dst, Ty::Dyn) + } + }); + } + // The enclosing function is *itself* a goroutine body: its capture + // parameters are thread-private, and its own writes went to a virtual + // slot rather than to `v` (see `inst::global`'s `StoreCellVal`). The + // slot holds the current content; `v` is only its value at entry. + if ssa.spawned_isolate { + // A child that writes needs somewhere for the write to land, and a + // thread-private slot is not addressable from another frame. + if self.mode == CaptureMode::Share && sig.cell_captures.contains(&(self.callee, k)) { + return Err(Unsupported::CallShape { + pc, + reason: "a goroutine's private capture cannot be written through a nested closure", + }); + } + return ssa.read_slot(ssa.cellparam_slot(outer), self.block, pc); + } + // A by-value capture parameter is one nothing writes: `StoreCellVal` on + // a non-cell `CellParam` demands a cell before it lowers, so reaching + // here means the enclosing function only reads it. Passing the value on + // keeps the child's reads correct and allocates nothing. + // + // Unless the child writes. That demand was recorded when *its* body + // lowered, and it propagates up exactly one frame here: the enclosing + // function's own capture has to become a cell, which its caller seeds. + if sig.cell_captures.contains(&(self.callee, k)) { + if sig.require_cell_capture(self.ctx.index as usize, self.ctx.param_count, outer) { + return Err(Unsupported::TypeMismatch { pc }); + } + // Already demanded, and it still arrived by value: whoever calls + // the enclosing function cannot give this capture a cell. + return Err(Unsupported::CallShape { + pc, + reason: "a capture written through two closure frames has no native cell to write to", + }); + } + Ok((v, ty)) + } +} diff --git a/aot/lower/src/cfg.rs b/aot/lower/src/cfg.rs index 5a8f49ba..fc03bfdd 100644 --- a/aot/lower/src/cfg.rs +++ b/aot/lower/src/cfg.rs @@ -15,14 +15,22 @@ pub(crate) fn mark_target( /// `(body_end, exit)` for block `[start, end)`. A fused compare-and-branch occupies /// the last two slots (`TestXxx` at `end-2`, consumed `Jmp` at `end-1`). +/// Where a block's instructions stop, and what ends it. +/// +/// The *first* instruction in the range that has an exit, not the last one. For +/// every branch and return those are the same instruction, because their +/// successors are block leaders and nothing can follow them inside a block. A +/// `try` region is where they differ: its body was outlined, so the pcs after +/// its `TryBegin` are not in this function's control flow and no leader +/// separates them from it. pub(crate) fn block_span(exits: &[Option], consumed: &[bool], start: usize, end: usize) -> (usize, Option) { - if end >= start + 2 && consumed[end - 1] { - return (end - 2, exits[end - 2]); - } - if end > start - && let Some(exit) = exits[end - 1] - { - return (end - 1, Some(exit)); + for pc in start..end { + if consumed[pc] { + continue; + } + if let Some(exit) = exits[pc] { + return (pc, Some(exit)); + } } (end, None) } @@ -30,7 +38,9 @@ pub(crate) fn block_span(exits: &[Option], consumed: &[bool], start: usize pub(crate) fn exit_successors(exit: Option, fallthrough: usize) -> Vec { match exit { None => vec![fallthrough], - Some(Exit::Ret(_)) => vec![], + // Both leave the function: an escape does it after writing the outcome + // code, so the *caller* takes the edge, not this one. + Some(Exit::Ret(_)) | Some(Exit::TryEscape { .. }) => vec![], Some(Exit::Jump(t)) => vec![t], Some(Exit::Cond { then_pc, else_pc, .. }) => vec![then_pc, else_pc], Some(Exit::FusedCmp { taken, fallthrough, .. }) @@ -40,6 +50,11 @@ pub(crate) fn exit_successors(exit: Option, fallthrough: usize) -> Vec { vec![taken, fallthrough] } + // The body is not in this function's control flow — it was outlined — + // so a region's successors are only where control can be *after* it. + Some(Exit::TryRegion { + handler, fallthrough, .. + }) => vec![handler, fallthrough], } } diff --git a/aot/lower/src/convert.rs b/aot/lower/src/convert.rs index df828cbb..60b702cb 100644 --- a/aot/lower/src/convert.rs +++ b/aot/lower/src/convert.rs @@ -10,6 +10,104 @@ pub(crate) fn coerce_to_f64(ssa: &mut Ssa, insts: &mut Vec, v: ValueId, ty f } +/// The language's name for a value of `ty` — what the VM's error messages say. +/// +/// Not `lk_aot_mir::ty_name`, which answers this backend's carrier names +/// (`list`, `maybe`). A program never wrote those; it wrote `List`. +pub(crate) fn language_type_name(ty: Ty) -> &'static str { + match ty { + Ty::Nil => "Nil", + Ty::Bool | Ty::MaybeBool => "Bool", + Ty::I64 | Ty::MaybeI64 => "Int", + Ty::F64 | Ty::MaybeF64 => "Float", + Ty::Str | Ty::MaybeStr => "String", + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::SliceI64 => "List", + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 => "Map", + Ty::Set => "Set", + Ty::Bytes => "Bytes", + Ty::Cell | Ty::Dyn => "Object", + } +} + +/// Reads a register in a scalar context, raising `message` — verbatim — if the +/// value turns out to be nil. +/// +/// The difference from [`read_scalar`] is the sentence. That one narrows a +/// carrier through `lkrt_maybe_*_unwrap`, which is handed a value and a bit and +/// so can only say `"runtime error"`; the interpreter, at the same point, names +/// the operator and both operand types. So `try { xs[9] + 1 } catch e { e }` +/// read two different strings depending on which backend ran it — a difference a +/// program can see, not just a reader. +/// +/// The sentence is built by the caller, where the operator and the other +/// operand's type are still known, and interned as a constant. Nothing about the +/// present path changes: the guard is a compare and a cold call, and the value +/// comes out of the carrier exactly as before. +pub(crate) fn read_scalar_saying( + ssa: &mut Ssa, + insts: &mut Vec, + globals: &mut Vec, + reg: u8, + block: usize, + pc: usize, + message: &str, +) -> Result { + let (v, ty) = ssa.read(reg, block, pc)?; + let payload = match ty { + Ty::MaybeI64 => Ty::I64, + Ty::MaybeF64 => Ty::F64, + Ty::MaybeStr => Ty::Str, + Ty::MaybeBool => Ty::Bool, + // Not nullable: the guard would have nothing to check. + _ => return read_scalar(ssa, insts, reg, block, pc), + }; + let present = ssa.new_val(); + insts.push(Inst::MaybePresent { + dst: present, + src: v, + maybe_ty: ty, + }); + let wide = ssa.new_val(); + insts.push(Inst::ZextBool { + dst: wide, + src: present, + }); + let text = ssa.new_val(); + insts.push(Inst::Const { + dst: text, + value: Const::Str(GlobalId(crate::prescan::intern_global(globals, message))), + }); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "maybe_guard"), + args: vec![wide, text], + }); + let value = ssa.new_val(); + insts.push(Inst::MaybeValue { + dst: value, + src: v, + maybe_ty: ty, + }); + // A `MaybeBool` payload is the 0/1 word; re-typed the way `read_scalar` does. + if payload == Ty::Bool { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: CmpOp::Ne, + float: false, + lhs: value, + rhs: zero, + }); + return Ok((dst, Ty::Bool)); + } + Ok((value, payload)) +} + /// Reads a register for a **scalar** (arithmetic/comparison/call/store) context, /// narrowing a [`Ty::MaybeI64`] to `I64` via a present-asserting unwrap /// ([`Inst::UnwrapMaybeI64`], which aborts if absent — matching the VM's halt on @@ -94,12 +192,30 @@ pub(crate) fn read_index_scalar( }); Ok(dst) } - _ => Err(Unsupported::TypeMismatch { pc }), + other => Err(Unsupported::OperandType { + pc, + want: "i64", + got: lk_aot_mir::ty_name(other), + }), } } /// [`read_scalar`] that also requires a specific type (the unwrap-aware counterpart /// of `Ssa::read_typed`). +/// +/// A `Dyn` is unboxed through the runtime's tag check rather than rejected — +/// the same rule, and the same `dyn.as_*` calls, that [`read_index_scalar`] +/// documents: wherever a type is *required* rather than merely expected, a +/// boxed value of that type is one, and a box holding something else raises +/// exactly where the VM raises. +/// +/// What made this matter: a parameter observes as `Dyn` the moment *any* call +/// site passes a nullable carrier (see `Sig::observe_param`), and that widens +/// it for every other call site too. `s.byte_at(i)` began answering a `Maybe` — +/// honestly, since an index past the end is nil — so `put_char(base, code)` +/// widened `put_char`'s `ascii` to `Dyn`, and the `ascii == 8` inside it then +/// had a boxed operand where an `I64` was wanted. The whole bare-metal kernel +/// stopped lowering, on a function that never touches a string. pub(crate) fn read_typed_scalar( ssa: &mut Ssa, insts: &mut Vec, @@ -107,13 +223,78 @@ pub(crate) fn read_typed_scalar( block: usize, want: Ty, pc: usize, +) -> Result { + read_typed_scalar_as(ssa, insts, reg, block, want, KeyUse::Value, pc) +} + +/// What a [`read_typed_scalar_as`] unbox is *for*, which decides what it says +/// when the box holds the wrong thing. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum KeyUse { + /// Any ordinary use: the generic runtime type error. + Value, + /// A map key or set member: a type no map can key is refused by name, the + /// wording the interpreter and the boxed-map path both use. + MapKey, +} + +/// [`read_typed_scalar`] that also unboxes a *map key* into the carrier's key +/// type, which a typed carrier needs because it stores the key unboxed and so +/// never reaches `vm_mirror::key_from_dyn`. +pub(crate) fn read_map_key( + ssa: &mut Ssa, + insts: &mut Vec, + reg: u8, + block: usize, + want: Ty, + pc: usize, +) -> Result { + read_typed_scalar_as(ssa, insts, reg, block, want, KeyUse::MapKey, pc) +} + +fn read_typed_scalar_as( + ssa: &mut Ssa, + insts: &mut Vec, + reg: u8, + block: usize, + want: Ty, + key_use: KeyUse, + pc: usize, ) -> Result { let (v, ty) = read_scalar(ssa, insts, reg, block, pc)?; if ty == want { - Ok(v) - } else { - Err(Unsupported::TypeMismatch { pc }) + return Ok(v); } + // A closure is never a scalar, so unboxing one is not a lowering — it is a + // guess that raises at run time. Refused here so the caller can widen + // whatever it was going to store the closure in (`inst::container`'s + // `keep_discovery`). A map *key* is left alone: there the runtime answers, + // and it answers with the interpreter's own sentence. + if key_use == KeyUse::Value && ssa.closure_values.contains(&v) { + return Err(Unsupported::TypeMismatch { pc }); + } + let unbox = match (ty, want, key_use) { + (Ty::Dyn, Ty::I64, KeyUse::MapKey) => "as_key_i64", + (Ty::Dyn, Ty::Str, KeyUse::MapKey) => "as_key_str", + (Ty::Dyn, Ty::I64, _) => "as_i64", + (Ty::Dyn, Ty::F64, _) => "as_f64", + (Ty::Dyn, Ty::Bool, _) => "as_bool", + (Ty::Dyn, Ty::Str, _) => "as_str", + _ => { + return Err(Unsupported::OperandType { + pc, + want: lk_aot_mir::ty_name(want), + got: lk_aot_mir::ty_name(ty), + }); + } + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", unbox), + args: vec![v], + }); + Ok(dst) } /// Converts a scalar to its display `Str` (the VM's `ToString`/interpolation @@ -142,6 +323,17 @@ pub(crate) fn to_display_str( ) -> Result<(ValueId, bool), Unsupported> { match ty { Ty::Str => Ok((v, false)), + // `nil` renders as the word, in every display context the VM has. It + // had no arm at all, so `"x" + nil` and `"${nil}"` fell back. + Ty::Nil => { + let gid = intern_global(globals, "nil"); + let dst = ssa.new_val(); + insts.push(Inst::Const { + dst, + value: Const::Str(GlobalId(gid)), + }); + Ok((dst, false)) + } // A `Maybe` displays its value when present and `nil` when absent // (matching the VM's display of a missing-key read). The value-side // conversion runs unconditionally (its result is arena-owned and @@ -153,31 +345,20 @@ pub(crate) fn to_display_str( src: v, maybe_ty: ty, }); + // Bool display goes through `from_bool`, not the i64 decimal text. + // `MaybeValue` narrows a `MaybeBool`'s word to a `Bool` itself, so + // the extracted value is already the scalar in every case. It used + // to be re-derived here with a `!= 0` against an `i64` zero, which + // is not well-typed IR: `println(m.get(k))` on a `Map` failed Cranelift verification rather than lowering, and + // with fallback on (the default) that reads as a program that + // merely declines to lower. let scalar_ty = match ty { - Ty::MaybeI64 | Ty::MaybeBool => Ty::I64, + Ty::MaybeI64 => Ty::I64, + Ty::MaybeBool => Ty::Bool, Ty::MaybeF64 => Ty::F64, _ => Ty::Str, }; - // Bool display goes through from_bool, not the i64 decimal text. - let raw = if ty == Ty::MaybeBool { - let zero = ssa.new_val(); - insts.push(Inst::Const { - dst: zero, - value: Const::I64(0), - }); - let b = ssa.new_val(); - insts.push(Inst::Cmp { - dst: b, - op: CmpOp::Ne, - float: false, - lhs: raw, - rhs: zero, - }); - b - } else { - raw - }; - let scalar_ty = if ty == Ty::MaybeBool { Ty::Bool } else { scalar_ty }; let (value_str, _) = to_display_str(ssa, insts, globals, raw, scalar_ty, false, pc)?; let present = ssa.new_val(); insts.push(Inst::MaybePresent { @@ -254,6 +435,54 @@ pub(crate) fn to_display_str( }); Ok((dst, true)) } + // `Set([1,2,3])`, sorted by member. + // + // This is the one container display that needs no mirror discipline: + // a set's *display* order is not its hash order, it is imposed — and + // imposed on the members' values, so both sides just compare content. + // (`RuntimeMapKey::display_order` is the rule; it used to sort the + // rendered text, which is why `Set([1, 2, 10])` printed `1,10,2`.) + Ty::Set => { + if !containers { + return Err(Unsupported::TypeMismatch { pc }); + } + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("set", "display"), + args: vec![v], + }); + Ok((dst, true)) + } + // `Bytes([104,105])` — rendered inside lkrt with the VM's exact + // separators. A container, so the scalar-only display contexts reject it + // like they reject a list. + Ty::Bytes => { + if !containers { + return Err(Unsupported::TypeMismatch { pc }); + } + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", "to_str"), + args: vec![v], + }); + Ok((dst, true)) + } + // A window prints as the list it windows — the VM renders a + // `HeapValue::Slice` through the same list formatter. + Ty::SliceI64 => { + if !containers { + return Err(Unsupported::TypeMismatch { pc }); + } + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("slice_h", "i64_display"), + args: vec![v], + }); + Ok((dst, true)) + } // A boxed Dyn from a mixed-list read: at runtime it is a scalar in // D2 (nested containers never box — see LoadHeapConst's scalar_only // guard), so the bare display mode is exact for both display paths. @@ -266,6 +495,66 @@ pub(crate) fn to_display_str( }); Ok((dst, true)) } + // A struct instance. `NewObject` marked it with its type id and the + // entry described that type to the runtime (name + field order), so the + // renderer produces the VM's `Name{f:v,…}` — including for a field that + // holds another struct, which is why this cannot be spelled out at the + // display site (see `docs/aot/aot-gaps-and-lkrt.md`). + // + // A statically typed map renders inside lkrt from the carrier's own + // iteration order. + // + // This used to say "a plain map stays out of the subset: its order is + // the underlying hash iteration order, which the two runtimes do not + // share" — a ruling that predates `lkrt/src/vm_mirror.rs`, whose whole + // job is to make them share it, and which + // `lit_protocol_matches_vm_iteration_order` checks against `lk-core` + // directly. The arm right below already displayed a `MapStrDyn`, so the + // ruling had been retired for one map type and left standing for the + // rest: `println({"a": 1})` cost a program its lowering while + // `println({"a": 1, "b": "x"})` did not. + // An int-keyed map is included too, but it took a carrier fix first: + // the VM runs *no* stage 2 for a non-string key + // (`typed_map_from_entries` returns `Mixed`, which is the stage-1 + // table), while `lit_finish_i64_*` used to rehash into an + // `FxMap` — a different hash and a second insertion sequence. + // `{1: 1.5, 2: 2.5}` iterated `2,1` in the VM and `1,2` natively. The + // carrier is now keyed by `vm_mirror::IntKey`, which hashes as + // `RtKey::Int`, and the finisher replays the literal order. + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapI64I64 | Ty::MapI64F64 => { + if !containers { + return Err(Unsupported::TypeMismatch { pc }); + } + let display_fn = match ty { + Ty::MapStrI64 => "str_i64_display", + Ty::MapStrF64 => "str_f64_display", + Ty::MapStrBool => "str_bool_display", + Ty::MapI64I64 => "i64_i64_display", + _ => "i64_f64_display", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", display_fn), + args: vec![v], + }); + Ok((dst, true)) + } + Ty::MapStrDyn => { + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "from_map"), + args: vec![v], + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", if containers { "display_quoted" } else { "display" }), + args: vec![boxed], + }); + Ok((dst, true)) + } _ => Err(Unsupported::TypeMismatch { pc }), } } diff --git a/aot/lower/src/dyn_box.rs b/aot/lower/src/dyn_box.rs index 324221af..ccef9f85 100644 --- a/aot/lower/src/dyn_box.rs +++ b/aot/lower/src/dyn_box.rs @@ -1,9 +1,51 @@ use super::*; -/// Normalizes a map operand to the `Map` carrier: `MapStrDyn` -/// passes through, a typed string-keyed map converts (iteration order is -/// preserved — the rebuild replays the source order, `vm_mirror`'s -/// argument), `nil` becomes an empty map (the VM accepts a nil merge base). +/// The `lkmap::KIND_*` number for a typed string-keyed map carrier, or `None` +/// for anything else (a boxed map, a non-map). +/// +/// One table, read by everything that tags a typed map handle: boxing +/// (`dyn.from_typed_map`) and the merge overlay both need the same numbering, +/// and a second copy of it would be a silent mismatch rather than an error. +pub(crate) fn typed_map_kind(ty: Ty) -> Option { + Some(match ty { + Ty::MapStrI64 => 0, + Ty::MapStrF64 => 1, + Ty::MapStrBool => 2, + Ty::MapI64I64 => 3, + Ty::MapI64F64 => 4, + _ => return None, + }) +} + +/// The `lkdyn::TLIST_*` number for a typed list carrier, or `None` for anything +/// else (a boxed list, a non-list). +/// +/// The list counterpart of [`typed_map_kind`], and read by the same kinds of +/// call sites for the same reason: one numbering, not two. +pub(crate) fn typed_list_kind(ty: Ty) -> Option { + Some(match ty { + Ty::ListI64 => 0, + Ty::ListF64 => 1, + Ty::ListStr => 2, + _ => return None, + }) +} + +/// Normalizes a map operand to the `Map` carrier: `MapStrDyn` passes +/// through and `nil` becomes an empty map (the VM accepts a nil merge base). +/// +/// A **typed** map rejects, and the reason is worth keeping: it used to convert, +/// with the claim that "iteration order is preserved — the rebuild replays the +/// source order". It does not. Re-inserting a map's entries into a fresh table +/// *in its iteration order* is a different insertion sequence from the one that +/// built it, and once deletions are in the history the two tables iterate +/// differently — the same mistake `DYN_RAW`'s doc warns about and that +/// `a_boxed_typed_map_keeps_its_order` pins for the boxing path. +/// +/// Here the copy is unavoidable (the merge helper wants a real `StrDynMap`), so +/// the arm is *gone* rather than fixed: a fallback is correct, a silent reorder +/// is not. Supporting it means a typed-map-aware merge in lkrt, with its own +/// order-conformance test — separate work, not a table entry. pub(crate) fn to_dyn_map_handle( ssa: &mut Ssa, insts: &mut Vec, @@ -11,11 +53,8 @@ pub(crate) fn to_dyn_map_handle( ty: Ty, pc: usize, ) -> Result { - let helper = match ty { - Ty::MapStrDyn => return Ok(v), - Ty::MapStrI64 => "str_i64_to_dyn", - Ty::MapStrF64 => "str_f64_to_dyn", - Ty::MapStrBool => "str_bool_to_dyn", + match ty { + Ty::MapStrDyn => Ok(v), Ty::Nil => { let dst = ssa.new_val(); insts.push(Inst::Call { @@ -23,17 +62,10 @@ pub(crate) fn to_dyn_map_handle( callee: AbiRef::new("map_h", "str_dyn_new"), args: Vec::new(), }); - return Ok(dst); + Ok(dst) } - _ => return Err(Unsupported::TypeMismatch { pc }), - }; - let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("map_h", helper), - args: vec![v], - }); - Ok(dst) + _ => Err(Unsupported::TypeMismatch { pc }), + } } /// Materializes a constant map key as a `Str` value (an interned global) for the @@ -72,6 +104,20 @@ pub(crate) fn to_dyn_list_handle( ty: Ty, pc: usize, ) -> Result { + // A boxed value is a list handle one tag guard away, and every caller that + // wanted one wrote that guard itself — `chain` did, inline, and `zip` did + // not, which is the whole of why `xs.zip(ys)` refused when `ys` was a + // parameter. `dyn.as_list` aborts on a non-list tag, the loud error the VM + // raises for the same call. + if ty == Ty::Dyn { + let unboxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(unboxed), + callee: AbiRef::new("dyn", "as_list"), + args: vec![v], + }); + return Ok(unboxed); + } let converter = match ty { Ty::ListDyn => return Ok(v), Ty::ListI64 => "i64_to_dyn", @@ -106,6 +152,10 @@ pub(crate) fn dyn_boxable_ty(ty: Ty) -> bool { | Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool + | Ty::MapI64I64 + | Ty::MapI64F64 + | Ty::Set + | Ty::Bytes | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr @@ -130,7 +180,7 @@ pub(crate) fn read_channel_id( let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("dyn", "as_i64"), + callee: AbiRef::new("dyn", "as_handle"), args: vec![v], }); Ok(dst) @@ -152,7 +202,7 @@ pub(crate) fn coerce_arg( pc: usize, ) -> Result { if want == Ty::Dyn && ty != Ty::Dyn { - return to_dyn_any(ssa, insts, v, ty, pc); + return to_dyn(ssa, insts, v, ty, pc); } if ty != want { return Err(Unsupported::TypeMismatch { pc }); @@ -160,11 +210,22 @@ pub(crate) fn coerce_arg( Ok(v) } -/// [`to_dyn`] extended to the nullable carriers: a `Maybe` boxes to its -/// payload's tag when present and to nil when absent (`dyn.from_maybe_*`), -/// preserving VM call semantics — a nil argument arrives as nil instead of -/// hitting the scalar-context unwrap abort. -pub(crate) fn to_dyn_any( +/// Boxes a typed value into a `Dyn`. +/// +/// A nullable carrier boxes to its payload's tag when present and to **nil** +/// when absent (`dyn.from_maybe_*`), because that is what the value *is*: the +/// VM has no `Maybe`, it has nil, and a carrier is this backend's way of +/// carrying "the VM would have nil here". Boxing is the point at which that +/// distinction stops mattering. +/// +/// This used to be two functions — one that refused a carrier and one that did +/// not — and every site except the call-argument marshaller reached for the +/// refusing one. So `xs[i] + 1` with a bounds-checked element, which is what +/// indexing *is*, dropped a whole module to the VM rather than lowering; the +/// refusal was never a semantic choice, only an unfinished match. A scalar +/// context still aborts on an absent value, but it reaches that through +/// `convert`'s unwrap, not through here. +pub(crate) fn to_dyn( ssa: &mut Ssa, insts: &mut Vec, v: ValueId, @@ -176,14 +237,28 @@ pub(crate) fn to_dyn_any( Ty::MaybeF64 => "from_maybe_f64", Ty::MaybeStr => "from_maybe_str", Ty::MaybeBool => "from_maybe_bool", - _ => return to_dyn(ssa, insts, v, ty, pc), + _ => return to_dyn_plain(ssa, insts, v, ty, pc), }; - let value = ssa.new_val(); + let value_narrow = ssa.new_val(); insts.push(Inst::MaybeValue { - dst: value, + dst: value_narrow, src: v, maybe_ty: ty, }); + // `MaybeValue` hands back a `MaybeBool`'s half as the `Bool` it is, and + // `from_maybe_bool` takes the word — the same widening the present half + // gets just below. Without it the call is not well-typed IR, so `"" + + // m.get(k)` on a `Map` failed Cranelift verification. + let value = if ty == Ty::MaybeBool { + let wide = ssa.new_val(); + insts.push(Inst::ZextBool { + dst: wide, + src: value_narrow, + }); + wide + } else { + value_narrow + }; let present_b = ssa.new_val(); insts.push(Inst::MaybePresent { dst: present_b, @@ -204,13 +279,10 @@ pub(crate) fn to_dyn_any( Ok(boxed) } -pub(crate) fn to_dyn( - ssa: &mut Ssa, - insts: &mut Vec, - v: ValueId, - ty: Ty, - pc: usize, -) -> Result { +/// [`to_dyn`] for everything that is not a nullable carrier. Only [`to_dyn`] +/// calls it; the split exists so the carrier arms have somewhere to fall +/// through to. +fn to_dyn_plain(ssa: &mut Ssa, insts: &mut Vec, v: ValueId, ty: Ty, pc: usize) -> Result { let from = match ty { Ty::Dyn => return Ok(v), Ty::I64 => "from_i64", @@ -219,47 +291,56 @@ pub(crate) fn to_dyn( Ty::Nil => "from_nil", Ty::ListDyn => "from_list", Ty::MapStrDyn => "from_map", - // Typed string maps box via a value-boxing conversion (cold path: - // a typed map crossing a `try$call` cell boundary). - Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool => { - let converter = match ty { - Ty::MapStrI64 => "str_i64_to_dyn", - Ty::MapStrF64 => "str_f64_to_dyn", - _ => "str_bool_to_dyn", - }; - let converted = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(converted), - callee: AbiRef::new("map_h", converter), - args: vec![v], + // Both box by tagging the handle in place — no rebuild, so identity and + // any mutation ride along. + Ty::Set => "from_set", + Ty::Bytes => "from_bytes", + // A window too — in place, so the box keeps tracking the list it + // windows. Without a box it could not enter a list, a map, a struct + // field or a `try` value at all, which is why every one of those + // dropped the whole program to the VM. + Ty::SliceI64 => "from_slice", + // A typed map boxes **in place**, under a tag naming its carrier. + // + // It used to convert — `str_i64_to_dyn` rebuilds the map into a + // `str -> Dyn` one by re-inserting in iteration order. That is a + // re-representation, and the copy's layout is not the original's once + // deletions are in the history, so `println([m])` listed its entries in + // an order the VM never produces. `DYN_RAW`'s doc already said boxing + // must not re-represent a container; this is the same rule, applied + // where it had been missed. + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapI64I64 | Ty::MapI64F64 => { + let kind = typed_map_kind(ty).expect("checked by the arm"); + let kind_v = ssa.new_val(); + insts.push(Inst::Const { + dst: kind_v, + value: Const::I64(kind), }); let boxed = ssa.new_val(); insts.push(Inst::Call { dst: Some(boxed), - callee: AbiRef::new("dyn", "from_map"), - args: vec![converted], + callee: AbiRef::new("dyn", "from_typed_map"), + args: vec![v, kind_v], }); return Ok(boxed); } - // Typed lists box via an element-wise conversion (cold path: only - // emitted where a typed list actually meets a Dyn). + // A typed list boxes **in place** too, for the same reason the typed + // maps above do — and here the rebuild was losing more than an order. + // `let xs = [1]; let c = [xs]; xs.push(2); c[0].len()` answered 1 where + // the VM answers 2, and `c[0].push(9)` appended to the copy: both + // directions of aliasing, on programs that compiled fully native. Ty::ListI64 | Ty::ListF64 | Ty::ListStr => { - let converter = match ty { - Ty::ListI64 => "i64_to_dyn", - Ty::ListF64 => "f64_to_dyn", - _ => "str_to_dyn", - }; - let converted = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(converted), - callee: AbiRef::new("list_h", converter), - args: vec![v], + let kind = typed_list_kind(ty).expect("checked by the arm"); + let kind_v = ssa.new_val(); + insts.push(Inst::Const { + dst: kind_v, + value: Const::I64(kind), }); let boxed = ssa.new_val(); insts.push(Inst::Call { dst: Some(boxed), - callee: AbiRef::new("dyn", "from_list"), - args: vec![converted], + callee: AbiRef::new("dyn", "from_typed_list"), + args: vec![v, kind_v], }); return Ok(boxed); } diff --git a/aot/lower/src/function.rs b/aot/lower/src/function.rs index a93310b4..b55b6e2a 100644 --- a/aot/lower/src/function.rs +++ b/aot/lower/src/function.rs @@ -1,5 +1,534 @@ use super::*; +/// Did anything before `pc` write `reg`? +/// +/// A textual scan rather than a question to the SSA: this runs while the exit +/// table is being built, before any block exists to ask about. The same +/// over-approximation as `written_registers` applies, and in the same +/// direction — a false positive costs a rejection. +/// How a value of this type is taken back out of a cell, if it can be. +/// +/// Boxing into a `Dyn` works for everything; coming back out is per type, and +/// the ones missing here are missing on purpose — a `Maybe` carrier, a channel, +/// a closure. Guessing at one produces a wrong value, so their regions reject. +/// How a register's value comes back out of the cell it travelled in. +/// +/// `None` means it cannot, and the region rejects. +/// See [`unbox_from_dyn`]. +enum CellReadBack { + /// The cell's content is the value; nothing to do. + Identity, + /// The ABI entry that takes the value back out. + Unbox(&'static str, &'static str), +} + +/// Whether a register of this type crosses a region as a **raw handle** rather +/// than a boxed value. +/// +/// A typed container cannot be boxed and read back: its boxing is an +/// element-wise conversion, so the round trip is a copy and the body's writes to +/// the original are lost. It is parked as-is instead, under `DYN_RAW`, and both +/// ends check the tag — crossing the two families is a loud failure rather than +/// a `Vec` walked as `Vec`. +/// +/// One function, read by the caller (which seeds and reads back) and by the body +/// (which writes on each assignment), so the two cannot disagree about a cell. +pub(crate) fn cell_is_raw(ty: Ty) -> bool { + matches!( + ty, + Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::MapStrI64 + | Ty::MapI64I64 + | Ty::MapStrF64 + | Ty::MapI64F64 + | Ty::MapStrBool + | Ty::Set + | Ty::Bytes + | Ty::SliceI64 + ) +} + +fn unbox_from_dyn(ty: Ty) -> Option { + Some(match ty { + // Already a boxed value: what the cell holds *is* the register's + // value, so there is nothing to convert. Not the same shape as the + // typed cases below — those name an ABI entry that reinterprets the + // cell's contents, and a container reinterpreted that way loses the + // mutation it travelled to carry (see this module's docs). + Ty::Dyn => CellReadBack::Identity, + // A register that holds nil *going in* says nothing about what the body + // will put there, and the body boxes whatever it writes — so the honest + // readback type is `Dyn`, not `Nil`. Reading it back as `Nil` would + // describe the seed rather than the value, which is why + // `let x = nil; try { x = 5; } catch e {}` was rejected outright. + Ty::Nil => CellReadBack::Identity, + Ty::I64 => CellReadBack::Unbox("dyn", "as_i64"), + // Answers 0/1 in an `i64`, so the caller narrows it back to a `Bool`. + Ty::Bool => CellReadBack::Unbox("dyn", "as_bool"), + Ty::F64 => CellReadBack::Unbox("dyn", "as_f64"), + Ty::Str => CellReadBack::Unbox("dyn", "as_str"), + // A container that is *already* boxed round-trips by pointer: + // `dyn.from_list` / `dyn.from_map` only tag the handle, and + // `dyn.as_list` / `dyn.as_map` check the tag and hand the same pointer + // back — so the register keeps its identity and the mutations it + // travelled to carry. + // + // A **typed** container cannot join them, and the reason is not caution: + // its boxing (`dyn_box`) is an element-wise *conversion* + // (`list_h::i64_to_dyn` builds a second list), so a round trip would + // hand back a copy — a different handle, with the body's writes to the + // original lost. Tagging the typed handle as `DYN_LIST` instead is worse + // than wrong: a `Vec` read as a `Vec` is a memory-safety + // bug. Giving them a round trip means an identity-preserving cell (a raw + // handle slot, not a boxed one), not another entry in this table. + Ty::ListDyn => CellReadBack::Unbox("dyn", "as_list"), + // A window is parked raw by `cell_is_raw` when the register already + // holds one; this is the other case — a register seeded `nil` that the + // body assigns a window to, which travels boxed like any other value. + Ty::SliceI64 => CellReadBack::Unbox("dyn", "as_slice"), + Ty::MapStrDyn => CellReadBack::Unbox("dyn", "as_map"), + _ => return None, + }) +} + +/// Whether a value of this type can be taken back out of a cell at all. +/// +/// The same table [`unbox_cell_value`] uses, asked without emitting anything — +/// what a call site consults before promising a callee that its cell holds one. +pub(crate) fn unbox_cell_value_supported(ty: Ty) -> bool { + unbox_from_dyn(ty).is_some() +} + +/// Joins what a cell was already agreed to hold with what this call site is +/// seeding it from. +/// +/// **Monotone, and that is the whole point.** The fixpoint's early passes +/// observe *provisional* types — a callee's return type is its `I64` default +/// until its body has been lowered once — so a rule that simply overwrote made +/// the agreement flip type every pass, the snapshot never settle, and the +/// budget run out: `examples/syntax/closure.lk` stopped lowering entirely. +/// Disagreement goes to `Dyn`, which is where a cell was before any of this, +/// and `Dyn` is absorbing. +pub(crate) fn join_cell_content(previous: Option, seeded: Ty) -> Ty { + match previous { + Some(prev) if prev == seeded => prev, + Some(_) => Ty::Dyn, + None if unbox_cell_value_supported(seeded) => seeded, + None => Ty::Dyn, + } +} + +/// Takes a value of type `ty` back out of the boxed `Dyn` a cell holds. +/// +/// One function for the three places that do it — the region's output cells, +/// the return channel, and a cell *input*'s reads inside the body — so the +/// `Bool` narrowing below cannot be remembered at two of them and forgotten at +/// the third. `None` means the type has no readback and the caller rejects. +pub(crate) fn unbox_cell_value(ssa: &mut Ssa, insts: &mut Vec, boxed: ValueId, ty: Ty) -> Option { + match unbox_from_dyn(ty)? { + CellReadBack::Identity => Some(boxed), + CellReadBack::Unbox(module, name) => { + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new(module, name), + args: vec![boxed], + }); + // `dyn.as_bool` answers an `i64`; a `Bool` operand is narrower, and + // the Cranelift verifier rejects the wide value. + if ty != Ty::Bool { + return Some(raw); + } + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let narrow = ssa.new_val(); + insts.push(Inst::Cmp { + dst: narrow, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + Some(narrow) + } + } +} + +/// The function a region's body became, or a rejection naming the region. +fn body_index_of(sig: &SigInfer, func_index: u32, begin_pc: usize) -> Result { + sig.try_bodies + .get(&(func_index, begin_pc)) + .copied() + .ok_or(Unsupported::TryRegion { + pc: begin_pc, + reason: "the body was not outlined", + }) +} + +/// Can a value of this type travel through the trampoline's argument buffer? +/// +/// The buffer is machine words, so the test is "does one word hold it": an +/// integer, and a container handle, which is a pointer. `F64` cannot — the ABI +/// passes it in XMM while the trampoline passes integers — and neither can the +/// two-register carriers (`Dyn`, the `Maybe`s), which have no single word to be. +fn crosses_as_word(ty: Ty) -> bool { + matches!( + ty, + Ty::I64 + // A `Bool` is 0/1 and an `F64` is eight bytes — both are machine + // words. Leaving `Bool` out is what made + // `fn probe(c: Bool) { let r = try { … } catch e { … }; }` reject + // while the same function with an `Int` parameter lowered. + // + // `F64` needs one more step, because the trampoline's signature is + // all `long long`: the body declares the parameter `I64` and reads + // the float back out of those bits (`Inst::BitsToFloat`). Declaring + // it `F64` instead made Cranelift read a *float* register — that + // compiled and segfaulted. + | Ty::Bool + | Ty::F64 + | Ty::Str + | Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::ListDyn + | Ty::MapStrI64 + | Ty::MapI64I64 + | Ty::MapStrF64 + | Ty::MapI64F64 + | Ty::MapStrBool + | Ty::MapStrDyn + | Ty::Set + | Ty::Bytes + // A window is a handle like the rest — it was the one carrier + // missing from this list, so a `try` that so much as *mentioned* a + // `xs.slice(a, b)` dropped the whole program to the VM while the + // same body over the list itself lowered. + | Ty::SliceI64 + ) +} + +/// Writes every tracked register whose definition changed into the cell its +/// caller allocated for it. +/// +/// `before` is what those registers held at the last mirror, in `cell_handles` +/// order. Asking the SSA what changed — rather than reading an opcode's `a` +/// field — is what makes this correct for *any* producer of a definition: an +/// ordinary instruction, and equally a nested region's write-back, which +/// produces its definitions in the exit handling where no opcode is in sight. +#[allow(clippy::too_many_arguments)] +fn mirror_cells( + ssa: &mut Ssa, + insts: &mut Vec, + sig: &SigInfer, + func_index: u32, + cell_handles: &[(u8, ValueId)], + before: &[Option], + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + for (index, (reg, handle)) in cell_handles.iter().enumerate() { + let now = ssa.current_def[block][*reg as usize]; + if now == before.get(index).copied().flatten() { + continue; + } + let Some((value, ty)) = now else { continue }; + // Written immediately, not at the end of the body: a raise can happen + // in the next call, and the VM shows whatever was assigned before it. + // Storing only on the way out would lose exactly the writes a handler + // is most likely to look at. + // The cell's kind is the caller's, not this store's type: a raw handle + // written into a value cell is a loud failure at the read + // (`cell_get_raw` checks the tag), and a register the caller saw as + // `nil` gets a *value* cell however containery the body's assignment + // turns out to be. + if cell_is_raw(ty) && sig.try_body_raw_cells.contains(&(func_index, *reg)) { + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "cell_set_raw"), + args: vec![*handle, value], + }); + continue; + } + // Into a value cell: boxing a typed *list* rebuilds it, so a mutation + // made after this store would not travel — reject rather than answer + // with a stale copy. Every other container boxes in place + // (`DYN_SET`/`DYN_BYTES`/`DYN_SLICE`/the typed map tags), so it carries + // whatever the body does to it. + if matches!(ty, Ty::ListI64 | Ty::ListF64 | Ty::ListStr) { + return Err(Unsupported::TryRegion { + pc, + reason: "a nil-seeded register is assigned a typed list, which cannot be boxed in place", + }); + } + let boxed = crate::dyn_box::to_dyn(ssa, insts, value, ty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "cell_set"), + args: vec![*handle, boxed], + }); + } + Ok(()) +} + +/// Whether this type occupies **two** machine registers. +/// +/// `Dyn` is `{tag, payload}` and each `Maybe` is `{value, present}`. Everything +/// else in the type set is one word or is not a value at all. A carrier crosses +/// the `try`-region boundary as two words rather than one +/// (`Inst::CarrierWord`), which is the only correct way to move it through a +/// buffer of `long long`s: the alternatives are to unwrap it — which aborts +/// when a `Maybe` is absent, where the body may only have asked +/// `x ?? default` — or to refuse, which is what it used to do. +/// +/// Exhaustive on purpose. A type missing from the two-register side would be +/// split into halves that do not exist; one wrongly on it would cross as two +/// words the body then binds as one parameter too many. +fn crosses_as_two_words(ty: Ty) -> bool { + match ty { + Ty::Dyn | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool => true, + Ty::I64 + | Ty::F64 + | Ty::Bool + | Ty::Str + | Ty::Nil + | Ty::Cell + | Ty::ListDyn + | Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::SliceI64 + | Ty::MapStrDyn + | Ty::MapStrI64 + | Ty::MapI64I64 + | Ty::MapStrF64 + | Ty::MapI64F64 + | Ty::MapStrBool + | Ty::Set + | Ty::Bytes => false, + } +} + +/// The closure identity a region input names, when it is one. +/// +/// Asked *before* the register is read, because reading it is what fails: a +/// lambda is a compile-time `GlobalRef` with no SSA value behind it, and the +/// generic reader reports that as `ReferenceAsValue`. Recording the identity is +/// a fixpoint discovery like every other — the body has already been lowered +/// once with this register as a plain word, so the answer only takes effect on +/// the pass after it is written down. +/// The recorded identity of a region input, when it still applies. +/// +/// [`lambda_region_input`] writes the map once and it stays written, which is +/// what makes the plumbing on the two sides of the boundary agree. It stops +/// applying when the same lambda later turns out to be used as a *value*: it is +/// then built at its definition (`inst/call.rs::bind_lambda`), so the parent's +/// register holds a closure handle and the input is an ordinary word. Without +/// this the two facts contradicted each other and the fixpoint could not +/// resolve it — the body kept asking for a value the discovery arm had already +/// granted, and the program stopped lowering for good. +pub(crate) fn try_body_lambda(sig: &SigInfer, body: u32, reg: u8) -> Option { + let identity = sig.try_body_lambdas.get(&(body, reg)).copied()?; + (!sig.value_lambdas.contains_key(&identity.fidx)).then_some(identity) +} + +fn lambda_region_input(ssa: &mut Ssa, sig: &mut SigInfer, body: u32, reg: u8, block: usize) -> Option { + let identity = match ssa.builtin_ref_at(reg, block)? { + GlobalRef::Lambda(fidx) => LambdaIdentity { fidx, captures: 0 }, + GlobalRef::Closure(fidx, caps) => LambdaIdentity { + fidx, + captures: caps.len() as u16, + }, + // A module or a builtin is re-derived inside the body by its own + // `GetGlobal`; a cell is an input, but a different kind of one + // ([`cell_region_input`]). + _ => return None, + }; + sig.try_body_lambdas.insert((body, reg), identity); + Some(identity) +} + +/// The upvalue cell a region input names, when it is one. +/// +/// A variable some closure captures is a *cell* — the register holds a +/// `GlobalRef::Cell` and its content lives in a virtual slot — so a region that +/// so much as reads one had no word to marshal and rejected on the body's +/// `LoadCellVal`. That is not a rare shape: a function parameter mentioned by +/// any lambda in the function is one, which is why the generated corpus of +/// nested regions lowered two programs in a hundred and ninety-one. +/// +/// It crosses as a **runtime** cell, the same object a closure's mutable +/// capture crosses as: the caller seeds one from the slot, the body names it as +/// a capture parameter (`inst::global` already reads and writes those through +/// `rt.cell_get`/`rt.cell_set`), and the caller reads the slot back afterwards. +/// So the body's writes are visible to the parent whether it returned or +/// raised, which is the property the region's register cells exist for. +fn cell_region_input( + ssa: &mut Ssa, + sig: &mut SigInfer, + capture_params: &[(ValueId, Ty)], + body: u32, + reg: u8, + block: usize, +) -> Option { + let input = match ssa.builtin_ref_at(reg, block)? { + GlobalRef::Cell(cid) => { + // A cell holding a callable *reference* has no runtime content at + // all; the lambda path is the one that carries those. + if ssa.cell_refs.contains_key(&cid) { + return None; + } + CellInput::Slot(cid) + } + // This function is itself a region's body (or a closure), so it holds + // the cell as a pointer already: the region one frame in gets the same + // pointer, and all three frames name one cell. + GlobalRef::CellParam(k) => match capture_params.get(k) { + Some(&(handle, Ty::Cell)) => CellInput::Handle(handle), + _ => return None, + }, + _ => return None, + }; + sig.try_body_cell_inputs.insert((body, reg)); + Some(input) +} + +/// Where the runtime cell a region input travels in comes from. +enum CellInput { + /// A cell of the enclosing function: the caller makes the runtime cell from + /// its virtual slot and reads the slot back after the region. + Slot(u32), + /// A cell the enclosing function already holds a pointer to, because it is + /// itself a body or a closure. Passed on as is — there is one cell, so + /// there is nothing to resync. + Handle(ValueId), +} + +/// Everything [`try_lambda_env`] needs about the one input it is marshaling. +struct LambdaEnvSite<'a> { + cap_ctx: CaptureCtx<'a>, + body: u32, + reg: u8, + identity: LambdaIdentity, + /// The runtime cell each cell input of this region travels in, and what it + /// is agreed to hold, by cell id. + /// + /// A capture of the crossing closure that names one of them must get *that* + /// cell rather than a snapshot of its content: the body writes through it, + /// and the closure is called after those writes. `try { a = a * 2; a = + /// clo(); }`, with `clo` capturing `a`, read the value `a` had entering the + /// region and answered `3` where the VM answered `6`. + region_cells: &'a std::collections::HashMap, + /// The lambda's own parameter count, which is where its capture slots begin + /// ([`SigInfer::require_cell_capture`] keys on it). + callee_param_count: usize, +} + +/// Appends a lambda region input's environment to the region call's arguments. +/// +/// The identity itself is not passed: the body seeds the register with the ref +/// (see the `try_params` binding), exactly the way an erased lambda argument +/// reaches an ordinary call. What crosses is one machine word per capture, in +/// capture order, so both sides walk `try_body_params` and agree on the layout +/// without either of them writing it down. +fn try_lambda_env( + ssa: &mut Ssa, + insts: &mut Vec, + sig: &mut SigInfer, + site: LambdaEnvSite<'_>, + call_args: &mut Vec, + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + let LambdaEnvSite { + cap_ctx, + body, + reg, + identity, + region_cells, + callee_param_count, + } = site; + if identity.captures == 0 { + return Ok(()); + } + let Some(GlobalRef::Closure(_, caps)) = ssa.builtin_ref_at(reg, block) else { + return Err(Unsupported::TryRegion { + pc, + reason: "a closure region input stopped resolving to the closure it was recorded as", + }); + }; + let resolver = CaptureSite::new(cap_ctx, identity.fidx, CaptureMode::Share, block, pc); + for (k, capture) in caps.iter().enumerate() { + let (v, ty) = match resolver.resolve(ssa, insts, sig, capture, k)? { + Some(resolved) => resolved, + None => { + let ClosureCapture::Cell(cid) = capture else { + unreachable!("only `Cell` is left to the call site") + }; + // The body carries this very variable in a runtime cell, and it + // is called *after* the body has written through it: the closure + // gets the cell, not a copy of what it held on the way in. That + // means the callee's capture has to be a cell at every call + // site, which is a demand the fixpoint already knows how to + // propagate. + if let Some(&(handle, content)) = region_cells.get(cid) { + // Written down *before* any retry is asked for. The pass + // that asks does not reach the record at the bottom of this + // loop, so the next pass's body would bind this env word as + // the by-value type it had before — and calling the closure + // with it re-observes the parameter, joins it away from + // `Cell`, and the pin is re-requested forever. That is a + // fixpoint that never converges, reported as a rejection at + // the region's first pc. + sig.try_body_lambda_env_tys.insert((body, reg, k as u8), Ty::Cell); + // The closure reads through the same agreement the body + // does — one cell, one opinion about what is in it. + let joined = join_cell_content(sig.cell_capture_tys.get(&(identity.fidx, k)).copied(), content); + let mut retry = sig.require_cell_capture(identity.fidx as usize, callee_param_count, k); + retry |= sig.cell_capture_tys.insert((identity.fidx, k), joined) != Some(joined); + if retry { + return Err(Unsupported::TypeMismatch { pc }); + } + (handle, Ty::Cell) + } else { + // Nothing in the region touches it, so its content at entry + // is its content throughout — a snapshot is exact. + // + // A capture the closure *writes* is a different matter: the + // cell would have to travel and be read back, and there is + // no region cell to hang that on. + if sig.cell_captures.contains(&(identity.fidx, k)) { + return Err(Unsupported::TryRegion { + pc, + reason: "a closure crossing into the region assigns what it captured", + }); + } + ssa.read_slot(ssa.cell_slot(*cid), block, pc)? + } + } + }; + // A cell is a pointer, which is a word; `crosses_as_word` is about + // *values*, and answers no for it. + if ty != Ty::Cell && !crosses_as_word(ty) { + return Err(Unsupported::TryRegion { + pc, + reason: "a closure crossing into the region captures a value wider than a machine word", + }); + } + sig.try_body_lambda_env_tys.insert((body, reg, k as u8), ty); + // Stored into the trampoline's word buffer as-is, exactly as an + // ordinary input is: an `F64` goes in by its eight bytes and the body + // reads it back out of them (`Inst::BitsToFloat`). + call_args.push(v); + } + Ok(()) +} + /// Lowers a single function to a [`MirFunction`]. User (non-entry) functions use /// the `(i64, ...) -> i64` ABI in this slice: params and return are `I64`, verified /// via typed reads / a return-type check — a mismatch rejects (falls back) rather @@ -32,11 +561,116 @@ pub(crate) fn lower_function( .map(|(pc, raw)| Instr::try_from_raw(*raw).map_err(|_| Unsupported::BadInstr { pc })) .collect::, _>>()?; + // 0. Protected regions. Each body was outlined into a function of its own + // before this ran (see `lower_module`), so the parent must not see those + // instructions as control flow at all: they are marked consumed, and the + // `TryBegin` becomes one exit with two successors. + let regions = crate::try_region::scan(func, &instrs)?; + // 1. Classify control-flow exits; a fused `TestXxx`+`Jmp` consumes the `Jmp`. let mut consumed = vec![false; code_len]; - let exits: Vec> = (0..code_len) + for region in ®ions { + for flag in consumed.iter_mut().take(region.body_end + 1).skip(region.body_start) { + *flag = true; + } + // The `Jmp` over the handler belongs to the region, not to the body. + if region.body_end + 1 < code_len && instrs[region.body_end + 1].opcode() == Opcode::Jmp { + consumed[region.body_end + 1] = true; + } + } + let mut exits: Vec> = (0..code_len) .map(|pc| exit_of(pc, &instrs, code_len, &mut consumed, &func.performance)) .collect::, _>>()?; + // This function's own escape trailers, when it *is* a region's body: the + // last `n` instructions, placeholders whose real meaning is "write outcome + // code `2 + k` and return" (`try_region::outline`). They are jump targets + // like any other, which is the whole point — a `break` out of the region + // stays an ordinary jump right up to here. + let escape_trailers = sig.try_body_escapes.get(&func_index).copied().unwrap_or(0); + for k in 0..escape_trailers { + exits[code_len - escape_trailers + k] = Some(Exit::TryEscape { code: 2 + k as i64 }); + } + for region in ®ions { + // A body that writes a register the enclosing function already defined + // would, outlined, write it in the *body's* frame and leave the + // parent's copy untouched. The program then computes a different + // answer with nothing said — the one outcome worse than not compiling. + // + // Registers the parent has no definition for are safe: the body owns + // them, and a later read of one is undefined, which rejects on its own. + // Carrying a write back out needs the value to live in memory rather + // than a register, which is the next piece of work. + // Registers the body assigns that the enclosing function already had: + // they travel through cells, because a write in the body's own frame is + // invisible here otherwise — and on the raise path the body never + // returns to hand anything back, while the VM still shows what it wrote + // before raising. + // No cell is created on the strength of "the parent wrote this + // register before the region". That was an over-approximation of the + // question that matters — *does anything read it after* — and it paid + // for the approximation twice: a dead call-window temporary the body + // happened to reuse got a cell, and its value at the region had no type + // that could come back out, so the whole region rejected. + // + // Instead every register the body writes and does not carry back is + // poisoned at the region's exit, and a later read of one fails naming + // itself. That error is what the fixpoint already turns into a cell. + // So the set below starts empty and is filled by being asked. + let mut cells: Vec = Vec::new(); + // Registers a later read proved the body had to write back: they are + // not visible to the scan above, because nothing in this function + // defines them — the body does. + let body_index = body_index_of(sig, func_index, region.begin_pc)?; + // What the body rebound, as the body itself reported. Until it has been + // lowered once there is no report, and the syntactic scan stands in — + // conservative, and replaced on the next pass. + let body_writes: Vec = match sig.try_body_rebound.get(&body_index) { + Some(set) => { + let mut v: Vec = set.iter().copied().collect(); + v.sort_unstable(); + v + } + None => crate::try_region::written_registers(&instrs, region.body_start, region.body_end), + }; + if let Some(extra) = sig.try_body_extra_cells.get(&body_index) { + for ® in extra { + if reg != region.catch_reg && !cells.contains(®) && body_writes.contains(®) { + cells.push(reg); + } + } + } + // A cell of *this* function, when this function is itself a region's + // body: somebody past this frame reads it, which is what having a cell + // means, so a nested region that writes it has to carry it back even + // though nothing here reads it afterwards. + // + // The read-after-the-region evidence the set above is built from cannot + // see that reader — it is a frame away. `try { try { r = f(); } catch e + // { r = -1; } } catch e2 { r = -2; }` is the whole shape: the outer + // body's only statement is the inner `try`, so it never reads `r`, and + // the inner body's assignment was dropped with nothing said. + for ® in sig.try_body_cells.get(&func_index).unwrap_or(&Vec::new()) { + if reg != region.catch_reg && !cells.contains(®) && body_writes.contains(®) { + cells.push(reg); + } + } + cells.sort_unstable(); + sig.try_body_cells.insert(body_index, cells); + let body = sig + .try_bodies + .get(&(func_index, region.begin_pc)) + .copied() + .ok_or(Unsupported::TryRegion { + pc: region.begin_pc, + reason: "the body was not outlined", + })?; + exits[region.begin_pc] = Some(Exit::TryRegion { + body, + catch_reg: region.catch_reg, + handler: region.handler, + fallthrough: region.fallthrough, + }); + } // 2. Block leaders. let mut leaders = std::collections::BTreeSet::new(); @@ -45,7 +679,7 @@ pub(crate) fn lower_function( for (pc, exit) in exits.iter().enumerate() { match exit { None => {} - Some(Exit::Ret(_)) => { + Some(Exit::Ret(_)) | Some(Exit::TryEscape { .. }) => { if pc + 1 < code_len { leaders.insert(pc + 1); } @@ -71,9 +705,28 @@ pub(crate) fn lower_function( mark_target(*taken, code_len, &mut leaders, &mut implicit_ret); mark_target(*fallthrough, code_len, &mut leaders, &mut implicit_ret); } + Some(Exit::TryRegion { + handler, fallthrough, .. + }) => { + mark_target(*handler, code_len, &mut leaders, &mut implicit_ret); + mark_target(*fallthrough, code_len, &mut leaders, &mut implicit_ret); + // Where the body's `break`/`continue` land: real edges out of + // this block, so real leaders. + for &target in escape_targets_at(®ions, pc) { + mark_target(target, code_len, &mut leaders, &mut implicit_ret); + } + } } } + // A function whose code simply runs out reaches the same one-past-end + // target an explicit exit would name, so it needs the same block. Only the + // last block can do this, and only when nothing in it is an exit. + let last_leader = *leaders.iter().next_back().expect("block 0 is always a leader"); + if block_span(&exits, &consumed, last_leader, code_len).1.is_none() { + implicit_ret = true; + } + // 3. Block ids (+ optional synthetic implicit-nil-return block). let leader_vec: Vec = leaders.iter().copied().collect(); let pc_to_block: BTreeMap = leader_vec.iter().enumerate().map(|(i, &pc)| (pc, i as u32)).collect(); @@ -99,10 +752,40 @@ pub(crate) fn lower_function( .enumerate() .map(|(bi, &start)| (start, leader_vec.get(bi + 1).copied().unwrap_or(code_len))) .collect(); + let mut successors: Vec> = vec![Vec::new(); total_blocks]; for (bi, &(start, end)) in block_bounds.iter().enumerate() { - let (_, exit) = block_span(&exits, &consumed, start, end); - for succ in exit_successors(exit, end) { - preds[block_of(succ)].push(bi); + let (exit_pc, exit) = block_span(&exits, &consumed, start, end); + successors[bi] = exit_successors(exit, end).into_iter().map(block_of).collect(); + if matches!(exit, Some(Exit::TryRegion { .. })) { + successors[bi].extend(escape_targets_at(®ions, exit_pc).iter().copied().map(block_of)); + } + } + + // Blocks control can actually get to, from the entry. Code after a `return` + // is not lowered and contributes no edges: with no predecessors of its own + // it has no definition for any register, and `read_recursive`'s empty-preds + // case answers "read before any definition" — which then propagated into + // every block it fell into, so `if c { return 1; } else { return 2; }` + // followed by one more line rejected the whole function over its own + // parameter. Bytecode also arrives from `.lkm` files, so the backend cannot + // rest on the compiler never emitting unreachable code. + let mut reachable = vec![false; total_blocks]; + let mut worklist = vec![0usize]; + reachable[0] = true; + while let Some(bi) = worklist.pop() { + for &succ in &successors[bi] { + if !reachable[succ] { + reachable[succ] = true; + worklist.push(succ); + } + } + } + for (bi, succs) in successors.iter().enumerate() { + if !reachable[bi] { + continue; + } + for &succ in succs { + preds[succ].push(bi); } } @@ -126,8 +809,14 @@ pub(crate) fn lower_function( .filter(|&&(fi, _, _)| fi == func_index) .map(|&(_, b, s)| (b, s)) .collect(); - ssa.dyn_empty_pcs = sig - .dyn_empty_lists + ssa.no_provenance_slots = sig + .no_phi_provenance + .iter() + .filter(|&&(fi, _, _)| fi == func_index) + .map(|&(_, b, s)| (b, s)) + .collect(); + ssa.dyn_literal_pcs = sig + .dyn_literals .iter() .filter(|&&(fi, _)| fi == func_index) .map(|&(_, p)| p) @@ -146,15 +835,216 @@ pub(crate) fn lower_function( // visible parameters, so signature order matches the call site. if let Some(id) = identities.get(r).copied().flatten() { if id.captures == 0 { - ssa.builtin_regs.insert((0, r as u8), GlobalRef::Lambda(id.fidx)); + ssa.bind_ref(0, r as u8, GlobalRef::Lambda(id.fidx)); } continue; } let pty = sig.param_ty(func_index as usize, r); let pv = ssa.new_val(); ssa.current_def[0][r] = Some((pv, pty)); + // `self` in `impl T { … }` *is* a `T`. Provenance otherwise comes only + // from a `NewObject`, so inside an impl method the receiver had none + // and `self.other()` fell out of the devirtualizing path — the whole + // "a method built on the type's other methods" shape, which is most of + // what methods are for, and the reason a trait default body could not + // be lowered at all. + // + // An ordinary parameter gets it from the call sites instead + // (`sig.param_structs`), which is the same carry `ret_structs` does for + // a returned struct — passing one to a function is at least as common + // as returning one, and without this `fn area(q: P) { return q.norm(); }` + // dropped the module to the VM while `q.w * q.h` in the same position + // lowered fine. + if pty == Ty::MapStrDyn { + let provenance = if r == 0 { + sig.traits.impl_owner(func_index) + } else { + None + } + .or_else(|| match sig.param_structs.get(&(func_index as usize, r)) { + Some(Some(crate::ssa::StructFact::Struct(name))) => Some(name.clone()), + _ => None, + }); + if let Some(type_name) = provenance { + ssa.set_struct(pv, type_name); + } else if let Some(fact) = sig.param_structs.get(&(func_index as usize, r)).cloned().flatten() { + // Not a struct, but proven: every call site passes an ordinary + // map. Without carrying that, `fn n(m) { return m.len(); }` + // could not tell a map from a struct instance and refused. + ssa.struct_facts.insert(pv, fact); + } + } fn_params.push((pv, pty)); } + // A try body's inputs: registers of the *enclosing* function, bound here as + // ordinary trailing parameters. They are all `I64` because the trampoline + // passes machine words; a body that needs something wider rejects when it + // reads it, which is the honest failure. + // Whether this function *is* a region's body, which is what makes the + // per-register snapshot below worth taking. + let is_try_body = sig.try_bodies.values().any(|&b| b == func_index); + let mut rebound: std::collections::HashSet = std::collections::HashSet::new(); + let try_params: Vec = sig.try_body_params.get(&func_index).cloned().unwrap_or_default(); + let mut try_param_bitcasts: Vec<(u8, ValueId)> = Vec::new(); + // The same read-back for a lambda input's `F64` *capture*, which has no + // register of its own: the closure ref already names the float value, so + // only the bitcast producing it is outstanding. + let mut entry_bitcasts: Vec<(ValueId, ValueId)> = Vec::new(); + // Carriers to reassemble at entry, from the two words they crossed as. + let mut entry_carriers: Vec<(ValueId, ValueId, ValueId, Ty)> = Vec::new(); + // Upvalue-cell inputs, bound here in `try_params` order (so the caller's + // argument layout is matched) and wired into `capture_params` below, once + // that exists — `inst::global` reads and writes a `CellParam` backed by a + // runtime cell through `rt.cell_get`/`rt.cell_set`, which is exactly the + // sharing this input needs. + let mut cell_input_params: Vec<(u8, ValueId)> = Vec::new(); + for ® in &try_params { + if sig.try_body_cell_inputs.contains(&(func_index, reg)) { + let pv = ssa.new_val(); + fn_params.push((pv, Ty::Cell)); + cell_input_params.push((reg, pv)); + continue; + } + // A lambda input: its identity is a compile-time fact the caller wrote + // down, so the register is seeded with the reference and only the + // environment is bound — one parameter per capture, in capture order, + // which is the order the caller pushed them. + if let Some(identity) = try_body_lambda(sig, func_index, reg) { + let mut caps = Vec::with_capacity(identity.captures as usize); + for k in 0..identity.captures { + let ety = sig + .try_body_lambda_env_tys + .get(&(func_index, reg, k as u8)) + .copied() + .unwrap_or(Ty::I64); + let ev = ssa.new_val(); + if ety == Ty::F64 { + fn_params.push((ev, Ty::I64)); + let f = ssa.new_val(); + entry_bitcasts.push((f, ev)); + caps.push(ClosureCapture::Value(f, Ty::F64)); + } else { + fn_params.push((ev, ety)); + caps.push(ClosureCapture::Value(ev, ety)); + } + } + let global_ref = if caps.is_empty() { + GlobalRef::Lambda(identity.fidx) + } else { + GlobalRef::Closure(identity.fidx, caps) + }; + ssa.bind_ref(0, reg, global_ref); + continue; + } + let ty = sig + .try_body_param_tys + .get(&(func_index, reg)) + .copied() + .unwrap_or(Ty::I64); + let is_closure_input = sig.try_body_closure_inputs.contains(&(func_index, reg)); + let struct_input = sig.try_body_struct_inputs.get(&(func_index, reg)).cloned(); + // The two words a carrier crossed as, fused back into one. + if crosses_as_two_words(ty) { + let lo = ssa.new_val(); + fn_params.push((lo, Ty::I64)); + let hi = ssa.new_val(); + fn_params.push((hi, Ty::I64)); + let carrier = ssa.new_val(); + entry_carriers.push((carrier, lo, hi, ty)); + ssa.current_def[0][reg as usize] = Some((carrier, ty)); + if is_closure_input { + ssa.closure_values.insert(carrier); + } + continue; + } + let pv = ssa.new_val(); + // An `F64` input is declared `I64` and read back out of those bits at + // entry: the trampoline calls this body through a `(long long, …)` + // signature (`lkrt/src/try_trampoline.c`), so every input arrives in an + // integer register. Declaring the parameter `F64` made Cranelift read a + // *float* register instead — it compiled and segfaulted. + if ty == Ty::F64 { + fn_params.push((pv, Ty::I64)); + try_param_bitcasts.push((reg, pv)); + } else { + ssa.current_def[0][reg as usize] = Some((pv, ty)); + // Carried across the boundary rather than re-derived: the body has + // no definition to look at. + if is_closure_input { + ssa.closure_values.insert(pv); + } + if let Some(fact) = struct_input { + ssa.struct_facts.insert(pv, fact); + } + fn_params.push((pv, ty)); + } + } + // The cells this body writes through, in the same order the caller passes + // them. They are handles, not values: the register keeps its own value in + // SSA, and every change to it is *also* written to the cell, so the caller + // sees it whether the body returned or raised. + let try_cells: Vec = sig.try_body_cells.get(&func_index).cloned().unwrap_or_default(); + let mut cell_handles: Vec<(u8, ValueId)> = Vec::with_capacity(try_cells.len()); + for ® in &try_cells { + let pv = ssa.new_val(); + fn_params.push((pv, Ty::Cell)); + cell_handles.push((reg, pv)); + } + + // The enclosing function's captures, when this body is a region's. They come + // before the outcome channel and are declared here rather than with the + // body's own captures (it has none) because they have to occupy capture + // indices `0..n` — a `LoadCapture k` inside the body is the *enclosing* + // closure's `k`. See `SigInfer::try_body_outer_captures`. + let outer_capture_tys: Vec = sig + .try_body_outer_captures + .get(&func_index) + .cloned() + .unwrap_or_default(); + let mut outer_capture_params: Vec<(ValueId, Ty)> = Vec::with_capacity(outer_capture_tys.len()); + for &cty in &outer_capture_tys { + // The same two shapes a region input crosses in: one machine word, or a + // carrier's two raw words reassembled before the body's first + // instruction. An `F64` declares `I64` and bit-casts, for the reason in + // `crosses_as_word`. + if crosses_as_two_words(cty) { + let lo = ssa.new_val(); + fn_params.push((lo, Ty::I64)); + let hi = ssa.new_val(); + fn_params.push((hi, Ty::I64)); + let carrier = ssa.new_val(); + entry_carriers.push((carrier, lo, hi, cty)); + outer_capture_params.push((carrier, cty)); + continue; + } + let cv = ssa.new_val(); + if cty == Ty::F64 { + fn_params.push((cv, Ty::I64)); + let f = ssa.new_val(); + entry_bitcasts.push((f, cv)); + outer_capture_params.push((f, Ty::F64)); + continue; + } + fn_params.push((cv, cty)); + outer_capture_params.push((cv, cty)); + } + + // A body that leaves the enclosing function's control flow — `return`, + // `break`, `continue` — takes an outcome flag cell saying which, and a + // `return` takes a second cell for the value. They come last, so nothing + // else shifts. (`SigInfer::try_body_returns`, `try_body_escapes`.) + let body_returns = sig.try_body_returns.contains(&func_index); + let outcome_flag = (body_returns || escape_trailers > 0).then(|| { + let flag = ssa.new_val(); + fn_params.push((flag, Ty::Cell)); + flag + }); + let return_channel = outcome_flag.filter(|_| body_returns).map(|flag| { + let value = ssa.new_val(); + fn_params.push((value, Ty::Cell)); + (flag, value) + }); + // An erased *capturing* closure argument: its environment (resolved at // the call site) arrives as hidden trailing parameters, one block per // erased parameter in parameter order. The register holds a Closure ref @@ -175,19 +1065,48 @@ pub(crate) fn lower_function( caps.push(ClosureCapture::Value(ev, ety)); env_offset += 1; } - ssa.builtin_regs.insert((0, r as u8), GlobalRef::Closure(id.fidx, caps)); + ssa.bind_ref(0, r as u8, GlobalRef::Closure(id.fidx, caps)); } // A capturing lambda's own environment arrives after any erased-argument // env blocks (the closure's by-value snapshot, appended by the `Call` // lowering); it occupies no register — `LoadCapture k` reads it directly. let spawned_isolate = sig.spawned_isolate.contains(&func_index); ssa.spawned_isolate = spawned_isolate; - let mut capture_params: Vec<(ValueId, Ty)> = Vec::with_capacity(capture_count); + // An environment that is entirely static references carries nothing at + // runtime, so it is not declared at all (`SigInfer::captures_all_static`). + let erased_environment = sig.captures_all_static(func_index as usize, capture_count); + let mut capture_params: Vec<(ValueId, Ty)> = Vec::with_capacity(capture_count + outer_capture_params.len()); + // A try body's capture list *is* the enclosing function's, so these go in + // first and keep their indices. `capture_count` is zero for such a body, so + // the loop below adds nothing after them. + for (k, &(cv, cty)) in outer_capture_params.iter().enumerate() { + capture_params.push((cv, cty)); + if cty == Ty::Cell { + ssa.cellparam_content.insert( + k, + sig.try_body_outer_cell_tys + .get(&(func_index, k)) + .copied() + .unwrap_or(Ty::Dyn), + ); + } + } for k in 0..capture_count { let cty = sig.param_ty(func_index as usize, param_count + env_total + k); let cv = ssa.new_val(); capture_params.push((cv, cty)); - fn_params.push((cv, cty)); + if !erased_environment { + fn_params.push((cv, cty)); + } + // What reads of this capture unbox to, when it arrived as a runtime + // cell. The call site wrote it down (`SigInfer::cell_capture_tys`); + // unset means `Dyn`, which is what a cell answered everywhere before. + if cty == Ty::Cell { + ssa.cellparam_content.insert( + k, + sig.cell_capture_tys.get(&(func_index, k)).copied().unwrap_or(Ty::Dyn), + ); + } // A spawned goroutine's cell captures are thread-private copies: // seed the virtual slot so body writes (isolate — never visible to // the spawner) go through plain SSA. @@ -196,28 +1115,121 @@ pub(crate) fn lower_function( ssa.write_slot(slot, 0, (cv, cty)); } } + // The upvalue-cell inputs join the environment: a try body has no captures + // of its own (`capture_count` is 0), so these are all of it, and naming them + // as capture parameters is what lets `LoadCellVal`/`StoreCellVal` reach them + // through the arm that already knows how to read and write a runtime cell. + for (reg, pv) in cell_input_params { + let k = capture_params.len(); + ssa.bind_ref(0, reg, GlobalRef::CellParam(k)); + capture_params.push((pv, Ty::Cell)); + // What reads of this cell unbox to, and what a store into it must + // agree with. Unset (a closure's own capture) means `Dyn`, which is + // what a cell answered everywhere before this. + ssa.set_cellparam_content_ty( + k, + reg, + sig.try_body_cell_input_tys + .get(&(func_index, reg)) + .copied() + .unwrap_or(Ty::Dyn), + ); + } let mut block_insts: Vec> = vec![Vec::new(); total_blocks]; let mut block_exit: Vec> = vec![None; total_blocks]; let mut ret_ty: Option = None; // Resolved terminator value reads (filled during each block's lowering). + // Regions whose body may leave this function's control flow: the ok edge + // gets a check block, and a dispatch behind it. Collected here and emitted + // after the block loop, where this function's own return type is known. + let mut try_exit_checks: Vec = Vec::new(); let mut ret_val: Vec> = vec![None; total_blocks]; let mut cond_val: Vec> = vec![None; total_blocks]; for (bi, &(start, end)) in block_bounds.iter().enumerate() { ssa.seal_ready()?; + if !reachable[bi] { + // Filled and left empty: it keeps its block id (successors are + // addressed by it) and gets a terminator in step 6. + ssa.mark_filled(bi); + continue; + } let (body_end, exit) = block_span(&exits, &consumed, start, end); if exit.is_none() { ssa.single_fallthrough_target[bi] = Some(end); } let mut insts = Vec::new(); + // A float input arrives as bits (see the parameter binding above): read + // it back as a float before the body's first instruction. + if bi == 0 { + for &(reg, bits) in &try_param_bitcasts { + let f = ssa.new_val(); + insts.push(Inst::BitsToFloat { dst: f, src: bits }); + ssa.current_def[0][reg as usize] = Some((f, Ty::F64)); + } + for &(dst, bits) in &entry_bitcasts { + insts.push(Inst::BitsToFloat { dst, src: bits }); + } + for &(dst, lo, hi, ty) in &entry_carriers { + insts.push(Inst::CarrierFromParts { dst, lo, hi, ty }); + } + } + // The entry describes every declared struct to the runtime before any + // user code runs: its type id, name, and field names in declaration + // order. `display` needs them where the *mark* is — at runtime — because + // a field holding another struct is a bare map by then and the display + // site cannot tell (see `docs/aot/aot-gaps-and-lkrt.md`). + if is_entry && bi == 0 { + for (tid, name, fields) in sig.traits.struct_fields.clone() { + let id = ssa.new_val(); + insts.push(Inst::Const { + dst: id, + value: Const::I64(tid), + }); + let name_v = const_str_value(&mut ssa, &mut insts, globals, &name); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("obj_ty", "begin"), + args: vec![id, name_v], + }); + for (field, declared) in &fields { + let field_v = const_str_value(&mut ssa, &mut insts, globals, field); + let declared_v = ssa.new_val(); + insts.push(Inst::Const { + dst: declared_v, + value: Const::I64(*declared), + }); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("obj_ty", "field"), + args: vec![id, field_v, declared_v], + }); + } + } + } #[allow(clippy::needless_range_loop)] // `pc` is the semantic bytecode index for pc in start..body_end { + // What the tracked registers held before this instruction, so a + // change can be noticed afterwards. Asking the SSA what changed is + // the same device the inputs use: no table of which opcode writes + // where, and therefore no entry in such a table to get wrong. + let before: Vec> = cell_handles + .iter() + .map(|(reg, _)| ssa.current_def[bi][*reg as usize]) + .collect(); + // And the same question asked of *every* register, which is what + // tells the parent which of them this body rebound. A mutation + // through a shared handle changes no `current_def` and so does not + // appear here — which is the whole difference the `a` field could + // not express. + let before_all: Vec> = (0..ssa.reg_count).map(|r| ssa.current_def[bi][r]).collect(); lower_inst( &mut LowerCtx { ssa: &mut ssa, globals, sig, func, + func_index, funcs, entry, module_globals, @@ -228,7 +1240,28 @@ pub(crate) fn lower_function( &instrs[pc], pc, )?; + if is_try_body { + for r in 0..ssa.reg_count { + if ssa.current_def[bi][r] != before_all[r] { + rebound.insert(r as u8); + } + } + } + mirror_cells(&mut ssa, &mut insts, sig, func_index, &cell_handles, &before, bi, pc)?; } + // The terminator can produce definitions too, and one of them is a + // *nested* region's write-back: an inner `try` hands its cells' values + // back here, in the exit handling, where the per-instruction mirror + // above has already run. Snapshotting across the terminator and + // mirroring after it is what carries an inner region's writes out + // through this body's own cells — without it, a `try` inside a `try` + // compiled to a program that dropped the inner body's assignments and + // said nothing. + let before_exit: Vec> = cell_handles + .iter() + .map(|(reg, _)| ssa.current_def[bi][*reg as usize]) + .collect(); + let before_all_exit: Vec> = (0..ssa.reg_count).map(|r| ssa.current_def[bi][r]).collect(); // Resolve the terminator's value reads while this block is current. match exit { Some(Exit::Ret(Some(reg))) => { @@ -247,53 +1280,603 @@ pub(crate) fn lower_function( && ret_closure_body_is_pure(&instrs) { record_ret_closure(sig, func_index as usize, candidate); + // Summarized: call sites build the closure from their + // own argument values and this body is never emitted, + // so there is nothing here to return. + return Err(Unsupported::Opcode { + pc: start, + op: Opcode::Return1, + }); } - return Err(Unsupported::Opcode { - pc: start, - op: Opcode::Return1, - }); + // Not summarizable — two returns in a branch, a capture the + // summary cannot express. That used to reject here, before + // a closure could be a *value*: now it falls through and + // returns one, which is what `pick(true)(5)` needs. } - let (v, ty) = ssa.read(reg, bi, start)?; - // A function discovered to mix return types boxes every - // return point: it returns `Dyn`, callers consume through - // the Dyn arms (plan M4.2 cross-function Dyn flow). - let force_dyn = !is_entry && sig.dyn_rets.contains(&func_index); - let (v, ty) = if force_dyn && ty != Ty::Dyn { - (to_dyn_any(&mut ssa, &mut insts, v, ty, start)?, Ty::Dyn) + // Through `read_value`: a `return` of a lambda that the + // closure-return summary above declined — one of two returns in + // a branch, say — hands back a closure *value*, which is what + // the caller then calls. + let (v, ty) = read_value( + &mut ssa, + &mut insts, + sig, + funcs, + CaptureCtx { + params: &capture_params, + index: func_index, + param_count, + }, + reg, + bi, + start, + )?; + // A try body's `return` is the enclosing function's, not this + // one's: set the flag, park the value, and return normally so + // the trampoline reports "did not raise". The caller checks the + // flag on the ok edge. + let parked = if let Some((flag, slot)) = return_channel { + // What the enclosing function will read back out of the + // cell. Recorded so its return type can be joined with this + // one; without it the readback took whatever type the + // function's *direct* returns happened to agree on. + match sig.try_body_ret_tys.entry(func_index) { + std::collections::hash_map::Entry::Vacant(slot) => { + slot.insert(ty); + } + std::collections::hash_map::Entry::Occupied(mut slot) if *slot.get() != ty => { + slot.insert(Ty::Dyn); + } + std::collections::hash_map::Entry::Occupied(_) => {} + } + let boxed = to_dyn(&mut ssa, &mut insts, v, ty, start)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "cell_set"), + args: vec![slot, boxed], + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + let marked = to_dyn(&mut ssa, &mut insts, one, Ty::I64, start)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "cell_set"), + args: vec![flag, marked], + }); + ret_val[bi] = None; + ret_ty = Some(Ty::Nil); + true } else { - (v, ty) + false }; - match ret_ty { - Some(prev) if prev != ty => { - // Heterogeneous but boxable returns are retriable: - // record the function, the fixpoint re-lowers it with - // every return boxed (the snapshot includes the set's - // size). Everything else stays a real reject. - if !is_entry && dyn_boxable_ty(prev) && dyn_boxable_ty(ty) { - sig.dyn_rets.insert(func_index); + // Everything below is about *this* function's return value, and + // a parked one is not that. Guarded rather than `continue`d: + // the loop's tail is what stores this block's instructions. + if !parked { + // The struct this return constructs, carried out to callers so + // a method on the result devirtualizes (`sig.ret_structs`). + // Joined across return points: two different structs, or one + // return that is not a struct, answer "unknown" rather than a + // name that is right only sometimes. + if !is_entry { + let returned = ssa.struct_facts.get(&v).cloned(); + match sig.ret_structs.entry(func_index) { + std::collections::hash_map::Entry::Vacant(slot) => { + slot.insert(returned); + } + std::collections::hash_map::Entry::Occupied(mut slot) => { + if *slot.get() != returned { + slot.insert(None); + } + } } - return Err(Unsupported::ReturnTypeConflict); } - _ => { - // Eagerly publish the first concrete return type so a - // self-recursive call later in this same body observes - // it instead of the stale `I64` default (a Bool-typed - // `return f(xs.skip(1))` chain would otherwise look - // heterogeneous forever). - if ret_ty.is_none() - && !is_entry - && let Some(slot) = sig.ret_types.get_mut(func_index as usize) - { - *slot = ty; - if let Some(known) = sig.ret_known.get_mut(func_index as usize) { - *known = true; + // A function discovered to mix return types boxes every + // return point: it returns `Dyn`, callers consume through + // the Dyn arms (plan M4.2 cross-function Dyn flow). + let force_dyn = !is_entry && sig.dyn_rets.contains(&func_index); + let (v, ty) = if force_dyn && ty != Ty::Dyn { + (to_dyn(&mut ssa, &mut insts, v, ty, start)?, Ty::Dyn) + } else { + (v, ty) + }; + match ret_ty { + Some(prev) if prev != ty => { + // Heterogeneous but boxable returns are retriable: + // record the function, the fixpoint re-lowers it with + // every return boxed (the snapshot includes the set's + // size). Everything else stays a real reject. + if !is_entry && dyn_boxable_ty(prev) && dyn_boxable_ty(ty) { + sig.dyn_rets.insert(func_index); + } + return Err(Unsupported::ReturnTypeConflict); + } + _ => { + // Eagerly publish the first concrete return type so a + // self-recursive call later in this same body observes + // it instead of the stale `I64` default (a Bool-typed + // `return f(xs.skip(1))` chain would otherwise look + // heterogeneous forever). + if ret_ty.is_none() + && !is_entry + && let Some(slot) = sig.ret_types.get_mut(func_index as usize) + { + *slot = ty; + if let Some(known) = sig.ret_known.get_mut(func_index as usize) { + *known = true; + } } + ret_ty = Some(ty); } - ret_ty = Some(ty); } + // A `Nil` return value renders as `ret void`. + ret_val[bi] = if ty == Ty::Nil { None } else { Some(v) }; } - // A `Nil` return value renders as `ret void`. - ret_val[bi] = if ty == Ty::Nil { None } else { Some(v) }; + } + Some(Exit::TryRegion { + body, + catch_reg, + handler: _region_handler, + fallthrough: _region_fallthrough, + }) => { + // Run the body under a handler, and bind what it raised. + // + // The caught value is written unconditionally, on both edges. + // Writing it only on the raise edge would leave the register + // undefined on the other one, and SSA has to agree about a + // register's definition at a join whether or not the path that + // defined it was taken. + // The body's inputs, read here where the enclosing function's + // values are still current. All `I64`: the trampoline passes + // machine words, and a body wanting something wider rejects + // when it reads it. + // Two passes, because a closure crossing the boundary may + // capture a variable that is *also* crossing as a cell: the + // cells are made first so the lambda can be handed the same + // object rather than a copy of what it held. Each input's words + // are collected positionally and flattened afterwards, so the + // argument order is still `try_body_params` order — which is + // what the body walks. + let region_params = sig.try_body_params.get(&body).cloned().unwrap_or_default(); + let mut input_words: Vec>> = vec![None; region_params.len()]; + // Upvalue-cell inputs, resynced from their runtime cells after + // the call: the body may have written through one, and the + // parent's slot is the only place that write can land. + let mut cell_input_values: Vec<(u32, ValueId, Ty)> = Vec::new(); + let mut region_cells: std::collections::HashMap = std::collections::HashMap::new(); + for (index, ®) in region_params.iter().enumerate() { + // A variable a closure captured: it lives in a slot behind a + // compile-time cell ref, so what crosses is a runtime cell + // seeded from that slot. + match cell_region_input(&mut ssa, sig, &capture_params, body, reg, bi) { + Some(CellInput::Slot(cid)) => { + let (cur, cur_ty) = ssa.read_slot(ssa.cell_slot(cid), bi, start)?; + // The content type the body reads through, unless a + // store inside it has already disagreed (which pins + // the entry to `Dyn` — see `try_body_cell_input_tys`). + let content = + join_cell_content(sig.try_body_cell_input_tys.get(&(body, reg)).copied(), cur_ty); + sig.try_body_cell_input_tys.insert((body, reg), content); + let boxed = crate::dyn_box::to_dyn(&mut ssa, &mut insts, cur, cur_ty, start)?; + let handle = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("rt", "cell_new"), + args: vec![boxed], + }); + input_words[index] = Some(vec![handle]); + region_cells.insert(cid, (handle, content)); + cell_input_values.push((cid, handle, content)); + continue; + } + Some(CellInput::Handle(handle)) => { + // The pointer is passed on unchanged, and so is what + // it is agreed to hold: this frame reads the same + // cell under the same type. + let content = ssa + .cellparam_content_ty(reg) + .filter(|_| sig.try_body_cell_input_tys.get(&(body, reg)) != Some(&Ty::Dyn)) + .unwrap_or(Ty::Dyn); + sig.try_body_cell_input_tys.insert((body, reg), content); + input_words[index] = Some(vec![handle]); + if let Some(GlobalRef::Cell(cid)) = ssa.builtin_ref_at(reg, bi) { + region_cells.insert(cid, (handle, content)); + } + continue; + } + None => {} + } + // Read as whatever it is, then decide whether it can cross. + // Forcing `I64` here is what used to reject a body that + // merely *looked at* a list the parent owned — a handle is a + // machine word, and the buffer the trampoline marshals into + // is machine words. + // + // A lambda is neither, and is left to the second pass. + if lambda_region_input(&mut ssa, sig, body, reg, bi).is_some() { + continue; + } + let (v, ty) = ssa.read(reg, bi, start)?; + // A register holding `nil` says what was there *going in*, + // not what the body will put back — the body boxes whatever + // it writes, which is what `unbox_from_dyn` says about the + // same value in a cell. So it crosses boxed, as `Dyn` does, + // rather than refusing for having no word of its own: + // + // fn t() -> String { + // let n = nil; + // try { let c = || n == nil; return "a" + c(); } + // catch e { return "E"; } + // } + // + // dropped its whole module to the VM for it. + let (v, ty) = if ty == Ty::Nil { + (to_dyn(&mut ssa, &mut insts, v, ty, start)?, Ty::Dyn) + } else { + (v, ty) + }; + // A two-register carrier crosses as its two raw words, put + // back together by the body (`Inst::CarrierFromParts`). + if crosses_as_two_words(ty) { + let mut word = |half| { + let dst = ssa.new_val(); + insts.push(Inst::CarrierWord { dst, src: v, half }); + dst + }; + let lo = word(lk_aot_mir::CarrierHalf::Lo); + let hi = word(lk_aot_mir::CarrierHalf::Hi); + sig.try_body_param_tys.insert((body, reg), ty); + // A closure handle is a `Dyn`, so it crosses here and + // not through the single-word branch below. + if ssa.closure_values.contains(&v) { + sig.try_body_closure_inputs.insert((body, reg)); + } else { + sig.try_body_closure_inputs.remove(&(body, reg)); + } + input_words[index] = Some(vec![lo, hi]); + continue; + } + if crosses_as_word(ty) { + sig.try_body_param_tys.insert((body, reg), ty); + match ssa.struct_facts.get(&v) { + Some(fact) => { + sig.try_body_struct_inputs.insert((body, reg), fact.clone()); + } + None => { + sig.try_body_struct_inputs.remove(&(body, reg)); + } + } + if ssa.closure_values.contains(&v) { + sig.try_body_closure_inputs.insert((body, reg)); + } else { + sig.try_body_closure_inputs.remove(&(body, reg)); + } + input_words[index] = Some(vec![v]); + } else { + // Not a word and not a carrier this knows how to split. + // Named, rather than reported as "some operand": which + // *type* could not cross is the whole content of the + // answer, and it is what a reader needs to know whether + // to widen this rule or to change the program. + sig.try_body_param_tys.remove(&(body, reg)); + return Err(Unsupported::OperandType { + pc: start, + want: "machine word", + got: lk_aot_mir::ty_name(ty), + }); + } + } + for (index, ®) in region_params.iter().enumerate() { + let Some(identity) = try_body_lambda(sig, body, reg) else { + continue; + }; + let mut env = Vec::new(); + try_lambda_env( + &mut ssa, + &mut insts, + sig, + LambdaEnvSite { + cap_ctx: CaptureCtx { + params: &capture_params, + index: func_index, + param_count, + }, + body, + reg, + identity, + region_cells: ®ion_cells, + callee_param_count: funcs.get(identity.fidx as usize).map_or(0, |f| f.param_count as usize), + }, + &mut env, + bi, + start, + )?; + input_words[index] = Some(env); + } + let mut call_args: Vec = Vec::new(); + for words in input_words { + let Some(words) = words else { + return Err(Unsupported::TryRegion { + pc: start, + reason: "a region input resolved to nothing the trampoline can carry", + }); + }; + call_args.extend(words); + } + // One cell per register the body assigns that this function + // already had. Seeded with the value it holds now, because a + // body that raises before assigning must leave it alone. + let cell_regs: Vec = sig.try_body_cells.get(&body).cloned().unwrap_or_default(); + let mut cell_values: Vec<(u8, ValueId, Ty)> = Vec::with_capacity(cell_regs.len()); + for ® in &cell_regs { + let (v, ty) = ssa.read(reg, bi, start)?; + // A typed container is parked as a raw handle: no boxing, so + // the same handle comes back and the body's writes stand. + if cell_is_raw(ty) { + // The kind is decided *here*, and written down: the + // body must not decide it a second time from the type + // it happens to store (see `try_body_raw_cells`). + sig.try_body_raw_cells.insert((body, reg)); + let handle = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("rt", "cell_new_raw"), + args: vec![v], + }); + call_args.push(handle); + cell_values.push((reg, handle, ty)); + continue; + } + sig.try_body_raw_cells.remove(&(body, reg)); + // A value crosses back only if it can be taken out of a + // cell again. Boxing is universal; unboxing is per type, + // and a type with no unboxer is a rejection rather than a + // guess. + if unbox_from_dyn(ty).is_none() { + return Err(Unsupported::TryRegion { + pc: start, + reason: "the body assigns a value that cannot be read back out of a cell", + }); + } + let boxed = crate::dyn_box::to_dyn(&mut ssa, &mut insts, v, ty, start)?; + let handle = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("rt", "cell_new"), + args: vec![boxed], + }); + call_args.push(handle); + cell_values.push((reg, handle, ty)); + } + // The outcome channel: a flag cell (seeded 0, "fell through") + // for a body that `return`s or carries a `break`/`continue` + // out, and a value cell (seeded nil) for the `return` alone. + // Every other region passes exactly what it always did. + // `body_end` is where the exit *is*, which for a region is its + // `TryBegin`; `start` is only the block's leader, and anything + // ahead of the `try` in the same block puts the two apart — + // `while c { i = i + 1; try { … } }` is enough. + let escape_targets: Vec = escape_targets_at(®ions, body_end).to_vec(); + let body_returns = sig.try_body_returns.contains(&body); + let fresh_cell = |ssa: &mut Ssa, insts: &mut Vec, seed: Ty| -> Result { + let raw = ssa.new_val(); + insts.push(Inst::Const { + dst: raw, + value: Const::I64(0), + }); + let boxed = crate::dyn_box::to_dyn(ssa, insts, raw, seed, start)?; + let handle = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("rt", "cell_new"), + args: vec![boxed], + }); + Ok(handle) + }; + // The enclosing function's captures, so a `LoadCapture k` + // inside the body resolves to the same thing it means out here. + // Positional and unconditional: index `k` has to stay index `k`, + // and a statically-known capture carries a dead word rather than + // shifting the ones after it — the same trade + // `ClosureCapture::StaticRef` already makes at an ordinary call. + // + // Refused for a goroutine's body: there a capture's current + // value lives in a thread-private slot rather than in the + // parameter, so handing the parameter on would pass the value it + // had at the spawn and hide every write since. + if !capture_params.is_empty() { + if spawned_isolate { + return Err(Unsupported::TryRegion { + pc: start, + reason: "the region is inside a goroutine body, whose captures are thread-private copies rather than the cells they came from", + }); + } + let mut tys = Vec::with_capacity(capture_params.len()); + for (k, &(cv, cty)) in capture_params.iter().enumerate() { + if let Some(callable) = sig.ref_captures.get(&(func_index, k)).cloned() { + sig.ref_captures.insert((body, k), callable); + } + if let Some(&content) = ssa.cellparam_content.get(&k) { + sig.try_body_outer_cell_tys.insert((body, k), content); + } + // A two-register carrier crosses as its two raw words + // and is put back together at the body's entry, exactly + // as a region input does. + // Boxed for the reason a region *input* is: a `nil` + // going in says nothing about what comes back. + let (cv, cty) = if cty == Ty::Nil { + (to_dyn(&mut ssa, &mut insts, cv, cty, start)?, Ty::Dyn) + } else { + (cv, cty) + }; + if crosses_as_two_words(cty) { + let mut word = |half| { + let dst = ssa.new_val(); + insts.push(Inst::CarrierWord { dst, src: cv, half }); + dst + }; + call_args.push(word(lk_aot_mir::CarrierHalf::Lo)); + call_args.push(word(lk_aot_mir::CarrierHalf::Hi)); + } else if crosses_as_word(cty) { + call_args.push(cv); + } else { + return Err(Unsupported::OperandType { + pc: start, + want: "machine word", + got: lk_aot_mir::ty_name(cty), + }); + } + tys.push(cty); + } + sig.try_body_outer_captures.insert(body, tys); + } + let outcome_flag = if body_returns || !escape_targets.is_empty() { + let flag = fresh_cell(&mut ssa, &mut insts, Ty::I64)?; + call_args.push(flag); + Some(flag) + } else { + None + }; + let return_channel = match outcome_flag.filter(|_| body_returns) { + Some(flag) => { + let value = fresh_cell(&mut ssa, &mut insts, Ty::Nil)?; + call_args.push(value); + Some((flag, value)) + } + None => None, + }; + if let Some(flag) = outcome_flag { + try_exit_checks.push(TryExitCheck { + block: bi, + flag, + value: return_channel.map(|(_, value)| value), + escape_targets, + body, + }); + } + let ok = ssa.new_val(); + insts.push(Inst::TryRegionCall { + dst: ok, + func: FuncId(body), + args: call_args, + }); + // The upvalue cells first, on the same principle. + for (cid, handle, content) in cell_input_values { + let cur = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(cur), + callee: AbiRef::new("rt", "cell_get"), + args: vec![handle], + }); + // Read back under the same type the body read through, so + // the parent's own later uses stay typed too. + let (value, ty) = match unbox_cell_value(&mut ssa, &mut insts, cur, content) { + Some(value) if content != Ty::Dyn => (value, content), + _ => (cur, Ty::Dyn), + }; + ssa.write_slot(ssa.cell_slot(cid), bi, (value, ty)); + } + // Read every cell back, before the branch, so both edges see + // what the body managed to write — including a body that + // raised half way through, which is what the VM shows. + for (reg, handle, ty) in cell_values { + if cell_is_raw(ty) { + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("rt", "cell_get_raw"), + args: vec![handle], + }); + ssa.write(reg, bi, (raw, ty)); + continue; + } + let got = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(got), + callee: AbiRef::new("rt", "cell_get"), + args: vec![handle], + }); + // See `unbox_from_dyn`: a nil seed comes back as whatever + // the body boxed, which is a `Dyn`. + let ty = if ty == Ty::Nil { Ty::Dyn } else { ty }; + let value = unbox_cell_value(&mut ssa, &mut insts, got, ty).expect("checked above"); + ssa.write(reg, bi, (value, ty)); + } + // Everything else the body wrote is gone: it was written in the + // body's frame, and nothing carried it back. Saying so is what + // makes a later read report itself instead of silently reading + // the value the parent had before the region. + // + // After the write-backs, so a register that *was* carried back + // keeps the definition it was just given. + // The same set the cells were chosen from: registers the body + // *rebound*. A container it merely mutated is not among them, + // and must not be — poisoning it would make the next read + // report itself, the fixpoint would give it a cell, and the + // round trip a cell implies is what loses the mutation. + let body_may_write: Vec = match sig.try_body_rebound.get(&body) { + Some(set) => set.iter().copied().collect(), + None => regions + .iter() + .find(|r| sig.try_bodies.get(&(func_index, r.begin_pc)) == Some(&body)) + .map(|span| crate::try_region::written_registers(&instrs, span.body_start, span.body_end)) + .unwrap_or_default(), + }; + for reg in &body_may_write { + if *reg != catch_reg && !cell_regs.contains(reg) { + ssa.poison(*reg, bi, body); + } + } + // A nested region's writes are *this* body's writes too, and + // this body has to report them to *its* caller whether or not + // the nested region managed to perform them on *this* pass — + // it cannot, until the nested region has its cells, which is a + // pass later. + // + // From the nested body's own report, which is `current_def`-based + // and therefore precise. The syntactic scan is not usable here: + // it names the `a` field of every instruction, so a container + // the region merely *mutates* (`ListPush a=receiver`) would be + // reported as rebound, the fixpoint would give it a cell, and + // the round trip a cell implies is what loses the mutation. Both + // spellings were tried; that one stopped + // `examples/syntax/closure.lk` lowering at all. + // + // Without the transitive report: + // + // try { try { b = clo(); } catch c1 { } } catch c2 { } + // acc.push(b); + // + // printed `b` from before the region, natively, with nothing + // said. + if is_try_body && let Some(nested) = sig.try_body_rebound.get(&body) { + rebound.extend(nested.iter().copied()); + } + let caught = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(caught), + callee: AbiRef::new("rt", "current_error"), + args: vec![], + }); + ssa.write(catch_reg, bi, (caught, Ty::Dyn)); + // The flag is an `i64` (1/0) and the terminator wants a Bool. + let flag = ssa.new_val(); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + insts.push(Inst::Cmp { + dst: flag, + op: CmpOp::Ne, + float: false, + lhs: ok, + rhs: zero, + }); + cond_val[bi] = Some(flag); } Some(Exit::Cond { cond, .. }) => { // VM truthiness (`truthy_unchecked`): only nil and false are @@ -316,6 +1899,7 @@ pub(crate) fn lower_function( | Ty::F64 | Ty::Str | Ty::ListI64 + | Ty::SliceI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn @@ -326,6 +1910,7 @@ pub(crate) fn lower_function( | Ty::MapStrBool | Ty::MapStrDyn | Ty::Set + | Ty::Bytes | Ty::Cell => { let c = ssa.new_val(); insts.push(Inst::Const { @@ -644,8 +2229,50 @@ pub(crate) fn lower_function( } ret_val[bi] = Some(boxed); } + // An escape trailer: the jump that reached it was a `break` or a + // `continue` belonging to a loop outside this region. Write which + // one into the outcome flag and return normally — the trampoline + // reports "did not raise", and the caller's check block takes the + // edge from there. + Some(Exit::TryEscape { code }) => { + let flag = outcome_flag.ok_or(Unsupported::TryRegion { + pc: start, + reason: "an escape trailer without an outcome flag", + })?; + let raw = ssa.new_val(); + insts.push(Inst::Const { + dst: raw, + value: Const::I64(code), + }); + let marked = to_dyn(&mut ssa, &mut insts, raw, Ty::I64, start)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "cell_set"), + args: vec![flag, marked], + }); + } _ => {} } + // What the terminator itself rebound — for a nested region, the + // registers its cells handed back — reported to this body's own caller + // and written through this body's own cells. + if is_try_body { + for (r, was) in before_all_exit.iter().enumerate() { + if ssa.current_def[bi][r] != *was { + rebound.insert(r as u8); + } + } + } + mirror_cells( + &mut ssa, + &mut insts, + sig, + func_index, + &cell_handles, + &before_exit, + bi, + start, + )?; block_insts[bi] = insts; block_exit[bi] = exit; ssa.mark_filled(bi); @@ -665,14 +2292,62 @@ pub(crate) fn lower_function( *pc_to_block.range(..=pc).next_back().map(|(_, id)| id).unwrap() } }; + // Synthetic blocks per region that may leave, after every real block and the + // implicit-return block (the same allocation `implicit_ret_block` uses): a + // check block, one test block per outcome past the first, and a return block + // for a body that `return`s. Ids are handed out in the order the blocks are + // pushed, so the vector's index and the `BlockId` stay the same number. + let mut synthetic_next = total_blocks as u32 + u32::from(implicit_ret_block.is_some()); + let check_layouts: Vec = try_exit_checks + .iter() + .map(|check| { + let outcomes = usize::from(check.value.is_some()) + check.escape_targets.len(); + let layout = CheckLayout { + check: synthetic_next, + tests: (1..outcomes).map(|i| synthetic_next + i as u32).collect(), + ret: check.value.map(|_| synthetic_next + outcomes as u32), + }; + synthetic_next += outcomes as u32 + u32::from(check.value.is_some()); + layout + }) + .collect(); + let mut forwarded_args: Vec<(usize, Vec, BlockId)> = Vec::new(); let mut mir_blocks: Vec = Vec::with_capacity(total_blocks); for bi in 0..leader_vec.len() { + if !reachable[bi] { + // No instructions, no params, and a terminator that names only + // itself — nothing about the rest of the function has to hold for + // a block control cannot enter. + mir_blocks.push(Block { + id: BlockId(bi as u32), + params: Vec::new(), + insts: Vec::new(), + term: Term::Br { + target: BlockId(bi as u32), + args: Vec::new(), + }, + }); + continue; + } let params: Vec<(ValueId, Ty)> = ssa.phis[bi].iter().map(|p| (p.param, p.ty)).collect(); let exit = block_exit[bi]; // Phi-edge conversions land after the block's own instructions, // before the terminator. let edge_tail = std::mem::take(&mut ssa.edge_insts[bi]); - let term = build_term(bi, exit, &ssa, &block_id, ret_val[bi], cond_val[bi]); + let mut term = build_term(bi, exit, &ssa, &block_id, ret_val[bi], cond_val[bi]); + // A region whose body may return: its ok edge goes to the check block + // instead, which forwards to the real fallthrough with the *same* + // arguments. Rewriting the edge rather than re-keying the phis is what + // keeps this local — the target's operands are still recorded against + // this block, and this is where they are read from. + if let Some(index) = try_exit_checks.iter().position(|check| check.block == bi) + && let Term::CondBr { + then_blk, then_args, .. + } = &mut term + { + forwarded_args.push((index, core::mem::take(then_args), *then_blk)); + *then_blk = BlockId(check_layouts[index].check); + } let mut insts = std::mem::take(&mut block_insts[bi]); insts.extend(edge_tail); mir_blocks.push(Block { @@ -686,11 +2361,25 @@ pub(crate) fn lower_function( let params: Vec<(ValueId, Ty)> = ssa.phis[id as usize].iter().map(|p| (p.param, p.ty)).collect(); // A Dyn-returning function's implicit return (falling off the end) // returns boxed nil — `ret void` in a `{i64,i64}` function is invalid. - let (insts, term) = if !is_entry && sig.dyn_rets.contains(&func_index) { + let (insts, term) = if !reachable[id as usize] { + ( + Vec::new(), + Term::Br { + target: BlockId(id), + args: Vec::new(), + }, + ) + } else if !is_entry && sig.dyn_rets.contains(&func_index) { let dummy = ssa.new_val(); let mut iv = Vec::new(); let boxed = to_dyn(&mut ssa, &mut iv, dummy, Ty::Nil, 0).expect("nil always boxes"); (iv, Term::Ret(Some(boxed))) + } else if ret_ty.is_some() { + // One path returns a value and another falls off the end, which + // answers nil. `ret void` in a value-returning function is not + // valid MIR, and there is no value of the return type that means + // nil — the same conflict two disagreeing `return`s produce. + return Err(Unsupported::ReturnTypeConflict); } else { (Vec::new(), Term::Ret(None)) }; @@ -702,7 +2391,215 @@ pub(crate) fn lower_function( }); } + // The check/dispatch/return blocks for each region whose body may leave. + // Emitted here because the *enclosing* function's return type is only + // settled once every block has been lowered, and the parked value has to + // come back out of its cell as that type. let ret = ret_ty.unwrap_or(Ty::Nil); + for (index, check) in try_exit_checks.iter().enumerate() { + let layout = &check_layouts[index]; + let (_, fallthrough_args, fallthrough) = forwarded_args + .iter() + .find(|(i, _, _)| *i == index) + .cloned() + .expect("every recorded check redirects exactly one edge"); + let mut check_insts = Vec::new(); + let raised = ssa.new_val(); + check_insts.push(Inst::Call { + dst: Some(raised), + callee: AbiRef::new("rt", "cell_get"), + args: vec![check.flag], + }); + let code = ssa.new_val(); + check_insts.push(Inst::Call { + dst: Some(code), + callee: AbiRef::new("dyn", "as_i64"), + args: vec![raised], + }); + // Where each outcome goes: `return` first (code 1), then the escapes in + // the order the region recorded them (code `2 + k`). An escape's edge + // carries the same phi operands the ok edge does — they leave the same + // block, from the same definitions — which is why they were recorded as + // successors of the region back in step 4. + let mut outcomes: Vec<(i64, BlockId, Vec)> = Vec::new(); + if let Some(ret_block) = layout.ret { + outcomes.push((1, BlockId(ret_block), Vec::new())); + } + for (k, &target) in check.escape_targets.iter().enumerate() { + let block = block_id(target); + outcomes.push(( + 2 + k as i64, + BlockId(block), + crate::ssa::args_to(&ssa, check.block, block as usize), + )); + } + // The chain: the last outcome is whatever is left, so it needs no test + // of its own, and a lone outcome needs no test block at all. + let mut chain: Vec = Vec::new(); + let entry = if outcomes.len() == 1 { + (outcomes[0].1, outcomes[0].2.clone()) + } else { + (BlockId(layout.tests[0]), Vec::new()) + }; + for (i, test_block) in layout.tests.iter().enumerate() { + let mut insts = Vec::new(); + let want = ssa.new_val(); + insts.push(Inst::Const { + dst: want, + value: Const::I64(outcomes[i].0), + }); + let hit = ssa.new_val(); + insts.push(Inst::Cmp { + dst: hit, + op: CmpOp::Eq, + float: false, + lhs: code, + rhs: want, + }); + let (else_blk, else_args) = match layout.tests.get(i + 1) { + Some(next) => (BlockId(*next), Vec::new()), + None => (outcomes[i + 1].1, outcomes[i + 1].2.clone()), + }; + chain.push(Block { + id: BlockId(*test_block), + params: Vec::new(), + insts, + term: Term::CondBr { + cond: hit, + then_blk: outcomes[i].1, + then_args: outcomes[i].2.clone(), + else_blk, + else_args, + }, + }); + } + // Code 0 is "the body fell off its end", the only outcome that resumes + // where the region left off — so it is always asked. + // + // It used to be skipped when the region's fallthrough *was* its handler, + // on the reading that this means "the body leaves on every path, so + // there is no ok edge". That reading is wrong, and by one letter: what + // the condition actually detects is that the compiler emitted no jump + // over the handler — which is also true when the **handler is empty**, + // because jumping over nothing is nothing to emit. So + // `try { if c { return 7; } } catch e { }` skipped the test, went + // straight to the return block on the path that did *not* return, and + // read the parked value out of a cell still holding nil. + // + // Asking anyway costs one compare, on a path that has just returned + // from a call. When the body really does leave on every path, code 0 + // cannot occur and the false edge is unreachable — valid, and dead. + let check_term = { + let zero = ssa.new_val(); + check_insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let left = ssa.new_val(); + check_insts.push(Inst::Cmp { + dst: left, + op: CmpOp::Ne, + float: false, + lhs: code, + rhs: zero, + }); + Term::CondBr { + cond: left, + then_blk: entry.0, + then_args: entry.1, + else_blk: fallthrough, + else_args: fallthrough_args, + } + }; + mir_blocks.push(Block { + id: BlockId(layout.check), + params: Vec::new(), + insts: check_insts, + term: check_term, + }); + mir_blocks.extend(chain); + + let (Some(ret_block), Some(value)) = (layout.ret, check.value) else { + continue; + }; + let mut ret_insts = Vec::new(); + let boxed = ssa.new_val(); + ret_insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("rt", "cell_get"), + args: vec![value], + }); + // This function may itself be a region's body, and then the `return` it + // is about to perform is not its own either: it belongs to whoever is + // two frames out. Forward it into *this* body's channel instead — the + // value is already boxed, so the hand-off is two `cell_set`s — and + // return normally, so the trampoline still reports "did not raise". + // + // Without the forward, the inner region's parked value was read back + // and then dropped, because a body's own return type is `Nil`: `try { + // try { return 11; } catch e { … } } catch e { … }` answered whatever + // the function fell through to. + if let Some((outer_flag, outer_value)) = return_channel { + ret_insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "cell_set"), + args: vec![outer_value, boxed], + }); + let one = ssa.new_val(); + ret_insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + let marked = to_dyn(&mut ssa, &mut ret_insts, one, Ty::I64, 0)?; + ret_insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "cell_set"), + args: vec![outer_flag, marked], + }); + mir_blocks.push(Block { + id: BlockId(ret_block), + params: Vec::new(), + insts: ret_insts, + term: Term::Ret(None), + }); + continue; + } + // What the *region* parked has to agree with what this function's own + // returns settled on — the value is read back as `ret`, and a list read + // back as a `Str` raises. Disagreeing takes the same retry two + // disagreeing direct returns take: re-lower with every return boxed. + if let Some(&parked_ty) = sig.try_body_ret_tys.get(&check.body) + && parked_ty != ret + && ret != Ty::Dyn + { + if dyn_boxable_ty(parked_ty) && dyn_boxable_ty(ret) && !is_entry { + sig.dyn_rets.insert(func_index); + } + return Err(Unsupported::ReturnTypeConflict); + } + let returned_value = match ret { + Ty::Nil => None, + // The same unboxing the output cells use; a type with no readback + // never got here, because the body's `return` had to box it in the + // first place. + other => match unbox_cell_value(&mut ssa, &mut ret_insts, boxed, other) { + Some(value) => Some(value), + None => { + return Err(Unsupported::TryRegion { + pc: 0, + reason: "the body returns a value that cannot be read back out of a cell", + }); + } + }, + }; + mir_blocks.push(Block { + id: BlockId(ret_block), + params: Vec::new(), + insts: ret_insts, + term: Term::Ret(returned_value), + }); + } + // User (non-entry) functions return scalars, `Str`/handle pointers // (arena-owned until exit), or nothing (`Nil` renders as `void`). // A `Maybe` carrier has no direct-call return form: retriable — the @@ -712,15 +2609,10 @@ pub(crate) fn lower_function( sig.dyn_rets.insert(func_index); return Err(Unsupported::ReturnTypeConflict); } - // The entry can return scalars (printed), but not a container handle (printing - // a list is not modelled yet) — reject so it falls back rather than print wrong. - if is_entry - && matches!( - ret, - Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::MapStrI64 | Ty::MapI64I64 | Ty::MapStrF64 | Ty::MapI64F64 - ) - { - return Err(Unsupported::ReturnTypeConflict); + // Entry container values are rendered by codegen through the same display + // ABI as `println`, so they are valid top-level return values. + if is_try_body { + sig.try_body_rebound.insert(func_index, rebound); } Ok(MirFunction { id: FuncId(func_index), @@ -733,3 +2625,54 @@ pub(crate) fn lower_function( export_name: if is_entry { None } else { func.export_name.clone() }, }) } + +/// One region whose body may leave the enclosing function's control flow. +/// +/// The body ran inside the trampoline and returned normally, so the ok edge +/// alone does not say what happened: the outcome flag does. This is what the +/// check block behind that edge needs in order to ask. +struct TryExitCheck { + /// The region's own block, whose ok edge is redirected to the check. + block: usize, + /// The flag cell: 0 fell through, 1 returned, `2 + k` took `escape_targets[k]`. + flag: ValueId, + /// The parked return value, for a body that `return`s. + value: Option, + /// Where each escape code lands, in the parent's pc space. + escape_targets: Vec, + /// Which outlined body this region is, so what it parks can be looked up. + body: u32, +} + +/// The synthetic blocks one [`TryExitCheck`] gets, in the order they are pushed. +struct CheckLayout { + /// Reads the outcome code and separates "fell through" from the rest. + check: u32, + /// One per outcome past the first: the last outcome is whatever is left, so + /// it needs no test of its own. + tests: Vec, + /// Performs the parked `return`, for a body that has one. + ret: Option, +} + +/// Where the region beginning at `pc` lets its body's `break`/`continue` out. +/// +/// Empty for every region that has none, which is most of them — and empty for +/// a pc that is not a region, so the caller can ask without checking first. +fn escape_targets_at(regions: &[crate::try_region::TryRegionShape], pc: usize) -> &[usize] { + regions + .iter() + .find(|region| region.begin_pc == pc) + .map_or(&[][..], |region| ®ion.escape_targets) +} + +/// A string constant as an SSA value, interned into the module's global table. +fn const_str_value(ssa: &mut Ssa, insts: &mut Vec, globals: &mut Vec, text: &str) -> ValueId { + let gid = crate::prescan::intern_global(globals, text); + let dst = ssa.new_val(); + insts.push(Inst::Const { + dst, + value: Const::Str(GlobalId(gid)), + }); + dst +} diff --git a/aot/lower/src/imports.rs b/aot/lower/src/imports.rs index 76b35da1..91941f59 100644 --- a/aot/lower/src/imports.rs +++ b/aot/lower/src/imports.rs @@ -64,11 +64,24 @@ impl ImportEnv { for import in imports { match import { ImportStmt::ModuleAlias { module, alias } => { - env.module_aliases.insert(alias.clone(), module.clone()); + // `use dep as name;` — the bundle is keyed by the binding + // the CLI queued it under, which is the alias. + if let Some(b) = bundle_by_path(alias) { + env.file_namespaces.insert(alias.clone(), b); + } else { + env.module_aliases.insert(alias.clone(), module.clone()); + } } ImportStmt::Namespace { alias, source } => match source { ImportSource::Module(module) => { - env.module_aliases.insert(alias.clone(), module.clone()); + // A bundled *package* module answers here the way a + // bundled file does; a stdlib module has no bundle and + // keeps the module-object binding. + if let Some(b) = bundle_by_path(module) { + env.file_namespaces.insert(alias.clone(), b); + } else { + env.module_aliases.insert(alias.clone(), module.clone()); + } } ImportSource::File(path) => { if let Some(b) = bundle_by_path(path) { @@ -81,11 +94,48 @@ impl ImportEnv { let bound = item.alias.clone().unwrap_or_else(|| item.name.clone()); match source { ImportSource::Module(module) => { - env.module_items.insert(bound, (module.clone(), item.name.clone())); + // Same rule as the file branch below when the + // module is a bundled package: the item is a + // merged function, not a member read off a + // module object. + let ctor = lk_core::stmt::struct_ctors::constructor_name(&item.name); + if let Some(fidx) = bundle_by_path(module) + .and_then(|b| bundles[b].fns.get(&item.name).or_else(|| bundles[b].fns.get(&ctor))) + .copied() + { + env.file_items.insert(bound, fidx); + } else { + env.module_items.insert(bound, (module.clone(), item.name.clone())); + } } + // Functions only, and that is now the whole of it: + // a renamed *constant* never reaches here, because + // the bundler folds its value into the reads of + // both names before this runs. + // + // It used to reach here and bind nothing — a + // `const` is not in `fns` — so `use { SIZE as + // TSS_SIZE }` left a `GetGlobal` of a slot nothing + // initialises: an error under `compile object:` and + // a fall back to the VM otherwise, while the + // unrenamed `SIZE` worked because bundling flattens + // a module's constants under their own names. See + // `collect_renamed_file_items` in the CLI's + // bundler, which is where the fold learns the other + // name. + // A `struct S` is bound through the constructor the + // declaring module generates beside it (`S$new`), + // which is what the VM's import resolution binds + // too — the type itself is not a value. Without + // this fallback `use { P } from "geo"` bound + // nothing and every read of `P` refused to lower, + // while the same type reached as `geo.P { … }` + // lowered fine: that spelling desugars to + // `geo.P$new(…)` and finds the function by name. ImportSource::File(path) => { + let ctor = lk_core::stmt::struct_ctors::constructor_name(&item.name); if let Some(fidx) = bundle_by_path(path) - .and_then(|b| bundles[b].fns.get(&item.name)) + .and_then(|b| bundles[b].fns.get(&item.name).or_else(|| bundles[b].fns.get(&ctor))) .copied() { env.file_items.insert(bound, fidx); @@ -104,7 +154,26 @@ impl ImportEnv { env.file_namespaces.insert(stem, b); } } - ImportStmt::Module { .. } => {} + // `use math;` binds the module under its own name — the same + // binding `use math as math;` makes. It was an empty arm, so + // the lowering could not tell an imported module from a global + // that happens to share its name: `chan` is both (a bare + // constructor function *and* a module), and `chan.new(1)` + // therefore lowered natively whether or not the file imported + // it, while the VM refused the unimported spelling. + ImportStmt::Module { module } => { + // A *package* dependency arrives here under its own name + // and is bundled under that name, so the same lookup a + // file import gets applies before the stdlib reading does. + // Without it the bundle was built and never consulted, and + // the call fell to `lower_module`, which knows stdlib only + // — the workspace example was the sweep's one fallback. + if let Some(b) = bundle_by_path(module) { + env.file_namespaces.insert(module.clone(), b); + } else { + env.module_aliases.insert(module.clone(), module.clone()); + } + } } } Ok(env) diff --git a/aot/lower/src/inst/call.rs b/aot/lower/src/inst/call.rs index 77512fa2..165445fd 100644 --- a/aot/lower/src/inst/call.rs +++ b/aot/lower/src/inst/call.rs @@ -16,6 +16,15 @@ pub(super) fn lower( let func = ctx.func; let funcs = ctx.funcs; let entry = ctx.entry; + let capture_params = ctx.capture_params; + let ctx_func_index = ctx.func_index; + // Where an onward capture (`ClosureCapture::CellParam`) reads from: this + // function's own hidden trailing parameters. + let cap_ctx = CaptureCtx { + params: capture_params, + index: ctx_func_index, + param_count: func.param_count as usize, + }; match instr.opcode() { Opcode::CallMethodK => { lower_method_call_k(ssa, insts, globals, func, funcs, entry, sig, instr, block, pc)?; @@ -34,6 +43,7 @@ pub(super) fn lower( funcs, entry, sig, + cap_ctx, callee_idx, instr.a(), instr.c() as usize, @@ -42,11 +52,13 @@ pub(super) fn lower( pc, )?; } - // Direct calls address the callee by index, so the loaded function - // value itself only flows into the compiler's global-table storage - // (`SetGlobal`), which stays a no-op. + // A function value in a register. Usually the compiler's global-table + // bookkeeping, which is a no-op natively — but also how a call to a + // function past index 255 is spelled, because `CallDirect` names its + // target in a byte. The index rides along so the `Call` arm can + // devirtualize it. Opcode::LoadFunction => { - ssa.builtin_regs.insert((block, instr.a()), GlobalRef::UserFn); + ssa.bind_ref(block, instr.a(), GlobalRef::UserFn(u32::from(instr.bx()))); } Opcode::MakeClosure => { // `a` = dst, `b` = function index, `c` = capture window base. A @@ -58,18 +70,48 @@ pub(super) fn lower( let fidx = instr.b() as usize; let callee = funcs.get(fidx).ok_or(Unsupported::BadConst { pc })?; if callee.capture_count == 0 { - ssa.builtin_regs - .insert((block, instr.a()), GlobalRef::Lambda(fidx as u32)); - return Ok(()); + return bind_lambda( + ssa, + insts, + sig, + funcs, + cap_ctx, + GlobalRef::Lambda(fidx as u32), + instr.a(), + block, + pc, + ); } let mut captures = Vec::with_capacity(callee.capture_count as usize); for k in 0..callee.capture_count { let reg = instr.c().wrapping_add(k as u8); // The compiler captures locals through upvalue cells (shared // mutable boxes); a plain value is captured directly. - if let Some(GlobalRef::Cell(cid)) = ssa.builtin_regs.get(&(block, reg)) { - captures.push(ClosureCapture::Cell(*cid)); - continue; + match ssa.builtin_ref_at(reg, block) { + Some(GlobalRef::Cell(cid)) => { + // A cell whose content is a callable *reference* has no + // runtime value to pass: the meaning goes to the callee + // through `sig.ref_captures`, and the slot carries a + // dead `0` so the ABI arity is unchanged. + if let Some(callable) = ssa.cell_refs.get(&cid).cloned() { + let key = (fidx as u32, k as usize); + if sig.ref_captures.get(&key) != Some(&callable) { + sig.ref_captures.insert(key, callable); + return Err(Unsupported::TypeMismatch { pc }); + } + captures.push(ClosureCapture::StaticRef); + continue; + } + captures.push(ClosureCapture::Cell(cid)); + continue; + } + // A closure nested in a closure captures what its parent + // captured; the parent holds that as a capture parameter. + Some(GlobalRef::CellParam(k)) => { + captures.push(ClosureCapture::CellParam(k)); + continue; + } + _ => {} } let (v, ty) = ssa.read(reg, block, pc)?; // Same set as call arguments: scalars and handles pass through, @@ -79,8 +121,61 @@ pub(super) fn lower( } captures.push(ClosureCapture::Value(v, ty)); } - ssa.builtin_regs - .insert((block, instr.a()), GlobalRef::Closure(fidx as u32, captures)); + // Nothing to pass: this is a plain function reference, which is + // what lets the list HOFs' typed fast paths accept it. + let global_ref = if captures.iter().all(|c| matches!(c, ClosureCapture::StaticRef)) { + GlobalRef::Lambda(fidx as u32) + } else { + GlobalRef::Closure(fidx as u32, captures) + }; + return bind_lambda(ssa, insts, sig, funcs, cap_ctx, global_ref, instr.a(), block, pc); + } + // `abx(CallNamed, call_base, (named_count << 7) | positional_count)`: + // the callee sits at `call_base`, the positional arguments follow it, + // then `named_count` (name, value) pairs. The callee is resolved the + // same way `Call` does; the arguments are permuted into frame order by + // name (see `lower_named_call`). + Opcode::CallNamed => { + let base = instr.a(); + let payload = instr.bx(); + let positional_count = (payload & 0x7F) as usize; + let named_count = (payload >> 7) as usize; + let callee_idx = match ssa.builtin_ref_at(base, block) { + Some(GlobalRef::Lambda(fidx)) | Some(GlobalRef::UserFn(fidx)) => fidx as usize, + // A stdlib member called by name — `regex.replace(s, pattern: p, + // replacement: r)`. The names come from the member's own row + // rather than from a user function's metadata; everything else + // (the permutation, the rejection rules) is the same problem. + Some(GlobalRef::ModuleFn(module, name)) => { + return lower_named_module_call( + ssa, + insts, + globals, + &module, + &name, + base, + positional_count, + named_count, + block, + pc, + ); + } + _ => return Err(Unsupported::Opcode { pc, op: instr.opcode() }), + }; + lower_named_call( + ssa, + insts, + funcs, + entry, + sig, + cap_ctx, + callee_idx, + base, + positional_count, + named_count, + block, + pc, + )?; } Opcode::Call => { // Register-window call: `a` = window base (the callee slot), `c` = @@ -89,11 +184,19 @@ pub(super) fn lower( // (closures, runtime values) rejects. let base = instr.a(); match ssa.builtin_ref_at(base, block) { - Some(GlobalRef::Builtin(Builtin::TryCall)) => { - lower_try_call(ssa, insts, funcs, entry, sig, base, instr.c() as usize, block, pc)?; - } Some(GlobalRef::Builtin(Builtin::Spawn)) => { - lower_spawn(ssa, insts, funcs, entry, sig, base, instr.c() as usize, block, pc)?; + lower_spawn( + ssa, + insts, + funcs, + entry, + sig, + cap_ctx, + base, + instr.c() as usize, + block, + pc, + )?; } Some(GlobalRef::Builtin(Builtin::MergeFields)) => { lower_merge_fields(ssa, insts, base, instr.c() as usize, block, pc)?; @@ -141,15 +244,34 @@ pub(super) fn lower( // routes both through the same core_methods) — forward // to the same lowering with the receiver at `base+1`. let argc = instr.c() as usize; - if matches!(module.as_str(), "iter" | "stream") - && method_role(&name).is_some_and(|role| role.forward) + if let Some(method) = forwards_to_method(module.as_str(), &name) && argc >= 1 { let (receiver, receiver_ty) = ssa.read(base.wrapping_add(1), block, pc)?; + // A stream operation works on the list behind the + // stream and answers a stream again. Unboxed here and + // re-boxed below, so `stream.map(s, f)` is a `Stream` + // to `typeof` and to display exactly as it is to the + // interpreter. + let stream_receiver = module.as_str() == "stream"; + let (receiver, receiver_ty) = if stream_receiver { + if receiver_ty != Ty::Dyn { + return Err(Unsupported::TypeMismatch { pc }); + } + let list = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(list), + callee: AbiRef::new("dyn", "stream_list"), + args: vec![receiver], + }); + (list, Ty::ListDyn) + } else { + (receiver, receiver_ty) + }; // The HOF spellings reuse the lambda-aware method // path (the lambda register offset matches with the // window base shifted one slot right). - if matches!(name.as_str(), "map" | "filter" | "reduce") { + if matches!(method, "map" | "filter" | "reduce") { if matches!(receiver_ty, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn) && let Some(result) = lower_list_hof_k( ssa, @@ -159,13 +281,14 @@ pub(super) fn lower( sig, receiver, receiver_ty, - &name, + method, base.wrapping_add(1), argc - 1, block, pc, )? { + let result = rebox_stream(ssa, insts, stream_receiver, result, pc)?; ssa.write(base, block, result); return Ok(()); } @@ -173,10 +296,35 @@ pub(super) fn lower( } let mut args = Vec::with_capacity(argc - 1); for i in 0..argc - 1 { - args.push(ssa.read(base.wrapping_add(2).wrapping_add(i as u8), block, pc)?); + let (v, ty) = ssa.read(base.wrapping_add(2).wrapping_add(i as u8), block, pc)?; + // `stream.chain(a, b)` takes a second *stream*, and + // the list method behind it takes a list. Every + // other stream operation's arguments are scalars, + // so a boxed one here is a stream. + if stream_receiver && ty == Ty::Dyn { + let list = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(list), + callee: AbiRef::new("dyn", "stream_list"), + args: vec![v], + }); + args.push((list, Ty::ListDyn)); + continue; + } + args.push((v, ty)); } - let result = - lower_method_dispatch(ssa, insts, globals, receiver, receiver_ty, &name, &args, block, pc)?; + let result = lower_method_dispatch( + ssa, + insts, + globals, + receiver, + receiver_ty, + method, + &args, + block, + pc, + )?; + let result = rebox_stream(ssa, insts, stream_receiver, result, pc)?; ssa.write(base, block, result); return Ok(()); } @@ -192,6 +340,7 @@ pub(super) fn lower( funcs, entry, sig, + cap_ctx, fidx as usize, base, instr.c() as usize, @@ -205,18 +354,61 @@ pub(super) fn lower( // (the VM's shared-mutable-cell semantics) and is appended as // a hidden trailing argument. Some(GlobalRef::Closure(fidx, captures)) => { + let site = CaptureSite::new(cap_ctx, fidx, CaptureMode::Share, block, pc); let mut resolved = Vec::with_capacity(captures.len()); - for capture in &captures { - let (v, ty) = match capture { - ClosureCapture::Cell(cid) => { + // A capture the body *assigns* to travels as a runtime cell + // this call site seeds and reads back afterwards + // (`SigInfer::cell_captures`); one it only reads keeps + // passing as a plain value. + let mut writebacks: Vec<(u32, ValueId, Ty)> = Vec::new(); + for (k, capture) in captures.iter().enumerate() { + let (v, ty) = match (site.resolve(ssa, insts, sig, capture, k)?, capture) { + (Some(resolved), _) => resolved, + (None, ClosureCapture::Cell(cid)) => { let slot = ssa.cell_slot(*cid); - ssa.read_slot(slot, block, pc)? + let (cur, cur_ty) = ssa.read_slot(slot, block, pc)?; + if sig.cell_captures.contains(&(fidx, k)) { + // What the callee's reads of this cell + // unbox to. Recorded here because this is + // where the type is known; the callee never + // sees anything but the pointer. + let content = + join_cell_content(sig.cell_capture_tys.get(&(fidx, k)).copied(), cur_ty); + sig.cell_capture_tys.insert((fidx, k), content); + let boxed = to_dyn(ssa, insts, cur, cur_ty, pc)?; + let cell = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(cell), + callee: AbiRef::new("rt", "cell_new"), + args: vec![boxed], + }); + writebacks.push((*cid, cell, content)); + (cell, Ty::Cell) + } else { + (cur, cur_ty) + } } - ClosureCapture::Value(v, ty) => (*v, *ty), + (None, _) => unreachable!("only `Cell` is left to the call site"), + }; + // A carrier the function ABI has no word for *boxes*, + // the way a call argument does — `lower_user_call` + // observes the boxed type and the callee reads it back + // as `Dyn`. Refusing instead meant + // + // let v = nil; + // let f = || v == nil; + // + // dropped its whole module to the VM: a capture of a + // variable the compiler had proved nil, which is an + // ordinary thing to write. The same shape as a nil + // *argument*, which `SigInfer::observe_param` has + // widened to `Dyn` all along. + let (v, ty) = match ty { + Ty::Nil | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool => { + (to_dyn(ssa, insts, v, ty, pc)?, Ty::Dyn) + } + _ => (v, ty), }; - if matches!(ty, Ty::Nil | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool) { - return Err(Unsupported::TypeMismatch { pc }); - } resolved.push((v, ty)); } lower_user_call( @@ -225,6 +417,7 @@ pub(super) fn lower( funcs, entry, sig, + cap_ctx, fidx as usize, base, instr.c() as usize, @@ -232,10 +425,75 @@ pub(super) fn lower( block, pc, )?; + // Re-sync the parent's tracked cell content from the cell + // the callee wrote through. + for (cid, cell, content) in writebacks { + let cur = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(cur), + callee: AbiRef::new("rt", "cell_get"), + args: vec![cell], + }); + // Back under the type the callee read through, so the + // caller's own later uses stay typed as well. + let (value, ty) = match unbox_cell_value(ssa, insts, cur, content) { + Some(value) if content != Ty::Dyn => (value, content), + _ => (cur, Ty::Dyn), + }; + let slot = ssa.cell_slot(cid); + ssa.write_slot(slot, block, (value, ty)); + } + } + // A plain function value, called through the register the + // bytecode had to load it into. No captures: a `fn` has none. + Some(GlobalRef::UserFn(fidx)) => { + lower_user_call( + ssa, + insts, + funcs, + entry, + sig, + cap_ctx, + fidx as usize, + base, + instr.c() as usize, + &[], + block, + pc, + )?; + } + // No compile-time ref: the callee may still be an ordinary + // value holding a closure, which is what `rt.closure_call` is + // for. A carrier counts — a closure read out of a list is a + // `Maybe`, and `lower_dyn_call` unwraps it. + None if matches!( + ssa.peek(base, block), + Some((_, Ty::Dyn | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool)) + ) => + { + lower_dyn_call(ssa, insts, base, instr.c() as usize, block, pc)?; + } + // The callee is a *capture* holding a closure value — the + // shape `fn twice(f) { return |x| f(f(x)); }` produces inside + // the returned closure, where `f` arrived as a `Dyn` capture + // parameter of the value form. + Some(GlobalRef::CellParam(k)) if matches!(cap_ctx.params.get(k), Some(&(_, Ty::Dyn | Ty::Cell))) => { + let &(value, ty) = cap_ctx.params.get(k).expect("checked"); + let callee = if ty == Ty::Cell { + let got = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(got), + callee: AbiRef::new("rt", "cell_get"), + args: vec![value], + }); + (got, Ty::Dyn) + } else { + (value, ty) + }; + lower_dyn_call_to(ssa, insts, callee, base, instr.c() as usize, block, pc)?; } Some(GlobalRef::Module(_)) | Some(GlobalRef::UserModule(_)) - | Some(GlobalRef::UserFn) | Some(GlobalRef::ArgList(_)) | Some(GlobalRef::Cell(_)) | Some(GlobalRef::CellParam(_)) @@ -248,3 +506,65 @@ pub(super) fn lower( } Ok(()) } + +/// Binds what `MakeClosure` produced to its destination register. +/// +/// Normally that is the compile-time reference, which is what lets a call to it +/// devirtualize. A lambda the program also uses *as a value* is built here +/// instead, once, and the register holds the closure handle from then on. +/// +/// Doing it at the definition rather than at each use is what makes identity +/// hold: the VM's closure compares by reference, so `let g = f; f == g` is true +/// and `[f, f]`'s two elements are one object. Materializing per use answered +/// `false` to both. The cost is that this lambda's calls stop devirtualizing — +/// paid only by lambdas the program actually passes around, since +/// `SigInfer::value_lambdas` is populated by the fixpoint's first attempt to +/// read one as a value. +#[allow(clippy::too_many_arguments)] +fn bind_lambda( + ssa: &mut Ssa, + insts: &mut Vec, + sig: &mut SigInfer, + funcs: &[FunctionData], + cap_ctx: CaptureCtx<'_>, + global_ref: GlobalRef, + dst: u8, + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + let fidx = match &global_ref { + GlobalRef::Lambda(fidx) | GlobalRef::Closure(fidx, _) => *fidx, + _ => u32::MAX, + }; + if sig.value_lambdas.contains_key(&fidx) + && let Some(value) = materialize_closure(ssa, insts, sig, funcs, cap_ctx, &global_ref, block, pc)? + { + ssa.write(dst, block, value); + return Ok(()); + } + ssa.bind_ref(block, dst, global_ref); + Ok(()) +} + +/// Re-boxes a stream operation's result, which is a list here and a `Stream` +/// to the interpreter. A non-stream receiver passes through. +fn rebox_stream( + ssa: &mut Ssa, + insts: &mut Vec, + stream_receiver: bool, + result: (ValueId, Ty), + pc: usize, +) -> Result<(ValueId, Ty), Unsupported> { + if !stream_receiver { + return Ok(result); + } + let (v, ty) = result; + let list = crate::dyn_box::to_dyn_list_handle(ssa, insts, v, ty, pc)?; + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "from_stream"), + args: vec![list], + }); + Ok((boxed, Ty::Dyn)) +} diff --git a/aot/lower/src/inst/container.rs b/aot/lower/src/inst/container.rs index dc0c29b4..d1f7f8b2 100644 --- a/aot/lower/src/inst/container.rs +++ b/aot/lower/src/inst/container.rs @@ -1,6 +1,7 @@ //! Container opcodes: list/map/object construction, indexing, mutation. use super::LowerCtx; +use crate::trait_env::DECLARED_ANY; use crate::*; pub(super) fn lower( @@ -14,6 +15,13 @@ pub(super) fn lower( let globals = &mut *ctx.globals; let sig = &mut *ctx.sig; let func = ctx.func; + let funcs = ctx.funcs; + // Where a lambda used as a value becomes a closure (`read_value`). + let cap_ctx = CaptureCtx { + params: ctx.capture_params, + index: ctx.func_index, + param_count: ctx.func.param_count as usize, + }; match instr.opcode() { Opcode::NewList => { // `a` = dst, `b` = base, `c` = count: a register-window list. The @@ -24,10 +32,18 @@ pub(super) fn lower( let mut elems = Vec::with_capacity(count); for i in 0..count { let reg = instr.b().wrapping_add(i as u8); - elems.push(ssa.read(reg, block, pc)?); + // Through `read_value`, so a lambda in the literal becomes a + // closure value here rather than reporting that it is a + // reference: `[|x| x + 1, |x| x * 2]` is the shape. + elems.push(read_value(ssa, insts, sig, funcs, cap_ctx, reg, block, pc)?); } let all = |t: Ty| elems.iter().all(|&(_, ty)| ty == t); - let materialized = if !elems.is_empty() && all(Ty::I64) { + // Same retry channel as the constant-list path above: a push of a + // wider element contradicted this literal's element type. + let contradicted = ssa.dyn_literal_pcs.contains(&pc); + let materialized = if contradicted { + None + } else if !elems.is_empty() && all(Ty::I64) { Some(("i64_new", "i64_push", Ty::ListI64)) } else if !elems.is_empty() && all(Ty::F64) { Some(("f64_new", "f64_push", Ty::ListF64)) @@ -52,6 +68,7 @@ pub(super) fn lower( } ssa.list_len.insert(handle, elems.len() as i64); ssa.list_base_len.insert(handle, elems.len() as i64); + ssa.literal_carrier.insert(handle, (pc, list_ty)); ssa.write(instr.a(), block, (handle, list_ty)); } else if !elems.is_empty() && elems.iter().all(|&(_, ty)| { @@ -68,6 +85,34 @@ pub(super) fn lower( | Ty::ListStr | Ty::ListDyn | Ty::MapStrDyn + // The typed maps box through the same `to_dyn` + // family. Leaving them out did not make `[m]` + // reject — no arm fired, so the destination kept + // only the ArgList view, and the call that read it + // printed `{"a":1}` where the VM printed + // `[{"a":1}]`. A missing element type is a wrong + // answer here, not a fallback. + | Ty::MapStrI64 + | Ty::MapStrF64 + | Ty::MapStrBool + | Ty::MapI64I64 + | Ty::MapI64F64 + | Ty::Set + | Ty::Bytes + // A window boxes in place too (`DYN_SLICE`), so + // `[w]` holds something that still tracks the list + // it windows — which is what the VM's + // `HeapValue::Slice` does. + | Ty::SliceI64 + // A nullable element boxes to nil when absent, + // which is the element the VM puts there: + // `[xs[9], 1]` is `[nil, 1]`. Their absence from + // this list is the same mistake the typed maps + // above were, one family later. + | Ty::MaybeI64 + | Ty::MaybeF64 + | Ty::MaybeStr + | Ty::MaybeBool ) }) { @@ -101,6 +146,14 @@ pub(super) fn lower( } ssa.list_len.insert(handle, elems.len() as i64); ssa.list_base_len.insert(handle, elems.len() as i64); + // Every element the same declared struct: the list remembers + // which, so an element read out of it is still that struct. + let elem_struct = elems.first().and_then(|&(v, _)| ssa.struct_name(v).map(str::to_string)); + if let Some(name) = elem_struct + && elems.iter().all(|&(v, _)| ssa.struct_name(v) == Some(name.as_str())) + { + ssa.list_elem_struct.insert(handle, name); + } ssa.write(instr.a(), block, (handle, Ty::ListDyn)); } else if elems.is_empty() { // An empty literal (`let flat = [];`) materializes as an @@ -119,10 +172,164 @@ pub(super) fn lower( ssa.list_len.insert(handle, 0); ssa.list_base_len.insert(handle, 0); ssa.write(instr.a(), block, (handle, Ty::ListDyn)); + } else { + // No arm materialized a handle. Falling through here left the + // destination with *only* the ArgList view, and a consumer that + // reads that view — a call window, which is the other thing + // this opcode spells — saw the elements rather than the list. + // `println([m])` printed `{"a":1}`. A list whose elements have + // no boxing is a fallback, not a silent unpack. + return Err(Unsupported::TypeMismatch { pc }); } // Recorded after the write (which clears the slot) so both views // coexist: SSA reads see the handle, method dispatch sees elements. - ssa.builtin_regs.insert((block, instr.a()), GlobalRef::ArgList(elems)); + ssa.bind_ref(block, instr.a(), GlobalRef::ArgList(elems)); + } + Opcode::NewMap => { + // `a` = dst, `b` = base, `c` = entry count: a register window of + // interleaved key/value pairs (`read_map_entries`). + // + // A map literal whose values are all constants is folded into a + // heap constant and lowered by `LoadHeapConst` above. This is the + // other half — `{"k": a}`, `{"a": f(3)}` — and it was missing + // entirely, so a program that built a record from anything it had + // computed fell back whole. The list spelling (`[a, a + 1]`) has + // always lowered, which is what made the hole invisible: the two + // literals read alike and only one of them compiled. + // + // The same builder as the constant path, deliberately: `lit_new` / + // `lit_set` accumulate boxed pairs in literal order and + // `lit_finish_` converts to the typed representation. The + // shape choice below therefore only has to mirror the arms there — + // and through them the VM's `typed_map_from_entries` — rather than + // being a second, independently-drifting classification. + let count = instr.c() as usize; + let mut entries = Vec::with_capacity(count); + for i in 0..count { + let key_reg = instr.b().wrapping_add((i * 2) as u8); + let val_reg = key_reg.wrapping_add(1); + // The written text of a constant key, so the map can borrow it + // out of the program image instead of copying it per instance. + // A computed key (`{name: 1}`) has none, and its string may be + // released while the map lives — that one is copied. + let const_key = ssa.const_str_at(key_reg, block, pc); + let key = ssa.read(key_reg, block, pc)?; + // Through `read_value`: a lambda written as a map's value + // becomes a closure here, the same way it does in a list + // literal. A *key* cannot be one — a callable is not a map key + // in this language — so that read stays as it was. + let val = read_value(ssa, insts, sig, funcs, cap_ctx, val_reg, block, pc)?; + entries.push((key, val, const_key)); + } + if entries.is_empty() { + // `{}` written as a window rather than a constant. The constant + // path types the key by lookahead; there is nothing to look at + // here, and a wrong guess only costs a fallback. + let handle = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("map_h", "str_i64_new"), + args: Vec::new(), + }); + ssa.write(instr.a(), block, (handle, Ty::MapStrI64)); + return Ok(()); + } + let all_keys = |t: Ty| entries.iter().all(|((_, kt), _, _)| *kt == t); + let all_vals = |t: Ty| entries.iter().all(|(_, (_, vt), _)| *vt == t); + let (finish_fn, map_ty) = if all_keys(Ty::Str) && all_vals(Ty::Bool) { + ("lit_finish_str_bool", Ty::MapStrBool) + } else if all_keys(Ty::Str) && all_vals(Ty::I64) { + ("lit_finish_str_i64", Ty::MapStrI64) + } else if all_keys(Ty::Str) && all_vals(Ty::F64) { + ("lit_finish_str_f64", Ty::MapStrF64) + } else if all_keys(Ty::I64) && all_vals(Ty::I64) { + ("lit_finish_i64_i64", Ty::MapI64I64) + } else if all_keys(Ty::I64) && all_vals(Ty::F64) { + ("lit_finish_i64_f64", Ty::MapI64F64) + } else if all_keys(Ty::Str) { + // Heterogeneous values under string keys: the boxed map. Each + // value has to survive boxing, which `to_dyn` decides — an + // unboxable one rejects there rather than here. + ("lit_finish_str_dyn", Ty::MapStrDyn) + } else { + return Err(Unsupported::Opcode { pc, op: instr.opcode() }); + }; + // Straight into the carrier the shape above already chose. + // + // This used to go through the two-stage literal builder + // (`lit_new`/`lit_set`/`lit_finish_*`): every key and value boxed, + // inserted into a `RtKey`-keyed map, then that map iterated and + // re-inserted into the typed one. Twice the hash inserts and twice + // the key allocations, plus a box per entry — a 24-entry map + // literal built 100k times took 1.8s where the same-sized list + // literal took 0.08s. + // + // The second stage existed to replay the VM's stage-1 *hash* order + // into stage 2. Since the VM's maps became insertion-ordered there + // is no such order to replay: inserting in written order is what + // both sides do. The builder stays for the shapes chosen at run + // time (`lit_finish_str_dyn` from a `MapRest`, the decoders). + let (new_fn, set_fn) = match map_ty { + Ty::MapStrBool | Ty::MapStrI64 => ("str_i64_new", "str_i64_set"), + Ty::MapStrF64 => ("str_f64_new", "str_f64_set"), + Ty::MapI64I64 => ("i64_i64_new", "i64_i64_set"), + Ty::MapI64F64 => ("i64_f64_new", "i64_f64_set"), + _ => ("str_dyn_new", "str_dyn_set"), + }; + let handle = ssa.new_val(); + // The entry count is known here, so the map is built at its final + // size rather than rehashing as it fills. + if new_fn == "str_dyn_new" { + let capacity = ssa.new_val(); + insts.push(Inst::Const { + dst: capacity, + value: Const::I64(entries.len() as i64), + }); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("map_h", "str_dyn_new_sized"), + args: vec![capacity], + }); + } else { + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("map_h", new_fn), + args: Vec::new(), + }); + } + // A literal is an ordinary map. Only `NewObject` builds a struct, + // and the two share the `MapStrDyn` carrier, so the collection + // operations need this said out loud to answer at all. + ssa.set_plain_map(handle); + for ((k, kt), (v, vt), const_key) in entries.clone() { + let value = match map_ty { + Ty::MapStrDyn => to_dyn(ssa, insts, v, vt, pc)?, + // A `bool` carrier stores its members as `i64` (it shares + // the `str_i64` ABI), and a MIR `Bool` is one bit. + Ty::MapStrBool => { + let wide = ssa.new_val(); + insts.push(Inst::ZextBool { dst: wide, src: v }); + wide + } + _ => v, + }; + let _ = (kt, vt); + // A constant key is re-materialised as the interned global and + // borrowed; anything else keeps the copying setter. + let (set_fn, k) = match (set_fn, const_key.as_deref()) { + ("str_dyn_set", Some(text)) => ("str_dyn_set_const", materialize_key(ssa, insts, globals, text)), + ("str_i64_set", Some(text)) => ("str_i64_set_const", materialize_key(ssa, insts, globals, text)), + ("str_f64_set", Some(text)) => ("str_f64_set_const", materialize_key(ssa, insts, globals, text)), + _ => (set_fn, k), + }; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("map_h", set_fn), + args: vec![handle, k, value], + }); + } + let _ = finish_fn; + ssa.write(instr.a(), block, (handle, map_ty)); } Opcode::GetIndexStrI | Opcode::SetIndexStrI => { // Composite string-int key access (`m["n${i}"]`): the key is the @@ -199,7 +406,7 @@ pub(super) fn lower( // An empty `[]` is ambiguous — a lookahead types it from the // first value pushed (a wrong guess only costs a fallback). if elems.is_empty() { - let (new_fn, list_ty) = if ssa.dyn_empty_pcs.contains(&pc) { + let (new_fn, list_ty) = if ssa.dyn_literal_pcs.contains(&pc) { // A consumer contradicted an earlier guess — the // fixpoint retry forces the Dyn materialization. ("dyn_new", Ty::ListDyn) @@ -217,7 +424,7 @@ pub(super) fn lower( args: Vec::new(), }); if list_ty != Ty::ListDyn { - ssa.empty_guess.insert(handle, (pc, list_ty)); + ssa.literal_carrier.insert(handle, (pc, list_ty)); } ssa.list_len.insert(handle, 0); ssa.list_base_len.insert(handle, 0); @@ -227,6 +434,34 @@ pub(super) fn lower( let all_int = elems.iter().all(|e| matches!(e, ConstRuntimeValueData::Int(_))); let all_float = elems.iter().all(|e| matches!(e, ConstRuntimeValueData::Float(_))); let all_str = elems.iter().all(|e| matches!(e, ConstRuntimeValueData::ShortStr(_))); + // A push of a wider element contradicted this literal's + // element type, so the fixpoint asked for it as a Dyn list. + // A homogeneous literal is as contradictable as an empty + // one: `let xs: List = [1, 2]; xs.push("a");` is the + // shape the VM answers by widening the carrier in place, + // and the only reason it was refused here is that a + // `Vec` cannot become a `Vec` after the fact. + // Building it Dyn from the start is the same answer. + if ssa.dyn_literal_pcs.contains(&pc) && elems.iter().all(const_is_dyn_boxable) { + let handle = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("list_h", "dyn_new"), + args: Vec::new(), + }); + for e in elems { + let boxed = box_const_scalar(ssa, insts, globals, e); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("list_h", "dyn_push"), + args: vec![handle, boxed], + }); + } + ssa.list_len.insert(handle, elems.len() as i64); + ssa.list_base_len.insert(handle, elems.len() as i64); + ssa.write(instr.a(), block, (handle, Ty::ListDyn)); + return Ok(()); + } let (new_fn, push_fn, list_ty) = if all_int { ("i64_new", "i64_push", Ty::ListI64) } else if all_float { @@ -281,6 +516,10 @@ pub(super) fn lower( } ssa.list_len.insert(handle, elems.len() as i64); ssa.list_base_len.insert(handle, elems.len() as i64); + // Recorded like an empty literal's guess: a later push of a + // wider element names this pc, and the fixpoint rebuilds it + // above as a Dyn list. + ssa.literal_carrier.insert(handle, (pc, list_ty)); ssa.write(instr.a(), block, (handle, list_ty)); } ConstHeapValueData::Map(entries) => { @@ -304,7 +543,10 @@ pub(super) fn lower( // wrong guess only costs a fallback), the value defaults // to `i64`; no entries means no order to mirror. if entries.is_empty() { - let new_fn = if empty_map_is_int_keyed(func, pc, instr.a()) { + let new_fn = if ssa.dyn_literal_pcs.contains(&pc) { + // A store of a wider value contradicted the guess. + ("str_dyn_new", Ty::MapStrDyn) + } else if empty_map_is_int_keyed(func, pc, instr.a()) { ("i64_i64_new", Ty::MapI64I64) } else { ("str_i64_new", Ty::MapStrI64) @@ -315,10 +557,19 @@ pub(super) fn lower( callee: AbiRef::new("map_h", new_fn.0), args: Vec::new(), }); + if new_fn.1 != Ty::MapStrDyn { + ssa.literal_carrier.insert(handle, (pc, new_fn.1)); + } + ssa.set_plain_map(handle); ssa.write(instr.a(), block, (handle, new_fn.1)); return Ok(()); } - let (finish_fn, map_ty) = if all_str_keys && all_bool_vals { + let contradicted = ssa.dyn_literal_pcs.contains(&pc) + && all_str_keys + && entries.iter().all(|(_, v)| const_is_dyn_boxable(v)); + let (finish_fn, map_ty) = if contradicted { + ("lit_finish_str_dyn", Ty::MapStrDyn) + } else if all_str_keys && all_bool_vals { ("lit_finish_str_bool", Ty::MapStrBool) } else if all_str_keys && all_int_vals { ("lit_finish_str_i64", Ty::MapStrI64) @@ -334,23 +585,50 @@ pub(super) fn lower( // Non-scalar values / mixed key kinds fall back. return Err(Unsupported::Opcode { pc, op: instr.opcode() }); }; - let lit = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(lit), - callee: AbiRef::new("map_h", "lit_new"), - args: Vec::new(), - }); + // Straight into the carrier, as the register-window path + // above does and for the same reason: the two-stage + // builder's second stage only existed to replay stage 1's + // *hash* order, and the VM's maps are insertion-ordered now. + let (new_fn, set_fn) = match map_ty { + Ty::MapStrBool | Ty::MapStrI64 => ("str_i64_new", "str_i64_set"), + Ty::MapStrF64 => ("str_f64_new", "str_f64_set"), + Ty::MapI64I64 => ("i64_i64_new", "i64_i64_set"), + Ty::MapI64F64 => ("i64_f64_new", "i64_f64_set"), + _ => ("str_dyn_new", "str_dyn_set"), + }; + let handle = ssa.new_val(); + // A literal knows how many entries it has, so the map is + // built at its final size instead of rehashing on the way. + if matches!(new_fn, "str_dyn_new" | "str_i64_new" | "str_f64_new") { + let capacity = ssa.new_val(); + insts.push(Inst::Const { + dst: capacity, + value: Const::I64(entries.len() as i64), + }); + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new( + "map_h", + match new_fn { + "str_i64_new" => "str_i64_new_sized", + "str_f64_new" => "str_f64_new_sized", + _ => "str_dyn_new_sized", + }, + ), + args: vec![capacity], + }); + } else { + insts.push(Inst::Call { + dst: Some(handle), + callee: AbiRef::new("map_h", new_fn), + args: Vec::new(), + }); + } + ssa.set_plain_map(handle); for (k, v) in entries { - let boxed_key = match k { + let key = match k { RuntimeMapKeyData::ShortStr(key) | RuntimeMapKeyData::String(key) => { - let raw = materialize_key(ssa, insts, globals, key); - let boxed = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(boxed), - callee: AbiRef::new("dyn", "from_str"), - args: vec![raw], - }); - boxed + materialize_key(ssa, insts, globals, key) } RuntimeMapKeyData::Int(ik) => { let raw = ssa.new_val(); @@ -358,29 +636,34 @@ pub(super) fn lower( dst: raw, value: Const::I64(*ik), }); - let boxed = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(boxed), - callee: AbiRef::new("dyn", "from_i64"), - args: vec![raw], - }); - boxed + raw } _ => return Err(Unsupported::Opcode { pc, op: instr.opcode() }), }; - let boxed_value = box_const_scalar(ssa, insts, globals, v); + let value = match map_ty { + Ty::MapStrDyn => box_const_scalar(ssa, insts, globals, v), + _ => unboxed_const_scalar(ssa, insts, globals, v) + .ok_or(Unsupported::Opcode { pc, op: instr.opcode() })?, + }; + // A literal's string key is an interned global, so the + // map borrows it rather than copying it per instance. + let set_fn = match set_fn { + _ if matches!(k, RuntimeMapKeyData::Int(_)) => set_fn, + "str_dyn_set" => "str_dyn_set_const", + "str_i64_set" => "str_i64_set_const", + "str_f64_set" => "str_f64_set_const", + other => other, + }; insts.push(Inst::Call { dst: None, - callee: AbiRef::new("map_h", "lit_set"), - args: vec![lit, boxed_key, boxed_value], + callee: AbiRef::new("map_h", set_fn), + args: vec![handle, key, value], }); } - let handle = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(handle), - callee: AbiRef::new("map_h", finish_fn), - args: vec![lit], - }); + let _ = finish_fn; + if map_ty != Ty::MapStrDyn { + ssa.literal_carrier.insert(handle, (pc, map_ty)); + } ssa.write(instr.a(), block, (handle, map_ty)); } ConstHeapValueData::LongString(s) => { @@ -417,35 +700,38 @@ pub(super) fn lower( }); let slot = ssa.cell_slot(cid); ssa.write_slot(slot, block, (nil, Ty::Nil)); - ssa.builtin_regs.insert((block, instr.a()), GlobalRef::Cell(cid)); + ssa.bind_ref(block, instr.a(), GlobalRef::Cell(cid)); } } } Opcode::Len => { // `a` = dst, `b` = container register; the length is always a plain `i64`, // regardless of element type (lists) or key/value type (maps). - let (handle, ty) = ssa.read(instr.b(), block, pc)?; - let (module, len_fn) = match ty { - // Strings count Unicode scalar values (the VM's char length). - Ty::Str => ("str", "char_len"), - Ty::ListI64 => ("list_h", "i64_len"), - Ty::ListF64 => ("list_h", "f64_len"), - Ty::ListStr => ("list_h", "str_len"), - Ty::MapStrI64 => ("map_h", "str_i64_len"), - Ty::MapI64I64 => ("map_h", "i64_i64_len"), - Ty::MapStrF64 => ("map_h", "str_f64_len"), - Ty::MapI64F64 => ("map_h", "i64_f64_len"), - Ty::ListDyn => ("list_h", "dyn_len"), - Ty::MapStrDyn => ("map_h", "str_dyn_len"), - Ty::Set => ("set", "len"), - // A boxed Dyn: length dispatches on the runtime tag. - Ty::Dyn => ("dyn", "len_of"), - _ => return Err(Unsupported::TypeMismatch { pc }), + // + // Read through `read_scalar`, so a `Maybe` receiver unwraps first — + // the VM raises on `nil.len()`, and so does the unwrap. A string + // list's loop variable is a `Maybe` (the element read is + // bounds-checked), so without this `for s in ["ab", "cde"] { + // s.len() }` dropped the whole program to the interpreter. + let (handle, ty) = read_scalar(ssa, insts, instr.b(), block, pc)?; + // A struct instance rides the map carrier and has no length: the + // interpreter answers "`len()` has no answer for P". Declining is + // what the method spelling does too (`lower_method_dispatch`). + if !ssa.is_plain_map(handle) && matches!(ty, Ty::MapStrDyn) { + return Err(Unsupported::TypeMismatch { pc }); + } + // Strings count Unicode scalar values (the VM's char length), which + // is a string call rather than a container one; every other carrier + // shares its row with `is_empty`. + let callee = if ty == Ty::Str { + AbiRef::new("str", "char_len") + } else { + container_len_abi(ty).ok_or(Unsupported::TypeMismatch { pc })? }; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new(module, len_fn), + callee, args: vec![handle], }); ssa.write(instr.a(), block, (dst, Ty::I64)); @@ -527,14 +813,27 @@ pub(super) fn lower( // (`lkrt vm_mirror.rs`, plan D1/D2). let (v, ty) = ssa.read(instr.b(), block, pc)?; match ty { - Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn => { + // A window iterates as itself — `len` and indexing on it are + // window-relative, which is exactly what the loop needs. The VM + // does the same (`to_iter` hands back the slice handle rather + // than materializing it). + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::SliceI64 => { ssa.write(instr.a(), block, (v, ty)); } - Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn => { + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 => { + // A struct instance rides the `MapStrDyn` carrier and is + // not iterable — the VM raises `ToIter target object is not + // iterable`. Iterating one handed the loop its fields as + // pairs. + if ty == Ty::MapStrDyn && !ssa.is_plain_map(v) { + return Err(Unsupported::TypeMismatch { pc }); + } let iter_fn = match ty { Ty::MapStrI64 => "str_i64_iter_pairs", Ty::MapStrF64 => "str_f64_iter_pairs", Ty::MapStrBool => "str_bool_iter_pairs", + Ty::MapI64I64 => "i64_i64_iter_pairs", + Ty::MapI64F64 => "i64_f64_iter_pairs", _ => "str_dyn_iter_pairs", }; let dst = ssa.new_val(); @@ -554,11 +853,38 @@ pub(super) fn lower( }); ssa.write(instr.a(), block, (dst, Ty::ListDyn)); } + // A set snapshots to its members, in the VM's order — both + // sides key by the same `RtKey` and fill by the same sequence, + // and a set has no second stage for anything else to enter. + Ty::Set => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("set", "iter"), + args: vec![v], + }); + ssa.write(instr.a(), block, (dst, Ty::ListDyn)); + } + // `Bytes` iterates its byte values, in order — no hash + // anywhere, so nothing to mirror. + Ty::Bytes => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", "to_i64_list"), + args: vec![v], + }); + ssa.write(instr.a(), block, (dst, Ty::ListI64)); + } + // A boxed value normalizes at run time, by tag, exactly as the + // arms above do by static type. This used to be `dyn.as_list` + // — a *list* guard — so a boxed map, set, bytes or string + // raised `runtime type error` in a loop the VM runs. Ty::Dyn => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("dyn", "as_list"), + callee: AbiRef::new("dyn", "to_iter"), args: vec![v], }); ssa.write(instr.a(), block, (dst, Ty::ListDyn)); @@ -574,41 +900,53 @@ pub(super) fn lower( // absent optional field is `str_dyn_get`'s Nil — matching the // VM's absent-Object-field nil. The type name is dropped: whole- // object display/`typeof` are not in the native subset. + // Sized: a struct literal knows its field count, and growing a map + // rehashes everything already in it. + let capacity = ssa.new_val(); + insts.push(Inst::Const { + dst: capacity, + value: Const::I64(i64::from(instr.c())), + }); let map = ssa.new_val(); insts.push(Inst::Call { dst: Some(map), - callee: AbiRef::new("map_h", "str_dyn_new"), - args: Vec::new(), + callee: AbiRef::new("map_h", "str_dyn_new_sized"), + args: vec![capacity], }); + let type_name = ssa.const_str_at(instr.b(), block, pc); for i in 0..instr.c() as usize { let key_reg = instr.b().wrapping_add(1).wrapping_add((i * 2) as u8); let value_reg = key_reg.wrapping_add(1); - let key = { - let kv = ssa.read(key_reg, block, pc).ok().map(|(v, _)| v); - kv.and_then(|v| ssa.const_strs.get(&v).cloned()) - .or_else(|| ssa.reg_const_str(key_reg, block)) - } - .ok_or(Unsupported::Opcode { pc, op: instr.opcode() })?; + let key = ssa + .const_str_at(key_reg, block, pc) + .ok_or(Unsupported::Opcode { pc, op: instr.opcode() })?; let key_v = materialize_key(ssa, insts, globals, &key); - let (vv, vty) = ssa.read(value_reg, block, pc)?; + // Through `read_value`: a struct field written as a lambda + // becomes a closure here, like a list element or a map value. + let (vv, vty) = read_value(ssa, insts, sig, funcs, cap_ctx, value_reg, block, pc)?; // `to_dyn_any`: a dynamically indexed field value arrives as // a `Maybe` carrier and boxes through `from_maybe_*` (nil // stays nil, like the VM's absent-element field value). - let boxed = to_dyn_any(ssa, insts, vv, vty, pc)?; + let boxed = to_dyn(ssa, insts, vv, vty, pc)?; + // `A { v: x }` with an untyped `x` is a store the type checker + // cannot see, exactly like `p["v"] = x` is — so it is measured + // against the declaration. Only when the value's own type does + // not already settle it: a literal `Int` into an `Int` field + // needs nothing, which is the common case and pays nothing. + if let Some(name) = type_name.as_deref() { + emit_declared_field_check(ssa, insts, globals, sig, name, &key, vty, boxed); + } insts.push(Inst::Call { dst: None, - callee: AbiRef::new("map_h", "str_dyn_set"), + // The field name is an interned global, so the map borrows + // it instead of copying it into every instance. + callee: AbiRef::new("map_h", "str_dyn_set_const"), args: vec![map, key_v, boxed], }); } - // Struct provenance (plan J1): the type name drives static - // method devirtualization; a type with registered trait impls - // also marks the handle for boxed runtime dispatch. - let type_name = { - let tv = ssa.read(instr.b(), block, pc).ok().map(|(v, _)| v); - tv.and_then(|v| ssa.const_strs.get(&v).cloned()) - .or_else(|| ssa.reg_const_str(instr.b(), block)) - }; + // Struct provenance (plan J1): the type name drives static method + // devirtualization; a type with registered trait impls also marks + // the handle for boxed runtime dispatch. if let Some(type_name) = type_name { if let Some(&tid) = sig.traits.type_ids.get(&type_name) { let tid_v = ssa.new_val(); @@ -622,7 +960,7 @@ pub(super) fn lower( args: vec![map, tid_v], }); } - ssa.struct_types.insert(map, type_name); + ssa.set_struct(map, type_name); } ssa.write(instr.a(), block, (map, Ty::MapStrDyn)); } @@ -631,60 +969,62 @@ pub(super) fn lower( // handle is a reference (matching the VM), so the push is visible through // aliases; no new SSA value is produced for the list. let (handle, list_ty) = ssa.read(instr.a(), block, pc)?; - // A boxed receiver (a cell readback across a `try$call` boundary) - // unwraps through the as_list guard: the push mutates the shared - // handle, exactly the VM's aliasing. - let (handle, list_ty) = if list_ty == Ty::Dyn { - let unboxed = ssa.new_val(); + // A boxed receiver pushes through `dyn.list_push`, which reaches + // the carrier behind the tag. It used to unwrap through + // `dyn.as_list` — correct while every boxed list was a + // `Vec`, and a lost write once typed carriers box in place, + // because that guard has to materialize one. + if list_ty == Ty::Dyn { + let (value, value_ty) = ssa.read(instr.b(), block, pc)?; + let boxed = to_dyn(ssa, insts, value, value_ty, pc)?; insts.push(Inst::Call { - dst: Some(unboxed), - callee: AbiRef::new("dyn", "as_list"), - args: vec![handle], + dst: None, + callee: AbiRef::new("dyn", "list_push"), + args: vec![handle, boxed], }); - (unboxed, Ty::ListDyn) - } else { - (handle, list_ty) - }; + return Ok(()); + } + // A lambda pushed into a list becomes a closure value, and the + // carrier has to be one that can hold it. + if let Some(GlobalRef::Lambda(_) | GlobalRef::Closure(..) | GlobalRef::UserFn(_)) = + ssa.builtin_ref_at(instr.b(), block) + { + let (value, value_ty) = read_value(ssa, insts, sig, funcs, cap_ctx, instr.b(), block, pc)?; + if list_ty != Ty::ListDyn && list_ty != Ty::Dyn { + return Err( + carrier_contradicted_here_or_at_callers(ssa, func, instr.a(), handle, list_ty) + .unwrap_or(Unsupported::TypeMismatch { pc }), + ); + } + let boxed = to_dyn(ssa, insts, value, value_ty, pc)?; + let (module, name) = if list_ty == Ty::Dyn { + ("dyn", "list_push") + } else { + ("list_h", "dyn_push") + }; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new(module, name), + args: vec![handle, boxed], + }); + return Ok(()); + } // Values read through `read_scalar` so a `Maybe` (a dynamic list // read like `xs[i]` in `flat.push(xs[i])`) unwraps first. // A push whose value type contradicts a guessed empty-`[]` // element type retries the literal as a Dyn list (fixpoint). - let guess_wrong = |ssa: &Ssa| { - if ssa.empty_guess.is_empty() { - None - } else { - // The handle itself when known; otherwise a handle read - // through an unsealed loop phi has no provenance yet — - // mark the pending guesses *of the receiver's own shape* - // (only those can be the contradicted literal; a - // correctly guessed `ListStr` elsewhere in the function - // must keep its typed lowering — `join` etc. have no Dyn - // arm). If shape-filtering leaves nothing, over-mark all - // (costs typed-ness, never correctness). - let pcs = match ssa.empty_guess.get(&handle) { - Some(&(pc0, _)) => vec![pc0], - None => { - let same_shape: Vec = ssa - .empty_guess - .values() - .filter(|&&(_, gty)| gty == list_ty) - .map(|&(p0, _)| p0) - .collect(); - if same_shape.is_empty() { - ssa.empty_guess.values().map(|&(p0, _)| p0).collect() - } else { - same_shape - } - } - }; - Some(Unsupported::EmptyListGuessWrong { pcs }) - } - }; + let guess_wrong = + |ssa: &Ssa| carrier_contradicted_here_or_at_callers(ssa, func, instr.a(), handle, list_ty); match list_ty { Ty::ListI64 => { + if let Some(e) = + nullable_into_typed_carrier(ssa, func, instr.b(), block, instr.a(), handle, list_ty) + { + return Err(e); + } let value = match read_typed_scalar(ssa, insts, instr.b(), block, Ty::I64, pc) { Ok(v) => v, - Err(e) => return Err(guess_wrong(ssa).unwrap_or(e)), + Err(e) => return Err(keep_discovery(e, guess_wrong(ssa))), }; insts.push(Inst::Call { dst: None, @@ -693,6 +1033,11 @@ pub(super) fn lower( }); } Ty::ListF64 => { + if let Some(e) = + nullable_into_typed_carrier(ssa, func, instr.b(), block, instr.a(), handle, list_ty) + { + return Err(e); + } let (bv, bty) = read_scalar(ssa, insts, instr.b(), block, pc)?; if !matches!(bty, Ty::I64 | Ty::F64) { return Err(guess_wrong(ssa).unwrap_or(Unsupported::TypeMismatch { pc })); @@ -708,9 +1053,14 @@ pub(super) fn lower( // Stored strings are arena-owned (interned constants or // register-visible arena strings), alive until exit, so the // pointer push involves no ownership transfer. + if let Some(e) = + nullable_into_typed_carrier(ssa, func, instr.b(), block, instr.a(), handle, list_ty) + { + return Err(e); + } let value = match read_typed_scalar(ssa, insts, instr.b(), block, Ty::Str, pc) { Ok(v) => v, - Err(e) => return Err(guess_wrong(ssa).unwrap_or(e)), + Err(e) => return Err(keep_discovery(e, guess_wrong(ssa))), }; insts.push(Inst::Call { dst: None, @@ -718,9 +1068,17 @@ pub(super) fn lower( args: vec![handle, value], }); } - // Mixed list: any boxable value pushes as a Dyn carrier. + // Mixed list: any boxable value pushes as a Dyn carrier — + // including a nullable one, which pushes **nil**. + // + // Read raw rather than through `read_scalar`: that narrows a + // carrier by asserting it is present, which is right where a + // number is required and wrong here. A Dyn list holds nil, and + // the VM puts nil in it, so `out.push(xs[i])` past the end of + // `xs` appended nil on the interpreter and *raised* compiled — + // a program that ran one way and died the other. Ty::ListDyn => { - let (bv, bty) = read_scalar(ssa, insts, instr.b(), block, pc)?; + let (bv, bty) = ssa.read(instr.b(), block, pc)?; let boxed = to_dyn(ssa, insts, bv, bty, pc)?; insts.push(Inst::Call { dst: None, @@ -748,24 +1106,42 @@ pub(super) fn lower( // A constant-name member read on a bundled file module resolves // to the merged function (`fib.iterative` → direct call target). if let Some(GlobalRef::UserModule(bundle)) = ssa.builtin_regs.get(&(block, instr.b())).cloned() { - let name = { - let key = ssa.read(instr.c(), block, pc).ok().map(|(v, _)| v); - key.and_then(|v| ssa.const_strs.get(&v).cloned()) - .or_else(|| ssa.reg_const_str(instr.c(), block)) - }; + let name = ssa.const_str_at(instr.c(), block, pc); let fidx = name.and_then(|n| sig.imports.bundles.get(bundle).and_then(|b| b.fns.get(&n)).copied()); let Some(fidx) = fidx else { return Err(Unsupported::Opcode { pc, op: instr.opcode() }); }; - ssa.builtin_regs.insert((block, instr.a()), GlobalRef::Lambda(fidx)); + ssa.bind_ref(block, instr.a(), GlobalRef::Lambda(fidx)); return Ok(()); } - if let Some(GlobalRef::Module(module)) = ssa.builtin_regs.get(&(block, instr.b())).cloned() { - let name = { - let key = ssa.read(instr.c(), block, pc).ok().map(|(v, _)| v); - key.and_then(|v| ssa.const_strs.get(&v).cloned()) - .or_else(|| ssa.reg_const_str(instr.c(), block)) - }; + // `use chan;` binds the module over the `chan()` global, and both + // are the *same name* — so `builtin_for_name` claims it first and + // the module case never got a chance: `chan.new(1)` dropped its + // module to the VM while `chan(1)` lowered. + // + // The bytecode does *not* tell them apart: `chan.new(1)` compiles to + // the same `GetGlobal chan` + `GetIndex "new"` whether or not the + // file wrote `use chan;`. What differs is at run time — the import + // replaces the global with the module object, and the VM's + // `GetIndex` only succeeds against that. Without the import the + // global still holds the constructor function and the VM answers + // `index target object is not indexable: "Function"`. + // + // So the import is what licenses the module spelling, and it is + // recorded: `sig.imports`. Reading it here is the difference + // between the two ends agreeing and a program that runs natively + // and fails under the VM. + let module_ref = match ssa.builtin_regs.get(&(block, instr.b())).cloned() { + Some(GlobalRef::Module(module)) => Some(module), + Some(GlobalRef::Builtin(Builtin::ChanNew)) + if sig.imports.module_aliases.get("chan").is_some_and(|m| m == "chan") => + { + Some("chan".to_string()) + } + _ => None, + }; + if let Some(module) = module_ref { + let name = ssa.const_str_at(instr.c(), block, pc); let Some(name) = name else { return Err(Unsupported::Opcode { pc, op: instr.opcode() }); }; @@ -775,8 +1151,18 @@ pub(super) fn lower( ssa.write(instr.a(), block, (dst, ty)); return Ok(()); } - ssa.builtin_regs - .insert((block, instr.a()), GlobalRef::ModuleFn(module, name)); + // `encoding.json`, `io.std`, `net.tcp`: reading a *submodule* + // off its parent gives another module object, not a function of + // the parent. Without this the chain stopped at the first dot, + // so `encoding.json.parse(s)` dropped the program to the VM + // while `use { json } from encoding;` lowered — the same rule + // the import path already applies (`is_submodule`). + let global_ref = if is_submodule(&module, &name) { + GlobalRef::Module(name) + } else { + GlobalRef::ModuleFn(module, name) + }; + ssa.bind_ref(block, instr.a(), global_ref); return Ok(()); } // `a` = dst, `b` = container register, `c` = key register. @@ -798,9 +1184,18 @@ pub(super) fn lower( dst: end, value: Const::I64(r_end), }); + // Every carrier that has a slice symbol, not the two this + // listed. `xs[1..3]` lowered for `List` and fell back for + // `List`, `List`, a mixed list, a `Bytes` and a + // window — the same operation, decided by which carrier the + // list happened to have. let (module, name, out_ty) = match list_ty { Ty::Str => ("str", "slice_chars", Ty::Str), Ty::ListI64 => ("list_h", "i64_slice", Ty::ListI64), + Ty::ListF64 => ("list_h", "f64_slice", Ty::ListF64), + Ty::ListStr => ("list_h", "str_slice", Ty::ListStr), + Ty::ListDyn => ("list_h", "dyn_slice", Ty::ListDyn), + Ty::Bytes => ("bytes_h", "slice", Ty::Bytes), _ => return Err(Unsupported::TypeMismatch { pc }), }; let dst = ssa.new_val(); @@ -831,11 +1226,36 @@ pub(super) fn lower( _ => return Err(Unsupported::TypeMismatch { pc }), }; let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("dyn", helper), - args: vec![handle, key], - }); + let field_name = (helper == "field").then(|| ssa.const_str_value(key)).flatten(); + match field_name + .as_deref() + .and_then(|name| struct_field_position(ssa, sig, handle, name).map(|i| (name, i))) + { + // A declared struct's field, by position — the boxed twin + // of the `MapStrDyn` read above. + Some((name, index)) => { + let index_v = ssa.new_val(); + insts.push(Inst::Const { + dst: index_v, + value: Const::I64(index as i64), + }); + let len_v = ssa.new_val(); + insts.push(Inst::Const { + dst: len_v, + value: Const::I64(name.len() as i64), + }); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "field_at"), + args: vec![handle, index_v, key, len_v], + }); + } + None => insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", helper), + args: vec![handle, key], + }), + } ssa.write(instr.a(), block, (dst, Ty::Dyn)); return Ok(()); } @@ -843,7 +1263,7 @@ pub(super) fn lower( // `index_string_at`); the Dyn carrier holds the nil itself. // (`for ch in "abc"` desugars to exactly this indexed read.) if list_ty == Ty::Str { - let key = read_typed_scalar(ssa, insts, instr.c(), block, Ty::I64, pc)?; + let key = read_map_key(ssa, insts, instr.c(), block, Ty::I64, pc)?; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), @@ -856,7 +1276,7 @@ pub(super) fn lower( // Mixed-value map indexed by string key: same accessor as // `GetFieldK` (missing key = Nil-tag Dyn). if list_ty == Ty::MapStrDyn { - let key = read_typed_scalar(ssa, insts, instr.c(), block, Ty::Str, pc)?; + let key = read_map_key(ssa, insts, instr.c(), block, Ty::Str, pc)?; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), @@ -866,12 +1286,98 @@ pub(super) fn lower( ssa.write(instr.a(), block, (dst, Ty::Dyn)); return Ok(()); } + // A key of a type the map cannot hold is a *miss*, and the answer + // is nil for every value of that type — so it is a constant rather + // than a call. The interpreter answers the same way, and the + // checker stopped refusing the shape, so `{"k": 1}[0]` reaches here + // now instead of being turned back at check time. + // A constant string key is not in a register to be read, so it is + // asked for by name — which is also how an int-keyed map sees the + // only key type it can be handed wrongly. + let key_ty = match ssa.const_str_at(instr.c(), block, pc) { + Some(_) => Ty::Str, + None => ssa.read(instr.c(), block, pc).map(|(_, t)| t).unwrap_or(Ty::Dyn), + }; + // + // A `Float` is not one of them, and it read like one for as long as + // this fold has existed. `nil`, `true` and an Int are all *keys* — + // a map simply does not have that one, so the read is a miss. A + // Float is not a key at all, and the interpreter says so out loud + // for a read exactly as it does for a store ("Float cannot be a map + // key or set member"). Folding it to nil answered where the + // interpreter raised, on both map key kinds. + let map_ty = matches!( + list_ty, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 + ); + if map_ty && key_ty == Ty::F64 { + let msg = materialize_key(ssa, insts, globals, "Float cannot be a map key or set member"); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "raise_msg"), + args: vec![msg], + }); + // A read has a destination and the store path does not, so the + // raise alone would leave this register undefined for whatever + // reads it next. `raise_msg` does not return, so the value is + // never observed — it only has to exist. + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "from_nil"), + args: vec![], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } + let map_key_mismatch = match list_ty { + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn => { + matches!(key_ty, Ty::I64 | Ty::Bool | Ty::Nil) + } + Ty::MapI64I64 | Ty::MapI64F64 => matches!(key_ty, Ty::Str | Ty::Bool | Ty::Nil), + _ => false, + }; + if map_key_mismatch { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "from_nil"), + args: vec![], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } + // A key the lowering cannot type, against a map whose key type it + // can: unboxing the key to the map's type raises for anything else, + // and a *read* has an answer — nil, the way a key that is simply + // absent does. So the map boxes and `dyn.get` dispatches on the + // key's tag at run time, which is what the interpreter does. + // + // Reads only. `m[k] = v` builds a key and stays where it was. + if key_ty == Ty::Dyn + && matches!( + list_ty, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 + ) + { + let boxed_map = to_dyn(ssa, insts, handle, list_ty, pc)?; + let (key_v, key_v_ty) = read_scalar(ssa, insts, instr.c(), block, pc)?; + let boxed_key = to_dyn(ssa, insts, key_v, key_v_ty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "get"), + args: vec![boxed_map, boxed_key], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } // String-keyed map reads take a `Str` key (dynamic template keys // included); a missing key is the `Maybe` nil model. if matches!(list_ty, Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool) { // A `Maybe` key (`freq[xs[i]]`) unwraps first (absent aborts — // the scalar-context rule). - let key = read_typed_scalar(ssa, insts, instr.c(), block, Ty::Str, pc)?; + let key = read_map_key(ssa, insts, instr.c(), block, Ty::Str, pc)?; let dst = ssa.new_val(); let maybe_ty = match list_ty { Ty::MapStrF64 => { @@ -893,7 +1399,14 @@ pub(super) fn lower( // Lists / int-keyed maps index with an `I64` (a `Maybe` index — // `xs[ys[j]]` — unwraps first, a boxed one goes through the tag // check). - let index_val = read_index_scalar(ssa, insts, instr.c(), block, pc)?; + // An int-keyed map's index is a *key*, and a key of a type no map + // can hold is refused by name rather than with the generic type + // error a list's index gives. + let index_val = if matches!(list_ty, Ty::MapI64I64 | Ty::MapI64F64) { + read_map_key(ssa, insts, instr.c(), block, Ty::I64, pc)? + } else { + read_index_scalar(ssa, insts, instr.c(), block, pc)? + }; // Fast path: a **provably in-range** access (constant list of known // length indexed by a constant in `[0, len)`) is a clean scalar `at`. let const_in_range = match (ssa.list_len.get(&handle), ssa.const_int.get(&index_val)) { @@ -902,6 +1415,23 @@ pub(super) fn lower( }; if let Some(idx) = const_in_range { let (at_fn, elem_ty) = match list_ty { + // A `Bytes` element is a `Dyn` whether or not the index is + // provably in range: one helper, one rule. + Ty::Bytes => { + let idx_v = ssa.new_val(); + insts.push(Inst::Const { + dst: idx_v, + value: Const::I64(idx), + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", "get"), + args: vec![handle, idx_v], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } Ty::ListI64 => ("i64_at", Ty::I64), Ty::ListF64 => ("f64_at", Ty::F64), Ty::ListStr => ("str_at", Ty::Str), @@ -921,6 +1451,11 @@ pub(super) fn lower( callee: AbiRef::new("list_h", at_fn), args: vec![handle, idx_v], }); + if elem_ty == Ty::Dyn + && let Some(name) = ssa.list_elem_struct.get(&handle).cloned() + { + ssa.set_struct(dst, name); + } ssa.write(instr.a(), block, (dst, elem_ty)); } else { // Dynamic / not-provably-in-range: the result is `Maybe` (VM: @@ -932,6 +1467,19 @@ pub(super) fn lower( // and prints `nil`. Either way there is no eager-abort shortcut that // would diverge from `return xs[oob]` printing `nil`. match list_ty { + // `b[i]` on a `Bytes`, which is also what `b.get(i)` + // compiles to. Like a dyn list, the Dyn's Nil tag is the + // absent case — negative counts from the end, out of range + // is nil. + Ty::Bytes => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", "get"), + args: vec![handle, index_val], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + } // Mixed list: no Maybe carrier needed — the Dyn's Nil tag // *is* the absent case (`dyn_at` maps OOB/negative-beyond // to Nil, matching the VM's nil-on-out-of-range). @@ -942,6 +1490,11 @@ pub(super) fn lower( callee: AbiRef::new("list_h", "dyn_at"), args: vec![handle, index_val], }); + // An element of a list of one declared struct is that + // struct, so `nodes[i].next` reads a declared field. + if let Some(name) = ssa.list_elem_struct.get(&handle).cloned() { + ssa.set_struct(dst, name); + } ssa.write(instr.a(), block, (dst, Ty::Dyn)); } Ty::ListI64 => { @@ -953,6 +1506,18 @@ pub(super) fn lower( }); ssa.write(instr.a(), block, (dst, Ty::MaybeI64)); } + // `w[i]` on a window: resolved against the window (negative + // counts from *its* end), then read through to the source — + // the VM's `slice_element`. + Ty::SliceI64 => { + let dst = ssa.new_val(); + insts.push(Inst::SliceGetMaybe { + dst, + handle, + index: index_val, + }); + ssa.write(instr.a(), block, (dst, Ty::MaybeI64)); + } Ty::ListF64 => { let dst = ssa.new_val(); insts.push(Inst::ListGetMaybeF64 { @@ -1002,16 +1567,144 @@ pub(super) fn lower( // For a **map**, the store always inserts-or-updates. An unsupported // container/key/value combination rejects (falls back). let (handle, list_ty) = ssa.read(instr.a(), block, pc)?; + // A `Float` key is the VM's loud "cannot be used as a key" error, + // and it is known *here*: no map carrier accepts one, so the store + // can only raise. Emitting the raise keeps the rest of the program + // native — refusing sent the whole thing back to the VM to produce + // the same error. + let map_ty = matches!( + list_ty, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 + ); + if map_ty && ssa.read(instr.b(), block, pc).map(|(_, t)| t) == Ok(Ty::F64) { + let msg = materialize_key(ssa, insts, globals, "Float cannot be a map key or set member"); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "raise_msg"), + args: vec![msg], + }); + return Ok(()); + } + // A boxed receiver stores through `dyn.index_set`, which reaches + // the carrier behind the tag. Both the key and the value travel + // boxed: which key shape a carrier accepts is the callee's rule + // (an integer key on a map is a key, not a position), and this + // side has no carrier to check it against. + if list_ty == Ty::Dyn { + let (kv, kty) = ssa.read(instr.b(), block, pc)?; + // `nil` and a Bool are keys the interpreter *stores* — `m[nil] + // = 1` gives `{nil:1}` — and no native map representation holds + // one: the boxed carrier is keyed by `String` and the typed ones + // by `String` or `i64`. So the store has no native form, and + // emitting one raised "runtime type error" on a program the + // interpreter answers. Falling back is the whole program on the + // VM, which is slower and right. + // + // Only these two, and only where the key's type says so. A `Str` + // or `I64` key stores natively as before, and a key this side + // cannot type still goes through — a fallback for every erased + // key would cost far more coverage than the shape is worth. + // Reading such a key is a different question and is answered: + // `lkrt_dyn_get` looks it up and misses. + if matches!(kty, Ty::Nil | Ty::Bool) { + return Err(Unsupported::TypeMismatch { pc }); + } + let key = to_dyn(ssa, insts, kv, kty, pc)?; + let (cv, cty) = ssa.read(instr.c(), block, pc)?; + let boxed = to_dyn(ssa, insts, cv, cty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("dyn", "index_set"), + args: vec![handle, key, boxed], + }); + return Ok(()); + } + // A key this side cannot type, stored into a map carrier that holds + // *one* key kind. The runtime unbox (`dyn.as_key_str` / + // `dyn.as_key_i64`) refuses every other kind — and the interpreter + // does not: an LK map takes nil, a Bool, an Int and a String alike, + // and which carrier holds it is a native representation choice no + // program asked for. So `fn put(m, k) { m[k] = 1; }` called once + // with a string and once with an integer stored the first and + // raised "runtime type error" on the second, where the interpreter + // answered `{"a":1,7:1}`. An explicit `{"a": 1}` literal reaches it + // too, so this is not about the empty-literal guess. + // + // Except a **closure**, which is provably not a key at all: there + // the runtime refusal is the interpreter's own sentence, word for + // word, and lowering it keeps the rest of the module native. + // `examples/syntax/closure_value.lk` writes that on purpose, inside + // a `try` — which is why the fact has to cross the region boundary + // (`SigInfer::try_body_closure_inputs`) rather than be re-derived. + // + // Measured: the generative fuzzer's fully-native count is unchanged + // at 300 cases, and no example loses its lowering. + if matches!( + list_ty, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 + ) && let Ok((kv, Ty::Dyn)) = ssa.read(instr.b(), block, pc) + && !ssa.closure_values.contains(&kv) + { + return Err(Unsupported::TypeMismatch { pc }); + } // String-keyed map stores take a `Str` key (dynamic template keys // included); the map ABI copies the key. + // A boxed map takes any value: box it and store. Without this arm + // the Dyn carrier existed but nothing could be put into it, so the + // retry below would have had nowhere to land. + if list_ty == Ty::MapStrDyn { + let key = read_map_key(ssa, insts, instr.b(), block, Ty::Str, pc)?; + // Raw, not `read_scalar`: a boxed map holds nil, so a nullable + // value stores as nil rather than asserting it is present. Same + // divergence the `ListDyn` push had — `m[k] = xs[i]` past the + // end of `xs` stored nil on the interpreter and raised compiled. + let (cv, cty) = ssa.read(instr.c(), block, pc)?; + let boxed = to_dyn(ssa, insts, cv, cty, pc)?; + // As in `SetFieldK`: a store into a declared field is measured + // against the declaration. The key here may be computed, so + // the constant-code form only applies when it is not. + match ssa.const_str_at(instr.b(), block, pc) { + Some(field) => emit_field_store_check(ssa, insts, globals, sig, handle, &field, cty, boxed), + // A computed key cannot be filtered by name, so this is + // the one store shape that asks at run time — and only in + // a module that declares a typed field at all. + None if sig.traits.struct_field_codes.values().any(|&code| code != DECLARED_ANY) => { + let key_dyn = to_dyn(ssa, insts, key, Ty::Str, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("obj_ty", "check_marked_dyn"), + args: vec![handle, key_dyn, boxed], + }); + } + None => {} + } + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("map_h", "str_dyn_set"), + args: vec![handle, key, boxed], + }); + return Ok(()); + } if matches!(list_ty, Ty::MapStrI64 | Ty::MapStrF64) { - let key = read_typed_scalar(ssa, insts, instr.b(), block, Ty::Str, pc)?; + if let Some(e) = nullable_into_typed_carrier(ssa, func, instr.c(), block, instr.a(), handle, list_ty) { + return Err(e); + } + let key = read_map_key(ssa, insts, instr.b(), block, Ty::Str, pc)?; let (cv, cty) = read_scalar(ssa, insts, instr.c(), block, pc)?; let (set_fn, value) = match (list_ty, cty) { (Ty::MapStrI64, Ty::I64) => ("str_i64_set", cv), (Ty::MapStrF64, Ty::F64) => ("str_f64_set", cv), (Ty::MapStrF64, Ty::I64) => ("str_f64_set", coerce_to_f64(ssa, insts, cv, cty)), - _ => return Err(Unsupported::TypeMismatch { pc }), + // The value contradicts what this map was built to hold — + // the same situation a push contradicting a list literal is, + // and the same answer: name the literal and let the fixpoint + // rebuild it with a Dyn carrier. + _ => { + return Err( + carrier_contradicted_here_or_at_callers(ssa, func, instr.a(), handle, list_ty) + .unwrap_or(Unsupported::TypeMismatch { pc }), + ); + } }; insts.push(Inst::Call { dst: None, @@ -1023,9 +1716,18 @@ pub(super) fn lower( // A boxed index unboxes through the tag check, as it does on the // read side: a store from a loop over a list has exactly the same // shape as a load. - let index = read_index_scalar(ssa, insts, instr.b(), block, pc)?; + let index = if matches!(list_ty, Ty::MapI64I64 | Ty::MapI64F64) { + read_map_key(ssa, insts, instr.b(), block, Ty::I64, pc)? + } else { + read_index_scalar(ssa, insts, instr.b(), block, pc)? + }; match list_ty { Ty::ListI64 => { + if let Some(e) = + nullable_into_typed_carrier(ssa, func, instr.c(), block, instr.a(), handle, list_ty) + { + return Err(e); + } let value = read_typed_scalar(ssa, insts, instr.c(), block, Ty::I64, pc)?; insts.push(Inst::Call { dst: None, @@ -1045,7 +1747,41 @@ pub(super) fn lower( args: vec![handle, index, value], }); } + // The other two carriers, so that `xs[i] = v` does not depend + // on the list's internal representation: `Int` and `Float` had + // arms here, `Str` and the boxed carrier did not, and the same + // two lines therefore stayed native or did not for a reason no + // program can observe. + Ty::ListStr => { + if let Some(e) = + nullable_into_typed_carrier(ssa, func, instr.c(), block, instr.a(), handle, list_ty) + { + return Err(e); + } + let value = read_typed_scalar(ssa, insts, instr.c(), block, Ty::Str, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("list_h", "str_set"), + args: vec![handle, index, value], + }); + } + // Raw, not `read_scalar`: a Dyn list holds nil, and the VM + // puts nil in it (`xs[0] = ys[oob]`). + Ty::ListDyn => { + let (cv, cty) = ssa.read(instr.c(), block, pc)?; + let value = crate::dyn_box::to_dyn(ssa, insts, cv, cty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("list_h", "dyn_set"), + args: vec![handle, index, value], + }); + } Ty::MapI64I64 => { + if let Some(e) = + nullable_into_typed_carrier(ssa, func, instr.c(), block, instr.a(), handle, list_ty) + { + return Err(e); + } let value = read_typed_scalar(ssa, insts, instr.c(), block, Ty::I64, pc)?; insts.push(Inst::Call { dst: None, @@ -1054,6 +1790,11 @@ pub(super) fn lower( }); } Ty::MapI64F64 => { + if let Some(e) = + nullable_into_typed_carrier(ssa, func, instr.c(), block, instr.a(), handle, list_ty) + { + return Err(e); + } let (cv, cty) = read_scalar(ssa, insts, instr.c(), block, pc)?; if !matches!(cty, Ty::I64 | Ty::F64) { return Err(Unsupported::TypeMismatch { pc }); @@ -1071,12 +1812,34 @@ pub(super) fn lower( Opcode::GetFieldK => { // `a` = dst, `b` = map register, `c` = key string-constant index. A // missing key is `nil` → the `Maybe` model (i64- or f64-valued map). + // + // Except when the "map" is a **module object**: `m.get(k)` with one + // argument compiles to a map read whatever `m` is, so `env.get(k)` + // arrives here rather than as a call, with `env` where the map + // belongs and `k` as the key. The VM dispatches that at run time; + // this side read the module as a value and reported it as a + // compile-time reference, so `env.get(k)` fell back while + // `env.get_or(k, d)` — an ordinary call — lowered. let (handle, map_ty) = ssa.read(instr.b(), block, pc)?; let key = func .consts .strings .get(instr.c() as usize) .ok_or(Unsupported::BadConst { pc })?; + // A constant *string* key against an int-keyed map is a miss, and + // nil for every such key — the same fold `GetIndex` takes for the + // other direction. Only reachable since the checker stopped + // refusing the shape. + if matches!(map_ty, Ty::MapI64I64 | Ty::MapI64F64) { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "from_nil"), + args: vec![], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } let key_v = materialize_key(ssa, insts, globals, key); let dst = ssa.new_val(); let result_ty = match map_ty { @@ -1107,11 +1870,34 @@ pub(super) fn lower( // Mixed-value map: the Dyn carrier's Nil tag *is* the // missing-key case — no Maybe wrapper needed. Ty::MapStrDyn => { - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("map_h", "str_dyn_get"), - args: vec![handle, key_v], - }); + // A declared struct's field sits at a known position, so + // this is an index rather than a hash of the key — see + // `lkrt_lkmap_str_dyn_get_at` for why the key travels + // along anyway. + match struct_field_position(ssa, sig, handle, key) { + Some(index) => { + let index_v = ssa.new_val(); + insts.push(Inst::Const { + dst: index_v, + value: Const::I64(index as i64), + }); + let len_v = ssa.new_val(); + insts.push(Inst::Const { + dst: len_v, + value: Const::I64(key.len() as i64), + }); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", "str_dyn_get_at"), + args: vec![handle, index_v, key_v, len_v], + }); + } + None => insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", "str_dyn_get"), + args: vec![handle, key_v], + }), + } Ty::Dyn } // A boxed Dyn (e.g. a nested map read out of a MapStrDyn): @@ -1140,25 +1926,82 @@ pub(super) fn lower( .ok_or(Unsupported::BadConst { pc })?; let key_v = materialize_key(ssa, insts, globals, key); let (set_fn, value) = match map_ty { - Ty::MapStrI64 => ( - "str_i64_set", - read_typed_scalar(ssa, insts, instr.b(), block, Ty::I64, pc)?, - ), + // A value the carrier cannot hold contradicts the literal this + // map was built from — the same situation a push contradicting + // a list literal is, and the same answer: name the literal so + // the fixpoint rebuilds it with a Dyn carrier. + Ty::MapStrI64 + if nullable_into_typed_carrier(ssa, func, instr.b(), block, instr.a(), handle, map_ty) + .is_some() => + { + return Err( + nullable_into_typed_carrier(ssa, func, instr.b(), block, instr.a(), handle, map_ty) + .expect("just checked"), + ); + } + Ty::MapStrI64 => match read_typed_scalar(ssa, insts, instr.b(), block, Ty::I64, pc) { + Ok(v) => ("str_i64_set", v), + Err(e) => { + return Err(keep_discovery( + e, + carrier_contradicted_here_or_at_callers(ssa, func, instr.a(), handle, map_ty), + )); + } + }, Ty::MapStrF64 => { + if let Some(e) = nullable_into_typed_carrier(ssa, func, instr.b(), block, instr.a(), handle, map_ty) + { + return Err(e); + } let (bv, bty) = read_scalar(ssa, insts, instr.b(), block, pc)?; if !matches!(bty, Ty::I64 | Ty::F64) { - return Err(Unsupported::TypeMismatch { pc }); + return Err( + carrier_contradicted_here_or_at_callers(ssa, func, instr.a(), handle, map_ty) + .unwrap_or(Unsupported::TypeMismatch { pc }), + ); } ("str_f64_set", coerce_to_f64(ssa, insts, bv, bty)) } + // A bool map rides the `str_i64` carrier, and its value crosses + // as that carrier's word — so a `Bool` is widened here, the way + // it is everywhere a `Bool` meets an `I64` ABI parameter. The + // carrier had no arm at all, which is why `m[k] = true` on a + // `Map` dropped the module to the VM: reading and + // deleting lowered, writing did not. + Ty::MapStrBool => { + let (bv, bty) = read_scalar(ssa, insts, instr.b(), block, pc)?; + let word = match bty { + Ty::I64 => bv, + Ty::Bool => { + let wide = ssa.new_val(); + insts.push(Inst::ZextBool { dst: wide, src: bv }); + wide + } + _ => { + return Err( + carrier_contradicted_here_or_at_callers(ssa, func, instr.a(), handle, map_ty) + .unwrap_or(Unsupported::TypeMismatch { pc }), + ); + } + }; + ("str_i64_set", word) + } // Struct-instance field stores (`p.x += 9` on a `NewObject` // map): any boxable value stores boxed, insert-or-update. Ty::MapStrDyn => { let (bv, bty) = ssa.read(instr.b(), block, pc)?; - ("str_dyn_set", to_dyn_any(ssa, insts, bv, bty, pc)?) + ("str_dyn_set", to_dyn(ssa, insts, bv, bty, pc)?) } _ => return Err(Unsupported::TypeMismatch { pc }), }; + // A store into a declared field is measured against the + // declaration. The struct type is usually known here, which makes + // the code a constant and the check a tag compare; when it is not, + // the mark answers at run time. + if map_ty == Ty::MapStrDyn { + let value_ty = ssa.peek(instr.b(), block).map(|(_, ty)| ty).unwrap_or(Ty::Dyn); + emit_field_store_check(ssa, insts, globals, sig, handle, key, value_ty, value); + } insts.push(Inst::Call { dst: None, callee: AbiRef::new("map_h", set_fn), @@ -1172,6 +2015,61 @@ pub(super) fn lower( // Dyn containers: list membership boxes the needle and defers to // the structural `dyn_contains`; map membership is a dedicated // `has` (a stored-nil value still counts, unlike get+tag). + // A **boxed** haystack: what membership means is the tag's answer, + // not the static type's, so the runtime picks. A map tests its + // keys, every other container its elements. + if list_ty == Ty::Dyn { + let (nv, nty) = ssa.read(instr.b(), block, pc)?; + let needle = to_dyn(ssa, insts, nv, nty, pc)?; + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("dyn", "contains"), + args: vec![handle, needle], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + ssa.write(instr.a(), block, (dst, Ty::Bool)); + return Ok(()); + } + // `needle in text` is `text.contains(needle)` — the same + // operation, and the method spelling already lowered while the + // operator sent the whole program back to the VM. + if list_ty == Ty::Str { + let needle = ssa.read_typed(instr.b(), block, Ty::Str, pc)?; + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("str", "contains"), + args: vec![handle, needle], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + ssa.write(instr.a(), block, (dst, Ty::Bool)); + return Ok(()); + } if list_ty == Ty::ListDyn || list_ty == Ty::MapStrDyn { let raw = ssa.new_val(); if list_ty == Ty::ListDyn { @@ -1206,6 +2104,39 @@ pub(super) fn lower( ssa.write(instr.a(), block, (dst, Ty::Bool)); return Ok(()); } + // `Bytes` and a window: both already have a `contains` symbol — + // the operator was the one place they were not containers, in the + // checker, in the VM and here. A needle the carrier cannot hold is + // the VM's `false`, not an error, which the `I64` needle read + // gives for free only when the needle *is* an `Int`; anything else + // keeps rejecting rather than guessing. + if matches!(list_ty, Ty::Bytes | Ty::SliceI64) { + let needle = ssa.read_typed(instr.b(), block, Ty::I64, pc)?; + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: match list_ty { + Ty::Bytes => AbiRef::new("bytes_h", "contains"), + _ => AbiRef::new("slice_h", "i64_contains"), + }, + args: vec![handle, needle], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + ssa.write(instr.a(), block, (dst, Ty::Bool)); + return Ok(()); + } // `key in map` tests key membership (VM `map_contains`): read the // map's `Maybe` for the key and take its present bit — no value // materialization needed. Mirrors the map `GetIndex` path. @@ -1249,7 +2180,7 @@ pub(super) fn lower( } // Int-keyed maps: same present-bit test with an `I64` key. if matches!(list_ty, Ty::MapI64I64 | Ty::MapI64F64) { - let key = read_typed_scalar(ssa, insts, instr.b(), block, Ty::I64, pc)?; + let key = read_map_key(ssa, insts, instr.b(), block, Ty::I64, pc)?; let maybe = ssa.new_val(); let maybe_ty = if list_ty == Ty::MapI64F64 { insts.push(Inst::MapGetMaybeI64F64 { @@ -1275,11 +2206,13 @@ pub(super) fn lower( ssa.write(instr.a(), block, (dst, Ty::Bool)); return Ok(()); } - // Typed-list `in` is *strictly* same-typed in the VM - // (`list_contains` matches on the needle's variant): `1.0 in - // [1, 2]` and `1 in [1.0]` are false — no numeric coercion, - // unlike `==`. A needle whose proven type can't match folds to - // constant false; a Dyn needle (runtime-typed) still rejects. + // Typed-list `in` compares numerically across `Int`/`Float`, the + // same rule `==` uses. It used to demand the *same* type here and + // in the VM, so `a == b` was true and `a in [b]` false for the + // same pair — and the answer depended on the list's internal + // representation, which no program can see. A needle whose proven + // type cannot match any element (a string against a number list) + // still folds to constant false; a Dyn needle still rejects. let (fn_name, needle) = match list_ty { Ty::ListI64 | Ty::ListF64 | Ty::ListStr => { let (nv, nty) = read_scalar(ssa, insts, instr.b(), block, pc)?; @@ -1287,6 +2220,8 @@ pub(super) fn lower( (Ty::ListI64, Ty::I64) => ("i64_contains", nv), (Ty::ListF64, Ty::F64) => ("f64_contains", nv), (Ty::ListStr, Ty::Str) => ("str_contains", nv), + (Ty::ListI64, Ty::F64) => ("i64_contains_f64", nv), + (Ty::ListF64, Ty::I64) => ("f64_contains_i64", nv), (_, Ty::Dyn) => return Err(Unsupported::TypeMismatch { pc }), _ => { let dst = ssa.new_val(); @@ -1351,9 +2286,292 @@ pub(super) fn lower( }); current = next; } + // `{ k: v, ..rest }` builds `rest` fresh, so it is an ordinary map + // whatever the source was — which is also why a map pattern's + // refusal to match a struct is the only thing keeping a struct out. + if map_ty == Ty::MapStrDyn { + ssa.set_plain_map(current); + } ssa.write(instr.a(), block, (current, map_ty)); } op => return Err(Unsupported::Opcode { pc, op }), } Ok(()) } + +/// The container literal whose carrier a store into `handle` contradicts. +/// +/// `None` when this function built no literal whose carrier is a judgement — +/// then the store is simply unsupported and the caller says so. +/// +/// The handle itself when its provenance is known; otherwise a handle read +/// through an unsealed loop phi has none yet, so the pending literals *of the +/// receiver's own shape* are marked (only those can be the contradicted one; a +/// correctly typed `ListStr` elsewhere in the function must keep its typed +/// lowering — `join` and friends have no Dyn arm). If shape-filtering leaves +/// nothing, over-mark all: that costs typed-ness, never correctness. +/// The same demand, for a store whose receiver may be a bare *parameter*. +/// +/// The literal to rebuild is in this function when the receiver traces to one. +/// A parameter has none: the container belongs to the caller, and the caller's +/// other aliases read the same allocation by its static type, so the carrier +/// has to be decided at the caller's literal (`SigInfer::dyn_params`). +/// Which rejection to report when a container write's value could not be read +/// *and* the container's carrier looks contradicted. +/// +/// The carrier answer stands in for a **type** failure only. An +/// `UndefinedOperand` is not one: it is a *discovery*, and the fixpoint keys a +/// `try` region's write-back cells on that exact variant +/// (`try_body_extra_cells`). Substituting the carrier rejection for it is how a +/// region's write reached nobody — +/// +/// ```lk +/// try { try { b = clo(); } catch c1 { } } catch c2 { } +/// acc.push(b); +/// ``` +/// +/// printed `b`'s value from *before* the region, natively, with no fallback and +/// no warning, while `let t = b; acc.push(t);` — the same program with a `Move` +/// in the way — was correct. A `ReferenceAsValue` is the same kind of thing: it +/// names a register the caller can be asked about, not a type that is wrong. +/// A nullable value on its way *into* a typed container, which cannot hold one. +/// +/// The store paths read their value through `read_scalar`/`read_typed_scalar`, +/// which narrows a carrier by asserting it is present. That is right where a +/// number is required and wrong here: the VM's list and map hold nil, so +/// `out.push(xs[i])` past the end of `xs` appends nil there — while the +/// compiled program asserted, found the value absent, and raised. Nothing +/// static caught it, because a `Maybe` narrows to `Int` and `Int` is +/// exactly what the carrier wants. +/// +/// So the *carrier* is what is wrong: a container that receives a nullable +/// value has to be a Dyn one. Reported as a contradiction of the literal it was +/// built from, which is the fixpoint's existing way of rebuilding it — the same +/// answer a push of a genuinely unboxable type already gets. +fn nullable_into_typed_carrier( + ssa: &Ssa, + func: &FunctionData, + value_reg: u8, + block: usize, + receiver_reg: u8, + handle: ValueId, + carrier: Ty, +) -> Option { + let ty = ssa.peek(value_reg, block).map(|(_, ty)| ty)?; + // A *boxed* value is the same situation as a nullable one and was not + // treated as it: unboxing it into the carrier is a guess that raises at run + // time, where widening the carrier answers. An empty `{}` guesses + // `str -> i64`, so + // + // fn s(v: Any) -> Int { let m = {}; m["k"] = v; return m.len(); } + // + // stored an `Int` and raised "runtime type error" for every other kind, + // while the interpreter stored all of them. The guess is meant to cost a + // widening — that is what this function is for — and the unbox spent it on + // a raise instead. + if !matches!(ty, Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool | Ty::Dyn) { + return None; + } + if ty == Ty::Dyn && carrier == Ty::Dyn { + return None; + } + carrier_contradicted_here_or_at_callers(ssa, func, receiver_reg, handle, carrier).or(Some( + Unsupported::OperandType { + pc: 0, + want: "a Dyn container, which is the only kind that holds this", + got: lk_aot_mir::ty_name(ty), + }, + )) +} + +fn keep_discovery(original: Unsupported, carrier: Option) -> Unsupported { + match original { + Unsupported::UndefinedOperand { .. } | Unsupported::ReferenceAsValue { .. } => original, + _ => carrier.unwrap_or(original), + } +} + +pub(crate) fn carrier_contradicted_here_or_at_callers( + ssa: &Ssa, + func: &FunctionData, + receiver_reg: u8, + handle: ValueId, + carrier: Ty, +) -> Option { + carrier_contradicted(ssa, handle, carrier).or_else(|| { + (u16::from(receiver_reg) < func.param_count) + .then_some(Unsupported::ParamCarrierContradicted { param: receiver_reg }) + }) +} + +pub(crate) fn carrier_contradicted(ssa: &Ssa, handle: ValueId, carrier: Ty) -> Option { + if ssa.literal_carrier.is_empty() { + return None; + } + let pcs = match ssa.literal_carrier.get(&handle) { + Some(&(pc0, _)) => vec![pc0], + None => { + let same_shape: Vec = ssa + .literal_carrier + .values() + .filter(|&&(_, gty)| gty == carrier) + .map(|&(p0, _)| p0) + .collect(); + if same_shape.is_empty() { + ssa.literal_carrier.values().map(|&(p0, _)| p0).collect() + } else { + same_shape + } + } + }; + Some(Unsupported::LiteralElemTypeContradicted { pcs }) +} + +/// Where a declared struct keeps this field, when the receiver is one. +fn struct_field_position(ssa: &Ssa, sig: &SigInfer, handle: ValueId, field: &str) -> Option { + let name = ssa.struct_name(handle)?; + sig.traits + .struct_field_index + .get(&(name.to_string(), field.to_string())) + .copied() +} + +/// Emits the declared-field check for a store the value's own type does not +/// already settle. +/// +/// The declared code is a compile-time constant, so the runtime side is a tag +/// compare (`lkrt_check_declared_field`) with no table lookup. A statically +/// satisfying store emits nothing at all — which is every field of an ordinary +/// `P { x: 1, y: 2 }`. +#[allow(clippy::too_many_arguments)] +fn emit_declared_field_check( + ssa: &mut Ssa, + insts: &mut Vec, + globals: &mut Vec, + sig: &SigInfer, + type_name: &str, + field: &str, + value_ty: Ty, + boxed: ValueId, +) { + let Some(&declared) = sig + .traits + .struct_field_codes + .get(&(type_name.to_string(), field.to_string())) + else { + return; + }; + if declared == DECLARED_ANY || statically_satisfies(declared, value_ty) { + return; + } + let type_v = materialize_key(ssa, insts, globals, type_name); + let field_v = materialize_key(ssa, insts, globals, field); + let declared_v = ssa.new_val(); + insts.push(Inst::Const { + dst: declared_v, + value: Const::I64(declared), + }); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("obj_ty", "check"), + args: vec![type_v, field_v, declared_v, boxed], + }); +} + +/// Whether a value of this MIR type always satisfies the declared code. +fn statically_satisfies(declared: i64, ty: Ty) -> bool { + use crate::trait_env::{DECLARED_BOOL, DECLARED_FLOAT, DECLARED_INT, DECLARED_NULLABLE, DECLARED_STR}; + match declared & !DECLARED_NULLABLE { + DECLARED_INT => ty == Ty::I64, + DECLARED_FLOAT => matches!(ty, Ty::I64 | Ty::F64), + DECLARED_BOOL => ty == Ty::Bool, + DECLARED_STR => ty == Ty::Str, + _ => true, + } +} + +/// The declared-field check for a *store* into a map that may be a struct +/// instance. +/// +/// Statically decided when the receiver's struct type is known — the common +/// case, and then a satisfying value emits nothing at all. Otherwise the mark +/// decides at run time, which is one table lookup on a path that had none of +/// this before and no guarantee either. +#[allow(clippy::too_many_arguments)] +fn emit_field_store_check( + ssa: &mut Ssa, + insts: &mut Vec, + globals: &mut Vec, + sig: &SigInfer, + handle: ValueId, + field: &str, + value_ty: Ty, + boxed: ValueId, +) { + if let Some(type_name) = ssa.struct_name(handle).map(str::to_string) { + emit_declared_field_check(ssa, insts, globals, sig, &type_name, field, value_ty, boxed); + return; + } + // A handle this function watched a *map literal* produce is not a struct + // instance — those come from `NewObject`, which records a struct type + // above. So there is nothing to check, and an ordinary `m[k] = v` loop + // pays nothing. + if ssa.literal_carrier.contains_key(&handle) { + return; + } + // Nor is there anything to check when *no declared struct has a field of + // this name with a type*: whatever this map is, this key cannot name a + // field a store could violate. A program with no structs emits none of + // this, and `m["count"] = v` only pays where some struct really declares + // `count`. + let constrained = sig + .traits + .struct_field_codes + .iter() + .any(|((_, name), code)| name == field && *code != DECLARED_ANY); + if !constrained { + return; + } + let field_v = materialize_key(ssa, insts, globals, field); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("obj_ty", "check_marked"), + args: vec![handle, field_v, boxed], + }); +} + +/// A constant scalar as the carrier stores it, unboxed. `None` for a constant +/// no typed carrier holds. +fn unboxed_const_scalar( + ssa: &mut Ssa, + insts: &mut Vec, + globals: &mut Vec, + value: &ConstRuntimeValueData, +) -> Option { + let dst = ssa.new_val(); + match value { + ConstRuntimeValueData::Int(v) => insts.push(Inst::Const { + dst, + value: Const::I64(*v), + }), + // A `bool` carrier stores its members as `i64`, which is why the + // `MapStrBool` shape shares the `str_i64` ABI. + ConstRuntimeValueData::Bool(v) => insts.push(Inst::Const { + dst, + value: Const::I64(i64::from(*v)), + }), + ConstRuntimeValueData::Float(v) => insts.push(Inst::Const { + dst, + value: Const::F64(*v), + }), + ConstRuntimeValueData::ShortStr(v) => return Some(materialize_key(ssa, insts, globals, v)), + ConstRuntimeValueData::Heap(heap) => match &**heap { + lk_core::vm::ConstHeapValueData::LongString(v) => { + return Some(materialize_key(ssa, insts, globals, v)); + } + _ => return None, + }, + _ => return None, + } + Some(dst) +} diff --git a/aot/lower/src/inst/control.rs b/aot/lower/src/inst/control.rs index ecec534b..7da3ded0 100644 --- a/aot/lower/src/inst/control.rs +++ b/aot/lower/src/inst/control.rs @@ -16,7 +16,7 @@ pub(super) fn lower( match instr.opcode() { Opcode::Raise => { // `bx` = the raised message string constant. The raise unwinds to - // the nearest native `try` frame (`try$call` — plan G); with no + // the nearest native `try` frame (plan G); with no // handler it aborts, exactly the VM's uncaught raise (the // differential harness treats VM exit-1 and a native SIGABRT as // matching failures). diff --git a/aot/lower/src/inst/global.rs b/aot/lower/src/inst/global.rs index ef887dad..648a8478 100644 --- a/aot/lower/src/inst/global.rs +++ b/aot/lower/src/inst/global.rs @@ -14,6 +14,8 @@ pub(super) fn lower( let sig = &mut *ctx.sig; let module_globals = ctx.module_globals; let capture_params = ctx.capture_params; + let ctx_func_index = ctx.func_index; + let ctx_param_count = ctx.func.param_count as usize; match instr.opcode() { Opcode::LoadCapture => { // `a` = dst, `bx` = capture index. Captures are cells: the loaded @@ -21,29 +23,51 @@ pub(super) fn lower( // trailing parameter (the cell's value at the call site). A direct // (non-cell) use of the register finds no SSA value and rejects. let k = instr.bx() as usize; + // A capture whose whole meaning is a callable reference. Checked + // before the bounds test because an all-static environment declares + // no parameters at all. + if let Some(callable) = sig.ref_captures.get(&(ctx_func_index, k)).cloned() { + ssa.bind_ref(block, instr.a(), callable); + return Ok(()); + } if k >= capture_params.len() { return Err(Unsupported::BadConst { pc }); } - ssa.builtin_regs.insert((block, instr.a()), GlobalRef::CellParam(k)); + ssa.bind_ref(block, instr.a(), GlobalRef::CellParam(k)); } Opcode::LoadCellVal => { // `a` = dst, `b` = cell register: reads the cell's current content. // The cell ref backtracks across blocks like any global ref; the // content read goes through the virtual slot (phis on demand). match ssa.builtin_ref_at(instr.b(), block) { + // The register already holds the callable (a ref capture): a + // cell read of it is the same reference. + Some(callable @ (GlobalRef::Lambda(_) | GlobalRef::UserFn(_))) => { + ssa.bind_ref(block, instr.a(), callable); + } Some(GlobalRef::CellParam(k)) => { let &(v, ty) = capture_params.get(k).ok_or(Unsupported::BadConst { pc })?; - // A runtime cell (a `try$call` boundary capture) reads + // A runtime cell (a nested-closure boundary capture) reads // through the shared slot; a spawned goroutine reads its // thread-private copy; by-value captures stay as-is. if ty == Ty::Cell { - let dst = ssa.new_val(); + let boxed = ssa.new_val(); insts.push(Inst::Call { - dst: Some(dst), + dst: Some(boxed), callee: AbiRef::new("rt", "cell_get"), args: vec![v], }); - ssa.write(instr.a(), block, (dst, Ty::Dyn)); + // A cell is dynamically typed, so a read answers `Dyn` + // — unless the frame that made this one wrote down what + // it holds, which a `try`-region cell input does. Then + // the read comes back as that type and the body's + // arithmetic on a captured variable lowers. + let content = ssa.cellparam_content.get(&k).copied().unwrap_or(Ty::Dyn); + let (value, ty) = match crate::unbox_cell_value(ssa, insts, boxed, content) { + Some(value) if content != Ty::Dyn => (value, content), + _ => (boxed, Ty::Dyn), + }; + ssa.write(instr.a(), block, (value, ty)); } else if ssa.spawned_isolate { let slot = ssa.cellparam_slot(k); let (sv, sty) = ssa.read_slot(slot, block, pc)?; @@ -53,20 +77,63 @@ pub(super) fn lower( } } Some(GlobalRef::Cell(cid)) => { + // A cell holding a lambda/closure gives the *reference* + // back: there is no runtime value to read. + if let Some(global_ref) = ssa.cell_refs.get(&cid).cloned() { + ssa.bind_ref(block, instr.a(), global_ref); + return Ok(()); + } let slot = ssa.cell_slot(cid); let (v, ty) = ssa.read_slot(slot, block, pc)?; ssa.write(instr.a(), block, (v, ty)); } - _ => return Err(Unsupported::Opcode { pc, op: instr.opcode() }), + _ => { + // No cell ref *here*. In a `try` body that is the ordinary + // case for a variable some closure captured: the ref lives + // in the enclosing function, and the read below reports the + // register by name so the region can pass its cell in + // (`cell_region_input`). A register that does have a plain + // definition is simply not a cell, and rejects. + ssa.read(instr.b(), block, pc)?; + return Err(Unsupported::Opcode { pc, op: instr.opcode() }); + } } } Opcode::StoreCellVal => { // `a` = cell register, `b` = value register: updates the tracked // cell content. A `CellParam` backed by a *runtime* cell (the - // `try$call` boundary) writes through the shared slot; a + // nested-closure boundary) writes through the shared slot; a // by-value capture parameter still rejects (no write-back path). match ssa.builtin_ref_at(instr.a(), block) { Some(GlobalRef::Cell(cid)) => { + // Storing a lambda/closure/function *reference* into the + // cell: there is no value to write, so the ref is recorded + // against the cell and every read of it gives the ref back. + // Only a *capture-free* callable. A `Closure(fidx, caps)` + // carries `ValueId`s from the function that built it, which + // name nothing in whoever reads the cell — recording one + // would hand the reader operands that do not exist. It + // refuses instead (the program falls back), and it refuses + // on purpose rather than by accident. + if let Some(stored) = ssa.builtin_ref_at(instr.b(), block) + && matches!(stored, GlobalRef::Lambda(_) | GlobalRef::UserFn(_)) + { + match ssa.cell_refs.get(&cid) { + // A cell that means two different things at two + // points is not something a single ref can answer. + Some(existing) if *existing != stored => { + return Err(Unsupported::Opcode { pc, op: instr.opcode() }); + } + _ => { + ssa.cell_refs.insert(cid, stored); + return Ok(()); + } + } + } + if ssa.cell_refs.contains_key(&cid) { + // Was a callable, now something else — same reason. + return Err(Unsupported::Opcode { pc, op: instr.opcode() }); + } let (v, ty) = ssa.read(instr.b(), block, pc)?; let slot = ssa.cell_slot(cid); ssa.write_slot(slot, block, (v, ty)); @@ -75,7 +142,27 @@ pub(super) fn lower( let &(cell, cty) = capture_params.get(k).ok_or(Unsupported::BadConst { pc })?; if cty == Ty::Cell { let (v, ty) = ssa.read(instr.b(), block, pc)?; - let boxed = to_dyn_any(ssa, insts, v, ty, pc)?; + // A store the reads would not agree with: the cell is + // one object, so the two ends cannot hold two opinions + // about it. Joining the entry to `Dyn` and retrying is + // the same discovery loop the rest of this file uses — + // the reads then come back boxed, as they always did. + let content = ssa.cellparam_content.get(&k).copied().unwrap_or(Ty::Dyn); + if content != Ty::Dyn && content != ty { + // Both spellings of the same agreement: a region + // input is keyed by the register its caller knows it + // by, a closure capture by its index. + match ssa.cellparam_reg(k) { + Some(reg) => { + sig.try_body_cell_input_tys.insert((ctx_func_index, reg), Ty::Dyn); + } + None => { + sig.cell_capture_tys.insert((ctx_func_index, k), Ty::Dyn); + } + } + return Err(Unsupported::TypeMismatch { pc }); + } + let boxed = to_dyn(ssa, insts, v, ty, pc)?; insts.push(Inst::Call { dst: None, callee: AbiRef::new("rt", "cell_set"), @@ -87,18 +174,33 @@ pub(super) fn lower( let (v, ty) = ssa.read(instr.b(), block, pc)?; let slot = ssa.cellparam_slot(k); ssa.write_slot(slot, block, (v, ty)); + } else if sig.require_cell_capture(ctx_func_index as usize, ctx_param_count, k) { + // First sight of an assignment to a by-value capture: + // record that this capture has to be a runtime cell and + // ask for a retry, so the caller seeds one. Same + // discovery loop as `dyn_rets`/`try_body_params` — the + // fact comes from the body actually lowering, not from + // guessing which register holds which capture. + return Err(Unsupported::TypeMismatch { pc }); } else { - // A by-value capture parameter has no write-back path. + // Already recorded and the parameter still came in by + // value: the caller cannot give this capture a cell + // (e.g. it is not a `MakeClosure` cell at all). return Err(Unsupported::Opcode { pc, op: instr.opcode() }); } } - _ => return Err(Unsupported::Opcode { pc, op: instr.opcode() }), + _ => { + // As `LoadCellVal`: the read names the register, which is + // how the enclosing function's cell becomes a region input. + ssa.read(instr.a(), block, pc)?; + return Err(Unsupported::Opcode { pc, op: instr.opcode() }); + } } } Opcode::SetGlobal => { // Storing a function value into the global table is the compiler's // top-level `fn` bookkeeping — a no-op natively. - if let Some(GlobalRef::UserFn) = ssa.builtin_regs.get(&(block, instr.a())) { + if let Some(GlobalRef::UserFn(_)) = ssa.builtin_regs.get(&(block, instr.a())) { return Ok(()); } // A top-level `let f = |x| …` stores a lambda ref: a no-op when the @@ -111,17 +213,8 @@ pub(super) fn lower( } return Err(Unsupported::Opcode { pc, op: instr.opcode() }); } - // Writing a global whose *name* this lowering recognizes would let - // later `GetGlobal` reads resolve to the stale builtin/module - // meaning and miscompile (`println = f; println(x)`), so those - // writes reject the program. let slot = instr.bx(); let name = module_globals.get(slot as usize).map(String::as_str); - if let Some(name) = name - && (builtin_for_name(name).is_some() || module_global(name)) - { - return Err(Unsupported::Opcode { pc, op: instr.opcode() }); - } // Mutable module global (a top-level `let` shared with functions). // Scalar slots stay typed when every write agrees; disagreeing or // non-scalar (but boxable) writes join the slot to `Dyn` — each @@ -129,6 +222,24 @@ pub(super) fn lower( let (v, ty) = ssa.read(instr.a(), block, pc)?; let obs = match ty { Ty::I64 | Ty::F64 | Ty::Bool | Ty::Str => ty, + // A container keeps its own type, and that is a correctness + // rule rather than an optimisation. + // + // Boxing one into a `Dyn` slot *re-represents* it — a + // `List` and a `List` are different memory, so + // `list_h.i64_to_dyn` builds a second container and the two + // stop being the same list. What that produced was a top-level + // `let xs = []` that functions pushed into and the top level + // read as empty: the global held the copy, the entry kept the + // original, and every backend printed a different number with + // no error anywhere. + // + // Keeping the type stores the handle, so there is one list. A + // slot two writes disagree about still falls to `Dyn` below, + // and that case *is* a copy — but it is also a slot that has + // held two different containers, where identity was already + // not a thing the program could rely on. + t if container_ty(t) => t, t if dyn_boxable_ty(t) => Ty::Dyn, _ => return Err(Unsupported::TypeMismatch { pc }), }; @@ -151,8 +262,45 @@ pub(super) fn lower( Some(Some(prev)) => *prev, None => return Err(Unsupported::Opcode { pc, op: instr.opcode() }), }; + // A container that ends up in a `Dyn` slot is the case above that + // cannot be saved, so it is refused rather than miscompiled. + // + // One shape reaches here for a reason that is not about the program: + // `let g = make();` where `make` returns a container. The signature + // fixpoint starts every return type at `I64`, so the *first* pass + // types the slot `I64`; the pass that learns the real type disagrees + // with it, and the map joins to `Dyn` and stays there — it is + // monotone on purpose, because a read lowered before the write would + // otherwise find the slot untyped. So this falls back today for a + // provisional guess rather than for anything the program does. + // + // The obvious fix does not work, and it is worth writing down which + // one. Making the entry *refuse* a call whose callee's return type + // is not yet known — safe-looking, since the entry cannot be + // recursive and the fixpoint runs again — recovers this shape and + // breaks another: `examples/syntax/defer.lk` began printing a list + // as empty, natively, with no fallback and no warning. An early + // pass that rejects is not a pass that did nothing. It is a pass + // that did not *observe* anything, and the parameter types the + // entry's calls would have contributed are missing from every pass + // after it. The fixpoint's passes are how facts are collected, not + // just attempts. + // + // The slot reaches `Dyn` two ways: two writes that disagree, and a + // reader that could observe the slot before it is written (only the + // `Dyn` carrier's zeroinit is nil). Either way the write has to box, + // boxing re-represents, and the writer's own register goes on + // referring to the container nobody else can see. That is a + // *silent* wrong answer — the program runs, prints a plausible + // number, and no check anywhere fires — which is worth a fallback. + if container_ty(ty) && slot_ty == Ty::Dyn { + return Err(Unsupported::ContainerGlobalBoxed { + pc, + name: name.unwrap_or("").to_string(), + }); + } let v = if slot_ty == Ty::Dyn && ty != Ty::Dyn { - to_dyn_any(ssa, insts, v, ty, pc)? + to_dyn(ssa, insts, v, ty, pc)? } else { v }; @@ -170,7 +318,14 @@ pub(super) fn lower( // zero — a read that could observe it must reject). let slot = instr.bx(); let name = module_globals.get(slot as usize).map(String::as_str); + // A slot the program writes is a user global, whatever it is + // called: `let time = [1]` shadows the stdlib module for the rest + // of the file, exactly as the import bindings below are already + // shadowed. Resolving by name regardless is what made a write to + // such a slot have to reject the whole program. + let shadowed = sig.shadowed_globals.get(slot as usize).copied().unwrap_or(false); let global_ref = match name { + _ if shadowed => None, Some(name) if let Some(builtin) = builtin_for_name(name) => Some(GlobalRef::Builtin(builtin)), // Two-level stdlib exports arrive as `module::member` global // names (`chan.close(c)` → `GetGlobal "chan::close"`). @@ -182,7 +337,7 @@ pub(super) fn lower( _ => None, }; if let Some(global_ref) = global_ref { - ssa.builtin_regs.insert((block, instr.a()), global_ref); + ssa.bind_ref(block, instr.a(), global_ref); return Ok(()); } // Import-derived bindings (aliases, `use {..} from`, bundled file @@ -193,7 +348,7 @@ pub(super) fn lower( { if let Some(module) = sig.imports.module_aliases.get(name) { let global_ref = GlobalRef::Module(module.clone()); - ssa.builtin_regs.insert((block, instr.a()), global_ref); + ssa.bind_ref(block, instr.a(), global_ref); return Ok(()); } if let Some((module, member)) = sig.imports.module_items.get(name) { @@ -204,16 +359,15 @@ pub(super) fn lower( } else { GlobalRef::ModuleFn(module.clone(), member.clone()) }; - ssa.builtin_regs.insert((block, instr.a()), global_ref); + ssa.bind_ref(block, instr.a(), global_ref); return Ok(()); } if let Some(&fidx) = sig.imports.file_items.get(name) { - ssa.builtin_regs.insert((block, instr.a()), GlobalRef::Lambda(fidx)); + ssa.bind_ref(block, instr.a(), GlobalRef::Lambda(fidx)); return Ok(()); } if let Some(&bundle) = sig.imports.file_namespaces.get(name) { - ssa.builtin_regs - .insert((block, instr.a()), GlobalRef::UserModule(bundle)); + ssa.bind_ref(block, instr.a(), GlobalRef::UserModule(bundle)); return Ok(()); } } @@ -221,7 +375,7 @@ pub(super) fn lower( // its function reference (initialization-order safe: the prescan // only accepts entry-prefix writes, which precede any user call). if let Some(fidx) = sig.lambda_globals.get(slot as usize).copied().flatten() { - ssa.builtin_regs.insert((block, instr.a()), GlobalRef::Lambda(fidx)); + ssa.bind_ref(block, instr.a(), GlobalRef::Lambda(fidx)); return Ok(()); } let initialized = sig.initialized_globals.get(slot as usize).copied().unwrap_or(false); @@ -253,6 +407,65 @@ pub(super) fn lower( Ok(()) } +/// Whether a value of this type is a handle to something that can be mutated. +/// +/// The distinction that matters for a global: a number, a bool or a string can +/// be copied into a slot and read back with nothing lost, while a container is a +/// *handle* and copying it into a differently-shaped slot makes a second +/// container. See the note at the `SetGlobal` arm. +/// +/// **Exhaustive on purpose.** This list decides two things at once — which +/// globals keep their own type, and which are *refused* when a slot joins to +/// `Dyn` — so a container missing from it is both boxed and not refused, which +/// is the definition of miscompiled. `Ty::Bytes`, `Ty::Set`, `Ty::MapStrDyn` +/// and `Ty::SliceI64` were missing, and the observable result was that +/// +/// ```lk +/// let b = "abc".bytes(); +/// fn f(n: Int) -> Int { return b[n] ?? -1; } +/// ``` +/// +/// printed `98` interpreted and died with `runtime type error` compiled — for +/// *any* index, including a constant one. The same program with `b` as a +/// parameter or a local was fine, and so were `List` and `String` globals, +/// which is why no example and no fuzz case ever showed it. +/// +/// Written as a `match` with no `_` arm so that a new `Ty` has to be classified +/// here rather than defaulting to "not a container". +pub(crate) fn container_ty(ty: Ty) -> bool { + match ty { + Ty::ListDyn + | Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::SliceI64 + | Ty::MapStrDyn + | Ty::MapStrI64 + | Ty::MapI64I64 + | Ty::MapStrF64 + | Ty::MapI64F64 + | Ty::MapStrBool + | Ty::Set + | Ty::Bytes => true, + // Scalars and the boxed carriers: copying one into a slot loses + // nothing, because there is no shared thing behind it. + Ty::I64 + | Ty::F64 + | Ty::Bool + | Ty::Str + | Ty::Nil + | Ty::MaybeI64 + | Ty::MaybeF64 + | Ty::MaybeStr + | Ty::MaybeBool + | Ty::Dyn + // A closure cell is a handle, but it never reaches a module global: a + // captured variable lives in the closure's environment, and `SetGlobal` + // of one is rejected before this by the `StoreCellVal` path. + | Ty::Cell => false, + } +} + /// The single table of global *names* this lowering gives a builtin meaning. /// /// `GetGlobal` resolves a read through it and `SetGlobal` rejects a write to @@ -260,7 +473,7 @@ pub(super) fn lower( /// lets through makes a later read resolve to the *builtin* meaning and ignore /// the rebinding. They had drifted: the write guard spelled out eight names /// while the read arm recognized twenty-one (`error`, `chan`, `send`, `recv`, -/// `spawn`, `try$call`, the `__lk_*` internals). +/// `spawn`, the `__lk_*` internals). /// /// Nothing reaches that gap today — the type checker rejects rebinding /// `chan`/`send`/`recv`/`spawn`/`println`/`Set`, and the `error`/`panic`/ @@ -268,13 +481,19 @@ pub(super) fn lower( /// latent divergence rather than a reproducible miscompile. Keeping one table /// is what makes the next `Builtin` addition safe by default. pub(crate) fn builtin_for_name(name: &str) -> Option { + // `cpu_*` is a rule, not a list: the LK name is `cpu_` followed by the + // entry's name under the ABI table's `cpu` module, and the table already + // knows which those are. Spelled out one arm per intrinsic, this was + // fourteen copies of that rule, and the fifteenth `cpu` entry would compile + // and link with no native meaning at all — the arm nobody remembered to + // add. The `&'static str` comes back out of the table rather than from + // `name`, which is also what gives the payload its lifetime. + if let Some(entry) = name.strip_prefix("cpu_") + && let Some(abi) = lk_aot_abi::find("cpu", entry) + { + return Some(Builtin::Cpu(abi.name)); + } Some(match name { - "cpu_barrier" => Builtin::Cpu("barrier", 0), - "cpu_compiler_barrier" => Builtin::Cpu("compiler_barrier", 0), - "cpu_irq_save" => Builtin::Cpu("irq_save", 0), - "cpu_irq_restore" => Builtin::Cpu("irq_restore", 1), - "cpu_wait_for_interrupt" => Builtin::Cpu("wait_for_interrupt", 0), - "cpu_timestamp" => Builtin::Cpu("timestamp", 0), "symbol_address" => Builtin::SymbolAddress, "call_address_2" => Builtin::CallAddress2, "volatile_read_u8" => Builtin::VolatileRead(8), @@ -300,15 +519,21 @@ pub(crate) fn builtin_for_name(name: &str) -> Option { "typeof" => Builtin::Typeof, "__lk_call_method" => Builtin::CallMethod, "Set" => Builtin::SetCtor, - "try$call" => Builtin::TryCall, "error" => Builtin::ErrorRaise, "__lk_merge_fields" => Builtin::MergeFields, "__lk_make_struct" => Builtin::MakeStruct, "__lk_bit_and" => Builtin::BitAnd, "__lk_bit_or" => Builtin::BitOr, + "__lk_bit_xor" => Builtin::BitXor, "__lk_bit_not" => Builtin::BitNot, "__lk_shl" => Builtin::Shl, "__lk_shr" => Builtin::Shr, + "__lk_shr_u" => Builtin::ShrU, + "__lk_lt_u" => Builtin::LtU, + "__lk_div_u" => Builtin::DivU, + "__lk_mod_u" => Builtin::ModU, + "__lk_u64_to_float" => Builtin::U64ToFloat, + "__lk_u64_str" => Builtin::U64Str, "chan" => Builtin::ChanNew, "send" => Builtin::ChanSend, "recv" => Builtin::ChanRecv, diff --git a/aot/lower/src/inst/mod.rs b/aot/lower/src/inst/mod.rs index 15e8987a..0ff4d04a 100644 --- a/aot/lower/src/inst/mod.rs +++ b/aot/lower/src/inst/mod.rs @@ -13,9 +13,9 @@ use crate::*; mod call; -mod container; +pub(crate) mod container; mod control; -mod global; +pub(crate) mod global; mod scalar; mod string; @@ -35,6 +35,9 @@ pub(crate) struct LowerCtx<'a> { pub(crate) sig: &'a mut SigInfer, /// The function being lowered (constant pools, performance facts). pub(crate) func: &'a FunctionData, + /// Its index in `funcs` — what a fact recorded *about this function* is + /// keyed by (see [`SigInfer::cell_captures`]). + pub(crate) func_index: u32, /// Every function in the module (call targets, capture counts). pub(crate) funcs: &'a [FunctionData], /// The module's entry function index. @@ -66,19 +69,23 @@ pub(crate) fn lower_inst( match instr.opcode() { LoadInt | LoadFloat | LoadBool | LoadNil | Move | Move2 | IsNil | IsList | IsMap | Not | AddInt | SubInt | MulInt | DivInt | ModInt | MidInt | MinInt | MaxInt | AddMulInt | Add2Int | AddListInt | SubListInt - | AddIntI | MulIntI | ModIntI | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat | CmpInt | CmpNeInt - | CmpLtInt | CmpLeInt | CmpGtInt | CmpGeInt | CastTo => scalar::lower(ctx, block, insts, instr, pc), + | AddIntI | MulIntI | ModIntI | Neg | FloorDivInt | AddFloat | SubFloat | MulFloat | DivFloat | ModFloat + | CmpInt | CmpNeInt | CmpLtInt | CmpLeInt | CmpGtInt | CmpGeInt | CastTo => { + scalar::lower(ctx, block, insts, instr, pc) + } LoadString | ToString | ConcatString | ConcatN | ListJoin | StringSplit => { string::lower(ctx, block, insts, instr, pc) } - CallMethodK | CallDirect | LoadFunction | MakeClosure | Call => call::lower(ctx, block, insts, instr, pc), + CallMethodK | CallDirect | CallNamed | LoadFunction | MakeClosure | Call => { + call::lower(ctx, block, insts, instr, pc) + } LoadCapture | LoadCellVal | StoreCellVal | SetGlobal | GetGlobal => global::lower(ctx, block, insts, instr, pc), - NewList | GetIndexStrI | SetIndexStrI | LoadHeapConst | Len | SliceFrom | NewRange | ToIter | NewObject - | ListPush | GetList | GetIndex | SetIndex | GetFieldK | SetFieldK | Contains | MapRest => { + NewList | NewMap | GetIndexStrI | SetIndexStrI | LoadHeapConst | Len | SliceFrom | NewRange | ToIter + | NewObject | ListPush | GetList | GetIndex | SetIndex | GetFieldK | SetFieldK | Contains | MapRest => { container::lower(ctx, block, insts, instr, pc) } diff --git a/aot/lower/src/inst/scalar.rs b/aot/lower/src/inst/scalar.rs index d17d32ef..5ae2ad44 100644 --- a/aot/lower/src/inst/scalar.rs +++ b/aot/lower/src/inst/scalar.rs @@ -12,6 +12,7 @@ pub(super) fn lower( pc: usize, ) -> Result<(), Unsupported> { let ssa = &mut *ctx.ssa; + let globals = &mut *ctx.globals; let func = ctx.func; match instr.opcode() { Opcode::LoadInt => { @@ -68,7 +69,7 @@ pub(super) fn lower( // would shadow the ref at its consumers (e.g. a recycled // register's stale definition burying a `println` ref). let dual_view = matches!(global_ref, GlobalRef::ArgList(_)); - ssa.builtin_regs.insert((block, instr.a()), global_ref); + ssa.bind_ref(block, instr.a(), global_ref); if dual_view && let Some(src) = ssa.current_def[block][instr.b() as usize] { ssa.write(instr.a(), block, src); } @@ -81,13 +82,13 @@ pub(super) fn lower( // Fused adjacent moves: `a ← b`, then `b ← c`. The VM reads `b` // before overwriting it; SSA reads naturally see the old value. if let Some(global_ref) = ssa.builtin_ref_at(instr.b(), block) { - ssa.builtin_regs.insert((block, instr.a()), global_ref); + ssa.bind_ref(block, instr.a(), global_ref); } else { let first = ssa.read(instr.b(), block, pc)?; ssa.write(instr.a(), block, first); } if let Some(global_ref) = ssa.builtin_ref_at(instr.c(), block) { - ssa.builtin_regs.insert((block, instr.b()), global_ref); + ssa.bind_ref(block, instr.b(), global_ref); } else { let second = ssa.read(instr.c(), block, pc)?; ssa.write(instr.b(), block, second); @@ -104,7 +105,28 @@ pub(super) fn lower( dst, value: Const::Bool(true), }), - Ty::I64 | Ty::F64 | Ty::Bool | Ty::Str => insts.push(Inst::Const { + // A container handle is never nil either, and leaving it out is + // what kept `?.` off the native path entirely: the operator + // lowers to `IsNil` on its receiver, so `m?.k` on a plain map + // and `p?.field` on a struct both refused — every field form of + // the operator, including the ones that cannot be nil at all. + Ty::I64 + | Ty::F64 + | Ty::Bool + | Ty::Str + | Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::ListDyn + | Ty::MapStrI64 + | Ty::MapStrF64 + | Ty::MapStrBool + | Ty::MapStrDyn + | Ty::MapI64I64 + | Ty::MapI64F64 + | Ty::Set + | Ty::Bytes + | Ty::SliceI64 => insts.push(Inst::Const { dst, value: Const::Bool(false), }), @@ -150,32 +172,22 @@ pub(super) fn lower( // nil) is not. Const-folds to a `Bool`, mirroring the VM's // `runtime_value_is_list`. let (v, ty) = ssa.read(instr.b(), block, pc)?; - // A boxed Dyn is list-ness only at runtime: test its tag (5 = - // DYN_LIST). Everything else const-folds. + // A boxed Dyn is list-ness only at runtime, and it is not one tag: + // `rt.is_list` answers for every representation, including the + // `String` the interpreter also calls list-like. if ty == Ty::Dyn { - let tag = ssa.new_val(); + let dst = ssa.new_val(); insts.push(Inst::Call { - dst: Some(tag), - callee: AbiRef::new("dyn", "tag"), + dst: Some(dst), + callee: AbiRef::new("dyn", "is_list"), args: vec![v], }); - let want = ssa.new_val(); - insts.push(Inst::Const { - dst: want, - value: Const::I64(5), - }); - let dst = ssa.new_val(); - insts.push(Inst::Cmp { - dst, - op: CmpOp::Eq, - float: false, - lhs: tag, - rhs: want, - }); ssa.write(instr.a(), block, (dst, Ty::Bool)); return Ok(()); } - let is_list = matches!(ty, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn); + // `Str` included for the same reason: `runtime_value_is_list` says + // true for one, and a `let [a, b] = "ab"` relies on it. + let is_list = matches!(ty, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::Str); let dst = ssa.new_val(); insts.push(Inst::Const { dst, @@ -189,32 +201,24 @@ pub(super) fn lower( // to a `Bool`, mirroring the VM's `runtime_value_is_map`. let (v, ty) = ssa.read(instr.b(), block, pc)?; if ty == Ty::Dyn { - let tag = ssa.new_val(); + let dst = ssa.new_val(); insts.push(Inst::Call { - dst: Some(tag), - callee: AbiRef::new("dyn", "tag"), + dst: Some(dst), + callee: AbiRef::new("dyn", "is_map"), args: vec![v], }); - let want = ssa.new_val(); - insts.push(Inst::Const { - dst: want, - value: Const::I64(6), - }); - let dst = ssa.new_val(); - insts.push(Inst::Cmp { - dst, - op: CmpOp::Eq, - float: false, - lhs: tag, - rhs: want, - }); ssa.write(instr.a(), block, (dst, Ty::Bool)); return Ok(()); } + // A struct instance rides the `Map` carrier and is not a + // map — the interpreter's `runtime_value_is_map` is + // `HeapValue::Map` alone. When the lowering knows the value is a + // struct it folds to `false`; when it does not, the runtime asks + // the arena type mark (`dyn.is_map` above). let is_map = matches!( ty, Ty::MapStrI64 | Ty::MapI64I64 | Ty::MapStrF64 | Ty::MapI64F64 | Ty::MapStrBool | Ty::MapStrDyn - ); + ) && !ssa.struct_name(v).is_some(); let dst = ssa.new_val(); insts.push(Inst::Const { dst, @@ -282,6 +286,34 @@ pub(super) fn lower( // the same source conversion the VM's // `cast_source_to_i64` performs. Ty::F64 => { + // A narrow target saturates to *its* range, so the + // conversion has to know the width. One shared + // implementation (`math.f64_to_machine_int`), the + // VM mirroring it: truncating to `i64` and then + // masking gave `-1` for `1 / 0` at `i32`. + if let Some(bits) = integer.int_kind().and_then(|kind| kind.bits()) + && bits < 64 + { + let signed = integer.int_kind().expect("machine target").is_signed(); + let bits_v = ssa.new_val(); + insts.push(Inst::Const { + dst: bits_v, + value: Const::I64(i64::from(bits)), + }); + let signed_v = ssa.new_val(); + insts.push(Inst::Const { + dst: signed_v, + value: Const::I64(i64::from(signed)), + }); + let narrowed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(narrowed), + callee: AbiRef::new("math", "f64_to_machine_int"), + args: vec![v, bits_v, signed_v], + }); + ssa.write(instr.a(), block, (narrowed, Ty::I64)); + return Ok(()); + } let truncated = ssa.new_val(); insts.push(Inst::FloatToInt { dst: truncated, src: v }); truncated @@ -320,6 +352,124 @@ pub(super) fn lower( } } } + // `A = floor(B / C)` on two `Int`s — the fused `math.floor(a / b)`. + // + // Floor, not truncation, so the truncating quotient is corrected by one + // when the operands' signs differ and the division was not exact. + // `(a ^ b) < 0` is that sign test. + Opcode::FloorDivInt => { + let lhs = read_typed_scalar(ssa, insts, instr.b(), block, Ty::I64, pc)?; + let rhs = read_typed_scalar(ssa, insts, instr.c(), block, Ty::I64, pc)?; + let quotient = ssa.new_val(); + insts.push(Inst::IntBin { + dst: quotient, + op: IntBinOp::Div, + lhs, + rhs, + }); + let remainder = ssa.new_val(); + insts.push(Inst::IntBin { + dst: remainder, + op: IntBinOp::Mod, + lhs, + rhs, + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let inexact = ssa.new_val(); + insts.push(Inst::Cmp { + dst: inexact, + op: CmpOp::Ne, + float: false, + lhs: remainder, + rhs: zero, + }); + let signs = ssa.new_val(); + insts.push(Inst::IntBin { + dst: signs, + op: IntBinOp::Xor, + lhs, + rhs, + }); + let opposite = ssa.new_val(); + insts.push(Inst::Cmp { + dst: opposite, + op: CmpOp::Lt, + float: false, + lhs: signs, + rhs: zero, + }); + let adjust = ssa.new_val(); + insts.push(Inst::BoolAnd { + dst: adjust, + lhs: inexact, + rhs: opposite, + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + let lowered = ssa.new_val(); + insts.push(Inst::IntBin { + dst: lowered, + op: IntBinOp::Sub, + lhs: quotient, + rhs: one, + }); + let dst = ssa.new_val(); + insts.push(Inst::Select { + dst, + cond: adjust, + then_v: lowered, + else_v: quotient, + ty: Ty::I64, + }); + ssa.write(instr.a(), block, (dst, Ty::I64)); + } + Opcode::Neg => { + // `-x`: `a` = dst, `b` = src. Integers negate as `0 - x` (exact, + // and it wraps at `i64::MIN` exactly as the VM's `wrapping_neg` + // does); floats need a real `fneg`, because `0.0 - 0.0` is `+0.0` + // where `-(0.0)` is `-0.0`. A boxed operand falls back — there is + // no `dyn.neg` in the ABI yet. + let (v, ty) = ssa.read(instr.b(), block, pc)?; + let dst = ssa.new_val(); + match ty { + Ty::I64 => { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + insts.push(Inst::IntBin { + dst, + op: IntBinOp::Sub, + lhs: zero, + rhs: v, + }); + ssa.write(instr.a(), block, (dst, Ty::I64)); + } + Ty::F64 => { + insts.push(Inst::FloatNeg { dst, src: v }); + ssa.write(instr.a(), block, (dst, Ty::F64)); + } + // A boxed operand dispatches at runtime, the same way `Not` + // does: Int and Float negate, anything else raises. + Ty::Dyn => { + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "neg"), + args: vec![v], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + } + _ => return Err(Unsupported::TypeMismatch { pc }), + } + } Opcode::Not => { // `!x`: `a` = dst, `b` = src. The VM negates a `Bool` and treats `Nil` as // `true`; a non-bool/non-nil operand is a VM error, so reject (fall back). @@ -366,97 +516,331 @@ pub(super) fn lower( // A Dyn operand routes both sides through the `dyn.*` helpers, // which carry the same promotion rules at runtime (`/` always // Float, type errors abort like the VM). Result stays `Ty::Dyn`. + // + // The raw reads stay in scope past the arms below: the nullable + // handling further down needs both operands' *declared* types to + // build the sentence the interpreter would have raised. + let (lv_raw, lty_raw) = ssa.read(instr.b(), block, pc)?; + let (rv_raw, rty_raw) = ssa.read(instr.c(), block, pc)?; + // `Str + Dyn`: the VM only accepts Str + Str here (anything + // else is a loud error), so unbox the Dyn side through the + // `as_str` tag guard (same loud failure) and emit a *typed* + // concat — the result stays `Str`, keeping a loop + // accumulator (`acc += s[i]`) same-typed through its phi. + // `Str + Dyn`: ask the runtime, which is where the VM's rule + // lives (`dyn.add` mirrors `Executor::dynamic_add`). This used + // to unbox the Dyn side with `as_str` — a *raise* unless it + // happened to hold a string — on the belief that the VM "only + // accepts Str + Str here". It does not: `"v=" + x` with a boxed + // Int is `v=1`, and `"p=" + xs` with a boxed list is the list + // `["p=", 1, 2]`, because a list operand outranks a string one. + // The old arm aborted both. + // + // The result is `Dyn` rather than `Str` for the same reason: a + // list operand makes it a list. A loop accumulator stays + // same-typed through its phi either way, since both sides of + // the phi come out of this arm. + // A nullable operand joins this arm for the same reason it + // joins the equality one: absent *is* nil, and the VM renders + // nil as `nil` here rather than refusing. `"[" + xs[9] + "]"` + // is `[nil]` on the interpreter and raised compiled. + let nullable_operand = |ty| matches!(ty, Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool); + if op == Opcode::AddInt + && (matches!((lty_raw, rty_raw), (Ty::Str, Ty::Dyn) | (Ty::Dyn, Ty::Str)) + || (lty_raw == Ty::Str && nullable_operand(rty_raw)) + || (nullable_operand(lty_raw) && rty_raw == Ty::Str)) { - let (lv_raw, lty_raw) = ssa.read(instr.b(), block, pc)?; - let (rv_raw, rty_raw) = ssa.read(instr.c(), block, pc)?; - // `Str + Dyn`: the VM only accepts Str + Str here (anything - // else is a loud error), so unbox the Dyn side through the - // `as_str` tag guard (same loud failure) and emit a *typed* - // concat — the result stays `Str`, keeping a loop - // accumulator (`acc += s[i]`) same-typed through its phi. - if op == Opcode::AddInt && matches!((lty_raw, rty_raw), (Ty::Str, Ty::Dyn) | (Ty::Dyn, Ty::Str)) { - let unbox = |ssa: &mut Ssa, insts: &mut Vec, v: ValueId, ty: Ty| { - if ty == Ty::Dyn { - let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("dyn", "as_str"), - args: vec![v], - }); - dst - } else { - v - } - }; - let lhs = unbox(ssa, insts, lv_raw, lty_raw); - let rhs = unbox(ssa, insts, rv_raw, rty_raw); - let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("str", "concat"), - args: vec![lhs, rhs], - }); - ssa.write(instr.a(), block, (dst, Ty::Str)); - return Ok(()); + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "add"), + args: vec![lhs, rhs], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } + // `Str + scalar` / `scalar + Str`: display-concatenate, the + // VM's fourth `dynamic_add` case. Statically known on both + // sides, so it needs no runtime dispatch — and it had no arm at + // all, which took `println(1 + "ab")` down with it. + if op == Opcode::AddInt + && matches!((lty_raw, rty_raw), (Ty::Str, _) | (_, Ty::Str)) + && matches!(lty_raw, Ty::Str | Ty::I64 | Ty::F64 | Ty::Bool | Ty::Nil) + && matches!(rty_raw, Ty::Str | Ty::I64 | Ty::F64 | Ty::Bool | Ty::Nil) + && (lty_raw, rty_raw) != (Ty::Str, Ty::Str) + { + let (l, l_fresh) = to_display_str(ssa, insts, globals, lv_raw, lty_raw, false, pc)?; + let dst = concat_display(ssa, insts, globals, l, rv_raw, rty_raw, false, pc)?; + if l_fresh { + free_owned_str(insts, l); } - // `list + list` concatenates into a fresh list (the VM's - // AddInt dispatch; the `[a, ..spread, b]` literal desugars to - // an `+` chain). Same-typed operands keep the typed carrier — - // display stays typed-exact (a `List` result still - // quotes) — while a Dyn/mixed side chains boxed (the VM's - // Mixed result displays bare, matching `dyn_chain`). - let is_list = |t: Ty| matches!(t, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn); - let list_chain = |lty: Ty, rty: Ty| match (lty, rty) { - _ if op != Opcode::AddInt => None, - (Ty::ListI64, Ty::ListI64) => Some(("i64_chain", Ty::ListI64)), - (Ty::ListF64, Ty::ListF64) => Some(("f64_chain", Ty::ListF64)), - (Ty::ListStr, Ty::ListStr) => Some(("str_chain", Ty::ListStr)), - // Cross-typed operands chain boxed — the VM's result is a - // Mixed list (bare-text display), exactly `dyn_chain`. - (l, r) if is_list(l) && is_list(r) => Some(("dyn_chain", Ty::ListDyn)), - _ => None, + ssa.write(instr.a(), block, (dst, Ty::Str)); + return Ok(()); + } + // `map + map` merges, the right side winning. Both operands + // box and the runtime does it, because the answer's key and + // value types are the two operands' widened — there is no + // typed carrier for "either of these" — and because the fill + // *sequence* is the contract (`lkrt_dyn_add` replays the VM's). + // + // Only string-keyed maps: the boxed map carrier is + // string-keyed, so an int-keyed merge has nowhere to land and + // keeps falling back rather than answering `{"3": 1}` where the + // VM answers `{3: 1}`. + let is_str_map = |t: Ty| matches!(t, Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn); + // `xs - ys` / `m - n` removes, and both go through the runtime + // for the reason the merge below does: the answer is built by + // filtering, in the left's own order. + let is_list = |t: Ty| matches!(t, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn); + // The *left* side decides, because that is how the VM dispatches + // `-`: a list on the left removes, whether the right is a list or a + // single value, and a map on the left removes keys the same way. + // Requiring both sides to be containers left the single-value forms + // with no lowering — they had no checker either, so nothing could + // reach them until now. + if op == Opcode::SubInt && (is_list(lty_raw) || is_str_map(lty_raw)) { + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "sub"), + args: vec![lhs, rhs], + }); + let (unbox, out_ty) = if is_list(lty_raw) { + ("as_list", Ty::ListDyn) + } else { + ("as_map", Ty::MapStrDyn) }; - if let Some((helper, out_ty)) = list_chain(lty_raw, rty_raw) { - let (lhs, rhs) = if out_ty == Ty::ListDyn { - ( - to_dyn_list_handle(ssa, insts, lv_raw, lty_raw, pc)?, - to_dyn_list_handle(ssa, insts, rv_raw, rty_raw, pc)?, - ) - } else { - (lv_raw, rv_raw) - }; - let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("list_h", helper), - args: vec![lhs, rhs], - }); - ssa.write(instr.a(), block, (dst, out_ty)); - return Ok(()); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", unbox), + args: vec![boxed], + }); + // `dyn.sub` returned a fresh container; removing keys from a + // struct instance yields a map, as it does in the interpreter. + if out_ty == Ty::MapStrDyn { + ssa.set_plain_map(dst); } - if lty_raw == Ty::Dyn || rty_raw == Ty::Dyn { - let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; - let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; - let helper = match op { - Opcode::AddInt => "add", - Opcode::SubInt => "sub", - Opcode::MulInt => "mul", - Opcode::DivInt => "div", - _ => "mod", - }; + ssa.write(instr.a(), block, (dst, out_ty)); + return Ok(()); + } + if op == Opcode::AddInt && is_str_map(lty_raw) && is_str_map(rty_raw) { + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "add"), + args: vec![lhs, rhs], + }); + // The answer is always a `str -> Dyn` map, so unbox to the + // typed handle rather than leaving it `Dyn` — every later + // read then stays on the typed path. + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "as_map"), + args: vec![boxed], + }); + // `dyn.add` on two maps built a fresh one; a fresh map is a + // map, whatever the operands were. + ssa.set_plain_map(dst); + ssa.write(instr.a(), block, (dst, Ty::MapStrDyn)); + return Ok(()); + } + // `list + list` concatenates into a fresh list (the VM's + // AddInt dispatch; the `[a, ..spread, b]` literal desugars to + // an `+` chain). Same-typed operands keep the typed carrier — + // display stays typed-exact (a `List` result still + // quotes) — while a Dyn/mixed side chains boxed (the VM's + // Mixed result displays bare, matching `dyn_chain`). + let is_list = |t: Ty| matches!(t, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn); + let list_chain = |lty: Ty, rty: Ty| match (lty, rty) { + _ if op != Opcode::AddInt => None, + (Ty::ListI64, Ty::ListI64) => Some(("i64_chain", Ty::ListI64)), + (Ty::ListF64, Ty::ListF64) => Some(("f64_chain", Ty::ListF64)), + (Ty::ListStr, Ty::ListStr) => Some(("str_chain", Ty::ListStr)), + // Cross-typed operands chain boxed — the VM's result is a + // Mixed list (bare-text display), exactly `dyn_chain`. + (l, r) if is_list(l) && is_list(r) => Some(("dyn_chain", Ty::ListDyn)), + _ => None, + }; + if let Some((helper, out_ty)) = list_chain(lty_raw, rty_raw) { + let (lhs, rhs) = if out_ty == Ty::ListDyn { + ( + to_dyn_list_handle(ssa, insts, lv_raw, lty_raw, pc)?, + to_dyn_list_handle(ssa, insts, rv_raw, rty_raw, pc)?, + ) + } else { + (lv_raw, rv_raw) + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", helper), + args: vec![lhs, rhs], + }); + ssa.write(instr.a(), block, (dst, out_ty)); + return Ok(()); + } + // `list + anything` and `anything + list`: the other operand joins + // the list, in position. This is the VM's rule and `lkrt_dyn_add` + // states it — "a list operand wins over a string one, so + // `"p=" + [1, 2]` is the list `["p=", 1, 2]` and not the text + // `p=[1,2]`" — and neither had a lowering, because the checker used + // to refuse the shape whenever it could see the types. It no longer + // does, so an accepted program that fell to the VM now stays here. + // + // The answer is always a list, so it unboxes to the typed handle + // the way the map merge above does, and every later read stays on + // the typed path. + if op == Opcode::AddInt && (is_list(lty_raw) != is_list(rty_raw)) { + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "add"), + args: vec![lhs, rhs], + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "as_list"), + args: vec![boxed], + }); + ssa.write(instr.a(), block, (dst, Ty::ListDyn)); + return Ok(()); + } + // A string on one side and a container on the other: the container + // renders the way `print` renders it. Reached only after the list + // and map arms above, so what is left is a `Set`, a byte string, a + // window, a struct or a callable — none of which had any meaning + // under `+` until the VM's concat started rendering them, the same + // correction interpolation took earlier. + // + // `dyn.add` already did this correctly for a boxed operand, so a + // program whose types were erased answered while the same program + // with known types refused to lower. + if op == Opcode::AddInt && ((lty_raw == Ty::Str) != (rty_raw == Ty::Str)) { + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "add"), + args: vec![lhs, rhs], + }); + // A string operand with no list in sight makes the answer a + // string, so it unboxes rather than staying `Dyn` — every later + // read then stays on the typed path. + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "as_str"), + args: vec![boxed], + }); + ssa.write(instr.a(), block, (dst, Ty::Str)); + return Ok(()); + } + // An int-keyed map has no boxed carrier to be rebuilt into, so + // `dyn.add`/`dyn.sub` would raise "map merge with a non-string key + // has no native carrier" — where the interpreter answers. The + // direct spellings already fall back; this is the same shape + // arriving boxed, which a `Maybe` key is enough to cause + // (`{1: 2} - "ab".bytes().first()`). Falling back answers it too, + // and answering wrongly is the only thing that must not happen. + if matches!(op, Opcode::AddInt | Opcode::SubInt) && matches!(lty_raw, Ty::MapI64I64 | Ty::MapI64F64) { + return Err(Unsupported::TypeMismatch { pc }); + } + if lty_raw == Ty::Dyn || rty_raw == Ty::Dyn { + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let helper = match op { + Opcode::AddInt => "add", + Opcode::SubInt => "sub", + Opcode::MulInt => "mul", + Opcode::DivInt => "div", + _ => "mod", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", helper), + args: vec![lhs, rhs], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } + // A nullable operand reads through the guard that carries the + // interpreter's own sentence, so `try { xs[9] + 1 } catch e { e }` + // is the same string on both backends. Both sides nullable is the + // one case a *static* sentence cannot get right — the VM names both + // operands, and whether the second one is absent is only known at + // run time — so that one boxes and asks `dyn.*`, which formats it + // from the values. + let nullable = |ty| matches!(ty, Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool); + if nullable(lty_raw) && nullable(rty_raw) { + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let helper = match op { + Opcode::AddInt => "add", + Opcode::SubInt => "sub", + Opcode::MulInt => "mul", + Opcode::DivInt => "div", + _ => "mod", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", helper), + args: vec![lhs, rhs], + }); + ssa.write(instr.a(), block, (dst, Ty::Dyn)); + return Ok(()); + } + let say = |absent_left: bool| { + let (l, r) = if absent_left { + ("Nil", language_type_name(rty_raw)) + } else { + (language_type_name(lty_raw), "Nil") + }; + arith_operand_message(op, l, r) + }; + let (lv, lty) = if nullable(lty_raw) { + read_scalar_saying(ssa, insts, globals, instr.b(), block, pc, &say(true))? + } else { + read_scalar(ssa, insts, instr.b(), block, pc)? + }; + let (rv, rty) = if nullable(rty_raw) { + read_scalar_saying(ssa, insts, globals, instr.c(), block, pc, &say(false))? + } else { + read_scalar(ssa, insts, instr.c(), block, pc)? + }; + match (lty, rty) { + // `/` yields a `Float` even for two `Int`s — the rule the + // checker, the constant folder and the `dyn` helpers above all + // state, and the one place that used to ignore it. Lowering it + // as `IntBin::Div` made a *native* `7 / 2` answer `3` where the + // VM answers `3.5`, and `1 / 0` abort where the VM says `inf`. + (Ty::I64, Ty::I64) if op == Opcode::DivInt => { + let lhs = ssa.new_val(); + insts.push(Inst::IntToFloat { dst: lhs, src: lv }); + let rhs = ssa.new_val(); + insts.push(Inst::IntToFloat { dst: rhs, src: rv }); let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("dyn", helper), - args: vec![lhs, rhs], + insts.push(Inst::FloatBin { + dst, + op: FloatBinOp::Div, + lhs, + rhs, }); - ssa.write(instr.a(), block, (dst, Ty::Dyn)); - return Ok(()); + ssa.write(instr.a(), block, (dst, Ty::F64)); } - } - let (lv, lty) = read_scalar(ssa, insts, instr.b(), block, pc)?; - let (rv, rty) = read_scalar(ssa, insts, instr.c(), block, pc)?; - match (lty, rty) { (Ty::I64, Ty::I64) => { let dst = ssa.new_val(); insts.push(Inst::IntBin { @@ -601,7 +985,33 @@ pub(super) fn lower( // through the `dyn.*` helpers, same as the Int family above. let (lv, lty) = read_scalar(ssa, insts, instr.b(), block, pc)?; let (rv, rty) = read_scalar(ssa, insts, instr.c(), block, pc)?; - if lty == Ty::Dyn || rty == Ty::Dyn { + // A `Str` operand is a *string* operation, not a float one: the + // compiler picks a float opcode from one operand's type without + // looking at the other, and `"" + (1.0 + 2.0)` folds its + // parenthesised half to a float constant. The interpreter answers + // that by falling back to its dynamic form, so this does too — the + // same `dyn.*` route the `Dyn` case below already takes. + // A container operand is the same situation one step further: the + // compiler picks the float opcode from *one* operand, so + // `[1.5, 2.5] - 1.5` arrives here as a `SubFloat` over a list and a + // float. The Int family routes those through `dyn.*`; without the + // same route the shape had no lowering at all, and it is the + // ordinary spelling of removing a float from a list of them. + let container = |t: Ty| { + matches!( + t, + Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::ListDyn + | Ty::MapStrI64 + | Ty::MapStrF64 + | Ty::MapStrBool + | Ty::MapStrDyn + ) + }; + if lty == Ty::Dyn || rty == Ty::Dyn || lty == Ty::Str || rty == Ty::Str || container(lty) || container(rty) + { let lhs = to_dyn(ssa, insts, lv, lty, pc)?; let rhs = to_dyn(ssa, insts, rv, rty, pc)?; let helper = match op { @@ -721,6 +1131,88 @@ pub(super) fn lower( } return Ok(()); } + // A nullable operand against a *non-nil* one. An absent carrier is + // nil, and nil equals nothing — so `xs[oob] == 4` is `false` and + // `!= 4` is `true`, which is what the VM answers. The scalar read + // below would instead assert the carrier present and raise, on a + // program the interpreter runs to completion. + // + // Only the equalities. An *ordered* compare against nil is an error + // in the VM too (`< expected Int, Float, or String, got Nil and + // Int`), so asserting presence there fails on the same programs. + let nullable = |ty| matches!(ty, Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool); + if (nullable(lty_raw) || nullable(rty_raw)) && matches!(cmp_op(op), CmpOp::Eq | CmpOp::Ne) { + let cop = cmp_op(op); + // The common shape — an integer element against an integer — + // stays typed: present *and* equal, one extra `and` rather than + // two boxes and a call. `xs[i] == k` in a search loop is this. + if let (Ty::MaybeI64, Ty::I64) | (Ty::I64, Ty::MaybeI64) = (lty_raw, rty_raw) { + let (carrier, carrier_ty, plain) = if nullable(lty_raw) { + (lv_raw, lty_raw, rv_raw) + } else { + (rv_raw, rty_raw, lv_raw) + }; + let present = ssa.new_val(); + insts.push(Inst::MaybePresent { + dst: present, + src: carrier, + maybe_ty: carrier_ty, + }); + let value = ssa.new_val(); + insts.push(Inst::MaybeValue { + dst: value, + src: carrier, + maybe_ty: carrier_ty, + }); + let same = ssa.new_val(); + insts.push(Inst::Cmp { + dst: same, + op: CmpOp::Eq, + float: false, + lhs: value, + rhs: plain, + }); + let equal = ssa.new_val(); + insts.push(Inst::BoolAnd { + dst: equal, + lhs: present, + rhs: same, + }); + if cop == CmpOp::Ne { + let dst = ssa.new_val(); + insts.push(Inst::Not { dst, src: equal }); + ssa.write(instr.a(), block, (dst, Ty::Bool)); + } else { + ssa.write(instr.a(), block, (equal, Ty::Bool)); + } + return Ok(()); + } + // Anything else boxes and asks the runtime, which is where the + // VM's equality rules live. + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("dyn", "eq"), + args: vec![lhs, rhs], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: if cop == CmpOp::Ne { CmpOp::Eq } else { CmpOp::Ne }, + float: false, + lhs: raw, + rhs: zero, + }); + ssa.write(instr.a(), block, (dst, Ty::Bool)); + return Ok(()); + } // A Dyn (or mixed-list) operand: box the other side and compare // through the `dyn.*` helpers (VM equality semantics live in // lkrt; ordered compares are numeric-only there, aborting like @@ -758,8 +1250,60 @@ pub(super) fn lower( ssa.write(instr.a(), block, (dst, Ty::Bool)); return Ok(()); } - let (lv, lty) = read_scalar(ssa, insts, instr.b(), block, pc)?; - let (rv, rty) = read_scalar(ssa, insts, instr.c(), block, pc)?; + // Only the *ordered* compares reach here with a nullable operand — + // the equalities were answered above — and an ordered compare + // against nil is an error in the interpreter too. Same treatment as + // arithmetic: carry the interpreter's own sentence, and let the + // both-nullable case be formatted from the values by `dyn.*`. + if nullable_cmp(lty_raw) && nullable_cmp(rty_raw) { + let lhs = to_dyn(ssa, insts, lv_raw, lty_raw, pc)?; + let rhs = to_dyn(ssa, insts, rv_raw, rty_raw, pc)?; + let helper = match cmp_op(op) { + CmpOp::Lt => "lt", + CmpOp::Le => "le", + CmpOp::Gt => "gt", + _ => "ge", + }; + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("dyn", helper), + args: vec![lhs, rhs], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + ssa.write(instr.a(), block, (dst, Ty::Bool)); + return Ok(()); + } + let ordered_say = |absent_left: bool| { + let (l, r) = if absent_left { + ("Nil", language_type_name(rty_raw)) + } else { + (language_type_name(lty_raw), "Nil") + }; + format!("{} expected Int, Float, or String, got {l} and {r}", compare_symbol(op)) + }; + let (lv, lty) = if nullable_cmp(lty_raw) { + read_scalar_saying(ssa, insts, globals, instr.b(), block, pc, &ordered_say(true))? + } else { + read_scalar(ssa, insts, instr.b(), block, pc)? + }; + let (rv, rty) = if nullable_cmp(rty_raw) { + read_scalar_saying(ssa, insts, globals, instr.c(), block, pc, &ordered_say(false))? + } else { + read_scalar(ssa, insts, instr.c(), block, pc)? + }; let (float, lhs, rhs) = match (lty, rty) { (Ty::I64, Ty::I64) => (false, lv, rv), // Bool equality (`b == true`): widen to i64 (the integer @@ -815,6 +1359,107 @@ pub(super) fn lower( }); (false, eq, one) } + // A `Set` is its member set: same size, every member present. + // Order-free, so nothing here depends on iteration order. + (Ty::Set, Ty::Set) => { + if !matches!(cmp_op(op), CmpOp::Eq | CmpOp::Ne) { + return Err(Unsupported::TypeMismatch { pc }); + } + let eq = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(eq), + callee: AbiRef::new("set", "eq"), + args: vec![lv, rv], + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + (false, eq, one) + } + // `Bytes` compares by content, the VM's rule (unlike a struct, + // which compared by handle until that was fixed). + (Ty::Bytes, Ty::Bytes) => { + if !matches!(cmp_op(op), CmpOp::Eq | CmpOp::Ne) { + return Err(Unsupported::TypeMismatch { pc }); + } + let eq = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(eq), + callee: AbiRef::new("bytes_h", "eq"), + args: vec![lv, rv], + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + (false, eq, one) + } + // Two string-keyed maps — and therefore two structs, which are + // marked maps. Both sides box to a `Dyn` map and `dyn.eq` + // decides: order-free, key-by-key, recursing with the VM's + // numeric coercion (`{"a": 1} == {"a": 1.0}`) and refusing + // across struct type marks (`P{x:1} != Q{x:1} != {"x":1}`). + // + // No map comparison lowered at all before this — not even + // `{"a": 1} == {"a": 1}` — while every list pairing did. + // + // Int-keyed maps stay out: there is no int-keyed `Dyn` map to + // normalize to, so they would need their own helper family + // rather than this one line. A fallback, not a wrong answer. + ( + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64, + ) => { + if !matches!(cmp_op(op), CmpOp::Eq | CmpOp::Ne) { + return Err(Unsupported::TypeMismatch { pc }); + } + // `to_dyn` of a `MapStrDyn` tags the handle in place, so a + // struct keeps its mark; a *typed* map's conversion + // rebuilds, and a typed map is never a struct. + let a = to_dyn(ssa, insts, lv, lty, pc)?; + let b = to_dyn(ssa, insts, rv, rty, pc)?; + let eq = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(eq), + callee: AbiRef::new("dyn", "eq"), + args: vec![a, b], + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + (false, eq, one) + } + // A window against a window or against a list: `xs.slice(0, 2) + // == [3, 1]` is true in the VM, because a window is a *range of + // a list* and not a distinct kind of value. Both sides box — + // `dyn.eq` knows the window tag and compares element-wise + // across it — rather than materializing the window, which would + // allocate a list to answer a question about one. + (Ty::SliceI64, Ty::SliceI64 | Ty::ListI64 | Ty::ListDyn) + | (Ty::ListI64 | Ty::ListDyn, Ty::SliceI64) => { + if !matches!(cmp_op(op), CmpOp::Eq | CmpOp::Ne) { + return Err(Unsupported::TypeMismatch { pc }); + } + let a = to_dyn(ssa, insts, lv, lty, pc)?; + let b = to_dyn(ssa, insts, rv, rty, pc)?; + let eq = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(eq), + callee: AbiRef::new("dyn", "eq"), + args: vec![a, b], + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + (false, eq, one) + } // A dyn list against any list: both sides normalize to dyn // lists and compare structurally (`dyn_eq` recurses with the // VM's numeric coercion). @@ -862,13 +1507,12 @@ pub(super) fn lower( return Ok(()); } (Ty::Str, Ty::Str) => { - // The VM only supports `==`/`!=` on strings (ordered comparisons - // are a runtime error), so reject the rest — falling back rather - // than computing an order the VM would refuse. - if !matches!(cmp_op(op), CmpOp::Eq | CmpOp::Ne) { - return Err(Unsupported::TypeMismatch { pc }); - } - // `str_cmp(a, b)` returns -1/0/1; comparing to 0 realizes `==`/`!=`. + // `str_cmp(a, b)` returns -1/0/1, so comparing it to 0 with the + // *same* operator realizes all six — `==`, `!=` and the four + // orderings alike. Only `==`/`!=` used to get here: the comment + // said "the VM only supports those on strings", which was never + // true (`Executor::number_compare` has always had a string arm) + // — it was the type checker that refused, and it no longer does. let cmp = ssa.new_val(); insts.push(Inst::Call { dst: Some(cmp), @@ -882,6 +1526,30 @@ pub(super) fn lower( }); (false, cmp, zero) } + // Two values of *different kinds* are never equal, and both + // kinds are known here — so the answer is a constant. + // + // Every arm above pairs a kind with itself (or Int with Float, + // which coerce). What was left was `1 == "a"`, `true == [1]`, + // `nil == 2.5` and the hundred-odd other cross-kind pairings — + // each a `false` the VM computes and the lowering refused, + // taking the whole program down with it. + // + // `eq_kind` returns `None` for anything whose kind is not + // static (`Dyn`, a `Maybe` carrier), and those must not fold: a + // `Maybe` is an Int *or* nil, which is two kinds. + (lk, rk) + if matches!(cmp_op(op), CmpOp::Eq | CmpOp::Ne) + && matches!((eq_kind(lk), eq_kind(rk)), (Some(a), Some(b)) if a != b) => + { + let dst = ssa.new_val(); + insts.push(Inst::Const { + dst, + value: Const::Bool(cmp_op(op) == CmpOp::Ne), + }); + ssa.write(instr.a(), block, (dst, Ty::Bool)); + return Ok(()); + } _ => return Err(Unsupported::TypeMismatch { pc }), }; let dst = ssa.new_val(); @@ -898,3 +1566,61 @@ pub(super) fn lower( } Ok(()) } + +/// The *kind* a value belongs to for equality: two values of different kinds +/// are never equal, whatever their contents. +/// +/// `Int` and `Float` share a kind because the VM coerces them (`1 == 1.0`), +/// and the four list representations share one because a list's element typing +/// is a storage detail, not part of its value — the same for the five map +/// carriers. `None` means the kind is not decidable at lower time: a `Dyn` is +/// whatever it is at runtime, and a `Maybe` is an Int *or* nil, which is +/// two kinds in one static type. +fn eq_kind(ty: Ty) -> Option { + Some(match ty { + Ty::Nil => 0, + Ty::Bool => 1, + Ty::I64 | Ty::F64 => 2, + Ty::Str => 3, + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::SliceI64 => 4, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 => 5, + Ty::Set => 6, + Ty::Bytes => 7, + _ => return None, + }) +} + +/// The sentence the interpreter raises when an arithmetic operand is the wrong +/// kind, for the operator `op` and the two operand type names. +/// +/// Three shapes, and they are the interpreter's own: `+` and `-` each name what +/// they accept (a list or map may be added or subtracted), and the rest share +/// the generic one. Probed against a running interpreter rather than read off +/// its source, operator by operator and side by side. +fn arith_operand_message(op: Opcode, lhs: &str, rhs: &str) -> String { + match op { + Opcode::AddInt => format!("Add expected numbers or strings, got {lhs} and {rhs}"), + Opcode::SubInt => format!("Sub expected numbers or list/map lhs, got {lhs} and {rhs}"), + Opcode::MulInt => format!("* expects Int or Float, got {lhs} and {rhs}"), + Opcode::DivInt => format!("/ expects Int or Float, got {lhs} and {rhs}"), + _ => format!("% expects Int or Float, got {lhs} and {rhs}"), + } +} + +/// Whether `ty` is a nullable carrier — the shape a bounds-checked read has. +fn nullable_cmp(ty: Ty) -> bool { + matches!(ty, Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool) +} + +/// The symbol a comparison opcode was written as. The interpreter names the +/// operator a program wrote, not the typed opcode the compiler chose. +fn compare_symbol(op: Opcode) -> &'static str { + match op { + Opcode::CmpLtInt => "<", + Opcode::CmpLeInt => "<=", + Opcode::CmpGtInt => ">", + Opcode::CmpGeInt => ">=", + Opcode::CmpNeInt => "!=", + _ => "==", + } +} diff --git a/aot/lower/src/inst/string.rs b/aot/lower/src/inst/string.rs index 195b56a1..7a841377 100644 --- a/aot/lower/src/inst/string.rs +++ b/aot/lower/src/inst/string.rs @@ -37,15 +37,21 @@ pub(super) fn lower( ssa.write(instr.a(), block, (dst, Ty::Str)); } Opcode::ToString => { - // `a` = dst, `b` = source. Display-convert to a `Str` (Str/Int/Bool - // supported; float/other fall back). + // `a` = dst, `b` = source. Display-convert to a `Str`. + // + // Containers included. `docs/semantics.md` used to rule that + // `ToString`/interpolation was a *scalar-only* path and a container + // there was a loud failure — the VM stopped doing that (`"${xs}"` + // is `[1,2,3]`, `"m=${m}"` is `m={"k":1}`), and this side kept + // mirroring the retired rule, so every template holding a list, + // map, set or struct dropped its module to the VM. let (v, ty) = ssa.read(instr.b(), block, pc)?; // Auto-Display (plan J1): a single-interpolation template string // (`"${point}"`) compiles to a bare `ToString`. let (v, ty) = apply_display_show(ssa, insts, funcs, entry, sig, v, ty, pc)?; // The result is register-visible, so it stays arena-owned (never // freed eagerly, reclaimed by `lkrt_cleanup` at exit). - let (s, _fresh) = to_display_str(ssa, insts, globals, v, ty, false, pc)?; + let (s, _fresh) = to_display_str(ssa, insts, globals, v, ty, true, pc)?; ssa.write(instr.a(), block, (s, Ty::Str)); } Opcode::ConcatString => { @@ -58,8 +64,8 @@ pub(super) fn lower( // registered `show` interpolates its result, like the VM. let (lv, lty) = apply_display_show(ssa, insts, funcs, entry, sig, lv, lty, pc)?; let (rv, rty) = apply_display_show(ssa, insts, funcs, entry, sig, rv, rty, pc)?; - let (l, l_fresh) = to_display_str(ssa, insts, globals, lv, lty, false, pc)?; - let dst = concat_display(ssa, insts, globals, l, rv, rty, false, pc)?; + let (l, l_fresh) = to_display_str(ssa, insts, globals, lv, lty, true, pc)?; + let dst = concat_display(ssa, insts, globals, l, rv, rty, true, pc)?; if l_fresh { free_owned_str(insts, l); } @@ -85,11 +91,11 @@ pub(super) fn lower( } else { let (v0, ty0) = ssa.read(start, block, pc)?; let (v0, ty0) = apply_display_show(ssa, insts, funcs, entry, sig, v0, ty0, pc)?; - let (mut acc, mut acc_fresh) = to_display_str(ssa, insts, globals, v0, ty0, false, pc)?; + let (mut acc, mut acc_fresh) = to_display_str(ssa, insts, globals, v0, ty0, true, pc)?; for i in 1..count { let (v, ty) = ssa.read(start.wrapping_add(i as u8), block, pc)?; let (v, ty) = apply_display_show(ssa, insts, funcs, entry, sig, v, ty, pc)?; - let dst = concat_display(ssa, insts, globals, acc, v, ty, false, pc)?; + let dst = concat_display(ssa, insts, globals, acc, v, ty, true, pc)?; // The consumed accumulator is dead; free it if this // lowering allocated it. if acc_fresh { @@ -103,17 +109,62 @@ pub(super) fn lower( ssa.write(instr.a(), block, (result, Ty::Str)); } Opcode::ListJoin => { - // `a` = dst, `b` = list, `c` = separator. The VM joins a *string* list; we - // support `List` with a `Str` separator → a fresh `Str`. + // `a` = dst, `b` = list, `c` = separator → a fresh `Str`. + // + // Only `ListStr` was accepted here, because the VM raised "list must + // contain only strings" for every other carrier: an arbitrary rule + // in one back end, faithfully reproduced as a second one in the + // other. The VM writes each element the way it writes it everywhere + // else now, and each helper below renders the way that carrier's + // `display` helper does — same renderer per carrier, which is what + // makes `2.0`, `-0.0` and `nan` come out identical on both ends. + // + // This is the *only* place `join` is lowered: the bytecode compiler + // matches the method by name alone (`call.rs`'s intrinsic table), so + // no `CallMethodK` named "join" can exist and the arm that sat in + // `lower_method.rs` beside `contains`/`index_of` was unreachable by + // construction. It was also what the old comment there reasoned + // about when it declined the numeric carriers — a decision argued + // from a branch that never ran. let (handle, list_ty) = ssa.read(instr.b(), block, pc)?; - if list_ty != Ty::ListStr { - return Err(Unsupported::TypeMismatch { pc }); - } + // `Bytes` and a window join their elements too — the same reading + // the arms above take, and the same one the VM takes now. They + // materialize first because their elements are not a list handle; + // one conversion, then the `i64` helper the values are. + // A boxed operand unboxes the same way, and reaches a helper that + // was already here: `dyn_join`. `join` being an opcode rather than a + // `CallMethodK` is why it did not — `METHOD_TABLE`'s `unbox_list` + // never sees it, so `fn show(xs) { return xs.join(", "); }`, which + // is how the method is usually written, refused while + // `["a"].join(", ")` lowered. `dyn.as_list` aborts on a non-list + // tag, the loud error the VM raises for the same call. + let (handle, list_ty) = if matches!(list_ty, Ty::Bytes | Ty::SliceI64 | Ty::Dyn) { + let list = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(list), + callee: match list_ty { + Ty::Bytes => AbiRef::new("bytes_h", "to_i64_list"), + Ty::Dyn => AbiRef::new("dyn", "as_list"), + _ => AbiRef::new("slice_h", "i64_to_list"), + }, + args: vec![handle], + }); + (list, if list_ty == Ty::Dyn { Ty::ListDyn } else { Ty::ListI64 }) + } else { + (handle, list_ty) + }; + let helper = match list_ty { + Ty::ListStr => "str_join", + Ty::ListI64 => "i64_join", + Ty::ListF64 => "f64_join", + Ty::ListDyn => "dyn_join", + _ => return Err(Unsupported::TypeMismatch { pc }), + }; let sep = ssa.read_typed(instr.c(), block, Ty::Str, pc)?; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "str_join"), + callee: AbiRef::new("list_h", helper), args: vec![handle, sep], }); ssa.write(instr.a(), block, (dst, Ty::Str)); diff --git a/aot/lower/src/lib.rs b/aot/lower/src/lib.rs index 2eaf735d..70204757 100644 --- a/aot/lower/src/lib.rs +++ b/aot/lower/src/lib.rs @@ -43,6 +43,7 @@ use lk_core::vm::{ ConstHeapValueData, ConstRuntimeValueData, FunctionData, Instr, ModuleArtifact, Opcode, RuntimeMapKeyData, }; +mod capture; mod cfg; mod convert; mod dyn_box; @@ -61,14 +62,16 @@ mod tables; #[cfg(test)] mod tests; mod trait_env; +mod try_region; mod unsupported; mod vocab; pub use self::imports::BundledImport; pub(crate) use self::imports::ImportEnv; +pub use self::tables::{module_abi_row_paths, named_parameter_rows}; pub use self::unsupported::Unsupported; pub(crate) use self::{ - cfg::*, convert::*, dyn_box::*, function::*, inst::*, lower_builtin::*, lower_call::*, lower_method::*, + capture::*, cfg::*, convert::*, dyn_box::*, function::*, inst::*, lower_builtin::*, lower_call::*, lower_method::*, lower_module::*, ops::*, prescan::*, sig::*, ssa::*, tables::*, trait_env::*, vocab::*, }; @@ -123,7 +126,23 @@ pub fn lower_bundled( // (their `LoadFunction` sites are skipped), invisible to the CallDirect/ // MakeClosure scan: root them like bundled imports. let traits = trait_env_prescan(module); - bundle_roots.extend(traits.impls.values().map(|&fidx| fidx as usize)); + // Impl methods are roots because trait dispatch reaches them through the + // registration table, invisible to the call scan — but only the ones whose + // *name* some call site actually uses. A method nobody names is not + // reachable by dispatch either, and rooting it meant lowering a body with + // no call site to type its parameters: they fell back to `I64`, the body + // read a field, and the whole module dropped to Tier 0 because of a method + // nobody calls. `CallMethodK` takes its name from the constant pool, so + // this set is exact. + let mut called_methods = called_method_names(module); + called_methods.extend(IMPLICIT_METHOD_HOOKS.iter().map(|name| (*name).to_string())); + bundle_roots.extend( + traits + .impls + .iter() + .filter(|((_, method), _)| called_methods.contains(method)) + .map(|(_, &fidx)| fidx as usize), + ); let mut reachable = reachable_functions(module, &bundle_roots); let global_count = module.globals.len(); @@ -137,17 +156,45 @@ pub fn lower_bundled( .collect(), ret_types: vec![Ty::I64; n], ret_known: vec![false; n], + try_bodies: std::collections::HashMap::new(), + try_body_params: std::collections::HashMap::new(), + try_body_param_tys: std::collections::HashMap::new(), + try_body_lambdas: std::collections::HashMap::new(), + try_body_closure_inputs: std::collections::HashSet::new(), + try_body_struct_inputs: std::collections::HashMap::new(), + try_body_cell_inputs: std::collections::HashSet::new(), + try_body_cell_input_tys: std::collections::HashMap::new(), + cell_capture_tys: std::collections::HashMap::new(), + value_lambdas: std::collections::HashMap::new(), + value_lambda_bodies: std::collections::HashSet::new(), + try_body_lambda_env_tys: std::collections::HashMap::new(), + try_body_rebound: std::collections::HashMap::new(), + try_body_cells: std::collections::HashMap::new(), + try_body_raw_cells: std::collections::HashSet::new(), + try_body_extra_cells: std::collections::HashMap::new(), + try_body_returns: std::collections::HashSet::new(), + try_body_ret_tys: std::collections::HashMap::new(), + try_body_escapes: std::collections::HashMap::new(), + try_body_outer_captures: std::collections::HashMap::new(), + try_body_outer_cell_tys: std::collections::HashMap::new(), conflict: false, dyn_loop_phis: std::collections::HashSet::new(), + no_phi_provenance: std::collections::HashSet::new(), dyn_rets: std::collections::HashSet::new(), + cell_captures: std::collections::HashSet::new(), + ref_captures: std::collections::HashMap::new(), + ret_structs: std::collections::HashMap::new(), + param_structs: std::collections::HashMap::new(), imports: ImportEnv::build(&artifact.imports, bundles)?, traits, force_dyn_globals: std::collections::HashSet::new(), spawned_isolate: std::collections::HashSet::new(), - dyn_empty_lists: std::collections::HashSet::new(), + dyn_literals: std::collections::HashSet::new(), + dyn_params: std::collections::HashSet::new(), global_tys: vec![None; global_count], initialized_globals: prescan_initialized_globals(module, global_count), lambda_globals: prescan_lambda_globals(module, global_count), + shadowed_globals: prescan_shadowed_globals(module, global_count), lambda_params: module .functions .iter() @@ -169,6 +216,65 @@ pub fn lower_bundled( // bodies whose `lambda_params` erase the lambda parameters). let mut funcs: Vec = module.functions.to_vec(); + // Outline every `try` body into a function of its own, before anything is + // lowered. + // + // Before, because a region's parent has to *call* the body, and a call + // needs a function to name. Afterwards the bodies are ordinary entries in + // this table: reachable, lowered by the same loop, and subject to the same + // signature fixpoint as everything else. A region whose body cannot be + // outlined is not recorded here, and the parent's own lowering then reports + // it — which is why the scan there runs again rather than trusting this one. + // Over a *growing* table: a body that itself contains a `try` is scanned + // when the loop reaches it, and its inner region is outlined the same way. + // That is what makes `try { try { … } catch { … } } catch { … }` lower — + // nesting is one more turn of the same crank, not a second mechanism. + let mut fi = 0; + while fi < funcs.len() { + let scanning = fi; + fi += 1; + if !reachable[scanning] { + continue; + } + let Ok(instrs) = funcs[scanning] + .code + .iter() + .map(|raw| Instr::try_from_raw(*raw)) + .collect::, _>>() + else { + continue; + }; + let Ok(regions) = try_region::scan(&funcs[scanning], &instrs) else { + continue; + }; + for region in ®ions { + let body = try_region::outline(&funcs[scanning], region); + let body_index = funcs.len() as u32; + funcs.push(body); + reachable.push(true); + // Outside the assertion, because `debug_assert_eq!` discards its + // *whole expression* in a release build — the call included. Written + // as an assertion, the signature tables never grew a row for a try + // body in an optimized `lk`, and the next pass indexed + // `sig.ret_types[body_index]` one past the end: every `try` program + // panicked the compiler, in every build anyone ships. The debug + // build was fine, which is what every gate used. + let pushed = sig.push_function(Vec::new(), Ty::Nil); + assert_eq!( + body_index, pushed, + "a try body's index must be its row in the signature tables" + ); + sig.try_bodies.insert((scanning as u32, region.begin_pc), body_index); + if region.body_returns { + sig.try_body_returns.insert(body_index); + } + if !region.escape_targets.is_empty() { + sig.try_body_escapes.insert(body_index, region.escape_targets.len()); + } + discover_try_params(&mut funcs, body_index, module, &mut sig); + } + } + // Fixpoint: re-lower every function, refining inferred parameter/return types // (bounded — the scalar lattice converges quickly). Transient failures are // tolerated here (a function may not lower until the types it depends on have @@ -187,11 +293,34 @@ pub fn lower_bundled( sig.specializations.len(), sig.ret_closures.clone(), sig.dyn_loop_phis.len(), - sig.dyn_empty_lists.len(), + sig.dyn_literals.len(), sig.dyn_rets.len(), + sig.ret_structs.clone(), sig.global_tys.clone(), sig.spawned_isolate.len(), sig.force_dyn_globals.len(), + sig.try_body_extra_cells + .values() + .map(std::collections::HashSet::len) + .sum::(), + sig.try_body_param_tys.clone(), + sig.try_body_rebound.clone(), + // Appended, not inserted: the snapshot is a positional tuple + // and the comparison below indexes it, so a new field in the + // middle renumbers every later one into comparing the wrong + // thing — silently, and a silent convergence is a miscompile. + sig.param_structs.clone(), + sig.dyn_params.len(), + sig.try_body_lambdas.clone(), + sig.try_body_lambda_env_tys.clone(), + sig.try_body_params.clone(), + sig.try_body_cell_inputs.clone(), + sig.try_body_cell_input_tys.clone(), + sig.cell_capture_tys.clone(), + sig.value_lambdas.clone(), + sig.no_phi_provenance.len(), + sig.try_body_closure_inputs.clone(), + sig.try_body_struct_inputs.clone(), ); // Call-site facts are re-derived every pass: an argument register // that resolves to a closure ref only once a summary lands (e.g. a @@ -200,6 +329,32 @@ pub fn lower_bundled( // the converged flags of the last fixpoint pass. sig.specialized.iter_mut().for_each(|flag| *flag = false); sig.plain_called.iter_mut().for_each(|flag| *flag = false); + // The first pass's observations are made from *provisional* types — + // a callee's return type is still its `I64` default until its body + // has been lowered once — and the parameter lattice joins + // monotonically, so a provisional observation is permanent: + // + // fn mk() -> List { return [1]; } + // fn add(xs: List, n: Int) -> Int { xs.push(n); return xs.len(); } + // add(mk(), 2) + // + // pass 1 saw `mk()` as `I64`, pass 2 saw the real `list`, the + // two joined to `Dyn`, and `add` took a boxed argument forever — + // from a fact that was never true. A boxed typed list is a *copy* + // (`list_h.i64_to_dyn` rebuilds it), so the push was lost. + // + // Discarded once, at the start of pass 2, rather than suppressed in + // pass 1: the table is also what decides a callee's rendered arity + // (hidden environment and capture arguments observe through it), and + // a pass that records nothing renders a signature the call sites do + // not match — `call to lk_fn_6 passes 2 machine argument(s), + // declared with 3`, which the fuzzer found on three seeds. Every + // pass from the second on accumulates exactly as before. + if passes == 1 { + sig.param_obs + .iter_mut() + .for_each(|slots| slots.iter_mut().for_each(|slot| *slot = None)); + } sig.conflict = false; for fi in 0..funcs.len() { if !reachable[fi] { @@ -238,9 +393,76 @@ pub fn lower_bundled( Err(Unsupported::DynLoopPhi { block, slot }) => { sig.dyn_loop_phis.insert((fi as u32, block, slot)); } - Err(Unsupported::EmptyListGuessWrong { pcs }) => { + // The provenance twin of the arm above. + Err(Unsupported::PhiProvenance { block, slot }) => { + sig.no_phi_provenance.insert((fi as u32, block, slot)); + } + // A closure used where a *value* is required. The value + // form is a clone with an all-`Dyn` signature, queued here + // and materialized with the other clones below; the + // original keeps the signature its static call sites + // resolved. + Err(Unsupported::ReferenceAsValue { lambda: Some(orig), .. }) + if !sig.value_lambdas.contains_key(&orig) && (orig as usize) < funcs.len() => + { + let arity = + funcs[orig as usize].param_count as usize + funcs[orig as usize].capture_count as usize; + let clone = sig.push_function(vec![Some(Ty::Dyn); arity], Ty::Dyn); + sig.ret_closure_poisoned[clone as usize] = true; + sig.dyn_rets.insert(clone); + sig.value_lambda_bodies.insert(clone); + sig.value_lambdas.insert(orig, clone); + sig.pending_clones.push(orig); + } + Err(Unsupported::ParamCarrierContradicted { param }) => { + sig.dyn_params.insert((fi as u32, param)); + } + Err(Unsupported::LiteralElemTypeContradicted { pcs }) => { for pc in pcs { - sig.dyn_empty_lists.insert((fi as u32, pc)); + sig.dyn_literals.insert((fi as u32, pc)); + } + } + // A register read after a `try` region with no definition + // *here* was defined inside the body — which runs in its own + // frame, so the value never came back. It has to travel + // through a cell, and which registers those are is exactly + // what this error names: record it and let the fixpoint + // lower the function again. + // + // Discovered rather than predicted, for the third time in + // this feature: "does anything after the region read what + // the body wrote" is a liveness question, and the SSA is + // already the thing that answers it. + // + // Which region: the one whose poison the read hit, which + // the error names. It used to name none, so the cell went + // to *every* region in the function — and a second region + // in the same function then got a cell for a register the + // first body had merely used as a scratch. The parent has + // no definition for such a register at its own region's + // start, so seeding the cell read it before pc 0 and the + // whole function fell back. An unattributed read is an + // ordinary undefined read: no cell fixes it. + Err(Unsupported::UndefinedOperand { + reg, body: Some(body), .. + }) if reg < 256 => { + sig.try_body_extra_cells.entry(body).or_default().insert(reg as u8); + } + // The mirror image, one frame in: a register *the body + // itself* cannot define is an **input**, and the parent has + // it. `discover_try_params` finds most of them before the + // fixpoint starts, but it stops at the first failure that is + // not this one — and a call through an input whose closure + // identity the parent has not recorded yet is exactly such a + // failure, resolved only on the pass after. Everything the + // body reads past that point is therefore found here. + Err(Unsupported::UndefinedOperand { reg, body: None, .. }) + if reg < 256 && sig.try_bodies.values().any(|&body| body == fi as u32) => + { + let params = sig.try_body_params.entry(fi as u32).or_default(); + if !params.contains(&(reg as u8)) { + params.push(reg as u8); + params.sort_unstable(); } } _ => {} @@ -252,6 +474,11 @@ pub fn lower_bundled( funcs.push(funcs[orig as usize].clone()); reachable.push(true); } + // The working list and the per-function tables are one indexing + // scheme (`SigInfer::push_function`); a queued clone is the only + // moment they legitimately differ, and it ends here. + debug_assert_eq!(funcs.len(), sig.param_obs.len(), "function tables out of step"); + debug_assert_eq!(funcs.len(), reachable.len(), "reachability out of step"); passes += 1; // Field-by-field comparison against the pre-pass snapshot: the same // convergence condition without cloning the whole state a second @@ -264,16 +491,65 @@ pub fn lower_bundled( && snapshot.2 == sig.specializations.len() && snapshot.3 == sig.ret_closures && snapshot.4 == sig.dyn_loop_phis.len() - && snapshot.5 == sig.dyn_empty_lists.len() + && snapshot.5 == sig.dyn_literals.len() && snapshot.6 == sig.dyn_rets.len() - && snapshot.7 == sig.global_tys - && snapshot.8 == sig.spawned_isolate.len() - && snapshot.9 == sig.force_dyn_globals.len(); + && snapshot.7 == sig.ret_structs + && snapshot.8 == sig.global_tys + && snapshot.9 == sig.spawned_isolate.len() + && snapshot.10 == sig.force_dyn_globals.len() + // Extra cells were counted into the *budget* below but left out + // of this conjunction, so a pass that discovered one still + // counted as converged — the fixpoint stopped one pass early and + // every signature that pass would have refined stayed at its + // default. That is how a call to a function returning nothing + // was emitted wanting a result: the caller had never seen the + // callee's real return type. + && snapshot.11 + == sig + .try_body_extra_cells + .values() + .map(std::collections::HashSet::len) + .sum::() + && snapshot.12 == sig.try_body_param_tys + && snapshot.13 == sig.try_body_rebound + && snapshot.14 == sig.param_structs + && snapshot.15 == sig.dyn_params.len() + && snapshot.16 == sig.try_body_lambdas + && snapshot.17 == sig.try_body_lambda_env_tys + && snapshot.18 == sig.try_body_params + && snapshot.19 == sig.try_body_cell_inputs + && snapshot.20 == sig.try_body_cell_input_tys + && snapshot.21 == sig.cell_capture_tys + && snapshot.22 == sig.value_lambdas + && snapshot.23 == sig.no_phi_provenance.len() + && snapshot.24 == sig.try_body_closure_inputs + && snapshot.25 == sig.try_body_struct_inputs; // Each retriable discovery (Dyn loop phi, empty-list re-guess, // boxed-returns function) legitimately consumes one extra pass, so // the safety valve budgets for them on top of the type lattice. - let discovery_budget = - sig.dyn_loop_phis.len() + sig.dyn_empty_lists.len() + sig.dyn_rets.len() + sig.force_dyn_globals.len(); + // Extra cells count too, now that they are how *every* cell is + // found: a region carries nothing back until a read reports that it + // must, and each report costs a pass. + let discovery_budget = sig.dyn_loop_phis.len() + + sig.no_phi_provenance.len() + + sig.dyn_literals.len() + + sig.dyn_rets.len() + + sig.force_dyn_globals.len() + + sig + .try_body_extra_cells + .values() + .map(std::collections::HashSet::len) + .sum::() + + sig.try_body_param_tys.len() + + sig.try_body_lambdas.len() + + sig.try_body_lambda_env_tys.len() + + sig.try_body_params.values().map(Vec::len).sum::() + + sig.try_body_cell_inputs.len() + + sig.try_body_cell_input_tys.len() + + sig.try_body_closure_inputs.len() + + sig.try_body_struct_inputs.len() + + sig.cell_capture_tys.len() + + sig.value_lambdas.len() * 2; if converged || passes > 2 * funcs.len() + 2 + discovery_budget { break; } @@ -359,7 +635,80 @@ pub fn lower_bundled( } (globals, functions, failures) }; - let (mut globals, mut functions, failures) = final_pass(&mut sig, &reachable, &funcs); + let (globals, functions, failures) = final_pass(&mut sig, &reachable, &funcs); + // A retriable discovery made *here* had nowhere to go. + // + // The fixpoint records them and runs again; `refine_signatures` then runs + // once, after convergence, and the final pass lowers against the refined + // signatures. A function that lowered cleanly every fixpoint pass can fail + // in that final one — refinement changed the types it sees — and its + // discovery was simply dropped. `error_unwrap.lk` is exactly that: its + // entry succeeds in the only fixpoint pass it needs and then reports a + // heterogeneous phi the retry would have fixed, from a pass with no retry + // after it. + // + // So take them and go round once. Once, not to convergence: the second + // final pass sees the same refined signatures as the first, so a discovery + // it makes is one the first pass could not have made either — and a loop + // here would be a loop over a fixed point. + let (mut globals, mut functions, failures) = { + enum Retriable { + LoopPhi(usize, usize), + PhiProvenance(usize, usize), + ParamCarrier(u8), + LiteralElemType(Vec), + } + let retriable: Vec<(usize, Retriable)> = failures + .iter() + .filter_map(|(fi, err)| match err { + Unsupported::DynLoopPhi { block, slot } => Some((*fi, Retriable::LoopPhi(*block, *slot))), + Unsupported::PhiProvenance { block, slot } => Some((*fi, Retriable::PhiProvenance(*block, *slot))), + // A push that widens a parameter's carrier is discovered only + // here: the fixpoint wipes its parameter observations once, so + // a callee reached only through a call site's observation is + // lowered against the `I64` default in every pass and never + // sees the typed carrier its caller passes. + Unsupported::ParamCarrierContradicted { param } => Some((*fi, Retriable::ParamCarrier(*param))), + // The third of the same kind, and it was missing. An empty `[]` + // is guessed from a lookahead for the first push into it; with + // no push in the function there is no evidence, so the guess is + // the default carrier — and a call site that hands it to a + // `Dyn` parameter contradicts that guess. Refinement is what + // makes the parameter `Dyn`, so the contradiction is *only* + // visible in the final pass, where nothing was listening. + // `fn f(v) { … } f([]); f(5);` — a list and a non-list at one + // parameter, which is an ordinary program — refused to lower. + Unsupported::LiteralElemTypeContradicted { pcs } => { + Some((*fi, Retriable::LiteralElemType(pcs.clone()))) + } + _ => None, + }) + .collect(); + if retriable.is_empty() { + (globals, functions, failures) + } else { + for (fi, what) in retriable { + match what { + Retriable::LoopPhi(block, slot) => { + sig.dyn_loop_phis.insert((fi as u32, block, slot)); + } + Retriable::PhiProvenance(block, slot) => { + sig.no_phi_provenance.insert((fi as u32, block, slot)); + } + Retriable::ParamCarrier(param) => { + sig.dyn_params.insert((fi as u32, param)); + } + Retriable::LiteralElemType(pcs) => { + for pc in pcs { + sig.dyn_literals.insert((fi as u32, pc)); + } + } + } + } + refine_signatures(&mut sig, &mut funcs, &mut reachable); + final_pass(&mut sig, &reachable, &funcs) + } + }; // A bundled module exports more than any one importer uses, and those // extras are rooted speculatively — their names are reached by a lookup // the bytecode scan cannot follow, so there is no telling in advance which @@ -392,10 +741,48 @@ pub fn lower_bundled( // hides behind its caller's transient ret-type check). if std::env::var_os("LK_AOT_DEBUG_FAILURES").is_some() { for (fi, err) in &failures { - eprintln!("lk-aot-lower: final-pass failure: fn{fi}: {err:?}"); + let at = match (err_pc(err), funcs.get(*fi)) { + (Some(pc), Some(f)) => match f.code.get(pc).and_then(|raw| Instr::try_from_raw(*raw).ok()) { + Some(instr) => { + // A method call's name lives in the constant pool, + // so "no lowering for this method" can say which + // one. Without it the listing named a shape and left + // the reader to look the index up by hand. + let name = match instr.opcode() { + Opcode::CallMethodK => f.consts.strings.get(instr.b() as usize), + Opcode::GetFieldK | Opcode::SetFieldK => f.consts.strings.get(instr.c() as usize), + _ => None, + }; + match name { + Some(name) => { + format!(" [{:?} `{name}` a={} c={}]", instr.opcode(), instr.a(), instr.c()) + } + None => format!( + " [{:?} a={} b={} c={}]", + instr.opcode(), + instr.a(), + instr.b(), + instr.c() + ), + } + } + None => format!(" [pc {pc} out of range: fn has {} instrs]", f.code.len()), + }, + (Some(pc), None) => format!(" [fn{fi} not in table of {}; pc {pc}]", funcs.len()), + _ => String::new(), + }; + // The name, not only the index: `name_failure` already resolves + // it for the *first* error, so the listing had it available and + // did not use it. Eleven identically-worded failures turned out + // to be one method only once they could be told apart. + let name = funcs + .get(*fi) + .and_then(|f| f.debug_name.as_deref()) + .map_or(String::new(), |n| format!(" `{n}`")); + eprintln!("lk-aot-lower: final-pass failure: fn{fi}{name}: {err:?}{at}"); } } - let first_error = failures[0].1.clone(); + let first_error = name_failure(&failures[0], &funcs); if !hybrid { return Err(first_error); } @@ -421,26 +808,35 @@ pub fn lower_bundled( loop { let mut marked_any = false; for (fi, _) in ¤t_failures { - let eligible = bridge_eligibility(*fi, &funcs, module.entry, &sig, &written); + // An outlined try body has no entry in the embedded artifact; + // only its nearest real ancestor can execute on the bridge VM. + // Nested bodies climb through their synthetic parents. Other + // synthetic functions are not bridgeable either. + let owner = bridge_artifact_owner(*fi, n, &sig.try_bodies); + let eligible = owner + .filter(|owner| !sig.vm_functions.contains_key(&(*owner as u32))) + .and_then(|owner| bridge_eligibility(owner, &funcs, module.entry, &sig, &written)); if std::env::var_os("LK_AOT_DEBUG_FAILURES").is_some() { // "why was this not bridged" is the usual question when a // program unexpectedly falls back to Tier 0. - eprintln!("lk-aot-lower: fn{fi} failed to lower; bridge-eligible: {eligible:?}"); + eprintln!( + "lk-aot-lower: fn{fi} failed to lower; bridge owner: {owner:?}; bridge-eligible: {eligible:?}" + ); } - if !sig.vm_functions.contains_key(&(*fi as u32)) - && let Some(param_count) = eligible + if let (Some(owner), Some(param_count)) = (owner, eligible) + && !sig.vm_functions.contains_key(&(owner as u32)) { - sig.vm_functions.insert(*fi as u32, param_count); + sig.vm_functions.insert(owner as u32, param_count); marked_any = true; } } if !marked_any { return Err(current_failures .first() - .map(|(_, err)| err.clone()) + .map(|failure| name_failure(failure, &funcs)) .unwrap_or(first_error)); } - let native_reachable = native_reachable_functions(&funcs, module.entry, &sig.vm_functions); + let native_reachable = native_reachable_functions(&funcs, module.entry, &sig.vm_functions, &sig.try_bodies); // Drop VM marks without any native-reachable call site (a callee // only ever called from inside the VM needs no bridge signature). sig.vm_functions @@ -493,23 +889,80 @@ pub fn lower_bundled( // not-natively-lowerable rather than emitting it as an internal codegen // error. Debug the underlying shape with `LK_AOT_DEBUG_FAILURES=1`. if let Err(error) = lk_aot_mir::validate(&lowered) { - if std::env::var_os("LK_AOT_DEBUG_FAILURES").is_some() { - eprintln!("lk-aot-lower: lowered module failed validation: {error:?}"); - } - return Err(Unsupported::InvalidMir); + return Err(Unsupported::InvalidMir(format!("{error:?}"))); } Ok(lowered) } /// Every function id the emitted code can reach: direct calls, protected /// calls, and function addresses taken as constants. +/// Attaches the failing function's name to its blocker. +/// +/// The name is what the front end recorded, and a function that has none — an +/// outlined `try` body, a lambda — keeps the bare blocker rather than being +/// given a made-up name: `fn41` is not more informative than the pc already is, +/// and it reads like something the reader could go and look up. +/// The bytecode offset a blocker is about, for the debug listing above. +/// +/// Local and private: it exists because the listing has a use for it, not as a +/// general accessor waiting for one. +fn err_pc(err: &Unsupported) -> Option { + match err { + Unsupported::In { inner, .. } => err_pc(inner), + Unsupported::ContainerGlobalBoxed { pc, .. } + | Unsupported::BadInstr { pc } + | Unsupported::Opcode { pc, .. } + | Unsupported::CallShape { pc, .. } + | Unsupported::TryRegion { pc, .. } + | Unsupported::UnresolvedGlobal { pc, .. } + | Unsupported::BadConst { pc } + | Unsupported::UndefinedOperand { pc, .. } + | Unsupported::ReferenceAsValue { pc, .. } + | Unsupported::TypeMismatch { pc } + | Unsupported::OperandType { pc, .. } + | Unsupported::BadTarget { pc } => Some(*pc), + _ => None, + } +} + +fn name_failure(failure: &(usize, Unsupported), funcs: &[FunctionData]) -> Unsupported { + let (fi, err) = failure; + match funcs.get(*fi).and_then(|f| f.debug_name.clone()) { + Some(function) => Unsupported::In { + function, + inner: Box::new(err.clone()), + }, + None => err.clone(), + } +} + +/// Maps an AOT-only outlined try body to the original artifact function that +/// owns it. Other synthetic functions have no VM counterpart and return None. +fn bridge_artifact_owner( + mut fi: usize, + artifact_functions: usize, + try_bodies: &std::collections::HashMap<(u32, usize), u32>, +) -> Option { + let parent_of: std::collections::HashMap = + try_bodies.iter().map(|(&(parent, _), &body)| (body, parent)).collect(); + let mut steps = 0usize; + while fi >= artifact_functions { + fi = *parent_of.get(&(fi as u32))? as usize; + steps += 1; + if steps > try_bodies.len() { + return None; + } + } + Some(fi) +} + fn referenced_functions(functions: &[MirFunction]) -> std::collections::HashSet { let mut referenced = std::collections::HashSet::new(); for function in functions { for block in &function.blocks { for inst in &block.insts { match inst { - Inst::CallFn { func, .. } | Inst::TryCall { func, .. } => { + Inst::CallFn { func, .. } | Inst::TryRegionCall { func, .. } => { referenced.insert(*func); } Inst::Const { @@ -525,3 +978,54 @@ fn referenced_functions(functions: &[MirFunction]) -> std::collections::HashSet< } referenced } + +/// Finds the registers a try body reads from outside itself, by lowering it. +/// +/// Each attempt either succeeds or names one register that was read with no +/// definition; that register is an input, so it is added and the body lowered +/// again. The loop is bounded by the register file, and it converges because +/// every iteration adds a register that was previously missing. +/// +/// Discovery rather than a table: knowing which operands each opcode reads +/// means writing one entry per opcode, and one wrong entry is a body that reads +/// a stale value — a wrong answer rather than a rejection. The SSA already +/// knows; this asks it. +fn discover_try_params( + funcs: &mut [FunctionData], + body_index: u32, + module: &lk_core::vm::ModuleData, + sig: &mut SigInfer, +) { + // The trampoline's arity switch caps this; past it the region rejects. + const MAX_PARAMS: usize = 8; + let mut params: Vec = Vec::new(); + for _ in 0..=MAX_PARAMS { + let mut scratch = Vec::new(); + let attempt = lower_function( + &funcs[body_index as usize], + funcs, + body_index, + module.entry, + false, + &mut scratch, + &module.globals, + sig, + ); + match attempt { + Err(Unsupported::UndefinedOperand { reg, .. }) if reg < 256 && !params.contains(&(reg as u8)) => { + params.push(reg as u8); + params.sort_unstable(); + // `param_count` stays 0. It is what binds registers 0..n-1 as + // parameters, and these parameters are *not* those registers — + // they are whichever ones the body reads from outside. Setting + // both is how the body ended up with each input twice: once + // under its own number and once under the low numbers. + sig.try_body_params.insert(body_index, params.clone()); + } + // Anything else — success, or a failure for another reason — ends + // the search. A body that cannot lower for its own reasons is the + // parent's rejection to report, with its own message. + _ => return, + } + } +} diff --git a/aot/lower/src/lower_builtin.rs b/aot/lower/src/lower_builtin.rs index dfdd6cad..98c12314 100644 --- a/aot/lower/src/lower_builtin.rs +++ b/aot/lower/src/lower_builtin.rs @@ -21,12 +21,10 @@ pub(crate) fn lower_builtin_call( } Builtin::CallMethod => { // Dispatched by the caller before reaching here. - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); - } - Builtin::TryCall => { - // Dispatched by the caller before reaching here (it needs the - // function table and the signature lattice). - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } Builtin::ErrorRaise => { // `error(v)`: raise the boxed value to the nearest `try` frame @@ -34,10 +32,13 @@ pub(crate) fn lower_builtin_call( // the VM's uncaught behaviour). The statement's result register // is never observed on the raise path; nil keeps SSA total. if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let (v, ty) = ssa.read(base.wrapping_add(1), block, pc)?; - let boxed = to_dyn_any(ssa, insts, v, ty, pc)?; + let boxed = to_dyn(ssa, insts, v, ty, pc)?; insts.push(Inst::Call { dst: None, callee: AbiRef::new("rt", "raise_dyn"), @@ -51,9 +52,46 @@ pub(crate) fn lower_builtin_call( ssa.write(base, block, (nil, Ty::Nil)); return Ok(()); } - Builtin::Shl | Builtin::Shr => { + Builtin::U64ToFloat => { + if argc != 1 { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); + } + let value = read_index_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("arith", "u64_to_f64"), + args: vec![value], + }); + ssa.write(base, block, (dst, Ty::F64)); + return Ok(()); + } + Builtin::U64Str => { + if argc != 1 { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); + } + let value = read_index_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "from_u64"), + args: vec![value], + }); + ssa.write(base, block, (dst, Ty::Str)); + return Ok(()); + } + Builtin::LtU | Builtin::DivU | Builtin::ModU => { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let lhs = read_index_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; let rhs = read_index_scalar(ssa, insts, base.wrapping_add(2), block, pc)?; @@ -62,10 +100,62 @@ pub(crate) fn lower_builtin_call( dst: Some(dst), callee: AbiRef::new( "arith", - if matches!(builtin, Builtin::Shl) { - "i64_shl" - } else { - "i64_shr" + match builtin { + Builtin::LtU => "u64_lt", + Builtin::DivU => "u64_div", + _ => "u64_rem", + }, + ), + args: vec![lhs, rhs], + }); + // `u64_lt` answers 1 or 0; the comparison's result is a Bool + // everywhere else, so it is one here too. + let ty = if matches!(builtin, Builtin::LtU) { + Ty::Bool + } else { + Ty::I64 + }; + if matches!(builtin, Builtin::LtU) { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + lhs: dst, + rhs: zero, + float: false, + }); + ssa.write(base, block, (b, ty)); + return Ok(()); + } + ssa.write(base, block, (dst, ty)); + return Ok(()); + } + Builtin::Shl | Builtin::Shr | Builtin::ShrU => { + if argc != 2 { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); + } + let lhs = read_index_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; + let rhs = read_index_scalar(ssa, insts, base.wrapping_add(2), block, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new( + "arith", + match builtin { + Builtin::Shl => "i64_shl", + // Logical, because the compiler only picks this name + // when the left operand is a `u64` — where bit 63 is + // part of the value and not its sign. + Builtin::ShrU => "u64_shr", + _ => "i64_shr", }, ), args: vec![lhs, rhs], @@ -73,19 +163,22 @@ pub(crate) fn lower_builtin_call( ssa.write(base, block, (dst, Ty::I64)); return Ok(()); } - Builtin::BitAnd | Builtin::BitOr => { + Builtin::BitAnd | Builtin::BitOr | Builtin::BitXor => { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let lhs = read_index_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; let rhs = read_index_scalar(ssa, insts, base.wrapping_add(2), block, pc)?; let dst = ssa.new_val(); insts.push(Inst::IntBin { dst, - op: if matches!(builtin, Builtin::BitAnd) { - IntBinOp::And - } else { - IntBinOp::Or + op: match builtin { + Builtin::BitAnd => IntBinOp::And, + Builtin::BitOr => IntBinOp::Or, + _ => IntBinOp::Xor, }, lhs, rhs, @@ -96,7 +189,10 @@ pub(crate) fn lower_builtin_call( Builtin::BitNot => { // `~x` = `x xor -1` (two's complement bitwise not). if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let v = read_index_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; let minus_one = ssa.new_val(); @@ -118,7 +214,10 @@ pub(crate) fn lower_builtin_call( // `chan(capacity[, type])` — the type string is a VM checker // hint, dropped natively. The channel value is its i64 id. if !(1..=2).contains(&argc) { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let cap = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let dst = ssa.new_val(); @@ -127,16 +226,28 @@ pub(crate) fn lower_builtin_call( callee: AbiRef::new("chan", "new"), args: vec![cap], }); - ssa.write(base, block, (dst, Ty::I64)); + // Boxed under `DYN_CHAN`, not the bare id: the id is an `Int` and a + // channel is not. `typeof` answered `Int`, display wrote the + // number, and `chan(1) == 1` was true. + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "from_chan"), + args: vec![dst], + }); + ssa.write(base, block, (boxed, Ty::Dyn)); return Ok(()); } Builtin::ChanSend => { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let ch = read_channel_id(ssa, insts, base.wrapping_add(1), block, pc)?; let (v, ty) = ssa.read(base.wrapping_add(2), block, pc)?; - let boxed = to_dyn_any(ssa, insts, v, ty, pc)?; + let boxed = to_dyn(ssa, insts, v, ty, pc)?; insts.push(Inst::Call { dst: None, callee: AbiRef::new("chan", "send"), @@ -152,7 +263,10 @@ pub(crate) fn lower_builtin_call( } Builtin::ChanRecv => { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let ch = read_channel_id(ssa, insts, base.wrapping_add(1), block, pc)?; let dst = ssa.new_val(); @@ -166,18 +280,27 @@ pub(crate) fn lower_builtin_call( } Builtin::Spawn => { // Dispatched by the caller (needs the function table/signatures). - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } Builtin::MergeFields | Builtin::MakeStruct => { // Dispatched by the caller (struct provenance needs `sig`). - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } Builtin::SelectBlock => { // Four parallel lists + the default flag; every list normalizes // to a dyn list, the result is the VM's exact // `[is_default, index, payload]` shape. if argc != 5 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let mut lists = Vec::with_capacity(4); for i in 0..4 { @@ -220,6 +343,10 @@ pub(crate) fn lower_builtin_call( let from = match list_ty { Ty::ListStr => "from_str_list", Ty::ListI64 => "from_i64_list", + // A constant list is `List` as soon as its + // elements are not one uniform type — and strings split + // by length, so `["ab", "aaaaaaaaaa"]` is not uniform. + Ty::ListDyn => "from_dyn_list", _ => return Err(Unsupported::TypeMismatch { pc }), }; let dst = ssa.new_val(); @@ -230,7 +357,12 @@ pub(crate) fn lower_builtin_call( }); dst } - _ => return Err(Unsupported::Opcode { pc, op: Opcode::Call }), + _ => { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); + } }; ssa.write(base, block, (result, Ty::Set)); return Ok(()); @@ -277,7 +409,10 @@ pub(crate) fn lower_builtin_call( // built eagerly (dead on the success path) so no extra control // flow is needed. if !(2..=3).contains(&argc) { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let negated = builtin == Builtin::AssertNe; let (lv, lty) = ssa.read(base.wrapping_add(1), block, pc)?; @@ -345,8 +480,8 @@ pub(crate) fn lower_builtin_call( // Maybe is nil: `assert_eq(m.get(missing), 3)` fails loud on // both sides). _ if dyn_boxable_ty(lty) && dyn_boxable_ty(rty) => { - let lb = to_dyn_any(ssa, insts, lv, lty, pc)?; - let rb = to_dyn_any(ssa, insts, rv, rty, pc)?; + let lb = to_dyn(ssa, insts, lv, lty, pc)?; + let rb = to_dyn(ssa, insts, rv, rty, pc)?; let eq = ssa.new_val(); insts.push(Inst::Call { dst: Some(eq), @@ -425,9 +560,23 @@ pub(crate) fn lower_builtin_call( free_owned_str(insts, msg); } } - Builtin::Cpu(entry, arity) => { - if argc != usize::from(arity) { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + Builtin::Cpu(entry) => { + // Arity and result come from the ABI table, which is the schema + // these calls are emitted against. Spelling either out here would + // be a second copy of a signature — and the failure of a copy that + // disagrees is not a build error but a call with the wrong number + // of arguments, or a result quietly overwritten with nil below. + let Some(abi) = lk_aot_abi::find("cpu", entry) else { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); + }; + if argc != abi.params.len() { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let mut call_args = Vec::with_capacity(argc); for index in 0..argc { @@ -438,8 +587,7 @@ pub(crate) fn lower_builtin_call( } call_args.push(value); } - // The two that produce a value; the rest are pure effect. - if entry == "irq_save" || entry == "timestamp" { + if abi.result != lk_aot_abi::AbiType::Nil { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), @@ -461,11 +609,17 @@ pub(crate) fn lower_builtin_call( // The name has to be a literal: a relocation is a name resolved at // link time, and a kernel has no symbol table to look one up in. if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let (name_value, _) = ssa.read(base.wrapping_add(1), block, pc)?; - let Some(symbol) = ssa.const_strs.get(&name_value).cloned() else { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + let Some(symbol) = ssa.const_str_value(name_value) else { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); }; let dst = ssa.new_val(); insts.push(Inst::SymbolAddr { dst, symbol }); @@ -474,7 +628,10 @@ pub(crate) fn lower_builtin_call( } Builtin::CallAddress2 => { if argc != 3 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let callee = read_index_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; let first = read_index_scalar(ssa, insts, base.wrapping_add(2), block, pc)?; @@ -493,7 +650,10 @@ pub(crate) fn lower_builtin_call( // just an address, and the type checker has already established // that this argument is a pointer of the matching width. if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } // `read_scalar`, not a bare read: an address that came out of a // container arrives as a `Maybe` carrier, and MMIO is a scalar @@ -501,15 +661,13 @@ pub(crate) fn lower_builtin_call( // the VM. let addr = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let dst = ssa.new_val(); - // An opaque `lkrt` call, not an inline load: Cranelift has no - // volatile flag, and its egraph pass will happily collapse two - // loads of one address into one. A call it cannot see through - // keeps both accesses. See lkrt/src/mmio.rs. - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("mmio", mmio_read_name(bits)), - args: vec![addr], - }); + // A real machine load. This used to be an opaque `lkrt` call, on + // the grounds that Cranelift has no volatile flag and its alias + // analysis collapses two loads of one address into one — which was + // measured, and true of a plain load. The way out is not a flag: + // `Inst::VolatileLoad` emits a `sequence_point` first, which costs + // no machine code and defeats the collapse. See its doc comment. + insts.push(Inst::VolatileLoad { dst, addr, bits }); // The VM writes a builtin's result to the call-window base. // // `return`, not `break`: this function ends by writing `nil` to @@ -521,18 +679,17 @@ pub(crate) fn lower_builtin_call( } Builtin::VolatileWrite(bits) => { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } // Both operands through `read_scalar` — see the read arm above. // Iterating a list and writing each element is the ordinary shape // of a driver's output loop, and its elements are `Maybe` carriers. let addr = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let value = read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)?; - insts.push(Inst::Call { - dst: None, - callee: AbiRef::new("mmio", mmio_write_name(bits)), - args: vec![addr, value], - }); + insts.push(Inst::VolatileStore { addr, value, bits }); // A write produces nothing, so the shared nil-return tail below is // exactly right — fall through to it rather than duplicating it. } @@ -540,7 +697,10 @@ pub(crate) fn lower_builtin_call( // `port_in_uN(port)`. Same shape as the MMIO read: one opaque call, // whose result the VM leaves at the call-window base. if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let port = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let dst = ssa.new_val(); @@ -554,7 +714,10 @@ pub(crate) fn lower_builtin_call( } Builtin::PortOut(bits) => { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let port = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let value = read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)?; @@ -570,15 +733,35 @@ pub(crate) fn lower_builtin_call( // type. Maybe carriers select between the scalar name and `Nil` at // runtime (a missing map key is `Nil` in the VM). if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let (v, ty) = ssa.read(base.wrapping_add(1), block, pc)?; + // Every proven type, not just the scalars: `typeof` asks what the + // value *is*, and a container is as proven as an `Int` here. With + // only the five scalars, `typeof([1, 2])` — and every other + // container — dropped the whole program to the VM. + // + // The names are the VM's (`RuntimeVal::type_name_in`), which is + // what `every_proven_type_has_a_typeof_name` compares them against. let scalar_name = |ty: Ty| match ty { Ty::I64 => Some("Int"), Ty::F64 => Some("Float"), Ty::Bool => Some("Bool"), Ty::Str => Some("String"), Ty::Nil => Some("Nil"), + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn => Some("List"), + // `MapStrDyn` is deliberately absent: it is also the struct + // carrier, so it has no static answer (see the arms below). + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapI64I64 | Ty::MapI64F64 => Some("Map"), + Ty::Set => Some("Set"), + Ty::Bytes => Some("Bytes"), + Ty::SliceI64 => Some("Slice"), + // A `Dyn` is whatever it is at run time, so its name is a + // runtime question — `dyn.type_name` answers it, and this + // static table cannot. _ => None, }; let result = match ty { @@ -607,6 +790,29 @@ pub(crate) fn lower_builtin_call( }); dst } + // A struct instance the lowering can name: answer the declared + // name, statically. Its carrier is `MapStrDyn`, and the static + // table said `Map` — so `typeof(p)` read `Map` compiled and + // `P` interpreted, a divergence no example happened to cover. + _ if ssa.struct_name(v).is_some() => { + let name = ssa.struct_name(v).expect("just matched").to_string(); + materialize_key(ssa, insts, globals, &name) + } + // A carrier that *may* be a struct at run time but is not + // proven one — a plain map and a struct instance share + // `MapStrDyn`, and a `Dyn` is anything. The runtime reads the + // type mark; guessing `Map` here would be a wrong answer half + // the time. + Ty::MapStrDyn | Ty::Dyn => { + let boxed = to_dyn(ssa, insts, v, ty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "type_name"), + args: vec![boxed], + }); + dst + } ty => match scalar_name(ty) { Some(name) => materialize_key(ssa, insts, globals, name), None => return Err(Unsupported::TypeMismatch { pc }), @@ -621,7 +827,10 @@ pub(crate) fn lower_builtin_call( // widens directly; a boxed condition evaluates the VM's // truthiness (`assert_truthy` = `!(Nil | Bool(false))`). if argc == 0 || argc > 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this builtin in this argument shape", + }); } let wide = match ssa.read(base.wrapping_add(1), block, pc)? { (v, Ty::Dyn) => { @@ -687,25 +896,3 @@ fn port_out_name(bits: u8) -> &'static str { _ => "out_u32", } } - -/// ABI entry names for volatile access, keyed by width. -/// -/// The widths are fixed by the intrinsic names the front end accepts, so an -/// unknown one here is a lowering bug rather than a user error. -fn mmio_read_name(bits: u8) -> &'static str { - match bits { - 8 => "read_u8", - 16 => "read_u16", - 32 => "read_u32", - _ => "read_u64", - } -} - -fn mmio_write_name(bits: u8) -> &'static str { - match bits { - 8 => "write_u8", - 16 => "write_u16", - 32 => "write_u32", - _ => "write_u64", - } -} diff --git a/aot/lower/src/lower_call.rs b/aot/lower/src/lower_call.rs index e713a3ef..80d4dfd6 100644 --- a/aot/lower/src/lower_call.rs +++ b/aot/lower/src/lower_call.rs @@ -13,19 +13,28 @@ pub(crate) fn lower_spawn( funcs: &[FunctionData], entry: u32, sig: &mut SigInfer, + cap_ctx: CaptureCtx<'_>, base: u8, argc: usize, block: usize, pc: usize, ) -> Result<(), Unsupported> { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "a spawned callee must be a statically known function with scalar arguments", + }); } let arg_reg = base.wrapping_add(1); let (fidx, caps) = match ssa.builtin_ref_at(arg_reg, block) { Some(GlobalRef::Closure(f, caps)) => (f as usize, caps), Some(GlobalRef::Lambda(f)) => (f as usize, Vec::new()), - _ => return Err(Unsupported::Opcode { pc, op: Opcode::Call }), + _ => { + return Err(Unsupported::CallShape { + pc, + reason: "a spawned callee must be a statically known function with scalar arguments", + }); + } }; if fidx >= funcs.len() || fidx == entry as usize @@ -33,7 +42,10 @@ pub(crate) fn lower_spawn( || caps.len() != funcs[fidx].capture_count as usize || caps.len() > 4 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "a spawned callee must be a statically known function with scalar arguments", + }); } sig.spawned_isolate.insert(fidx as u32); // Snapshot the captures into the argument block, boxed. @@ -46,21 +58,29 @@ pub(crate) fn lower_spawn( callee: AbiRef::new("rt", "spawn_args_new"), args: Vec::new(), }); + let site = CaptureSite::new(cap_ctx, fidx as u32, CaptureMode::Snapshot, block, pc); for (k, capture) in caps.iter().enumerate() { - let (v, ty) = match capture { - ClosureCapture::Cell(cid) => { + // Isolate: every capture crosses as a private copy taken here, so a + // cell is read for its *content* rather than passed by pointer. + let (v, ty) = match site.resolve(ssa, insts, sig, capture, k)? { + Some(resolved) => resolved, + None => { + let ClosureCapture::Cell(cid) = capture else { + unreachable!("only `Cell` is left to the call site") + }; let slot = ssa.cell_slot(*cid); ssa.read_slot(slot, block, pc)? } - ClosureCapture::Value(v, ty) => (*v, *ty), }; - let boxed = to_dyn_any(ssa, insts, v, ty, pc)?; + let boxed = to_dyn(ssa, insts, v, ty, pc)?; insts.push(Inst::Call { dst: None, callee: AbiRef::new("rt", "spawn_args_push"), args: vec![b, boxed], }); - let want = sig.observe_param(fidx, k, Ty::Dyn); + // Boxed into `Dyn` on the way in, so the callee's parameter is + // never a typed struct: no provenance to carry. + let want = sig.observe_param(fidx, k, Ty::Dyn, None); if want != Ty::Dyn { return Err(Unsupported::TypeMismatch { pc }); } @@ -97,7 +117,14 @@ pub(crate) fn lower_spawn( callee: AbiRef::new("rt", spawn_fn), args, }); - ssa.write(base, block, (dst, Ty::I64)); + // Boxed under `DYN_TASK`; see `chan` for why the bare id is not enough. + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "from_task"), + args: vec![dst], + }); + ssa.write(base, block, (boxed, Ty::Dyn)); Ok(()) } @@ -114,18 +141,46 @@ pub(crate) fn lower_merge_fields( pc: usize, ) -> Result<(), Unsupported> { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "a field merge needs two map operands", + }); } let (bv, bty) = ssa.read(base.wrapping_add(1), block, pc)?; let (ov, oty) = ssa.read(base.wrapping_add(2), block, pc)?; let base_map = to_dyn_map_handle(ssa, insts, bv, bty, pc)?; - let overlay_map = to_dyn_map_handle(ssa, insts, ov, oty, pc)?; let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("map_h", "str_dyn_merge"), - args: vec![base_map, overlay_map], - }); + // The overlay is walked where it lives rather than converted. A struct + // update's overlay is the `{x: 42}` field literal — a *typed* map — and + // converting it meant re-inserting its entries into a fresh table in its + // iteration order, which is not the sequence that built it. The overlay's + // order is the tail of the merged result's, so that was a reorder waiting + // to be noticed (see `map_h.str_dyn_merge_typed`). + match typed_map_kind(oty) { + Some(kind) => { + let kind_v = ssa.new_val(); + insts.push(Inst::Const { + dst: kind_v, + value: Const::I64(kind), + }); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", "str_dyn_merge_typed"), + args: vec![base_map, ov, kind_v], + }); + } + None => { + let overlay_map = to_dyn_map_handle(ssa, insts, ov, oty, pc)?; + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", "str_dyn_merge"), + args: vec![base_map, overlay_map], + }); + } + } + // Merging two maps makes an ordinary one, whichever the operands were: + // `{..p, ..q}` on struct instances is a map in the interpreter too. + ssa.set_plain_map(dst); ssa.write(base, block, (dst, Ty::MapStrDyn)); Ok(()) } @@ -145,15 +200,16 @@ pub(crate) fn lower_make_struct( pc: usize, ) -> Result<(), Unsupported> { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "a struct construction needs a constant type name and a map of fields", + }); } let name_reg = base.wrapping_add(1); - let type_name = { - let nv = ssa.read(name_reg, block, pc).ok().map(|(v, _)| v); - nv.and_then(|v| ssa.const_strs.get(&v).cloned()) - .or_else(|| ssa.reg_const_str(name_reg, block)) - } - .ok_or(Unsupported::Opcode { pc, op: Opcode::Call })?; + let type_name = ssa.const_str_at(name_reg, block, pc).ok_or(Unsupported::CallShape { + pc, + reason: "a struct construction needs a constant type name and a map of fields", + })?; let (fv, fty) = ssa.read(base.wrapping_add(2), block, pc)?; let fields = to_dyn_map_handle(ssa, insts, fv, fty, pc)?; let dst = ssa.new_val(); @@ -170,104 +226,17 @@ pub(crate) fn lower_make_struct( }); insts.push(Inst::Call { dst: None, - callee: AbiRef::new("map_h", "obj_mark"), + // The checked mark: this shape rebuilt the map from a base, so its + // entries were never measured against the declaration. + callee: AbiRef::new("map_h", "obj_mark_checked"), args: vec![dst, tid_v], }); } - ssa.struct_types.insert(dst, type_name); + ssa.set_struct(dst, type_name); ssa.write(base, block, (dst, Ty::MapStrDyn)); Ok(()) } -/// `try$call(closure)` — the try/catch desugar's protected call (plan G). -/// The body closure lowers as a normal `Dyn`-returning function; the call -/// site emits [`Inst::TryCall`], which codegen expands into `rt.try_push` + -/// `_setjmp` + a conditional body call joining into the `[ok, value]` dyn -/// list the desugared destructuring consumes. Mutable captures (`UpvalCell`) -/// materialize as *runtime cells* across the boundary: the body writes -/// through the shared slot, and the caller re-reads it afterwards, so the -/// SSA-tracked cell world stays coherent. -#[allow(clippy::too_many_arguments)] -pub(crate) fn lower_try_call( - ssa: &mut Ssa, - insts: &mut Vec, - funcs: &[FunctionData], - entry: u32, - sig: &mut SigInfer, - base: u8, - argc: usize, - block: usize, - pc: usize, -) -> Result<(), Unsupported> { - if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); - } - let arg_reg = base.wrapping_add(1); - let (fidx, caps) = match ssa.builtin_ref_at(arg_reg, block) { - Some(GlobalRef::Closure(f, caps)) => (f as usize, caps), - Some(GlobalRef::Lambda(f)) => (f as usize, Vec::new()), - _ => return Err(Unsupported::Opcode { pc, op: Opcode::Call }), - }; - if fidx >= funcs.len() - || fidx == entry as usize - || funcs[fidx].param_count != 0 - || caps.len() != funcs[fidx].capture_count as usize - { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); - } - let mut args = Vec::with_capacity(caps.len()); - let mut cell_writebacks: Vec<(u32, ValueId)> = Vec::new(); - for (k, capture) in caps.iter().enumerate() { - let (v, ty) = match capture { - ClosureCapture::Cell(cid) => { - // Seed a runtime cell with the current content; the body - // mutates through it, the write-back below re-syncs. - let slot = ssa.cell_slot(*cid); - let (cur, cur_ty) = ssa.read_slot(slot, block, pc)?; - let boxed = to_dyn_any(ssa, insts, cur, cur_ty, pc)?; - let cell = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(cell), - callee: AbiRef::new("rt", "cell_new"), - args: vec![boxed], - }); - cell_writebacks.push((*cid, cell)); - (cell, Ty::Cell) - } - ClosureCapture::Value(v, ty) => (*v, *ty), - }; - let want = sig.observe_param(fidx, k, ty); - args.push(coerce_arg(ssa, insts, v, ty, want, pc)?); - } - // The body's return crosses the boundary boxed (`dyn_rets`, the same - // retriable convergence the dyn HOF family uses). - if !sig.dyn_rets.contains(&(fidx as u32)) { - sig.dyn_rets.insert(fidx as u32); - return Err(Unsupported::TypeMismatch { pc }); - } - if sig.ret_types.get(fidx).copied() != Some(Ty::Dyn) { - return Err(Unsupported::TypeMismatch { pc }); - } - let dst = ssa.new_val(); - insts.push(Inst::TryCall { - dst, - func: FuncId(fidx as u32), - args, - }); - for (cid, cell) in cell_writebacks { - let cur = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(cur), - callee: AbiRef::new("rt", "cell_get"), - args: vec![cell], - }); - let slot = ssa.cell_slot(cid); - ssa.write_slot(slot, block, (cur, Ty::Dyn)); - } - ssa.write(base, block, (dst, Ty::ListDyn)); - Ok(()) -} - /// Lowers a call to user function `callee_idx` with the register-window layout /// shared by `CallDirect` and indirect `Call` (callee/result at `dst_reg`, /// args at `[dst_reg+1, dst_reg+1+argc)`): reads the typed arguments, refines @@ -283,6 +252,7 @@ pub(crate) fn lower_user_call( funcs: &[FunctionData], entry: u32, sig: &mut SigInfer, + cap_ctx: CaptureCtx<'_>, callee_idx: usize, dst_reg: u8, argc: usize, @@ -302,7 +272,8 @@ pub(crate) fn lower_user_call( op: Opcode::CallDirect, }); } - if captures.len() != funcs[callee_idx].capture_count as usize { + let capture_count = funcs[callee_idx].capture_count as usize; + if captures.len() != capture_count && !sig.captures_all_static(callee_idx, capture_count) { return Err(Unsupported::Opcode { pc, op: Opcode::CallDirect, @@ -356,20 +327,13 @@ pub(crate) fn lower_user_call( sig.conflict = true; return Err(Unsupported::TypeMismatch { pc }); } - let clone = sig.param_obs.len() as u32; let env_total: usize = identity.iter().flatten().map(|id| id.captures as usize).sum(); - sig.param_obs.push(vec![ - None; - funcs[callee_idx].param_count as usize - + env_total - + funcs[callee_idx].capture_count as usize - ]); - sig.ret_types.push(sig.ret_types[callee_idx]); - sig.ret_known - .push(sig.ret_known.get(callee_idx).copied().unwrap_or(false)); - sig.ret_closures.push(None); - sig.ret_closure_poisoned.push(false); - sig.lambda_params.push(identity.clone()); + let arity = + funcs[callee_idx].param_count as usize + env_total + funcs[callee_idx].capture_count as usize; + let ret_known = sig.ret_known.get(callee_idx).copied().unwrap_or(false); + let clone = sig.push_function(vec![None; arity], sig.ret_types[callee_idx]); + sig.ret_known[clone as usize] = ret_known; + sig.lambda_params[clone as usize] = identity.clone(); sig.specializations.insert(key, clone); sig.pending_clones.push(callee_idx as u32); clone as usize @@ -397,7 +361,7 @@ pub(crate) fn lower_user_call( // Nullable shapes have no typed capture form: box to Dyn, so the // eventual consumer joins its parameter to Dyn like any call site. let (v, ty) = if matches!(ty, Ty::Nil | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool) { - (to_dyn_any(ssa, insts, v, ty, pc)?, Ty::Dyn) + (to_dyn(ssa, insts, v, ty, pc)?, Ty::Dyn) } else { (v, ty) }; @@ -406,7 +370,7 @@ pub(crate) fn lower_user_call( if (dst_reg as usize) < ssa.reg_count { ssa.current_def[block][dst_reg as usize] = None; } - ssa.builtin_regs.insert((block, dst_reg), GlobalRef::Closure(lf, caps)); + ssa.bind_ref(block, dst_reg, GlobalRef::Closure(lf, caps)); return Ok(()); } // Tier 1 bridge call (`docs/aot/tier1-hybrid.md`): the callee runs on the @@ -459,17 +423,24 @@ pub(crate) fn lower_user_call( // Erased capturing closure: its environment (resolved to current // cell contents at this call site) travels as hidden trailing // arguments, in parameter order. - Some(_) => { + Some(LambdaIdentity { fidx: lambda, .. }) => { let Some(GlobalRef::Closure(_, caps)) = ssa.builtin_ref_at(arg_reg, block) else { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "the callee does not resolve to a statically known function", + }); }; - for capture in &caps { - let (v, ty) = match capture { - ClosureCapture::Cell(cid) => { + let site = CaptureSite::new(cap_ctx, lambda, CaptureMode::Share, block, pc); + for (k, capture) in caps.iter().enumerate() { + let (v, ty) = match site.resolve(ssa, insts, sig, capture, k)? { + Some(resolved) => resolved, + None => { + let ClosureCapture::Cell(cid) = capture else { + unreachable!("only `Cell` is left to the call site") + }; let slot = ssa.cell_slot(*cid); ssa.read_slot(slot, block, pc)? } - ClosureCapture::Value(v, ty) => (*v, *ty), }; env_args.push((v, ty)); } @@ -482,8 +453,44 @@ pub(crate) fn lower_user_call( // nullable carriers intact: they observe as `Dyn` and box, so the // callee receives nil as nil (VM call semantics) instead of the // scalar-context unwrap abort. - let (aval, aty) = ssa.read(arg_reg, block, pc)?; - let want = sig.observe_param(callee_idx, i, aty); + // Through `read_value`: an argument that is a lambda the callee cannot + // erase — a struct constructor's field, say — becomes a closure value + // here. A lambda the callee *can* erase never reaches this line; the + // identity vector above took it. + let (aval, aty) = read_value(ssa, insts, sig, funcs, cap_ctx, arg_reg, block, pc)?; + let want = sig.observe_param(callee_idx, i, aty, ssa.struct_facts.get(&aval)); + // A typed container reaching an erased parameter has to be built Dyn. + // + // `want` is `Dyn` here because two call sites disagreed on the + // carrier, so the callee sees the list only through its tag and a + // `push` goes to `dyn.list_push`. That push may widen — and a + // `Vec` cannot become a `Vec` after the fact, because the + // caller's aliases read the old allocation. The VM widens the carrier + // in place, so the only representation both backends can agree on is + // a Dyn list from the literal onward. Same retry channel as a + // contradicted `[]`: the fixpoint rebuilds the literal and this call + // site then passes a `ListDyn`, which does not re-trigger. + // + // Two ways to learn that: `want` is `Dyn` (two call sites disagreed on + // the carrier, so the callee pushes through `dyn.list_push` and the + // widening is invisible to it at compile time), or the callee was + // lowered once and reported the push itself (`dyn_params`), which is + // the monomorphic case a single call site produces. + if matches!( + aty, + Ty::ListI64 + | Ty::ListF64 + | Ty::ListStr + | Ty::MapStrI64 + | Ty::MapStrF64 + | Ty::MapStrBool + | Ty::MapI64I64 + | Ty::MapI64F64 + ) && (want == Ty::Dyn || sig.dyn_params.contains(&(callee_idx as u32, i as u8))) + && let Some(unsupported) = crate::inst::container::carrier_contradicted(ssa, aval, aty) + { + return Err(unsupported); + } arg_tys.push(want); args.push(coerce_arg(ssa, insts, aval, aty, want, pc)?); } @@ -491,13 +498,13 @@ pub(crate) fn lower_user_call( // environment values first, then the callee's own captures. Their types // refine the same monomorphization lattice as visible parameters. for (k, &(ev, ety)) in env_args.iter().enumerate() { - let want = sig.observe_param(callee_idx, argc + k, ety); + let want = sig.observe_param(callee_idx, argc + k, ety, ssa.struct_facts.get(&ev)); arg_tys.push(want); args.push(coerce_arg(ssa, insts, ev, ety, want, pc)?); } let env_total = env_args.len(); for (k, &(cval, cty)) in captures.iter().enumerate() { - let want = sig.observe_param(callee_idx, argc + env_total + k, cty); + let want = sig.observe_param(callee_idx, argc + env_total + k, cty, ssa.struct_facts.get(&cval)); arg_tys.push(want); args.push(coerce_arg(ssa, insts, cval, cty, want, pc)?); } @@ -553,7 +560,369 @@ pub(crate) fn lower_user_call( func: FuncId(callee_idx as u32), args, }); + seed_ret_struct(ssa, sig, callee_idx, dst); ssa.write(dst_reg, block, (dst, ret)); } Ok(()) } + +/// `CallNamed` — a call written with `name: value` arguments. +/// +/// The whole opcode had no native lowering, so every named call dropped its +/// module to the VM. That became load-bearing when `module.Type { … }` started +/// desugaring to one (`stmt::struct_ctors`), which is how a cross-module struct +/// literal is built. +/// +/// It devirtualizes the same way a positional call does, plus one step: the +/// argument *order*. The window is `[base]` callee, `positional` values, then +/// `named_count` (name, value) pairs — and every name is a string constant the +/// compiler emitted, so the permutation into the callee's frame order is a +/// compile-time fact. `FunctionData::param_names` is that order, and +/// `positional_param_count` is where the named ones begin. +/// +/// Rejects rather than guesses when anything is not statically known: a name +/// that is not a constant, a callee with no name metadata, a missing or +/// duplicate name, or a parameter with a default the call site omits (the +/// default expression lives in the callee's body, which the VM evaluates on +/// entry — there is nothing to read here). +#[allow(clippy::too_many_arguments)] +pub(crate) fn lower_named_call( + ssa: &mut Ssa, + insts: &mut Vec, + funcs: &[FunctionData], + entry: u32, + sig: &mut SigInfer, + cap_ctx: CaptureCtx<'_>, + callee_idx: usize, + base: u8, + positional_count: usize, + named_count: usize, + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + let reject = || Unsupported::Opcode { + pc, + op: Opcode::CallNamed, + }; + let callee = funcs.get(callee_idx).ok_or_else(reject)?; + if callee_idx == entry as usize || callee.capture_count != 0 { + return Err(reject()); + } + let param_count = callee.param_count as usize; + let declared_positional = callee.positional_param_count as usize; + if callee.param_names.len() != param_count + || positional_count != declared_positional + || positional_count + named_count != param_count + { + return Err(reject()); + } + + // Frame order: the positional prefix as written, then each named parameter + // filled from whichever pair carries its name. + let mut args: Vec> = vec![None; param_count]; + for (i, slot) in args.iter_mut().enumerate().take(positional_count) { + // Through `read_value`, so a lambda written as a field of a struct + // literal becomes a closure: `H { f: |x| x + 1 }` desugars to a named + // call, and this is where its arguments are read. + let arg_reg = base.wrapping_add(1).wrapping_add(i as u8); + *slot = Some(read_value(ssa, insts, sig, funcs, cap_ctx, arg_reg, block, pc)?); + } + for pair in 0..named_count { + let name_reg = base + .wrapping_add(1) + .wrapping_add(positional_count as u8) + .wrapping_add((pair * 2) as u8); + let value_reg = name_reg.wrapping_add(1); + let name = ssa.const_str_at(name_reg, block, pc).ok_or_else(reject)?; + let slot = callee.param_names[declared_positional..] + .iter() + .position(|param| &**param == name.as_str()) + .ok_or_else(reject)? + + declared_positional; + if args[slot].is_some() { + return Err(reject()); + } + args[slot] = Some(read_value(ssa, insts, sig, funcs, cap_ctx, value_reg, block, pc)?); + } + let args = args.into_iter().collect::>>().ok_or_else(reject)?; + + let (dst, ty) = emit_call_with_args(ssa, insts, funcs, entry, sig, callee_idx, args, Opcode::CallNamed, pc)?; + ssa.write(base, block, (dst, ty)); + Ok(()) +} + +/// The runtime's closure arity switch (`lkrt::lkclosure`), counting visible +/// parameters and captures together. +pub(crate) const LK_CLOSURE_MAX_ARGS: usize = 8; + +/// Reads a register **as a value**, building a closure for it when it names a +/// lambda. +/// +/// The one entry point for "I need a value here". A register that names a +/// lambda holds a compile-time reference and no SSA value, and the sites that +/// need one — a container store, an argument, an indirect call — are exactly +/// the sites that reported `ReferenceAsValue`. Materializing *here*, at the +/// consumer, is what keeps a register to one meaning: binding both a reference +/// and a value to it was tried and every mover that carried one and not the +/// other produced a different wrong answer (`docs/aot/aot-gaps-and-lkrt.md` +/// §30). +#[allow(clippy::too_many_arguments)] +pub(crate) fn read_value( + ssa: &mut Ssa, + insts: &mut Vec, + sig: &mut SigInfer, + funcs: &[FunctionData], + cap_ctx: CaptureCtx<'_>, + reg: u8, + block: usize, + pc: usize, +) -> Result { + if let Some(global_ref) = ssa.builtin_ref_at(reg, block) + && let Some(value) = materialize_closure(ssa, insts, sig, funcs, cap_ctx, &global_ref, block, pc)? + { + return Ok(value); + } + ssa.read(reg, block, pc) +} + +/// Builds a lambda's runtime closure value. +/// +/// `None` when the program has not asked for one: a closure that is only built +/// and called stays a compile-time reference and keeps devirtualizing, which is +/// why this is demand-driven rather than uniform. +/// +/// The address taken is the *clone*'s (`SigInfer::value_lambdas`), whose +/// signature is all-`Dyn`. The environment travels in the same argument block a +/// `spawn` builds, and the runtime appends it at the call — the order the +/// native signature already declares. +#[allow(clippy::too_many_arguments)] +pub(crate) fn materialize_closure( + ssa: &mut Ssa, + insts: &mut Vec, + sig: &mut SigInfer, + funcs: &[FunctionData], + cap_ctx: CaptureCtx<'_>, + global_ref: &GlobalRef, + block: usize, + pc: usize, +) -> Result, Unsupported> { + let (fidx, captures) = match global_ref { + GlobalRef::Lambda(fidx) | GlobalRef::UserFn(fidx) => (*fidx, Vec::new()), + GlobalRef::Closure(fidx, captures) => (*fidx, captures.clone()), + _ => return Ok(None), + }; + let Some(&body) = sig.value_lambdas.get(&fidx) else { + return Ok(None); + }; + let callee = funcs.get(fidx as usize).ok_or(Unsupported::BadConst { pc })?; + // A lambda whose environment is *entirely* static references carries + // nothing at run time, so `MakeClosure` recorded it as a bare `Lambda` + // (`captures_all_static`) — correct for a call that resolves those + // references statically, and wrong for a value, whose clone still has that + // many capture parameters and nothing to fill them with. It read past the + // end of an empty environment and called whatever it found: + // + // let add = |x| x + 1; + // let fs = [|y| add(y) * 10]; + // fs[0](2) // 30 interpreted, "value is not callable" compiled + // + // So the environment is rebuilt from the references themselves, which the + // loop below then materializes one by one. + let captures = if captures.is_empty() && callee.capture_count > 0 { + vec![ClosureCapture::StaticRef; callee.capture_count as usize] + } else { + captures + }; + if callee.param_count as usize + captures.len() > LK_CLOSURE_MAX_ARGS { + return Err(Unsupported::CallShape { + pc, + reason: "a closure value with this many parameters and captures is past the runtime's arity switch", + }); + } + let env = if captures.is_empty() { + None + } else { + let block_v = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(block_v), + callee: AbiRef::new("rt", "spawn_args_new"), + args: Vec::new(), + }); + // A closure outlives the frame that built it, so a cell's *content* + // crosses into it — the same snapshot a goroutine takes. + let site = CaptureSite::new(cap_ctx, body, CaptureMode::Snapshot, block, pc); + for (k, capture) in captures.iter().enumerate() { + // A capture whose whole meaning is a *callable reference* — the + // lambda captured another lambda, and the environment slot carries + // a dead `0` because the callee resolves it statically. A closure + // *value* cannot: nothing resolves its environment later, so the + // reference has to become a value too, recursively. + // + // Without this the slot really would carry the `0`, and calling the + // capture answered "value is not callable" for a function that + // exists. `fn twice(f) { return |x| f(f(x)); }` is the shape. + if matches!(capture, ClosureCapture::StaticRef) { + let Some(referenced) = sig.ref_captures.get(&(fidx, k)).cloned() else { + return Err(Unsupported::CallShape { + pc, + reason: "a closure value captures a callable this lowering cannot name", + }); + }; + let Some((v, ty)) = materialize_closure(ssa, insts, sig, funcs, cap_ctx, &referenced, block, pc)? + else { + // The referenced callable is not a value lambda *yet*: ask + // for it the way every other consumer does, so the fixpoint + // records the demand and the next pass finds it. + return Err(Unsupported::ReferenceAsValue { + pc, + reg: 0, + what: referenced.describe(), + lambda: match referenced { + GlobalRef::Lambda(f) | GlobalRef::Closure(f, _) | GlobalRef::UserFn(f) => Some(f), + _ => None, + }, + }); + }; + let boxed = to_dyn(ssa, insts, v, ty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "spawn_args_push"), + args: vec![block_v, boxed], + }); + continue; + } + let (v, ty) = match site.resolve(ssa, insts, sig, capture, k)? { + Some(resolved) => resolved, + None => { + let ClosureCapture::Cell(cid) = capture else { + unreachable!("only `Cell` is left to the call site") + }; + ssa.read_slot(ssa.cell_slot(*cid), block, pc)? + } + }; + let boxed = to_dyn(ssa, insts, v, ty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "spawn_args_push"), + args: vec![block_v, boxed], + }); + } + Some(block_v) + }; + let code = ssa.new_val(); + insts.push(Inst::Const { + dst: code, + value: Const::FnAddr(FuncId(body)), + }); + let env_is_empty = env.is_none(); + let env_ptr = match env { + Some(block_v) => block_v, + None => { + let null = ssa.new_val(); + insts.push(Inst::Const { + dst: null, + value: Const::I64(0), + }); + null + } + }; + let params = ssa.new_val(); + insts.push(Inst::Const { + dst: params, + value: Const::I64(i64::from(callee.param_count)), + }); + // Only so `display` prints what the interpreter prints. The *original* + // index, not the clone's: the clone is this pipeline's bookkeeping and no + // program can observe it. + let index = ssa.new_val(); + insts.push(Inst::Const { + dst: index, + value: Const::I64(i64::from(fidx)), + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("rt", "closure_new"), + args: vec![code, env_ptr, params, index], + }); + ssa.closure_values.insert(dst); + if env_is_empty { + ssa.closure_fidx.insert(dst, fidx); + } + Ok(Some((dst, Ty::Dyn))) +} + +/// `f(args…)` where `f` is an ordinary value: a closure built by +/// [`materialize_closure`], reached through the runtime's arity switch. +pub(crate) fn lower_dyn_call( + ssa: &mut Ssa, + insts: &mut Vec, + base: u8, + argc: usize, + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + // Through `read_scalar`, so a carrier unwraps first: a closure that came + // out of a list is a `Maybe`, and handing the carrier to the runtime made + // it answer "value is not callable" for a value that is one. + let callee = read_scalar(ssa, insts, base, block, pc)?; + lower_dyn_call_to(ssa, insts, callee, base, argc, block, pc) +} + +/// [`lower_dyn_call`] with the callee already in hand — for the sites where the +/// register names it rather than holding it, which a capture parameter does. +pub(crate) fn lower_dyn_call_to( + ssa: &mut Ssa, + insts: &mut Vec, + callee: Reg, + base: u8, + argc: usize, + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + if argc > LK_CLOSURE_MAX_ARGS { + return Err(Unsupported::CallShape { + pc, + reason: "a call through a closure value with this many arguments is past the runtime's arity switch", + }); + } + let (callee, callee_ty) = callee; + let callee = if callee_ty == Ty::Dyn { + callee + } else { + to_dyn(ssa, insts, callee, callee_ty, pc)? + }; + let args = if argc == 0 { + let null = ssa.new_val(); + insts.push(Inst::Const { + dst: null, + value: Const::I64(0), + }); + null + } else { + let block_v = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(block_v), + callee: AbiRef::new("rt", "spawn_args_new"), + args: Vec::new(), + }); + for i in 0..argc { + let (v, ty) = ssa.read(base.wrapping_add(1).wrapping_add(i as u8), block, pc)?; + let boxed = to_dyn(ssa, insts, v, ty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "spawn_args_push"), + args: vec![block_v, boxed], + }); + } + block_v + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("rt", "closure_call"), + args: vec![callee, args], + }); + ssa.write(base, block, (dst, Ty::Dyn)); + Ok(()) +} diff --git a/aot/lower/src/lower_method.rs b/aot/lower/src/lower_method.rs index 2fda68fc..70fdca19 100644 --- a/aot/lower/src/lower_method.rs +++ b/aot/lower/src/lower_method.rs @@ -15,18 +15,21 @@ pub(crate) fn lower_method_call( ) -> Result<(), Unsupported> { let (receiver, receiver_ty) = ssa.read(base.wrapping_add(1), block, pc)?; let name_reg = base.wrapping_add(2); - let name = { - let name_v = ssa.read(name_reg, block, pc).ok().map(|(v, _)| v); - name_v - .and_then(|v| ssa.const_strs.get(&v).cloned()) - .or_else(|| ssa.reg_const_str(name_reg, block)) - }; + let name = { ssa.const_str_at(name_reg, block, pc) }; let Some(name) = name else { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this method on this receiver type", + }); }; let args = match ssa.builtin_regs.get(&(block, base.wrapping_add(3))) { Some(GlobalRef::ArgList(elems)) => elems.clone(), - _ => return Err(Unsupported::Opcode { pc, op: Opcode::Call }), + _ => { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this method on this receiver type", + }); + } }; let result = lower_method_dispatch(ssa, insts, globals, receiver, receiver_ty, &name, &args, block, pc)?; ssa.write(base, block, result); @@ -57,12 +60,49 @@ pub(crate) fn lower_method_call_k( .get(instr.b() as usize) .ok_or(Unsupported::BadConst { pc })? .clone(); - let (receiver, receiver_ty) = ssa.read(base, block, pc)?; let argc = instr.c() as usize; + // A **module object** receiver is a module function call, not a method + // call: `encoding.json.parse(s)` compiles to `CallMethodK` with `parse` as + // the name and `encoding.json` as the receiver, and there was no arm for + // that — the receiver holds a lowering-time ref, not an SSA value, so the + // read below reported "register r7 is read before any definition" and the + // whole program fell back. Only the selective import + // (`use { json } from encoding;`) lowered. + if let Some(GlobalRef::Module(module)) = ssa.builtin_ref_at(base, block) { + // `lower_module_call` reads its arguments from `base + 1`, which is + // where a method call's arguments already sit. + return lower_module_call(ssa, insts, &module, &name, base, argc, block, pc); + } + // A `Maybe` receiver unwraps first, which is what the VM does: calling a + // method on an absent one raises (`lkrt_maybe_*_unwrap` raises too, so the + // two agree, including on being catchable). Without it a list's loop + // variable — a `Maybe`, since the element read is bounds-checked — could be + // printed but not asked anything: `for s in ["ab", "cde"] { s.len() }` + // dropped the whole program to the VM. + let (receiver, receiver_ty) = read_scalar(ssa, insts, base, block, pc)?; // A boxed Dyn receiver unwraps through the as_list guard for list-only // method names (a non-list tag aborts — the VM's method-on-wrong-type is // a loud error too). Names shared with str/map receivers stay boxed. let role = method_role(&name); + /// The methods whose answer is a list of the receiver's elements, whatever + /// carrier held them. See the arm below. + /// + /// `flatten` is not here: a `Bytes` and an `i64` window hold scalars, so + /// flattening one is a no-op and the checker declines it. `join` is not + /// here either — the bytecode compiler matches it by name into the fused + /// `ListJoin`, so no method call by that name reaches this. + /// + /// `concat` is here for a window and **not** for a `Bytes`: two byte + /// strings joined are a byte string, so that one keeps its carrier and has + /// its own arm. Taking it away would answer a `List` for a shape the + /// language already spells as `Bytes`. + fn answers_a_list_of_the_elements(receiver_ty: Ty, name: &str) -> bool { + match receiver_ty { + Ty::Bytes => matches!(name, "enumerate" | "zip" | "chain" | "chunk"), + Ty::SliceI64 => matches!(name, "enumerate" | "zip" | "chain" | "chunk" | "concat"), + _ => false, + } + } let (receiver, receiver_ty) = if receiver_ty == Ty::Dyn && role.is_some_and(|role| role.unbox_list) { let unboxed = ssa.new_val(); insts.push(Inst::Call { @@ -71,17 +111,23 @@ pub(crate) fn lower_method_call_k( args: vec![receiver], }); (unboxed, Ty::ListDyn) - } else if receiver_ty == Ty::Dyn && role.is_some_and(|role| role.unbox_map) { - // Map-only method names unbox through the as_map guard (a parsed - // json/yaml value flows as Dyn); `get` stays ambiguous (lists have - // it too) and keeps rejecting. - let unboxed = ssa.new_val(); + } else if answers_a_list_of_the_elements(receiver_ty, &name) { + // The operations whose answer is a *list of the elements*: they mean + // the same on `Bytes` and on a window as on a `List`, and cannot keep + // the carrier, so they are the list's — reached by materializing once + // and letting the list arms run. Six arms per carrier would be six + // copies of `enumerate`'s pairing and `chunk`'s grouping, and the VM + // delegates for exactly that reason. + let list = ssa.new_val(); insts.push(Inst::Call { - dst: Some(unboxed), - callee: AbiRef::new("dyn", "as_map"), + dst: Some(list), + callee: match receiver_ty { + Ty::Bytes => AbiRef::new("bytes_h", "to_i64_list"), + _ => AbiRef::new("slice_h", "i64_to_list"), + }, args: vec![receiver], }); - (unboxed, Ty::MapStrDyn) + (list, Ty::ListI64) } else { (receiver, receiver_ty) }; @@ -108,15 +154,52 @@ pub(crate) fn lower_method_call_k( // List HOF with a compiled zero-capture lambda callback (fn-pointer ABI): // handled before the generic argument reads, because the lambda register // carries a `GlobalRef::Lambda`, not an SSA value. - if matches!(receiver_ty, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn) + // + // `Bytes` joins by *becoming* an `Int` list first. Its elements are byte + // values, so `to_i64_list` loses nothing, and the channel below then answers + // the same shapes the VM does: `map` and `reduce` are already list-shaped + // there, and only `filter` has to come back — the VM keeps a filtered + // `Bytes` as `Bytes`, because filtering removes elements without changing + // any. Without this the three closure methods were the last of the fourteen + // still dropping their module to the VM. + // + // A `Slice` joins the same way and for the same reason, with one difference + // in the other direction: `w.filter(f)` answers a **List**, not a window + // (`builtin_method_sig` says so — a window is a range of its source, and a + // filtered window is not one), so nothing has to come back. + let hof_receiver = + if matches!(receiver_ty, Ty::Bytes | Ty::SliceI64) && matches!(name.as_str(), "map" | "filter" | "reduce") { + let listed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(listed), + callee: AbiRef::new( + if receiver_ty == Ty::Bytes { "bytes_h" } else { "slice_h" }, + if receiver_ty == Ty::Bytes { + "to_i64_list" + } else { + "i64_to_list" + }, + ), + args: vec![receiver], + }); + Some(listed) + } else { + None + }; + let hof_ty = if hof_receiver.is_some() { + Ty::ListI64 + } else { + receiver_ty + }; + if matches!(hof_ty, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn) && let Some(result) = lower_list_hof_k( ssa, insts, funcs, entry, sig, - receiver, - receiver_ty, + hof_receiver.unwrap_or(receiver), + hof_ty, &name, base, argc, @@ -124,6 +207,18 @@ pub(crate) fn lower_method_call_k( pc, )? { + let result = match (hof_receiver, name.as_str()) { + (Some(_), "filter") if receiver_ty == Ty::Bytes => { + let bytes = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(bytes), + callee: AbiRef::new("bytes_h", "from_i64_list"), + args: vec![result.0], + }); + (bytes, Ty::Bytes) + } + _ => result, + }; ssa.write(base, block, result); return Ok(()); } @@ -150,6 +245,71 @@ pub(crate) fn lower_method_call_k( /// return via `dyn_rets` — both retriable discoveries). /// /// Returns `Ok(None)` when neither shape applies (generic dispatch decides). +/// The `impl` registered for a built-in type constructor, by base name. +/// +/// The table is keyed by the impl target's *type text*, and a container's is +/// written out: `impl List` is recorded as `List`, `impl Map` as +/// `Map`. Matching the base name is exact rather than a guess, +/// because the language refuses an impl that names an element type — "`List` +/// is not distinguishable from another element type at run time — write `List`" +/// — so a constructor has at most one impl block's worth of methods. +/// +/// Linear, like `TraitEnv::impl_owner` beside it and for the same reason: impl +/// blocks are counted in the dozens. +fn builtin_impl_for<'a>(sig: &'a SigInfer, type_name: &str, method: &str) -> Option<&'a u32> { + sig.traits.impls.iter().find_map(|((target, name), fidx)| { + let base = target.split('<').next().unwrap_or(target); + (base == type_name && name == method).then_some(fidx) + }) +} + +/// The language's name for a built-in receiver, as an `impl` block spells it. +/// +/// `None` for the carriers that are not a type a program can write an `impl` +/// for — a `Maybe`, a cell, a boxed `Dyn` whose real type is only known at run +/// time (that one dispatches through `traits.methods` instead). +fn builtin_impl_type_name(ty: Ty) -> Option<&'static str> { + match ty { + Ty::Nil => Some("Nil"), + Ty::Bool => Some("Bool"), + Ty::I64 => Some("Int"), + Ty::F64 => Some("Float"), + Ty::Str => Some("String"), + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn => Some("List"), + // A window's impl target is `Slice`, not `List`: the interpreter + // dispatches it as `Slice`. + Ty::SliceI64 => Some("Slice"), + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapI64I64 | Ty::MapI64F64 => Some("Map"), + Ty::Set => Some("Set"), + Ty::Bytes => Some("Bytes"), + // `MapStrDyn` is the struct carrier as well as a map, and the struct + // arm above claims it first. + _ => None, + } +} + +/// Whether the built-in method table declares `name` for this receiver. +/// +/// The precedence check for the arm above, asked of `builtin_method_arity` — +/// the same declaration `lk check` reads — so a method added to the language +/// cannot be shadowed here by an `impl` that predates it. +fn builtin_declares_method(ty: Ty, name: &str) -> bool { + use lk_core::typ::BuiltinReceiverKind; + let kind = match ty { + Ty::Str => BuiltinReceiverKind::Str, + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn => BuiltinReceiverKind::List, + Ty::SliceI64 => BuiltinReceiverKind::Slice, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 => { + BuiltinReceiverKind::Map + } + Ty::Set => BuiltinReceiverKind::Set, + Ty::Bytes => BuiltinReceiverKind::Bytes, + // A scalar has no built-in method surface at all, so nothing to shadow. + _ => return false, + }; + lk_core::typ::builtin_method_arity(kind, name).is_some() +} + #[allow(clippy::too_many_arguments)] pub(crate) fn lower_trait_method_k( ssa: &mut Ssa, @@ -165,8 +325,40 @@ pub(crate) fn lower_trait_method_k( block: usize, pc: usize, ) -> Result, Unsupported> { + // `impl Int { fn dbl(self) … }` — a user method on a *built-in* receiver. + // The struct case below has always dispatched; this one had no path at all, + // so `(5).dbl()`, `"a".shout()` and `[1,2].second()` each dropped their + // whole module to the VM. + // + // Only when the built-in table declares nothing by that name for this + // receiver, because that is the interpreter's precedence: `impl List { fn + // len(self) -> Int { return 99; } }` does not shadow `len`, and + // `[1, 2].len()` is 2. Asked of the same table the checker asks, rather + // than of a list kept here. + if let Some(type_name) = builtin_impl_type_name(receiver_ty) + && !builtin_declares_method(receiver_ty, name) + && let Some(&fidx) = builtin_impl_for(sig, type_name, name) + { + let mut call_args = Vec::with_capacity(argc + 1); + call_args.push((receiver, receiver_ty)); + for i in 0..argc { + call_args.push(ssa.read(base.wrapping_add(1).wrapping_add(i as u8), block, pc)?); + } + return emit_call_with_args( + ssa, + insts, + funcs, + entry, + sig, + fidx as usize, + call_args, + Opcode::CallMethodK, + pc, + ) + .map(Some); + } if receiver_ty == Ty::MapStrDyn - && let Some(type_name) = ssa.struct_types.get(&receiver).cloned() + && let Some(type_name) = ssa.struct_name(receiver).map(str::to_string) && let Some(&fidx) = sig.traits.impls.get(&(type_name, name.to_string())) { let mut call_args = Vec::with_capacity(argc + 1); @@ -174,10 +366,26 @@ pub(crate) fn lower_trait_method_k( for i in 0..argc { call_args.push(ssa.read(base.wrapping_add(1).wrapping_add(i as u8), block, pc)?); } - return emit_trait_call(ssa, insts, funcs, entry, sig, fidx as usize, call_args, pc).map(Some); + return emit_call_with_args( + ssa, + insts, + funcs, + entry, + sig, + fidx as usize, + call_args, + Opcode::CallMethodK, + pc, + ) + .map(Some); } - if receiver_ty == Ty::Dyn - && argc == 0 + // Runtime dispatch. The receiver may be boxed already (`Dyn`) or a struct + // carrier whose type the lowering could not name — a `MapStrDyn` parameter + // two call sites pass different structs to, which `param_structs` poisons + // on purpose. Both know their type at *run time*, in the arena mark this + // instruction reads, so both dispatch; only the `Dyn` case used to, and the + // other one took the whole module to the VM instead. + if matches!(receiver_ty, Ty::Dyn | Ty::MapStrDyn) && let Some(arms) = sig.traits.methods.get(name).cloned() && !arms.is_empty() { @@ -186,7 +394,10 @@ pub(crate) fn lower_trait_method_k( let f = fidx as usize; if f >= funcs.len() || fidx == entry - || funcs[f].param_count != 1 + // `self` plus the method's own arguments. Every arm is called + // through one rendered signature, so an arm of another arity is + // not a shape this can dispatch. + || funcs[f].param_count as usize != 1 + argc || funcs[f].capture_count != 0 || sig.specialized.get(f).copied().unwrap_or(false) { @@ -195,7 +406,11 @@ pub(crate) fn lower_trait_method_k( if let Some(flag) = sig.plain_called.get_mut(f) { *flag = true; } - sig.observe_param(f, 0, Ty::Dyn); + // A runtime-dispatched arm receives `self` and every argument + // boxed, so its parameters are `Dyn` and carry no struct name. + for slot in 0..=argc { + sig.observe_param(f, slot, Ty::Dyn, None); + } if !sig.dyn_rets.contains(&fidx) { sig.dyn_rets.insert(fidx); retry = true; @@ -209,10 +424,22 @@ pub(crate) fn lower_trait_method_k( if retry { return Err(Unsupported::TypeMismatch { pc }); } + // Read the arguments *before* boxing the receiver, so a failure leaves + // no half-emitted boxing in the stream. + let mut raw_args = Vec::with_capacity(argc); + for i in 0..argc { + raw_args.push(ssa.read(base.wrapping_add(1).wrapping_add(i as u8), block, pc)?); + } + let self_arg = to_dyn(ssa, insts, receiver, receiver_ty, pc)?; + let mut args = Vec::with_capacity(argc); + for (v, ty) in raw_args { + args.push(to_dyn(ssa, insts, v, ty, pc)?); + } let dst = ssa.new_val(); insts.push(Inst::TraitDispatch { dst, - self_arg: receiver, + self_arg, + args, arms: arms.iter().map(|&(tid, f)| (tid, FuncId(f))).collect(), }); return Ok(Some((dst, Ty::Dyn))); @@ -220,10 +447,17 @@ pub(crate) fn lower_trait_method_k( Ok(None) } -/// Emits a devirtualized trait-impl call (`self` is the first argument), -/// refining the callee's signature through the shared parameter lattice. +/// Emits a devirtualized call to `fidx` with arguments **already in frame +/// order**, refining the callee's signature through the shared parameter +/// lattice. +/// +/// The window-order readers (`lower_user_call`) cannot serve a call whose +/// arguments are not laid out in parameter order: trait dispatch puts `self` +/// first, and a named call (`lower_named_call`) permutes by name. `label` is +/// the opcode a rejection should name, since that is the only thing the two +/// callers do not share. #[allow(clippy::too_many_arguments)] -pub(crate) fn emit_trait_call( +pub(crate) fn emit_call_with_args( ssa: &mut Ssa, insts: &mut Vec, funcs: &[FunctionData], @@ -231,6 +465,7 @@ pub(crate) fn emit_trait_call( sig: &mut SigInfer, fidx: usize, call_args: Vec<(ValueId, Ty)>, + label: Opcode, pc: usize, ) -> Result<(ValueId, Ty), Unsupported> { if fidx >= funcs.len() @@ -238,10 +473,7 @@ pub(crate) fn emit_trait_call( || funcs[fidx].param_count as usize != call_args.len() || funcs[fidx].capture_count != 0 { - return Err(Unsupported::Opcode { - pc, - op: Opcode::CallMethodK, - }); + return Err(Unsupported::Opcode { pc, op: label }); } if sig.specialized.get(fidx).copied().unwrap_or(false) { sig.conflict = true; @@ -252,7 +484,7 @@ pub(crate) fn emit_trait_call( } let mut args = Vec::with_capacity(call_args.len()); for (i, (v, ty)) in call_args.into_iter().enumerate() { - let want = sig.observe_param(fidx, i, ty); + let want = sig.observe_param(fidx, i, ty, ssa.struct_facts.get(&v)); args.push(coerce_arg(ssa, insts, v, ty, want, pc)?); } let ret = sig.ret_types.get(fidx).copied().unwrap_or(Ty::I64); @@ -275,9 +507,22 @@ pub(crate) fn emit_trait_call( func: FuncId(fidx as u32), args, }); + seed_ret_struct(ssa, sig, fidx, dst); Ok((dst, ret)) } +/// Records the struct a call's result is known to be (`sig.ret_structs`). +/// +/// The one place the callee's returned type name reaches the caller. Without it +/// the name stopped at the function boundary and `make(3, 4).norm()` had an +/// untyped receiver — the same missing-provenance failure as an `impl` method's +/// `self`, one call deeper. +pub(crate) fn seed_ret_struct(ssa: &mut Ssa, sig: &SigInfer, fidx: usize, dst: ValueId) { + if let Some(Some(fact)) = sig.ret_structs.get(&(fidx as u32)) { + ssa.struct_facts.insert(dst, fact.clone()); + } +} + /// The VM's auto-Display (`try_runtime_display_show`): `print`/`println` /// formatting and string interpolation call a struct instance's registered /// `show` method. Mirrors it in display contexts: an operand with struct @@ -295,10 +540,23 @@ pub(crate) fn apply_display_show( pc: usize, ) -> Result<(ValueId, Ty), Unsupported> { if ty == Ty::MapStrDyn - && let Some(type_name) = ssa.struct_types.get(&v).cloned() - && let Some(&fidx) = sig.traits.impls.get(&(type_name, "show".to_string())) + && let Some(type_name) = ssa.struct_name(v).map(str::to_string) + && let Some(&fidx) = sig + .traits + .impls + .get(&(type_name, crate::trait_env::IMPLICIT_METHOD_HOOKS[0].to_string())) { - return emit_trait_call(ssa, insts, funcs, entry, sig, fidx as usize, vec![(v, ty)], pc); + return emit_call_with_args( + ssa, + insts, + funcs, + entry, + sig, + fidx as usize, + vec![(v, ty)], + Opcode::CallMethodK, + pc, + ); } Ok((v, ty)) } @@ -331,9 +589,14 @@ pub(crate) fn lower_list_hof_k( block: usize, pc: usize, ) -> Result, Unsupported> { + // Either spelling of "this register names a capture-free lambda": the + // compile-time reference, or the closure value it becomes once the program + // also uses it as a value (`Ssa::closure_fidx`). let lambda_at = |ssa: &Ssa, reg: u8| match ssa.builtin_regs.get(&(block, reg)) { Some(GlobalRef::Lambda(fidx)) => Some(*fidx as usize), - _ => None, + _ => ssa + .peek(reg, block) + .and_then(|(v, _)| ssa.closure_fidx.get(&v).map(|&fidx| fidx as usize)), }; let elem = match receiver_ty { Ty::ListI64 => Ty::I64, @@ -344,7 +607,9 @@ pub(crate) fn lower_list_hof_k( }; let seed_params = |sig: &mut SigInfer, fidx: usize, arity: usize, ty: Ty| { for i in 0..arity { - sig.observe_param(fidx, i, ty); + // Callback parameters seeded from the receiver's element type, + // which is never a struct carrier here. + sig.observe_param(fidx, i, ty, None); } }; // The dyn family: convert the receiver, seed `Dyn` parameters; `map`/ @@ -353,10 +618,34 @@ pub(crate) fn lower_list_hof_k( let dyn_list_of = |ssa: &mut Ssa, insts: &mut Vec, receiver: ValueId| -> Result { to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc) }; + // A callback that is an ordinary value: not a lambda the lowering can name, + // but a `DYN_CLOSURE` at run time — `xs.map(fs[0])`, or a callback that + // arrived as a parameter. Same folds, called through the closure. + let closure_at = |ssa: &mut Ssa, insts: &mut Vec, reg: u8| -> Option { + match crate::convert::read_scalar(ssa, insts, reg, block, pc) { + Ok((v, Ty::Dyn)) => Some(v), + _ => None, + } + }; match (name, argc) { ("map" | "filter", 1) => { let Some(fidx) = lambda_at(ssa, base.wrapping_add(1)) else { - return Ok(None); + let Some(callee) = closure_at(ssa, insts, base.wrapping_add(1)) else { + return Ok(None); + }; + let list = dyn_list_of(ssa, insts, receiver)?; + let hof = if name == "filter" { + "dyn_filter_closure" + } else { + "dyn_map_closure" + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", hof), + args: vec![list, callee], + }); + return Ok(Some((dst, Ty::ListDyn))); }; if fidx >= funcs.len() || fidx == entry as usize || funcs[fidx].param_count != 1 { return Err(Unsupported::Opcode { @@ -452,7 +741,19 @@ pub(crate) fn lower_list_hof_k( } ("reduce", 2) => { let Some(fidx) = lambda_at(ssa, base.wrapping_add(2)) else { - return Ok(None); + let Some(callee) = closure_at(ssa, insts, base.wrapping_add(2)) else { + return Ok(None); + }; + let (init_raw, init_ty) = ssa.read(base.wrapping_add(1), block, pc)?; + let list = dyn_list_of(ssa, insts, receiver)?; + let init = to_dyn(ssa, insts, init_raw, init_ty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_reduce_closure"), + args: vec![list, init, callee], + }); + return Ok(Some((dst, Ty::Dyn))); }; if fidx >= funcs.len() || fidx == entry as usize || funcs[fidx].param_count != 2 { return Err(Unsupported::Opcode { @@ -512,7 +813,7 @@ pub(crate) fn lower_list_hof_k( return Err(Unsupported::TypeMismatch { pc }); } let list = dyn_list_of(ssa, insts, receiver)?; - let init = to_dyn_any(ssa, insts, init_raw, init_ty, pc)?; + let init = to_dyn(ssa, insts, init_raw, init_ty, pc)?; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), @@ -525,6 +826,176 @@ pub(crate) fn lower_list_hof_k( } } +/// See the note at the top of [`lower_method_dispatch`]. +#[allow(clippy::too_many_arguments)] +fn nullable_needle_in_typed_container( + ssa: &mut Ssa, + insts: &mut Vec, + globals: &mut Vec, + receiver: ValueId, + receiver_ty: Ty, + name: &str, + args: &[(ValueId, Ty)], + block: usize, + pc: usize, +) -> Result, Unsupported> { + if !matches!(name, "contains" | "count" | "index_of") { + return Ok(None); + } + // A Dyn container holds nil, so its own arms box the carrier and answer + // truthfully; only the typed ones need this. + if matches!(receiver_ty, Ty::ListDyn | Ty::Dyn | Ty::MapStrDyn) { + return Ok(None); + } + let [(needle, needle_ty)] = args else { + return Ok(None); + }; + let payload = match needle_ty { + Ty::MaybeI64 => Ty::I64, + Ty::MaybeF64 => Ty::F64, + Ty::MaybeStr => Ty::Str, + Ty::MaybeBool => Ty::Bool, + _ => return Ok(None), + }; + let present = ssa.new_val(); + insts.push(Inst::MaybePresent { + dst: present, + src: *needle, + maybe_ty: *needle_ty, + }); + let value = ssa.new_val(); + insts.push(Inst::MaybeValue { + dst: value, + src: *needle, + maybe_ty: *needle_ty, + }); + let (found, found_ty) = lower_method_dispatch( + ssa, + insts, + globals, + receiver, + receiver_ty, + name, + &[(value, payload)], + block, + pc, + )?; + // What the same call answers when the needle is not there. `contains` and + // `count` say so with a constant; `index_of` says nil, which is a boxed + // value and selects component-wise like any other carrier. + let missing = match found_ty { + Ty::Bool => { + let dst = ssa.new_val(); + insts.push(Inst::Const { + dst, + value: Const::Bool(false), + }); + dst + } + Ty::I64 => { + let dst = ssa.new_val(); + insts.push(Inst::Const { + dst, + value: Const::I64(0), + }); + dst + } + Ty::Dyn => { + let raw = ssa.new_val(); + insts.push(Inst::Const { + dst: raw, + value: Const::I64(0), + }); + crate::dyn_box::to_dyn(ssa, insts, raw, Ty::Nil, pc)? + } + // Some other answer shape: leave it to the ordinary arms, which will + // refuse rather than guess what "not found" means for it. + _ => return Ok(None), + }; + let dst = ssa.new_val(); + insts.push(Inst::Select { + dst, + cond: present, + then_v: found, + else_v: missing, + ty: found_ty, + }); + Ok(Some((dst, found_ty))) +} + +/// Whether the receiver can be *shown* not to hold a needle of this type. +/// +/// A byte string holds bytes, a string's members are its substrings, and a map +/// or set is keyed by nil/Bool/Int/String. When the needle's type is outside +/// what the container can hold, the interpreter answers "absent" — it does not +/// refuse — and the answer is the same for every value of that type, so it is a +/// constant rather than a call. +/// +/// The receiver must *have* the method first. A map has `has` and `delete` and +/// no `contains`, `index_of` or `count` at all, so folding those to "absent" +/// answered `{}.contains(x)` where the interpreter says "a Map has no method +/// `contains`". The pairs are listed rather than assumed for that reason. +/// +/// `Dyn` and the nullable carriers are never "shown" anything: they may be the +/// right kind at run time. +fn never_matches(receiver_ty: Ty, name: &str, needle_ty: Ty) -> bool { + let is_map = matches!( + receiver_ty, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 + ); + let has_method = match name { + // A sequence searches; a map does not, and a set answers `contains` + // only. + "contains" => !is_map, + "index_of" | "count" => !is_map && receiver_ty != Ty::Set, + "has" => is_map, + "delete" => is_map || receiver_ty == Ty::Set, + _ => false, + }; + if !has_method { + return false; + } + let concrete = !matches!( + needle_ty, + Ty::Dyn | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool + ); + if !concrete { + return false; + } + match receiver_ty { + Ty::Str => needle_ty != Ty::Str, + Ty::Bytes | Ty::SliceI64 => needle_ty != Ty::I64, + // A set's member type is not in the carrier, so the only thing shown + // here is that the value cannot be a member of *any* set. + Ty::Set => !matches!(needle_ty, Ty::Nil | Ty::Bool | Ty::I64 | Ty::Str), + // A map's key type *is* in the carrier, and it is the whole answer: a + // string-keyed map does not hold an Int key, whatever the Int is. + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn => needle_ty != Ty::Str, + Ty::MapI64I64 | Ty::MapI64F64 => needle_ty != Ty::I64, + _ => false, + } +} + +/// Whether a needle type reaches the carrier's own typed search helper. +/// +/// `Int` and `Float` both fit a numeric carrier, because the language compares +/// them as numbers — `1 in [1.0]` is true. Everything else is a value the +/// carrier cannot hold, and the search answers "absent" rather than refusing. +fn fits_carrier(receiver_ty: Ty, name: &str, needle_ty: Ty) -> bool { + match receiver_ty { + // `contains` has a helper for an `f64` needle against an `i64` list — + // `1.5 in [1, 2]` is a real question and the answer is `false` — while + // `index_of` and `count` have none and take the boxed route. + Ty::ListI64 if name == "contains" => matches!(needle_ty, Ty::I64 | Ty::F64), + Ty::ListI64 => needle_ty == Ty::I64, + // A `List` coerces an `Int` needle in its own arm. + Ty::ListF64 if name == "contains" => matches!(needle_ty, Ty::I64 | Ty::F64), + Ty::ListF64 => needle_ty == Ty::F64, + Ty::ListStr => needle_ty == Ty::Str, + _ => true, + } +} + /// The shared per-(receiver type, method name, argument types) dispatch table. #[allow(clippy::too_many_arguments)] pub(crate) fn lower_method_dispatch( @@ -538,100 +1009,300 @@ pub(crate) fn lower_method_dispatch( block: usize, pc: usize, ) -> Result { + // A nullable needle looked for in a *typed* container. + // + // `List` cannot hold nil, so an absent needle is simply not there: + // `contains` is false, `count` is zero, `index_of` is nil. The typed arms + // below all match the needle's type exactly, so a `Maybe` matched none + // of them and the whole module fell back — for a question whose answer was + // already known. + // + // Answered by asking with the payload and *selecting*, rather than by + // rebuilding the receiver as a Dyn list: the receiver is not the problem, + // and rebuilding it would turn a lookup into an allocation. The payload of + // an absent carrier is a value nobody wrote, so the search's answer on that + // path is discarded rather than trusted. + if let Some(result) = + nullable_needle_in_typed_container(ssa, insts, globals, receiver, receiver_ty, name, args, block, pc)? + { + return Ok(result); + } + // A struct instance rides the `Map` carrier, and a map's + // *collection* methods are not its. The interpreter has a different heap + // value and refuses each of these, naming the struct — so answering for the + // fields is a wrong answer: `s.len()` was the field count and `s.keys()` + // the field names. + // + // Reading a field is not among them; that is what the carrier is for. These + // programs always raise, so declining to lower them costs nothing anyone + // runs. + // A `MapStrDyn` receiver has to be *proven* a map, not merely not proven a + // struct: a parameter one call site hands a struct and another a map has no + // fact at all, and that is exactly where the wrong answer was. + if matches!( + name, + "len" | "is_empty" | "keys" | "values" | "has" | "delete" | "contains" | "clear" + ) && (ssa.struct_name(receiver).is_some() || (receiver_ty == Ty::MapStrDyn && !ssa.is_plain_map(receiver))) + { + return Err(Unsupported::TypeMismatch { pc }); + } + let receiver_is_map = matches!( + receiver_ty, + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 + ); + let map_missing_method = receiver_is_map && matches!(name, "contains" | "index_of" | "count"); let result: Reg = match (receiver_ty, name, args) { // Boxed-element list long tail (runtime-polymorphic receivers). - (Ty::ListDyn, "take", [(n, Ty::I64)]) => { + // `take` / `skip` over every carrier and both directions. Neither looks + // at the element, and they were written out per carrier — which is how + // `f64` and `str` ended up with neither, so `[1.5, 2.5].take(1)` dropped + // its whole module to the VM. + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, name @ ("take" | "skip"), [(n, Ty::I64)]) => { + let callee = match (receiver_ty, name) { + (Ty::ListI64, "take") => "i64_take", + (Ty::ListI64, _) => "i64_skip", + (Ty::ListF64, "take") => "f64_take", + (Ty::ListF64, _) => "f64_skip", + (Ty::ListStr, "take") => "str_take", + (Ty::ListStr, _) => "str_skip", + (_, "take") => "dyn_take", + (_, _) => "dyn_skip", + }; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_take"), + callee: AbiRef::new("list_h", callee), args: vec![receiver, *n], }); - (dst, Ty::ListDyn) + (dst, receiver_ty) } - (Ty::ListDyn, "skip", [(n, Ty::I64)]) => { + + (Ty::ListI64, "unique", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_skip"), - args: vec![receiver, *n], + callee: AbiRef::new("list_h", "i64_unique"), + args: vec![receiver], }); - (dst, Ty::ListDyn) + (dst, Ty::ListI64) } - // `concat` with any dyn-list side: both sides normalize to dyn lists - // (typed sides convert element-wise, cold path) and chain. - (Ty::ListDyn | Ty::ListI64 | Ty::ListF64 | Ty::ListStr, "concat", [(other, oty)]) - if receiver_ty == Ty::ListDyn || *oty == Ty::ListDyn || *oty == Ty::Dyn => - { - let lhs = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; - let rhs = match *oty { - Ty::Dyn => { - let unboxed = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(unboxed), - callee: AbiRef::new("dyn", "as_list"), - args: vec![*other], - }); - unboxed - } - oty => to_dyn_list_handle(ssa, insts, *other, oty, pc)?, + // `xs.sort()` / `xs.reverse()` — fresh copies (the VM sorts/reverses + // a snapshot; the receiver is untouched). + // `sort` is per carrier because its *order* is per carrier — see + // `list_sort!` in lkrt, where the `f64` comparator is not a total order + // once a NaN is present and the answer is therefore an artifact of which + // sort call is used. The boxed carrier is absent on purpose: its order is + // `compare_runtime_values` across kinds, which is a mirror worth its own + // conformance test rather than a copy. + // `sum` on the two numeric carriers, and `min`/`max` on the three + // ordered ones — the same orders `sort` uses just above, from the same + // comparators in lkrt. + // + // The boxed carrier is out for the reason `sort` states: its order is + // `compare_runtime_values` across kinds, a mirror that wants its own + // conformance test rather than a copy. A `List` has no `sum` for + // the reason the VM gives — summing strings is a mistake, not a join — + // and with no row that call falls back and raises there. + (Ty::ListI64 | Ty::ListF64, "sum", []) => { + let (callee, ty) = match receiver_ty { + Ty::ListI64 => ("i64_sum", Ty::I64), + _ => ("f64_sum", Ty::F64), }; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_chain"), - args: vec![lhs, rhs], + callee: AbiRef::new("list_h", callee), + args: vec![receiver], + }); + (dst, ty) + } + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr, "min" | "max", []) => { + // An `AbiRef` names a `&'static str`, so the carrier × direction + // pair is spelled out rather than assembled. + let callee = match (receiver_ty, name) { + (Ty::ListI64, "min") => "i64_min", + (Ty::ListI64, _) => "i64_max", + (Ty::ListF64, "min") => "f64_min", + (Ty::ListF64, _) => "f64_max", + (_, "min") => "str_min", + _ => "str_max", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", callee), + args: vec![receiver], + }); + // Boxed: an empty sequence answers nil, which no unboxed carrier + // can hold. + (dst, Ty::Dyn) + } + (Ty::Bytes, "sum", []) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", "sum"), + args: vec![receiver], + }); + (dst, Ty::I64) + } + (Ty::Bytes, "min" | "max", []) => { + let callee = if name == "min" { "min" } else { "max" }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", callee), + args: vec![receiver], + }); + (dst, Ty::Dyn) + } + // The boxed carrier joins `sort`, `min` and `max`: its order is + // `dyn_compare`, the VM's `compare_runtime_values` mirrored against the + // VM itself rather than copied from it. What made a copy the wrong + // shape is what the mirror had to get right — the VM keeps two rank + // tables and they have to be shown to agree, a window is a list but + // shares a tag value with the end of the map range, and a struct is a + // marked map here and a distinct heap kind there. + (Ty::ListDyn, "sort", []) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_sort"), + args: vec![receiver], }); (dst, Ty::ListDyn) } - (Ty::ListI64, "unique", []) => { + (Ty::ListDyn, "sum", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_unique"), + callee: AbiRef::new("list_h", "dyn_sum"), args: vec![receiver], }); - (dst, Ty::ListI64) + // Boxed: `Int` unless an element was a Float, which is decided per + // list rather than per carrier. + (dst, Ty::Dyn) } - // `xs.sort()` / `xs.reverse()` — fresh copies (the VM sorts/reverses - // a snapshot; the receiver is untouched). - (Ty::ListI64, "sort", []) => { + (Ty::ListDyn, "min" | "max", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_sort"), + callee: AbiRef::new("list_h", if name == "min" { "dyn_min" } else { "dyn_max" }), args: vec![receiver], }); - (dst, Ty::ListI64) + // Boxed: an empty sequence answers nil, which no unboxed carrier + // can hold. + (dst, Ty::Dyn) } - (Ty::ListI64, "reverse", []) => { + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr, "sort", []) => { + let callee = match receiver_ty { + Ty::ListI64 => "i64_sort", + Ty::ListF64 => "f64_sort", + _ => "str_sort", + }; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_reverse"), + callee: AbiRef::new("list_h", callee), args: vec![receiver], }); - (dst, Ty::ListI64) + (dst, receiver_ty) } - // `.is_empty()` — `len == 0` over the same per-type len ABI. - ( - Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrDyn, - "is_empty", - [], - ) => { - let (module, len_fn) = match receiver_ty { - Ty::ListI64 => ("list_h", "i64_len"), - Ty::ListF64 => ("list_h", "f64_len"), - Ty::ListStr => ("list_h", "str_len"), - Ty::ListDyn => ("list_h", "dyn_len"), - Ty::MapStrI64 => ("map_h", "str_i64_len"), - Ty::MapStrF64 => ("map_h", "str_f64_len"), - _ => ("map_h", "str_dyn_len"), + // `reverse` does not look at the element, so it is one arm over the + // carriers rather than four written one at a time — which is how it came + // to exist for `Int` and nowhere else, dropping `[1.5, 2.5].reverse()`'s + // whole module to the VM. + (Ty::ListI64, "count", [(value, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "i64_count"), + args: vec![receiver, *value], + }); + (dst, Ty::I64) + } + (Ty::ListF64, "count", [(value, Ty::F64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "f64_count"), + args: vec![receiver, *value], + }); + (dst, Ty::I64) + } + (Ty::ListStr, "count", [(value, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "str_count"), + args: vec![receiver, *value], + }); + (dst, Ty::I64) + } + // The two carriers `count` was missing while `index_of` — the same scan + // in the VM, and now the same scan here — had them. A needle of any + // type is a question a boxed list can answer, so it boxes rather than + // being restricted the way the typed arms above are. + (Ty::ListDyn, "count", [(value, vty)]) => { + let boxed = to_dyn(ssa, insts, *value, *vty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_count"), + args: vec![receiver, boxed], + }); + (dst, Ty::I64) + } + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "reverse", []) => { + let callee = match receiver_ty { + Ty::ListI64 => "i64_reverse", + Ty::ListF64 => "f64_reverse", + Ty::ListStr => "str_reverse", + _ => "dyn_reverse", }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", callee), + args: vec![receiver], + }); + (dst, receiver_ty) + } + // `.is_empty()` — `len == 0` over the same per-type len ABI. + // + // A boxed receiver takes `dyn.len_of` rather than the method table's + // `unbox_list`, for the reason `contains` does: this arm serves maps + // too, and unboxing one to a list aborts. `len_of` is the dispatch + // `xs.len()` already takes on a boxed receiver, so the two spellings + // answer through one function. + // `xs.len()` written as a *method call*. Normally it is the fused `Len` + // opcode and never reaches this table — but the compiler cannot use the + // opcode when the module has a user `impl` that could shadow the name, + // and then every carrier without an arm here fell back. Six had one; + // the typed lists and the typed maps did not. + // + // The same `container_len_abi` row `Len` and `is_empty` read. `Str` is + // not in it (a string's length is its count of Unicode scalar values) + // and keeps its own arm below. + (_, "len", []) if container_len_abi(receiver_ty).is_some() => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: container_len_abi(receiver_ty).expect("guarded by the arm"), + args: vec![receiver], + }); + (dst, Ty::I64) + } + // + // The carrier list is `container_len_abi`'s, shared with the `Len` + // opcode. It used to be a second table naming eight of them, and the + // three it left out — both integer-keyed maps and the bool map — each + // lowered `m.len()` and refused `m.is_empty()`. + (_, "is_empty", []) if container_len_abi(receiver_ty).is_some() => { let len = ssa.new_val(); insts.push(Inst::Call { dst: Some(len), - callee: AbiRef::new(module, len_fn), + callee: container_len_abi(receiver_ty).expect("guarded by the arm"), args: vec![receiver], }); let zero = ssa.new_val(); @@ -649,107 +1320,75 @@ pub(crate) fn lower_method_dispatch( }); (b, Ty::Bool) } - // `.slice(start[, end])` — negative aborts (VM loud), end clamps. - (Ty::ListI64, "slice", [(start, Ty::I64), (end, Ty::I64)]) => { + // `.slice(start[, end])` — a **window** over the receiver, not a copy + // of it (negative aborts as the VM does; `end` clamps). This returned + // `Ty::ListI64` until the VM's `.slice()` became a view: the two + // backends then disagreed about whether a write to the source shows + // through, and about whether `.to_list()` existed at all. + // `xs.slice(start)` — the one-argument form, whose end defaults to the + // length. `Ty::Str` had both arities and a list had only the two-arg + // one, so `xs.slice(1)` dropped the program to the VM. + (Ty::ListI64, "slice", [(start, Ty::I64)]) => { + let end = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(end), + callee: AbiRef::new("list_h", "i64_len"), + args: vec![receiver], + }); let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_slice_method"), - args: vec![receiver, *start, *end], + callee: AbiRef::new("slice_h", "i64_new"), + args: vec![receiver, *start, end], }); - (dst, Ty::ListI64) + (dst, Ty::SliceI64) } - // Map iteration family (order = the VM's, layout mirror): keys/ - // values snapshots (Mixed → dyn lists), delete-with-removed-value. - (Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn, "keys" | "values", []) => { - let family = match receiver_ty { - Ty::MapStrI64 => "str_i64", - Ty::MapStrF64 => "str_f64", - Ty::MapStrBool => "str_bool", - _ => "str_dyn", - }; - let abi_name: &'static str = match (family, name) { - ("str_i64", "keys") => "str_i64_keys", - ("str_i64", _) => "str_i64_values", - ("str_f64", "keys") => "str_f64_keys", - ("str_f64", _) => "str_f64_values", - ("str_bool", "keys") => "str_bool_keys", - ("str_bool", _) => "str_bool_values", - (_, "keys") => "str_dyn_keys", - _ => "str_dyn_values", - }; - let dst = ssa.new_val(); + // `clear()` returns the receiver, which is what the VM's `clear` gives + // back — the same handle, now empty. Every carrier at once: the + // operation does not look at the element type. + // `clear()` empties the receiver and hands *it* back — one rule for + // every container, stated once. It used to be three arms with three + // copies of the answer, and two of them (a map's and a set's) said + // `nil` instead: the convention was in the list arm's comment and + // nowhere a reader of the other two would look. + (_, "clear", []) if clear_helper(receiver_ty).is_some() => { + let (module, helper) = clear_helper(receiver_ty).expect("checked by the guard"); insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("map_h", abi_name), + dst: None, + callee: AbiRef::new(module, helper), args: vec![receiver], }); - (dst, Ty::ListDyn) + (receiver, receiver_ty) } - (Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn, "delete" | "remove", [(k, Ty::Str)]) => { - let abi_name = match receiver_ty { - Ty::MapStrI64 => "str_i64_delete", - Ty::MapStrF64 => "str_f64_delete", - Ty::MapStrBool => "str_bool_delete", - _ => "str_dyn_delete", + // The other element types slice through `*_slice_from`, which has been + // in the ABI all along — only the dispatch table stopped at `i64`. Same + // shape as `chain`: the runtime could do it, nothing asked. + // + // `i64` above answers a *window* (`SliceI64`); these answer a fresh + // list. Both are what `slice` means — the window is an optimisation the + // other carriers do not have, not a different result. + (Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "slice", [(start, Ty::I64)]) => { + let helper = match receiver_ty { + Ty::ListF64 => "f64_slice_from", + Ty::ListStr => "str_slice_from", + _ => "dyn_slice_from", }; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("map_h", abi_name), - args: vec![receiver, *k], - }); - (dst, Ty::Dyn) - } - // `m.has(k)` on typed string maps — the dynamic-lookup present bit. - (Ty::MapStrI64 | Ty::MapStrBool, "has", [(k, Ty::Str)]) => { - let looked = ssa.new_val(); - insts.push(Inst::MapGetMaybe { - dst: looked, - handle: receiver, - key: *k, - }); - let present = ssa.new_val(); - insts.push(Inst::MaybePresent { - dst: present, - src: looked, - maybe_ty: Ty::MaybeI64, - }); - (present, Ty::Bool) - } - (Ty::MapStrF64, "has", [(k, Ty::Str)]) => { - let looked = ssa.new_val(); - insts.push(Inst::MapGetMaybeStrF64 { - dst: looked, - handle: receiver, - key: *k, - }); - let present = ssa.new_val(); - insts.push(Inst::MaybePresent { - dst: present, - src: looked, - maybe_ty: Ty::MaybeF64, - }); - (present, Ty::Bool) - } - // Set methods (VM `core_methods` set family): membership/mutation - // return Bool, `len` Int, `clear` Nil. Elements box to Dyn — a Float - // aborts inside lkrt (the VM's loud "cannot be used as a key"). - (Ty::Set, "len", []) => { - let dst = ssa.new_val(); - insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("set", "len"), - args: vec![receiver], + callee: AbiRef::new("list_h", helper), + args: vec![receiver, *start], }); - (dst, Ty::I64) + (dst, receiver_ty) } - (Ty::Set, "is_empty", []) => { - let len = ssa.new_val(); + // `contains` likewise: the helpers exist for every carrier. + (Ty::ListF64, "contains", [(needle, Ty::F64 | Ty::I64)]) => { + let needle = coerce_to_f64(ssa, insts, *needle, args[0].1); + let found = ssa.new_val(); insts.push(Inst::Call { - dst: Some(len), - callee: AbiRef::new("set", "len"), - args: vec![receiver], + dst: Some(found), + callee: AbiRef::new("list_h", "f64_contains"), + args: vec![receiver, needle], }); let zero = ssa.new_val(); insts.push(Inst::Const { @@ -759,24 +1398,102 @@ pub(crate) fn lower_method_dispatch( let b = ssa.new_val(); insts.push(Inst::Cmp { dst: b, - op: CmpOp::Eq, + op: CmpOp::Ne, float: false, - lhs: len, + lhs: found, rhs: zero, }); (b, Ty::Bool) } - (Ty::Set, "has" | "contains" | "add" | "delete" | "remove", [(v, vty)]) => { - let boxed = to_dyn_any(ssa, insts, *v, *vty, pc)?; - let abi_name = match name { - "has" | "contains" => "has", - "add" => "add", - _ => "delete", + // A container searched for something it cannot hold at all. The + // interpreter answers "absent" rather than refusing, and the answer is + // the same for every value of that type, so it is a constant. + // `"abc".contains(1)`, `b.contains("a")`, `s.contains(1.5)` — each of + // them was a refusal on this side and an answer on the other. + (_, name @ ("contains" | "has" | "index_of" | "count" | "delete"), [(_, nty)]) + if never_matches(receiver_ty, name, *nty) => + { + let dst = ssa.new_val(); + match name { + "index_of" | "delete" => { + // Both answer a boxed nil when absent: `index_of` has no + // position and `delete` had nothing to return. + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "from_nil"), + args: vec![], + }); + (dst, Ty::Dyn) + } + "count" => { + insts.push(Inst::Const { + dst, + value: Const::I64(0), + }); + (dst, Ty::I64) + } + _ => { + insts.push(Inst::Const { + dst, + value: Const::Bool(false), + }); + (dst, Ty::Bool) + } + } + } + // A typed list searched for something its carrier cannot hold. The + // answer is `false`/nil/`0` — the interpreter says so, and its operator + // spelling `v in xs` has always said so — but the typed helpers take + // the carrier's own element and there is nothing to hand them. Boxing + // the receiver reaches the helpers that compare by value, which is the + // same route a `ListDyn` receiver already takes below. + // + // Placed before the typed arms would shadow them, so it guards on the + // needle *not* fitting: `Int` and `Float` both fit a numeric carrier, + // because `1 in [1.0]` is true. + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr, name @ ("contains" | "index_of" | "count"), [(needle, nty)]) + if !fits_carrier(receiver_ty, name, *nty) => + { + let handle = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + let boxed = to_dyn(ssa, insts, *needle, *nty, pc)?; + let helper = match name { + "contains" => "dyn_contains", + "index_of" => "dyn_index_of", + _ => "dyn_count", }; - let wide = ssa.new_val(); + let dst = ssa.new_val(); insts.push(Inst::Call { - dst: Some(wide), - callee: AbiRef::new("set", abi_name), + dst: Some(dst), + callee: AbiRef::new("list_h", helper), + args: vec![handle, boxed], + }); + match name { + "contains" => { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: dst, + rhs: zero, + }); + (b, Ty::Bool) + } + "index_of" => (dst, Ty::Dyn), + _ => (dst, Ty::I64), + } + } + (Ty::ListDyn, "contains", [(needle, nty)]) => { + let boxed = to_dyn(ssa, insts, *needle, *nty, pc)?; + let found = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(found), + callee: AbiRef::new("list_h", "dyn_contains"), args: vec![receiver, boxed], }); let zero = ssa.new_val(); @@ -789,31 +1506,25 @@ pub(crate) fn lower_method_dispatch( dst: b, op: CmpOp::Ne, float: false, - lhs: wide, + lhs: found, rhs: zero, }); (b, Ty::Bool) } - (Ty::Set, "clear", []) => { - insts.push(Inst::Call { - dst: None, - callee: AbiRef::new("set", "clear"), - args: vec![receiver], - }); - let nil = ssa.new_val(); - insts.push(Inst::Const { - dst: nil, - value: Const::Nil, - }); - (nil, Ty::Nil) - } - // `s.starts_with(prefix)` — byte-prefix test, exactly Rust/VM semantics. - (Ty::Str, "starts_with", [(prefix, Ty::Str)]) => { - let dst = ssa.new_val(); + (Ty::Dyn, "contains", [(needle, nty)]) => { + // The one container name that must *not* take the method table's + // `unbox_list`: `contains` answers for maps, sets, strings, windows + // and bytes too, and unboxing to a list would abort on all of them. + // `dyn.seq_contains` is the `in` operator's runtime dispatch minus + // the map: the VM gives a map `in` and gives it no `contains` + // method, so the operator's own entry would answer where the VM + // raises. + let boxed = to_dyn(ssa, insts, *needle, *nty, pc)?; + let found = ssa.new_val(); insts.push(Inst::Call { - dst: Some(dst), - callee: AbiRef::new("str", "starts_with"), - args: vec![receiver, *prefix], + dst: Some(found), + callee: AbiRef::new("dyn", "seq_contains"), + args: vec![receiver, boxed], }); let zero = ssa.new_val(); insts.push(Inst::Const { @@ -825,321 +1536,1394 @@ pub(crate) fn lower_method_dispatch( dst: b, op: CmpOp::Ne, float: false, - lhs: dst, + lhs: found, rhs: zero, }); (b, Ty::Bool) } - // `s.contains(needle)` — byte-substring test, exactly Rust/VM semantics. - // `m.has(key)` on a mixed-value map — key membership (stored-nil - // still counts, see `str_dyn_has`). - // `xs.first()` / `xs.last()` — nil when empty: exactly the dynamic- - // index `Maybe` model (an OOB/absent `get_pair` is `present = 0`), - // so both reuse the existing ListGetMaybe machinery, no new ABI. - (Ty::ListI64 | Ty::ListF64 | Ty::ListStr, "first", []) => { - let idx = ssa.new_val(); - insts.push(Inst::Const { - dst: idx, - value: Const::I64(0), - }); - let dst = ssa.new_val(); - let maybe_ty = match receiver_ty { - Ty::ListI64 => { - insts.push(Inst::ListGetMaybe { - dst, - handle: receiver, - index: idx, - }); - Ty::MaybeI64 - } - Ty::ListF64 => { - insts.push(Inst::ListGetMaybeF64 { - dst, - handle: receiver, - index: idx, - }); - Ty::MaybeF64 - } - _ => { - insts.push(Inst::ListGetMaybeStr { - dst, - handle: receiver, - index: idx, - }); - Ty::MaybeStr - } - }; - (dst, maybe_ty) - } - (Ty::ListI64 | Ty::ListF64 | Ty::ListStr, "last", []) => { - let (len_module, len_fn) = match receiver_ty { - Ty::ListI64 => ("list_h", "i64_len"), - Ty::ListF64 => ("list_h", "f64_len"), - _ => ("list_h", "str_len"), - }; - let len = ssa.new_val(); + (Ty::Bytes, "slice", [(from, Ty::I64)]) => { + let end = ssa.new_val(); insts.push(Inst::Call { - dst: Some(len), - callee: AbiRef::new(len_module, len_fn), + dst: Some(end), + callee: AbiRef::new("bytes_h", "len"), args: vec![receiver], }); - let one = ssa.new_val(); - insts.push(Inst::Const { - dst: one, - value: Const::I64(1), - }); - let idx = ssa.new_val(); - insts.push(Inst::IntBin { - dst: idx, - op: IntBinOp::Sub, - lhs: len, - rhs: one, - }); - let dst = ssa.new_val(); - let maybe_ty = match receiver_ty { - Ty::ListI64 => { - insts.push(Inst::ListGetMaybe { - dst, - handle: receiver, - index: idx, - }); - Ty::MaybeI64 - } - Ty::ListF64 => { - insts.push(Inst::ListGetMaybeF64 { - dst, - handle: receiver, - index: idx, - }); - Ty::MaybeF64 - } - _ => { - insts.push(Inst::ListGetMaybeStr { - dst, - handle: receiver, - index: idx, - }); - Ty::MaybeStr - } - }; - (dst, maybe_ty) - } - // `xs.concat(ys)` — same semantics as chain (the VM implements both - // as lhs ++ rhs into a fresh list). - (Ty::ListI64, "concat", [(other, Ty::ListI64)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_chain"), - args: vec![receiver, *other], + callee: AbiRef::new("bytes_h", "slice"), + args: vec![receiver, *from, end], }); - (dst, Ty::ListI64) + (dst, Ty::Bytes) } - // `xs.join(sep)` on a string list → one string. - (Ty::ListStr, "join", [(sep, Ty::Str)]) => { + (Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "slice", [(start, Ty::I64), (end, Ty::I64)]) => { + let helper = match receiver_ty { + Ty::ListF64 => "f64_slice", + Ty::ListStr => "str_slice", + _ => "dyn_slice", + }; let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "str_join"), - args: vec![receiver, *sep], + callee: AbiRef::new("list_h", helper), + args: vec![receiver, *start, *end], }); - (dst, Ty::Str) + (dst, receiver_ty) } - // `xs.get(i)` — safe index: nil on OOB, i.e. exactly the dynamic- - // index Maybe model (reused, no new ABI). - (Ty::ListI64, "get", [(idx, Ty::I64)]) => { + (Ty::ListI64, "slice", [(start, Ty::I64), (end, Ty::I64)]) => { let dst = ssa.new_val(); - insts.push(Inst::ListGetMaybe { - dst, - handle: receiver, - index: *idx, + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("slice_h", "i64_new"), + args: vec![receiver, *start, *end], }); - (dst, Ty::MaybeI64) + (dst, Ty::SliceI64) } - (Ty::ListF64, "get", [(idx, Ty::I64)]) => { + // `end` omitted means "to the end of the window" — its own length, not + // the length of the list it looks into. Every other carrier already + // had this form: a list's reaches a slice *opcode* rather than a + // `CallMethodK`, and `Bytes` has the arm above. A window was the one + // receiver where `xs.slice(1)` dropped the module to the VM. + (Ty::SliceI64, "slice", [(start, Ty::I64)]) => { + let end = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(end), + callee: AbiRef::new("slice_h", "i64_len"), + args: vec![receiver], + }); let dst = ssa.new_val(); - insts.push(Inst::ListGetMaybeF64 { - dst, - handle: receiver, - index: *idx, + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("slice_h", "i64_sub"), + args: vec![receiver, *start, end], }); - (dst, Ty::MaybeF64) + (dst, Ty::SliceI64) } - (Ty::ListStr, "get", [(idx, Ty::I64)]) => { + // A window on a window resolves against the original source rather + // than nesting, matching `dispatch_slice_builtin_method`. + (Ty::SliceI64, "slice", [(start, Ty::I64), (end, Ty::I64)]) => { let dst = ssa.new_val(); - insts.push(Inst::ListGetMaybeStr { - dst, - handle: receiver, - index: *idx, + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("slice_h", "i64_sub"), + args: vec![receiver, *start, *end], }); - (dst, Ty::MaybeStr) + (dst, Ty::SliceI64) } - // `List` slicing/concat helpers (VM core_methods semantics). - (Ty::ListI64, "take", [(n, Ty::I64)]) => { + // The copy, asked for by name — the operation `.slice()` used to + // perform silently. + (Ty::SliceI64, "to_list", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_take"), - args: vec![receiver, *n], + callee: AbiRef::new("slice_h", "i64_to_list"), + args: vec![receiver], }); (dst, Ty::ListI64) } - (Ty::ListI64, "skip", [(n, Ty::I64)]) => { + // The read half of the list surface, *through* the window: a window + // exists so that asking it for a sum does not build a list first, and + // these nine used to drop the whole module to the VM — the same + // "almost native receiver" shape `Bytes` had. `take`/`skip` are + // sub-windows for the same reason, and keep the count guard: a count is + // not a position, so a negative one is a refusal rather than a window + // measured from the end. + (Ty::SliceI64, "sum", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_skip"), - args: vec![receiver, *n], + callee: AbiRef::new("slice_h", "i64_sum"), + args: vec![receiver], }); - (dst, Ty::ListI64) + (dst, Ty::I64) } - (Ty::ListI64, "chain", [(other, Ty::ListI64)]) => { + (Ty::SliceI64, "min" | "max", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "i64_chain"), - args: vec![receiver, *other], + callee: AbiRef::new("slice_h", if name == "min" { "i64_min" } else { "i64_max" }), + args: vec![receiver], }); - (dst, Ty::ListI64) + (dst, Ty::Dyn) } - (Ty::MapStrDyn, "has", [(key, Ty::Str)]) => { + // The ABI's `I64` 0/1 becomes a `Bool` by comparing it, exactly as the + // `Bytes` arm does — a `Bool`-typed value that is really an i64 makes + // codegen emit `uextend` on something already 64 bits wide, and the + // Cranelift verifier rejects the function. + (Ty::SliceI64, "contains", [(value, Ty::I64)]) => { let raw = ssa.new_val(); insts.push(Inst::Call { dst: Some(raw), - callee: AbiRef::new("map_h", "str_dyn_has"), - args: vec![receiver, *key], + callee: AbiRef::new("slice_h", "i64_contains"), + args: vec![receiver, *value], }); let zero = ssa.new_val(); insts.push(Inst::Const { dst: zero, value: Const::I64(0), }); - let b = ssa.new_val(); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + (dst, Ty::Bool) + } + (Ty::SliceI64, "count", [(value, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("slice_h", "i64_count"), + args: vec![receiver, *value], + }); + (dst, Ty::I64) + } + // A reversed window is not a window of the source, so it materializes + // — the same rule `map` follows here. Composed from the two symbols + // that already exist rather than a third that would answer the same. + (Ty::SliceI64, "reverse" | "sort" | "unique", []) => { + let list = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(list), + callee: AbiRef::new("slice_h", "i64_to_list"), + args: vec![receiver], + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new( + "list_h", + match name { + "sort" => "i64_sort", + "unique" => "i64_unique", + _ => "i64_reverse", + }, + ), + args: vec![list], + }); + (dst, Ty::ListI64) + } + (Ty::SliceI64, "index_of", [(value, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("slice_h", "i64_index_of"), + args: vec![receiver, *value], + }); + (dst, Ty::Dyn) + } + (Ty::SliceI64, "take" | "skip", [(count, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("slice_h", if name == "take" { "i64_take" } else { "i64_skip" }), + args: vec![receiver, *count], + }); + (dst, Ty::SliceI64) + } + // `first`/`last` are `[0]` and `[-1]`, which the window's own indexed + // read already is — including the nil an empty window answers. + (Ty::SliceI64, "first" | "last", []) => { + let index = ssa.new_val(); + insts.push(Inst::Const { + dst: index, + value: Const::I64(if name == "first" { 0 } else { -1 }), + }); + let dst = ssa.new_val(); + insts.push(Inst::SliceGetMaybe { + dst, + handle: receiver, + index, + }); + (dst, Ty::MaybeI64) + } + // `w.get(i)` — the same read as `w[i]`, answering nil instead of + // failing, which is what `.get()` means on a list too. + (Ty::SliceI64, "get", [(index, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::SliceGetMaybe { + dst, + handle: receiver, + index: *index, + }); + (dst, Ty::MaybeI64) + } + // Map iteration family (order = the VM's, layout mirror): keys/ + // values snapshots (Mixed → dyn lists), delete-with-removed-value. + // A **boxed** map receiver: the tag decides the carrier at run time, so + // these dispatch inside the runtime instead of unboxing first. They + // used to go through `dyn.as_map`, which hands back a `str_dyn` handle + // — fine for a boxed `Map` and a `runtime type error` for + // every typed carrier, on programs the VM answers. + (Ty::Dyn, "keys" | "values", []) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", if name == "keys" { "map_keys" } else { "map_values" }), + args: vec![receiver], + }); + (dst, Ty::ListDyn) + } + (Ty::Dyn, "has", [(k, Ty::Str)]) => { + let wide = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(wide), + callee: AbiRef::new("dyn", "map_has"), + args: vec![receiver, *k], + }); + // The ABI answers a machine-width flag; `Ty::Bool` is one bit, and + // handing the wide value over as-is makes codegen extend an `i64` + // to `i64` and the verifier reject the function. + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let present = ssa.new_val(); + insts.push(Inst::Cmp { + dst: present, + op: CmpOp::Ne, + float: false, + lhs: wide, + rhs: zero, + }); + (present, Ty::Bool) + } + // `delete` writes, which is why the dispatch is per operation: an + // `as_map` that materialized a copy would answer `keys`/`values`/`has` + // and silently drop this one. + (Ty::Dyn, "delete", [(k, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "map_delete"), + args: vec![receiver, *k], + }); + (dst, Ty::Dyn) + } + (Ty::MapI64I64 | Ty::MapI64F64, "keys" | "values", []) => { + let abi_name: &'static str = match (receiver_ty, name) { + (Ty::MapI64I64, "keys") => "i64_i64_keys", + (Ty::MapI64I64, _) => "i64_i64_values", + (_, "keys") => "i64_f64_keys", + _ => "i64_f64_values", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", abi_name), + args: vec![receiver], + }); + (dst, Ty::ListDyn) + } + (Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn, "keys" | "values", []) => { + let family = match receiver_ty { + Ty::MapStrI64 => "str_i64", + Ty::MapStrF64 => "str_f64", + Ty::MapStrBool => "str_bool", + _ => "str_dyn", + }; + let abi_name: &'static str = match (family, name) { + ("str_i64", "keys") => "str_i64_keys", + ("str_i64", _) => "str_i64_values", + ("str_f64", "keys") => "str_f64_keys", + ("str_f64", _) => "str_f64_values", + ("str_bool", "keys") => "str_bool_keys", + ("str_bool", _) => "str_bool_values", + (_, "keys") => "str_dyn_keys", + _ => "str_dyn_values", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", abi_name), + args: vec![receiver], + }); + (dst, Ty::ListDyn) + } + // `m.clear()`, the one container method the map did not lower. + // `remove` is *not* a map method — the interpreter has `delete`, and + // says so. Accepting it here meant the compiled build answered where + // the VM raised, which is the worse direction: a program that cannot + // run at all ran, and only on one backend. + (Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn, "delete", [(k, Ty::Str)]) => { + let abi_name = match receiver_ty { + Ty::MapStrI64 => "str_i64_delete", + Ty::MapStrF64 => "str_f64_delete", + Ty::MapStrBool => "str_bool_delete", + _ => "str_dyn_delete", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("map_h", abi_name), + args: vec![receiver, *k], + }); + (dst, Ty::Dyn) + } + (Ty::MapI64I64 | Ty::MapI64F64, "delete", [(k, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new( + "map_h", + if receiver_ty == Ty::MapI64I64 { + "i64_i64_delete" + } else { + "i64_f64_delete" + }, + ), + args: vec![receiver, *k], + }); + (dst, Ty::Dyn) + } + // `m.has(k)` on integer-keyed maps — the same present bit the string + // arms below take, off the integer-key lookup. + (Ty::MapI64I64 | Ty::MapI64F64, "has", [(k, Ty::I64)]) => { + let looked = ssa.new_val(); + let maybe_ty = if receiver_ty == Ty::MapI64I64 { + insts.push(Inst::MapGetMaybeI64Key { + dst: looked, + handle: receiver, + key: *k, + }); + Ty::MaybeI64 + } else { + insts.push(Inst::MapGetMaybeI64F64 { + dst: looked, + handle: receiver, + key: *k, + }); + Ty::MaybeF64 + }; + let present = ssa.new_val(); + insts.push(Inst::MaybePresent { + dst: present, + src: looked, + maybe_ty, + }); + (present, Ty::Bool) + } + // `m.has(k)` on typed string maps — the dynamic-lookup present bit. + (Ty::MapStrI64 | Ty::MapStrBool, "has", [(k, Ty::Str)]) => { + let looked = ssa.new_val(); + insts.push(Inst::MapGetMaybe { + dst: looked, + handle: receiver, + key: *k, + }); + let present = ssa.new_val(); + insts.push(Inst::MaybePresent { + dst: present, + src: looked, + maybe_ty: Ty::MaybeI64, + }); + (present, Ty::Bool) + } + (Ty::MapStrF64, "has", [(k, Ty::Str)]) => { + let looked = ssa.new_val(); + insts.push(Inst::MapGetMaybeStrF64 { + dst: looked, + handle: receiver, + key: *k, + }); + let present = ssa.new_val(); + insts.push(Inst::MaybePresent { + dst: present, + src: looked, + maybe_ty: Ty::MaybeF64, + }); + (present, Ty::Bool) + } + // Set methods (VM `core_methods` set family): membership/mutation + // return Bool, `len` Int, `clear` Nil. Elements box to Dyn — a Float + // aborts inside lkrt (the VM's loud "cannot be used as a key"). + // Only the spellings the language actually has. This accepted `has` and + // `remove` too, and the type checker rejects both — so those two names + // could never reach a lowering, while a reader here would conclude + // `st.has(x)` works. The membership rule is `contains` wherever it is + // unambiguous (list, set, string) and `has` on a map, where "contains + // what — a key or a value?" is a real question; `in` works on all of + // them. See `docs/semantics.md`. + (Ty::Set, "contains" | "add" | "delete", [(v, vty)]) => { + let boxed = to_dyn(ssa, insts, *v, *vty, pc)?; + let abi_name = match name { + "contains" => "has", + "add" => "add", + _ => "delete", + }; + let wide = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(wide), + callee: AbiRef::new("set", abi_name), + args: vec![receiver, boxed], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: wide, + rhs: zero, + }); + (b, Ty::Bool) + } + // `s.values()` is the members in iteration order — the same list `for x + // in s` walks, which `set.iter` already builds. It was the one Set method + // with no arm, so a function using it dropped to the VM while the `for` + // loop over the same set stayed native. + // + // The order is a hash order, so this rides the mirror discipline that + // makes set iteration lowerable at all (`set_iteration_order_matches_the_vm`, + // and the single `RtKey` behind it). + // The set operations. The `kind` operand picks which; the numbering is + // `lkset::SET_OP_*` / `SET_REL_*`, and a second copy of it here would be + // a silent mismatch rather than an error — so it is one `match` beside + // the name that produced it. + (Ty::Set, "union" | "intersection" | "difference" | "symmetric_difference", [(other, Ty::Set)]) => { + let kind = ssa.new_val(); + insts.push(Inst::Const { + dst: kind, + value: Const::I64(match name { + "union" => 0, + "intersection" => 1, + "difference" => 2, + _ => 3, + }), + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("set", "combine"), + args: vec![receiver, *other, kind], + }); + (dst, Ty::Set) + } + (Ty::Set, "is_subset" | "is_superset" | "is_disjoint", [(other, Ty::Set)]) => { + let kind = ssa.new_val(); + insts.push(Inst::Const { + dst: kind, + value: Const::I64(match name { + "is_subset" => 0, + "is_superset" => 1, + _ => 2, + }), + }); + let wide = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(wide), + callee: AbiRef::new("set", "relate"), + args: vec![receiver, *other, kind], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Cmp { + dst, + op: CmpOp::Ne, + float: false, + lhs: wide, + rhs: zero, + }); + (dst, Ty::Bool) + } + (Ty::Set, "values", []) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("set", "iter"), + args: vec![receiver], + }); + (dst, Ty::ListDyn) + } + // `s.byte_at(i)` — one byte as a number, the only string read that + // allocates nothing. `Pure`, so the optimizer may hoist it out of a loop + // that reads the same index twice; `char_at` next to it cannot be, + // because it builds a string. + (Ty::Str, "byte_at", [(index, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::StrByteAtMaybe { + dst, + handle: receiver, + index: *index, + }); + (dst, Ty::MaybeI64) + } + // `s.starts_with(prefix)` — byte-prefix test, exactly Rust/VM semantics. + (Ty::Str, "starts_with", [(prefix, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "starts_with"), + args: vec![receiver, *prefix], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: dst, + rhs: zero, + }); + (b, Ty::Bool) + } + // `s.contains(needle)` — byte-substring test, exactly Rust/VM semantics. + // `m.has(key)` on a mixed-value map — key membership (stored-nil + // still counts, see `str_dyn_has`). + // `xs.first()` / `xs.last()` / `xs.pop()` — nil when empty: exactly the + // dynamic-index `Maybe` model (an OOB/absent `get_pair` is `present = 0`), + // so all three reuse the existing ListGetMaybe machinery, no new read ABI. + // + // One arm for the three because they differ only in *which* index and + // whether the element is then dropped. Written apart, `first`/`last` + // covered three carriers and left the boxed one out, and `pop` existed + // nowhere at all — so a single `xs.pop()` dropped its module to the VM. + // + // The boxed carrier reads through `dyn_at`, whose out-of-range answer is + // already nil, so its `Maybe` is the `Dyn` itself. That also means an + // empty `pop` and a stored nil are the same answer — which is what the VM + // says too. + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::Dyn, name @ ("first" | "last" | "pop"), []) => { + let idx = ssa.new_val(); + if name == "first" { + insts.push(Inst::Const { + dst: idx, + value: Const::I64(0), + }); + } else { + // `len - 1`, which is -1 for an empty list — and every carrier's + // read answers nil for that, so emptiness needs no branch. + let (len_mod, len_fn) = match receiver_ty { + Ty::ListI64 => ("list_h", "i64_len"), + Ty::ListF64 => ("list_h", "f64_len"), + Ty::ListStr => ("list_h", "str_len"), + // A boxed receiver: the tag says which carrier, here and at + // each of the two steps below. + Ty::Dyn => ("dyn", "len_of"), + _ => ("list_h", "dyn_len"), + }; + let len = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(len), + callee: AbiRef::new(len_mod, len_fn), + args: vec![receiver], + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + insts.push(Inst::IntBin { + dst: idx, + op: IntBinOp::Sub, + lhs: len, + rhs: one, + }); + } + let dst = ssa.new_val(); + let maybe_ty = match receiver_ty { + Ty::ListI64 => { + insts.push(Inst::ListGetMaybe { + dst, + handle: receiver, + index: idx, + }); + Ty::MaybeI64 + } + Ty::ListF64 => { + insts.push(Inst::ListGetMaybeF64 { + dst, + handle: receiver, + index: idx, + }); + Ty::MaybeF64 + } + Ty::ListStr => { + insts.push(Inst::ListGetMaybeStr { + dst, + handle: receiver, + index: idx, + }); + Ty::MaybeStr + } + Ty::Dyn => { + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "index"), + args: vec![receiver, idx], + }); + Ty::Dyn + } + _ => { + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_at"), + args: vec![receiver, idx], + }); + Ty::Dyn + } + }; + // `pop` is that read plus the drop. Read first: the value has to come + // out before the element it names is gone. + if name == "pop" { + let (drop_mod, drop_fn) = match receiver_ty { + Ty::ListI64 => ("list_h", "i64_drop_last"), + Ty::ListF64 => ("list_h", "f64_drop_last"), + Ty::ListStr => ("list_h", "str_drop_last"), + Ty::Dyn => ("dyn", "list_drop_last"), + _ => ("list_h", "dyn_drop_last"), + }; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new(drop_mod, drop_fn), + args: vec![receiver], + }); + } + (dst, maybe_ty) + } + // `xs.insert(i, v)` answers the receiver (the VM mutates in place and + // evaluates to the list); `xs.remove_at(i)` answers the element it took + // out, and raises rather than answering nil when the index is out of + // range — so unlike `pop` its result is the element type, not a `Maybe`. + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::Dyn, "insert", [(at, _), (value, vty)]) => { + // A nullable value has no place in a typed list, and this arm would + // otherwise hand the carrier straight to `i64_insert` — caught, but + // by an argument-count mismatch inside the ABI rather than by + // anything that names the problem. Reported as a contradiction of + // the literal instead, which is also the fixpoint's cue to rebuild + // the list as a Dyn one and lower after all. `push` already does + // this; `insert` is the same store one method along. + if matches!(*vty, Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool) && receiver_ty != Ty::ListDyn + { + return Err(crate::inst::container::carrier_contradicted(ssa, receiver, receiver_ty) + .unwrap_or(Unsupported::TypeMismatch { pc })); + } + // A boxed receiver reaches the carrier behind the tag rather than + // unboxing: `dyn.as_list` is read-only, so the insert would land in + // a materialized copy. Same rule `push` follows. + if receiver_ty == Ty::Dyn { + let boxed = to_dyn(ssa, insts, *value, *vty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("dyn", "list_insert"), + args: vec![receiver, *at, boxed], + }); + return Ok((receiver, receiver_ty)); + } + let (callee, value) = match receiver_ty { + Ty::ListI64 => ("i64_insert", *value), + Ty::ListF64 => ("f64_insert", coerce_to_f64(ssa, insts, *value, *vty)), + Ty::ListStr => ("str_insert", *value), + _ => ("dyn_insert", to_dyn(ssa, insts, *value, *vty, pc)?), + }; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("list_h", callee), + args: vec![receiver, *at, value], + }); + (receiver, receiver_ty) + } + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::Dyn, "remove_at", [(at, Ty::I64)]) => { + // See `insert`: a boxed receiver reaches the carrier by tag. + if receiver_ty == Ty::Dyn { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "list_remove_at"), + args: vec![receiver, *at], + }); + return Ok((dst, Ty::Dyn)); + } + let (callee, out) = match receiver_ty { + Ty::ListI64 => ("i64_remove_at", Ty::I64), + Ty::ListF64 => ("f64_remove_at", Ty::F64), + Ty::ListStr => ("str_remove_at", Ty::Str), + _ => ("dyn_remove_at", Ty::Dyn), + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", callee), + args: vec![receiver, *at], + }); + (dst, out) + } + // `xs.join(sep)` → one string, on every carrier that has one. + // + // The numeric arms were absent on purpose: the VM raised "list must + // contain only strings", so lowering them would have made native answer + // where the VM refused. That rule is gone — the VM writes each element + // the way it writes it everywhere else — and the helpers here render the + // same way the `*_display` ones do, which is what keeps the two ends + // agreeing about `1.0` and `-0.0`. + // `xs.get(i)` — safe index: nil on OOB, i.e. exactly the dynamic- + // index Maybe model (reused, no new ABI). + (Ty::ListI64, "get", [(idx, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::ListGetMaybe { + dst, + handle: receiver, + index: *idx, + }); + (dst, Ty::MaybeI64) + } + (Ty::ListF64, "get", [(idx, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::ListGetMaybeF64 { + dst, + handle: receiver, + index: *idx, + }); + (dst, Ty::MaybeF64) + } + (Ty::ListStr, "get", [(idx, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::ListGetMaybeStr { + dst, + handle: receiver, + index: *idx, + }); + (dst, Ty::MaybeStr) + } + // `List` slicing/concat helpers (VM core_methods semantics). + // `xs.chain(ys)` is `xs + ys`, and the operator path has always covered + // every list pairing: same-typed keeps its carrier, cross-typed chains + // boxed (the VM's result there is a Mixed list, which is what + // `dyn_chain` builds). The method path had one arm — `ListI64` twice — + // so `line.chain([byte])` with a boxed element did not lower, and in the + // x86 kernel that one shape was eleven of the eighteen blockers. + // + // One operation, one rule: this mirrors `inst::scalar`'s `list_chain`. + // + // `concat` is the same operation under a second name, and it used to have + // its own two narrower arms — one for `ListI64 ++ ListI64`, one for + // "either side is boxed". So `xs.chain(ys)` lowered on all four carriers + // while `xs.concat(ys)` lowered on two, and which spelling a program used + // decided whether it stayed native. Both arms were subsumed by this one; + // deleting them is the fix, not adding two more. + ( + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, + "chain" | "concat", + [(other, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::Dyn)], + ) => { + // A boxed argument is ordinary here: a callee's return type is + // *observed*, and a long `a.chain(b).chain(c)…` chain can see one of + // its operands as `Dyn` before the fixpoint has settled. The unbox + // itself is `to_dyn_list_handle`'s below — it used to be written out + // here, and `zip`, which needs the same thing, did not have a copy. + let other_ty = args[0].1; + let (helper, out_ty) = match (receiver_ty, other_ty) { + (Ty::ListI64, Ty::ListI64) => ("i64_chain", Ty::ListI64), + (Ty::ListF64, Ty::ListF64) => ("f64_chain", Ty::ListF64), + (Ty::ListStr, Ty::ListStr) => ("str_chain", Ty::ListStr), + _ => ("dyn_chain", Ty::ListDyn), + }; + let (lhs, rhs) = if out_ty == Ty::ListDyn { + ( + to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?, + to_dyn_list_handle(ssa, insts, *other, other_ty, pc)?, + ) + } else { + (receiver, *other) + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", helper), + args: vec![lhs, rhs], + }); + (dst, out_ty) + } + // A key the lowering cannot type. `m.has(k)` and `k in m` are one + // question — the interpreter answers both from `map_contains` — so this + // takes `dyn.contains`, which is the `in` operator's dispatch and asks + // exactly that of a map. Placed after the `Str` arms, which keep the + // direct `map_h` call. + // + // Both are total: a value that cannot be a key is not one the map + // holds, so the answer is `false` rather than a raise. Building a key + // still refuses — `m.set(1.5, x)` says so. + // + // The `Maybe` carriers are here because that is what iterating a typed + // list hands you: the element read is bounds-checked, so `for w in + // words { m.has(w) }` — the ordinary way to write it — arrives as + // `maybe` and matched none of these. Boxing preserves the absent + // case as nil, which the map answers `false` for, exactly as the + // interpreter does. + ( + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 | Ty::Dyn, + "has", + [ + ( + key, + kty @ (Ty::Dyn + | Ty::I64 + | Ty::F64 + | Ty::Bool + | Ty::Nil + | Ty::MaybeI64 + | Ty::MaybeF64 + | Ty::MaybeStr + | Ty::MaybeBool), + ), + ], + ) => { + let map = to_dyn(ssa, insts, receiver, receiver_ty, pc)?; + let boxed = to_dyn(ssa, insts, *key, *kty, pc)?; + let found = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(found), + callee: AbiRef::new("dyn", "contains"), + args: vec![map, boxed], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: found, + rhs: zero, + }); + (b, Ty::Bool) + } + (Ty::MapStrDyn, "has", [(key, Ty::Str)]) => { + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("map_h", "str_dyn_has"), + args: vec![receiver, *key], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + (b, Ty::Bool) + } + // `m.len()` / `xs.len()` on Dyn containers (method form of `Len`). + // Methods whose VM result is a mixed list regardless of the receiver + // (chunk/enumerate/zip pairs are nested; unique/flatten come back + // `TypedList::Mixed`): the receiver converts to a dyn-list handle up + // front, one lkrt helper per method mirrors core_methods.rs. + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "chunk", [(n, Ty::I64)]) => { + let handle = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_chunk"), + args: vec![handle, *n], + }); + (dst, Ty::ListDyn) + } + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "enumerate", []) => { + let handle = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_enumerate"), + args: vec![handle], + }); + (dst, Ty::ListDyn) + } + ( + Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, + "zip", + [(other, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn | Ty::Dyn)], + ) => { + let lhs = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + let rhs = to_dyn_list_handle(ssa, insts, *other, args[0].1, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_zip"), + args: vec![lhs, rhs], + }); + (dst, Ty::ListDyn) + } + (Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "unique", []) => { + let handle = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_unique"), + args: vec![handle], + }); + (dst, Ty::ListDyn) + } + // `flatten` on a carrier that cannot hold a list is a copy, and that is + // exactly what `slice_from(0)` is. A typed list has no nesting to undo by + // construction, so this needs no helper of its own — and without it a + // program calling `.flatten()` generically fell off a cliff depending on + // which carrier the list happened to have, which is not a distinction any + // program can see. + (Ty::ListI64 | Ty::ListF64 | Ty::ListStr, "flatten", []) => { + let helper = match receiver_ty { + Ty::ListI64 => "i64_slice_from", + Ty::ListF64 => "f64_slice_from", + _ => "str_slice_from", + }; + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", helper), + args: vec![receiver, zero], + }); + (dst, receiver_ty) + } + (Ty::ListDyn, "flatten", []) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_flatten"), + args: vec![receiver], + }); + (dst, Ty::ListDyn) + } + // `split` is an intrinsic in the bytecode compiler, so the *method* + // spelling becomes `Opcode::StringSplit` and never arrives here. The + // module spelling does arrive, now that `string.f(s, …)` forwards like + // `iter.f(xs, …)` always has — same helper as the opcode lowering, so + // the two spellings cannot drift. + (Ty::Str, "split", [(sep, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "split"), + args: vec![receiver, *sep], + }); + (dst, Ty::ListStr) + } + (Ty::Str, "contains", [(needle, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "contains"), + args: vec![receiver, *needle], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); insts.push(Inst::Cmp { dst: b, op: CmpOp::Ne, float: false, - lhs: raw, + lhs: dst, rhs: zero, }); (b, Ty::Bool) } - // `m.len()` / `xs.len()` on Dyn containers (method form of `Len`). - (Ty::MapStrDyn, "len", []) => { + // `s.len()` — Unicode scalar count (the VM's `chars().count()`). + (Ty::Str, "len", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("map_h", "str_dyn_len"), + callee: AbiRef::new("str", "char_len"), args: vec![receiver], }); (dst, Ty::I64) } - (Ty::ListDyn, "len", []) => { + // `s.is_empty()` — char_len == 0 (an empty string is empty in both + // byte and char terms), no new ABI. + (Ty::Str, "is_empty", []) => { + let len = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(len), + callee: AbiRef::new("str", "char_len"), + args: vec![receiver], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Eq, + float: false, + lhs: len, + rhs: zero, + }); + (b, Ty::Bool) + } + // `s.ends_with(suffix)` — byte-suffix test (see `starts_with`). + (Ty::Str, "ends_with", [(suffix, Ty::Str)]) => { + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("str", "ends_with"), + args: vec![receiver, *suffix], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + (b, Ty::Bool) + } + // The read surface every sequence shares, in *characters* — the unit + // `len()` counts and `[i]` indexes. + // + // Only `substring(start, length)` and `find` used to lower, and both + // called byte-indexed helpers while the VM counted characters, so the + // two backends disagreed on any text with a multi-byte character in it. + // Those two methods are gone; these are what replaced them, and + // `str.slice_chars` has had the VM's exact semantics all along. + (Ty::Str, "index_of", [(needle, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "index_of"), + args: vec![receiver, *needle], + }); + (dst, Ty::Dyn) + } + (Ty::Str, "slice", [(start, Ty::I64)]) => { + let end = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(end), + callee: AbiRef::new("str", "char_len"), + args: vec![receiver], + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "slice_chars"), + args: vec![receiver, *start, end], + }); + (dst, Ty::Str) + } + (Ty::Str, "slice", [(start, Ty::I64), (end, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "slice_chars"), + args: vec![receiver, *start, *end], + }); + (dst, Ty::Str) + } + // Not `slice_chars(s, 0, n)`: a count is not a position, so a negative + // one is a refusal rather than a window measured from the tail. Written + // that way, `"abc".take(-1)` answered `"ab"` compiled and raised + // interpreted — the List and Bytes carriers had guarded helpers all + // along, and String is the one that reused the window. + (Ty::Str, "take" | "skip", [(count, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", if name == "take" { "take" } else { "skip" }), + args: vec![receiver, *count], + }); + (dst, Ty::Str) + } + // `first`/`last` are `[0]` and `[-1]`, which `char_at` already is — + // `s.get(i)` — the same call `first`/`last` make with a fixed index. + // + // Written as a method it reaches an *opcode* rather than this table, so + // this arm has one caller: `string.get(s, i)`, which forwards here. The + // module spelling was the one that fell back, while the method spelling + // it forwards to lowered — the split `string.join` documents from the + // other side. + (Ty::Str, "get", [(index, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "char_at"), + args: vec![receiver, *index], + }); + (dst, Ty::Dyn) + } + // including the nil an empty string answers. + (Ty::Str, "first" | "last", []) => { + let index = ssa.new_val(); + insts.push(Inst::Const { + dst: index, + value: Const::I64(if name == "first" { 0 } else { -1 }), + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "char_at"), + args: vec![receiver, index], + }); + (dst, Ty::Dyn) + } + // `"a {} b".format(x, …)` — the receiver is the template, which makes + // this the same compile-time expansion `println("a {} b", x)` already + // does. Both go through `format_parts`, so the placeholder rules + // (leftover `{}` stay literal, leftover arguments append space + // separated) cannot drift between the two spellings. + // + // Variadic, and its arguments' types vary — the reason it was the last + // `string` member lowering on neither spelling. Neither matters once + // the expansion is static: each argument is display-converted at its + // own type, exactly as a `println` argument is. + (Ty::Str, "format", _) => { + // The template has to be a constant, for the same reason `println`'s + // does: the pieces are decided at compile time. A computed template + // falls back. + let Some(fmt) = ssa.const_str_value(receiver) else { + return Err(Unsupported::CallShape { + pc, + reason: "format needs a constant template to expand at compile time", + }); + }; + let parts = crate::lower_module::format_parts(&fmt, args, pc)?; + let (value, _fresh) = crate::lower_module::fold_parts_to_str(ssa, insts, globals, parts, pc)?; + (value, Ty::Str) + } + // Fresh-string unary transforms (VM core_methods semantics: `lower`/ + // `upper` are Unicode `to_lowercase`/`to_uppercase`, `reverse` is + // char-wise, `trim` is Rust `str::trim`). + (Ty::Str, "lower" | "upper" | "trim" | "reverse", []) => { + let helper = match name { + "lower" => "lower", + "upper" => "upper", + "trim" => "trim", + _ => "reverse", + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", helper), + args: vec![receiver], + }); + (dst, Ty::Str) + } + (Ty::Str, "repeat", [(n, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "repeat"), + args: vec![receiver, *n], + }); + (dst, Ty::Str) + } + // The transforms that used to have only a module spelling. Each calls + // the same `str` symbol the `string.…` row calls, so the two spellings + // are one implementation here as well as in the VM. + (Ty::Str, "capitalize" | "title", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_len"), + callee: AbiRef::new("str", if name == "capitalize" { "capitalize" } else { "title" }), args: vec![receiver], }); + (dst, Ty::Str) + } + (Ty::Str, "strip", [(chars, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "strip"), + args: vec![receiver, *chars], + }); + (dst, Ty::Str) + } + // The fill is optional, and its default is a space — materialized here + // rather than given a second ABI symbol, so both arities reach one + // helper. (A row per arity is how `string.replace` ended up lowering + // only when `all` was left out.) + (Ty::Str, "pad_left" | "pad_right", [(width, Ty::I64)] | [(width, Ty::I64), (_, Ty::Str)]) => { + let fill = match args { + [_, (fill, _)] => *fill, + _ => { + let space = ssa.new_val(); + insts.push(Inst::Const { + dst: space, + value: Const::Str(GlobalId(crate::prescan::intern_global(globals, " "))), + }); + space + } + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", if name == "pad_left" { "pad_left" } else { "pad_right" }), + args: vec![receiver, *width, fill], + }); + (dst, Ty::Str) + } + (Ty::Str, "count", [(needle, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "count"), + args: vec![receiver, *needle], + }); (dst, Ty::I64) } - // Methods whose VM result is a mixed list regardless of the receiver - // (chunk/enumerate/zip pairs are nested; unique/flatten come back - // `TypedList::Mixed`): the receiver converts to a dyn-list handle up - // front, one lkrt helper per method mirrors core_methods.rs. - (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "chunk", [(n, Ty::I64)]) => { - let handle = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + // `String?`, so the carrier is Dyn — nil when the affix was not there. + (Ty::Str, "strip_prefix" | "strip_suffix", [(affix, Ty::Str)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_chunk"), - args: vec![handle, *n], + callee: AbiRef::new( + "str", + if name == "strip_prefix" { + "strip_prefix" + } else { + "strip_suffix" + }, + ), + args: vec![receiver, *affix], }); - (dst, Ty::ListDyn) + (dst, Ty::Dyn) } - (Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "enumerate", []) => { - let handle = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + (Ty::Str, "replace", [(from, Ty::Str), (to, Ty::Str)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_enumerate"), - args: vec![handle], + callee: AbiRef::new("str", "replace"), + args: vec![receiver, *from, *to], + }); + (dst, Ty::Str) + } + // `s.replace(from, to, all)` — `all: false` replaces the first + // occurrence alone. The runtime entry takes a count rather than a flag + // (negative meaning no limit), so the same call serves both and a flag + // that is not a literal lowers too. + (Ty::Str, "replace", [(from, Ty::Str), (to, Ty::Str), (all, Ty::Bool)]) => { + let unlimited = ssa.new_val(); + insts.push(Inst::Const { + dst: unlimited, + value: Const::I64(-1), + }); + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + let limit = ssa.new_val(); + insts.push(Inst::Select { + dst: limit, + cond: *all, + then_v: unlimited, + else_v: one, + ty: Ty::I64, + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "replace_limited"), + args: vec![receiver, *from, *to, limit], + }); + (dst, Ty::Str) + } + // `s.chars()` — a dyn list, whose display quotes its strings exactly as + // the VM's `TypedList::String` does. (The VM built a *Mixed* list when + // this was written, which printed `[a,b]` against the module spelling's + // `["a","b"]`; both sides say `["a","b"]` now.) + (Ty::Str, "chars", []) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "chars"), + args: vec![receiver], }); (dst, Ty::ListDyn) } - ( - Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn, - "zip", - [(other, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn)], - ) => { - let lhs = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; - let rhs = to_dyn_list_handle(ssa, insts, *other, args[0].1, pc)?; + // `s.bytes()` — the string's UTF-8 bytes as a `Bytes` handle. + (Ty::Str, "bytes", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_zip"), - args: vec![lhs, rhs], + callee: AbiRef::new("bytes_h", "from_str"), + args: vec![receiver], }); - (dst, Ty::ListDyn) + (dst, Ty::Bytes) } - (Ty::ListF64 | Ty::ListStr | Ty::ListDyn, "unique", []) => { - let handle = to_dyn_list_handle(ssa, insts, receiver, receiver_ty, pc)?; + // `xs.to_bytes()` — the inverse of `b.to_list()`, and the body behind + // the `bytes.from_list(xs)` spelling. + (Ty::ListDyn, "to_bytes", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_unique"), - args: vec![handle], + callee: AbiRef::new("bytes_h", "from_dyn_list"), + args: vec![receiver], }); - (dst, Ty::ListDyn) + (dst, Ty::Bytes) } - (Ty::ListDyn, "flatten", []) => { + (Ty::ListI64, "to_bytes", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("list_h", "dyn_flatten"), + callee: AbiRef::new("bytes_h", "from_i64_list"), args: vec![receiver], }); - (dst, Ty::ListDyn) + (dst, Ty::Bytes) } - (Ty::Str, "contains", [(needle, Ty::Str)]) => { + (Ty::Bytes, "to_string_utf8" | "to_string_lossy", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", "contains"), - args: vec![receiver, *needle], - }); - let zero = ssa.new_val(); - insts.push(Inst::Const { - dst: zero, - value: Const::I64(0), - }); - let b = ssa.new_val(); - insts.push(Inst::Cmp { - dst: b, - op: CmpOp::Ne, - float: false, - lhs: dst, - rhs: zero, + callee: AbiRef::new("bytes_h", if name == "to_string_utf8" { "utf8" } else { "utf8_lossy" }), + args: vec![receiver], }); - (b, Ty::Bool) + (dst, Ty::Str) } - // `s.len()` — Unicode scalar count (the VM's `chars().count()`). - (Ty::Str, "len", []) => { + (Ty::Bytes, "concat", [(other, Ty::Bytes)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", "char_len"), - args: vec![receiver], + callee: AbiRef::new("bytes_h", "concat"), + args: vec![receiver, *other], }); - (dst, Ty::I64) + (dst, Ty::Bytes) } - // `s.is_empty()` — char_len == 0 (an empty string is empty in both - // byte and char terms), no new ABI. - (Ty::Str, "is_empty", []) => { + // The `bytes` module's members are also reachable as methods. + (Ty::Bytes, "is_empty", []) => { + // Through `len == 0`, like `Set::is_empty`: the ABI answers an `i64` + // and a `Bool` operand has to be an i1. let len = ssa.new_val(); insts.push(Inst::Call { dst: Some(len), - callee: AbiRef::new("str", "char_len"), + callee: AbiRef::new("bytes_h", "len"), args: vec![receiver], }); let zero = ssa.new_val(); @@ -1157,96 +2941,120 @@ pub(crate) fn lower_method_dispatch( }); (b, Ty::Bool) } - // `s.ends_with(suffix)` — byte-suffix test (see `starts_with`). - (Ty::Str, "ends_with", [(suffix, Ty::Str)]) => { - let raw = ssa.new_val(); + (Ty::Bytes, "get", [(index, Ty::I64)]) => { + let dst = ssa.new_val(); insts.push(Inst::Call { - dst: Some(raw), - callee: AbiRef::new("str", "ends_with"), - args: vec![receiver, *suffix], - }); - let zero = ssa.new_val(); - insts.push(Inst::Const { - dst: zero, - value: Const::I64(0), - }); - let b = ssa.new_val(); - insts.push(Inst::Cmp { - dst: b, - op: CmpOp::Ne, - float: false, - lhs: raw, - rhs: zero, + dst: Some(dst), + callee: AbiRef::new("bytes_h", "get"), + args: vec![receiver, *index], }); - (b, Ty::Bool) + (dst, Ty::Dyn) } - // `s.find(needle)` — byte index or -1 (the VM's `str::find`). - (Ty::Str, "find", [(needle, Ty::Str)]) => { + (Ty::Bytes, "slice", [(from, Ty::I64), (to, Ty::I64)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", "find"), - args: vec![receiver, *needle], + callee: AbiRef::new("bytes_h", "slice"), + args: vec![receiver, *from, *to], }); - (dst, Ty::I64) + (dst, Ty::Bytes) } - // Fresh-string unary transforms (VM core_methods semantics: `lower`/ - // `upper` are Unicode `to_lowercase`/`to_uppercase`, `reverse` is - // char-wise, `trim` is Rust `str::trim`). - (Ty::Str, "lower" | "upper" | "trim" | "reverse", []) => { - let helper = match name { - "lower" => "lower", - "upper" => "upper", - "trim" => "trim", - _ => "reverse", - }; + // The rest of `Bytes`. It had a carrier and four methods, so ten of its + // fourteen dropped the whole module to the VM — a receiver kind that is + // *almost* native is the shape a coverage number cannot show. + // + // `first`/`last` are `get(0)` / `get(-1)`: the read rule already counts a + // negative position from the end, so they need no helper of their own. + (Ty::Bytes, "first" | "last", []) => { + let index = ssa.new_val(); + insts.push(Inst::Const { + dst: index, + value: Const::I64(if name == "first" { 0 } else { -1 }), + }); let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", helper), - args: vec![receiver], + callee: AbiRef::new("bytes_h", "get"), + args: vec![receiver, index], }); - (dst, Ty::Str) + (dst, Ty::Dyn) } - (Ty::Str, "repeat", [(n, Ty::I64)]) => { + (Ty::Bytes, "take" | "skip", [(n, Ty::I64)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", "repeat"), + callee: AbiRef::new("bytes_h", if name == "take" { "take" } else { "skip" }), args: vec![receiver, *n], }); - (dst, Ty::Str) + (dst, Ty::Bytes) } - // `s.substring(start, length)` — byte-indexed in the VM (a - // non-boundary index aborts loudly, like the VM's panic). - (Ty::Str, "substring", [(start, Ty::I64), (length, Ty::I64)]) => { + (Ty::Bytes, "to_list", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", "substring"), - args: vec![receiver, *start, *length], + callee: AbiRef::new("bytes_h", "to_i64_list"), + args: vec![receiver], }); - (dst, Ty::Str) + (dst, Ty::ListI64) } - (Ty::Str, "replace", [(from, Ty::Str), (to, Ty::Str)]) => { + // The two pure sequence operations `Bytes` was missing while it had + // every other read of the list surface. `reverse` answers a `Bytes` — + // shape-preserving and element-type-independent, like `take`/`slice`. + (Ty::Bytes, "count", [(needle, Ty::I64)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", "replace"), - args: vec![receiver, *from, *to], + callee: AbiRef::new("bytes_h", "count"), + args: vec![receiver, *needle], }); - (dst, Ty::Str) + (dst, Ty::I64) } - // `s.chars()` — the VM returns a *Mixed* list (bare-text display), - // so the native carrier is a dyn list, not a typed string list. - (Ty::Str, "chars", []) => { + (Ty::Bytes, "reverse" | "sort" | "unique", []) => { let dst = ssa.new_val(); insts.push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("str", "chars"), + callee: AbiRef::new( + "bytes_h", + match name { + "sort" => "sort", + "unique" => "unique", + _ => "reverse", + }, + ), args: vec![receiver], }); - (dst, Ty::ListDyn) + (dst, Ty::Bytes) + } + (Ty::Bytes, "index_of", [(needle, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", "index_of"), + args: vec![receiver, *needle], + }); + (dst, Ty::Dyn) + } + (Ty::Bytes, "contains", [(needle, Ty::I64)]) => { + let raw = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(raw), + callee: AbiRef::new("bytes_h", "contains"), + args: vec![receiver, *needle], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: raw, + rhs: zero, + }); + (b, Ty::Bool) } // `m.get(key)` on string-keyed maps: the missing-key `Maybe` model. (Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool, "get", [(key, Ty::Str)]) => { @@ -1279,13 +3087,122 @@ pub(crate) fn lower_method_dispatch( }; (dst, maybe_ty) } + // `m.get(key, default)` — the same lookup, with the absent case + // answered by the caller's value rather than nil. Both halves were + // already here (`MaybePresent` is how `has` is lowered, `MaybeValue` + // is how a `Maybe` reaches a phi edge); a `Select` between them is the + // whole method. Without it, a map read with a fallback — an ordinary + // way to write one — dropped the module to the VM. + // + // The result is the plain scalar, not a `Maybe`: a default is always + // present, so the answer cannot be nil. + (Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool, "get", [(key, Ty::Str), (fallback, fallback_ty)]) + | (Ty::MapI64I64 | Ty::MapI64F64, "get", [(key, Ty::I64), (fallback, fallback_ty)]) => { + let (maybe_ty, scalar_ty) = match receiver_ty { + Ty::MapStrF64 | Ty::MapI64F64 => (Ty::MaybeF64, Ty::F64), + Ty::MapStrBool => (Ty::MaybeBool, Ty::Bool), + _ => (Ty::MaybeI64, Ty::I64), + }; + // A default of another carrier would have to widen the answer past + // the map's value type; that is the boxed-map question, not this + // one. + if *fallback_ty != scalar_ty { + return Err(Unsupported::TypeMismatch { pc }); + } + let looked = ssa.new_val(); + match receiver_ty { + Ty::MapStrF64 => insts.push(Inst::MapGetMaybeStrF64 { + dst: looked, + handle: receiver, + key: *key, + }), + Ty::MapI64F64 => insts.push(Inst::MapGetMaybeI64F64 { + dst: looked, + handle: receiver, + key: *key, + }), + Ty::MapI64I64 => insts.push(Inst::MapGetMaybeI64Key { + dst: looked, + handle: receiver, + key: *key, + }), + _ => insts.push(Inst::MapGetMaybe { + dst: looked, + handle: receiver, + key: *key, + }), + } + // `MaybeValue` narrows a `MaybeBool`'s word to a `Bool` itself, so + // the extracted value is already `scalar_ty` for all of them. + let value = ssa.new_val(); + insts.push(Inst::MaybeValue { + dst: value, + src: looked, + maybe_ty, + }); + let present = ssa.new_val(); + insts.push(Inst::MaybePresent { + dst: present, + src: looked, + maybe_ty, + }); + let dst = ssa.new_val(); + insts.push(Inst::Select { + dst, + cond: present, + then_v: value, + else_v: *fallback, + ty: scalar_ty, + }); + (dst, scalar_ty) + } + // `m.get(k, default)` with a key this side cannot type, or one of + // another kind. The typed arm above needs the carrier's own key type; + // this reaches the runtime's keyed lookup, which is total for a key + // kind the map simply does not hold and raises — with the + // interpreter's own `map.get() key:` prefix — for a kind that is not a + // key at all. A stored nil answers the default, because the + // interpreter cannot tell it from an absent key either. + ( + Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool | Ty::MapStrDyn | Ty::MapI64I64 | Ty::MapI64F64 | Ty::Dyn, + "get", + [(key, kty), (fallback, fty)], + ) if !matches!( + (receiver_ty, kty), + (Ty::MapStrI64 | Ty::MapStrF64 | Ty::MapStrBool, Ty::Str) | (Ty::MapI64I64 | Ty::MapI64F64, Ty::I64) + ) => + { + let map = to_dyn(ssa, insts, receiver, receiver_ty, pc)?; + let boxed_key = to_dyn(ssa, insts, *key, *kty, pc)?; + let boxed_default = to_dyn(ssa, insts, *fallback, *fty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("dyn", "map_get_or"), + args: vec![map, boxed_key, boxed_default], + }); + (dst, Ty::Dyn) + } // `m.set(key, value)` on string-keyed maps. + // + // A bool map rides the `str_i64` carrier, so its value crosses as the + // word — and it arrives as a `Bool`, the narrower machine type, which + // has to be widened. Only `Ty::I64` was accepted here, so + // `m.set(k, true)` on a `Map` dropped the module to the + // VM. (Ty::MapStrI64, "set", [(key, Ty::Str), (value, Ty::I64)]) - | (Ty::MapStrBool, "set", [(key, Ty::Str), (value, Ty::I64)]) => { + | (Ty::MapStrBool, "set", [(key, Ty::Str), (value, Ty::I64 | Ty::Bool)]) => { + let value = if args.get(1).map(|(_, ty)| *ty) == Some(Ty::Bool) { + let wide = ssa.new_val(); + insts.push(Inst::ZextBool { dst: wide, src: *value }); + wide + } else { + *value + }; insts.push(Inst::Call { dst: None, callee: AbiRef::new("map_h", "str_i64_set"), - args: vec![receiver, *key, *value], + args: vec![receiver, *key, value], }); let nil = ssa.new_val(); insts.push(Inst::Const { @@ -1308,6 +3225,109 @@ pub(crate) fn lower_method_dispatch( (nil, Ty::Nil) } // `xs.contains(v)` on typed lists (fcmp semantics for f64, like the VM). + // `index_of` on an int list. The VM has it on every sequence; here it + // existed only on `Str`, so `[1,2,3].index_of(2)` dropped its module to + // the VM — same answer, just slower, which is the kind of gap neither + // the differential corpus nor the coverage gate can see. + // + // One arm per carrier, and each takes exactly the needle its `contains` + // takes — in the VM both answer through one `typed_list_position`, so a + // carrier that accepts a needle for `contains` and refuses it here would + // make `xs.contains(v)` and `xs.index_of(v) != nil` disagree about which + // programs lower. + (Ty::ListI64, "index_of", [(v, Ty::I64)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "i64_index_of"), + args: vec![receiver, *v], + }); + (dst, Ty::Dyn) + } + (Ty::ListF64, "index_of", [(needle, Ty::F64 | Ty::I64)]) => { + let needle = coerce_to_f64(ssa, insts, *needle, args[0].1); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "f64_index_of"), + args: vec![receiver, needle], + }); + (dst, Ty::Dyn) + } + (Ty::ListStr, "index_of", [(needle, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "str_index_of"), + args: vec![receiver, *needle], + }); + (dst, Ty::Dyn) + } + (Ty::ListDyn, "index_of", [(needle, nty)]) => { + let boxed = to_dyn(ssa, insts, *needle, *nty, pc)?; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "dyn_index_of"), + args: vec![receiver, boxed], + }); + (dst, Ty::Dyn) + } + // `xs.contains(s)` on a string list. `str_contains` was declared in the + // ABI and reached only from the `in` operator, so `"a" in xs` lowered and + // `xs.contains("a")` did not — two spellings of one question, and the + // comment above these arms states the invariant that breaks: a carrier + // whose `index_of` lowers and whose `contains` does not makes + // `xs.contains(v)` and `xs.index_of(v) != nil` disagree about which + // programs stay native. + (Ty::ListStr, "contains", [(v, Ty::Str)]) => { + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("list_h", "str_contains"), + args: vec![receiver, *v], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: dst, + rhs: zero, + }); + (b, Ty::Bool) + } + // An `f64` needle against an `i64` list: `1.5 in [1, 2]` is false and + // `1.0 in [1, 2]` is true, because the two compare as numbers. The ABI + // has carried this helper for a while with nothing calling it — the + // shape was unreachable while the checker refused `[1, 2].contains(1.5)`. + (Ty::ListI64, "contains", [(v, Ty::F64)]) => { + let found = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(found), + callee: AbiRef::new("list_h", "i64_contains_f64"), + args: vec![receiver, *v], + }); + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let b = ssa.new_val(); + insts.push(Inst::Cmp { + dst: b, + op: CmpOp::Ne, + float: false, + lhs: found, + rhs: zero, + }); + (b, Ty::Bool) + } (Ty::ListI64, "contains", [(v, Ty::I64)]) => { let dst = ssa.new_val(); insts.push(Inst::Call { @@ -1330,9 +3350,129 @@ pub(crate) fn lower_method_dispatch( }); (b, Ty::Bool) } - _ => return Err(Unsupported::Opcode { pc, op: Opcode::Call }), + // No method of that name on that receiver — but a map's entry or a + // struct's field may *hold* a callable, which the interpreter calls + // (`CallMethodK`'s callable-property path). `m["inc"](3)` and `h.f(2)` + // are that, and they used to be the one spelling of a closure value + // that did not lower: `let f = m["inc"]; f(3);` did, so one meaning had + // a fast form and a slow one. + // + // Only after every real method arm has declined, so nothing here can + // shadow a method. + // A *boxed* receiver is deliberately not here. Its runtime type decides + // which method it is — `c.name.upper()` reads a struct field, so the + // receiver types `Dyn` and the interpreter dispatches `upper` on the + // String it holds. Taking the property path instead answered "a Map has + // no method `upper`" for a string that has one. The arm used to name an + // ABI function that does not exist, so the module failed MIR validation + // and every such program fell back — which hid the error and made the + // whole module unlowerable rather than this one call. + (Ty::MapStrDyn, _, _) => { + let key = materialize_key(ssa, insts, globals, name); + let property = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(property), + callee: AbiRef::new("map_h", "str_dyn_get"), + args: vec![receiver, key], + }); + let block_v = if args.is_empty() { + let null = ssa.new_val(); + insts.push(Inst::Const { + dst: null, + value: Const::I64(0), + }); + null + } else { + let b = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(b), + callee: AbiRef::new("rt", "spawn_args_new"), + args: Vec::new(), + }); + for &(v, ty) in args { + let boxed = to_dyn(ssa, insts, v, ty, pc)?; + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "spawn_args_push"), + args: vec![b, boxed], + }); + } + b + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("rt", "closure_call_property"), + args: vec![property, block_v, key], + }); + (dst, Ty::Dyn) + } + _ if map_missing_method => { + // This shape reaches lowering only when the static checker cannot + // prove the empty/mixed map's method surface. The interpreter raises + // before inspecting the argument, so native code must do the same. + // Emitting a raise also lets an enclosing native `try` catch it; + // refusing the region body here made codegen see a dangling + // `TryRegionCall` target. + let text = ssa.new_val(); + insts.push(Inst::Const { + dst: text, + value: Const::Str(GlobalId(crate::prescan::intern_global( + globals, + &format!( + "a Map has no method `{name}`, and this map has no key `{name}` holding a function either" + ), + ))), + }); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("rt", "raise_msg"), + args: vec![text], + }); + let nil = ssa.new_val(); + insts.push(Inst::Const { + dst: nil, + value: Const::Nil, + }); + (nil, Ty::Nil) + } + _ => { + return Err(Unsupported::UnsupportedMethod { + pc, + method: name.to_string(), + receiver: lk_aot_mir::ty_name(receiver_ty), + args: args.iter().map(|(_, ty)| lk_aot_mir::ty_name(*ty)).collect(), + }); + } }; let _ = globals; let _ = block; Ok(result) } + +/// The runtime helper that empties a container of this type, when there is one. +/// +/// The *answer* to `clear()` is not here: it is the receiver, for every +/// container, and the single arm that calls this says so once. +fn clear_helper(receiver_ty: Ty) -> Option<(&'static str, &'static str)> { + let helper = match receiver_ty { + Ty::ListI64 => ("list_h", "i64_clear"), + Ty::ListF64 => ("list_h", "f64_clear"), + Ty::ListStr => ("list_h", "str_clear"), + Ty::ListDyn => ("list_h", "dyn_clear"), + // `Map` rides the `str_i64` carrier. + Ty::MapStrI64 | Ty::MapStrBool => ("map_h", "str_i64_clear"), + Ty::MapStrF64 => ("map_h", "str_f64_clear"), + Ty::MapStrDyn => ("map_h", "str_dyn_clear"), + Ty::MapI64I64 => ("map_h", "i64_i64_clear"), + Ty::MapI64F64 => ("map_h", "i64_f64_clear"), + Ty::Set => ("set", "clear"), + // A boxed receiver: the tag says which carrier. Reached when the same + // container name meets two carriers — `fn empty(c) { c.clear(); }` + // called with a `Map` and a `Map` — which + // is a `Dyn` and had no arm at all. + Ty::Dyn => ("dyn", "clear"), + _ => return None, + }; + Some(helper) +} diff --git a/aot/lower/src/lower_module.rs b/aot/lower/src/lower_module.rs index a1d576c7..df6aaa9b 100644 --- a/aot/lower/src/lower_module.rs +++ b/aot/lower/src/lower_module.rs @@ -1,6 +1,6 @@ use super::*; -/// Lowers a `module.method(args)` call whose member [`module_call_abi`] maps to +/// Lowers a `module.method(args)` call whose member [`module_call_abi_rows`] maps to /// a typed lkrt ABI entry. Arity and argument types must match the schema /// exactly; the result (or nil) is written to the call-window base register. #[allow(clippy::too_many_arguments)] @@ -25,26 +25,72 @@ pub(crate) fn lower_module_call( match name { "from_list" | "collect" => { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let (v, ty) = ssa.read(base.wrapping_add(1), block, pc)?; + if name == "collect" { + // The list behind the stream. Its own value, so the stream + // it came from keeps its identity. + if ty != Ty::Dyn { + return Err(Unsupported::TypeMismatch { pc }); + } + let list = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(list), + callee: AbiRef::new("dyn", "stream_list"), + args: vec![v], + }); + ssa.write(base, block, (list, Ty::ListDyn)); + return Ok(()); + } if !matches!(ty, Ty::ListI64 | Ty::ListF64 | Ty::ListStr | Ty::ListDyn) { return Err(Unsupported::TypeMismatch { pc }); } - ssa.write(base, block, (v, ty)); + // Boxed under `DYN_STREAM`: a stream is not the list it is + // materialized into, and a box is a value of its own — which is + // what lets `from_list` answer a stream without the caller's + // `xs` becoming one. + let list = to_dyn_list_handle(ssa, insts, v, ty, pc)?; + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "from_stream"), + args: vec![list], + }); + ssa.write(base, block, (boxed, Ty::Dyn)); return Ok(()); } - "range" => { - if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); - } - let start = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; - let end = read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)?; - let one = ssa.new_val(); - insts.push(Inst::Const { - dst: one, - value: Const::I64(1), - }); + // The same three arities `iter.range` takes: the one-argument form + // counts from 0 and the third argument is the step. Only the + // two-argument form was accepted, so `stream.range(n)` — the + // shortest way to write it — dropped its module to the VM. + "range" if (1..=3).contains(&argc) => { + let (start, end) = if argc == 1 { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + let end = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; + (zero, end) + } else { + let start = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; + let end = read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)?; + (start, end) + }; + let one = if argc == 3 { + read_typed_scalar(ssa, insts, base.wrapping_add(3), block, Ty::I64, pc)? + } else { + let one = ssa.new_val(); + insts.push(Inst::Const { + dst: one, + value: Const::I64(1), + }); + one + }; let exclusive = ssa.new_val(); insts.push(Inst::Const { dst: exclusive, @@ -56,7 +102,14 @@ pub(crate) fn lower_module_call( callee: AbiRef::new("list_h", "i64_from_range"), args: vec![start, end, one, exclusive], }); - ssa.write(base, block, (handle, Ty::ListI64)); + let list = to_dyn_list_handle(ssa, insts, handle, Ty::ListI64, pc)?; + let boxed = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(boxed), + callee: AbiRef::new("dyn", "from_stream"), + args: vec![list], + }); + ssa.write(base, block, (boxed, Ty::Dyn)); return Ok(()); } _ => {} @@ -64,7 +117,10 @@ pub(crate) fn lower_module_call( } if module == "iter" && name == "range" { if !(1..=3).contains(&argc) { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let (start, end) = if argc == 1 { let zero = ssa.new_val(); @@ -103,12 +159,46 @@ pub(crate) fn lower_module_call( ssa.write(base, block, (handle, Ty::ListI64)); return Ok(()); } + // `string.to_int(text[, base])` — the base is optional in the language and + // not in the ABI, so a missing one is materialized as 10 here rather than + // duplicating the entry. Only the String arm lowers: `to_int(3.99)` is a + // Float and takes the generic path (it has no ABI row, so it falls back). + if module == "string" && name == "to_int" { + if !(1..=2).contains(&argc) { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); + } + let text = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::Str, pc)?; + let radix = if argc == 2 { + read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)? + } else { + let ten = ssa.new_val(); + insts.push(Inst::Const { + dst: ten, + value: Const::I64(10), + }); + ten + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "to_int"), + args: vec![text, radix], + }); + ssa.write(base, block, (dst, Ty::Dyn)); + return Ok(()); + } // `math.floor`/`ceil`/`round` dispatch on the argument's static type, // matching the VM's `integer_round`: an `Int` passes through unchanged, a // `Float` rounds via the lkrt helper (`f64::xxx() as i64`). if module == "math" && matches!(name, "floor" | "ceil" | "round") { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let (v, ty) = read_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; match ty { @@ -131,12 +221,104 @@ pub(crate) fn lower_module_call( } return Ok(()); } + // `math.clamp(v[, min[, max]])` — the module defaults `min` to 0 and `max` + // to 100, and a default lives in the export wrapper, which this side cannot + // read. So the two short arities materialize the same constants the + // declaration states rather than growing a row each; the full arity takes + // the row below. Named spellings (`clamp(v, max: 9)`) still go through the + // row, which is where the permutation is. + if module == "math" && name == "clamp" && (1..=2).contains(&argc) { + let value = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; + let min = if argc == 2 { + read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)? + } else { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::I64(0), + }); + zero + }; + let max = ssa.new_val(); + insts.push(Inst::Const { + dst: max, + value: Const::I64(100), + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("math", "clamp_i64"), + args: vec![value, min, max], + }); + ssa.write(base, block, (dst, Ty::I64)); + return Ok(()); + } + // Four members that answer differently for an Int than for a Float, and so + // dispatch on the argument's static type the way `math.floor` and + // `math.abs` below do rather than taking a promoting ABI row. Each Int arm + // is the module's own: `trunc` and `to_int` hand an Int back unchanged, + // `fract` answers `0.0` for one, and `to_float` widens. A Bool is a number + // to the two converters and to nothing else, matching the module's arms. + if module == "math" && matches!(name, "trunc" | "fract" | "to_int" | "to_float") { + if argc != 1 { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); + } + let (v, ty) = read_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; + // A Bool crosses as its word, which is the 0/1 the module converts. + let (v, ty) = if ty == Ty::Bool && matches!(name, "to_int" | "to_float") { + let wide = ssa.new_val(); + insts.push(Inst::ZextBool { dst: wide, src: v }); + (wide, Ty::I64) + } else { + (v, ty) + }; + let result = match (name, ty) { + ("trunc", Ty::I64) | ("to_int", Ty::I64) => (v, Ty::I64), + ("to_float", Ty::F64) => (v, Ty::F64), + ("to_float", Ty::I64) => { + let f = ssa.new_val(); + insts.push(Inst::IntToFloat { dst: f, src: v }); + (f, Ty::F64) + } + ("fract", Ty::I64) => { + let zero = ssa.new_val(); + insts.push(Inst::Const { + dst: zero, + value: Const::F64(0.0), + }); + (zero, Ty::F64) + } + (_, Ty::F64) => { + let (helper, ret) = match name { + "trunc" => ("trunc_f64", Ty::F64), + "fract" => ("fract_f64", Ty::F64), + _ => ("to_int_f64", Ty::I64), + }; + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("math", helper), + args: vec![v], + }); + (dst, ret) + } + _ => return Err(Unsupported::TypeMismatch { pc }), + }; + ssa.write(base, block, result); + return Ok(()); + } // `math.abs` returns its argument's type: Int → wrapping integer abs // (select(x < 0, 0 - x, x), sub wraps like the VM's release build), // Float → fabs via select on the float compare. if module == "math" && name == "abs" { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let (v, ty) = read_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; if !matches!(ty, Ty::I64 | Ty::F64) { @@ -190,7 +372,10 @@ pub(crate) fn lower_module_call( match name { "stdin" | "stdout" | "stderr" => { if argc != 0 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let handle = match name { "stdin" => 0, @@ -207,7 +392,10 @@ pub(crate) fn lower_module_call( } "write" | "writeln" => { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let handle = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let data = ssa.read_typed(base.wrapping_add(2), block, Ty::Str, pc)?; @@ -227,7 +415,10 @@ pub(crate) fn lower_module_call( } "flush" => { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let handle = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; insts.push(Inst::Call { @@ -245,7 +436,10 @@ pub(crate) fn lower_module_call( } "read_to_string" => { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let handle = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let dst = ssa.new_val(); @@ -257,14 +451,22 @@ pub(crate) fn lower_module_call( ssa.write(base, block, (dst, Ty::Str)); return Ok(()); } - _ => return Err(Unsupported::Opcode { pc, op: Opcode::Call }), + _ => { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); + } } } // `datetime.add`/`sub` are plain Int arithmetic (`timestamp ± seconds`); // `is_weekend` returns the helper's 0/1 as a `Bool`. if module == "datetime" && matches!(name, "add" | "sub") { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let ts = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let secs = read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)?; @@ -280,7 +482,10 @@ pub(crate) fn lower_module_call( } if module == "datetime" && name == "is_weekend" { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let ts = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let wide = ssa.new_val(); @@ -310,7 +515,10 @@ pub(crate) fn lower_module_call( // out of the subset. if module == "time" && name == "since" { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let start = read_typed_scalar(ssa, insts, base.wrapping_add(1), block, Ty::I64, pc)?; let end = read_typed_scalar(ssa, insts, base.wrapping_add(2), block, Ty::I64, pc)?; @@ -328,7 +536,10 @@ pub(crate) fn lower_module_call( // the VM's `min_max`); same-type scalar pairs lower to a select. if module == "math" && matches!(name, "min" | "max") { if argc != 2 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let (l, lty) = read_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; let (r, rty) = read_scalar(ssa, insts, base.wrapping_add(2), block, pc)?; @@ -357,7 +568,10 @@ pub(crate) fn lower_module_call( // `math.sign` keeps its argument's numeric flavor (the module's two arms). if module == "math" && name == "sign" { if argc != 1 { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); } let (v, ty) = read_scalar(ssa, insts, base.wrapping_add(1), block, pc)?; let sign_fn = match ty { @@ -374,15 +588,252 @@ pub(crate) fn lower_module_call( ssa.write(base, block, (dst, ty)); return Ok(()); } - let Some((callee, param_tys, ret_ty)) = module_call_abi(module, name) else { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + // `task.join_all(a, b, …)` — await each, in order, into a list. + // + // Variadic, so no row can describe it: a row has one arity. The three + // spellings the VM accepts are `join_all(a, b)`, `join_all(a)` (one task) + // and `join_all([a, b])` (a list of them); the first two are the same loop, + // and the list form needs the elements out of a handle, which is the + // `ListI64` case below. + if module == "task" && name == "join_all" && argc >= 1 { + let list = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(list), + callee: AbiRef::new("list_h", "dyn_new"), + args: Vec::new(), + }); + let await_into = |ssa: &mut Ssa, insts: &mut Vec, handle: ValueId| { + let value = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(value), + callee: AbiRef::new("rt", "task_await"), + args: vec![handle], + }); + insts.push(Inst::Call { + dst: None, + callee: AbiRef::new("list_h", "dyn_push"), + args: vec![list, value], + }); + }; + // `join_all([a, b])`: a single `List` of task handles. Its length + // is only known at run time, so the awaits are a loop in lkrt rather + // than unrolled here — which this lowering has no way to emit, so the + // list form stays a fallback and only the handle forms lower. + let mut handles = Vec::with_capacity(argc); + for index in 0..argc { + let reg = base.wrapping_add(1).wrapping_add(index as u8); + // A task travels boxed under `DYN_TASK`; this reads the id behind + // it, and a bare `I64` still passes through. + handles.push(crate::dyn_box::read_channel_id(ssa, insts, reg, block, pc)?); + } + for handle in handles { + await_into(ssa, insts, handle); + } + ssa.write(base, block, (list, Ty::ListDyn)); + return Ok(()); + } + // `string.slice(s, start)` — as above, defaulting to the character length. + if module == "string" && name == "slice" && argc == 2 { + let text = ssa.read_typed(base.wrapping_add(1), block, Ty::Str, pc)?; + let start = ssa.read_typed(base.wrapping_add(2), block, Ty::I64, pc)?; + let end = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(end), + callee: AbiRef::new("str", "char_len"), + args: vec![text], + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("str", "slice_chars"), + args: vec![text, start, end], + }); + ssa.write(base, block, (dst, Ty::Str)); + return Ok(()); + } + // `bytes.slice(b, start)` — the two-argument form, whose `end` defaults to + // the length. A row has one arity, so the default belongs here. + if module == "bytes" && name == "slice" && argc == 2 { + let handle = ssa.read_typed(base.wrapping_add(1), block, Ty::Bytes, pc)?; + let start = ssa.read_typed(base.wrapping_add(2), block, Ty::I64, pc)?; + let end = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(end), + callee: AbiRef::new("bytes_h", "len"), + args: vec![handle], + }); + let dst = ssa.new_val(); + insts.push(Inst::Call { + dst: Some(dst), + callee: AbiRef::new("bytes_h", "slice"), + args: vec![handle, start, end], + }); + ssa.write(base, block, (dst, Ty::Bytes)); + return Ok(()); + } + let arg_regs: Vec = (0..argc).map(|i| base.wrapping_add(1).wrapping_add(i as u8)).collect(); + lower_module_abi_call(ssa, insts, module, name, base, &arg_regs, block, pc) +} + +/// A stdlib module member called with `name: value` arguments. +/// +/// The window is the same one [`lower_named_call`] reads — callee at `base`, +/// the positional prefix, then `(name, value)` pairs — and the permutation is +/// the row's `named` list, which is the stdlib export's own `named(...)` +/// declaration. Every name is a constant the compiler emitted, so the ordering +/// is a compile-time fact. +/// +/// Rejects rather than guesses: a member with no names, a name that is not a +/// constant, an unknown or duplicated name, or a call that leaves a named +/// parameter out. That last one is not laziness — an omitted parameter takes +/// the *default*, and a default lives in the stdlib export wrapper, not in +/// anything this side can read. +#[allow(clippy::too_many_arguments)] +pub(crate) fn lower_named_module_call( + ssa: &mut Ssa, + insts: &mut Vec, + globals: &mut Vec, + module: &str, + name: &str, + base: u8, + positional_count: usize, + named_count: usize, + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + let reject = || Unsupported::Opcode { + pc, + op: Opcode::CallNamed, }; - if argc != param_tys.len() { - return Err(Unsupported::Opcode { pc, op: Opcode::Call }); + let argc = positional_count + named_count; + let row = module_call_abi_rows(module, name) + // The row's `named` list is the member's whole declaration, which can be + // longer than what this call passes (`string.replace` declares `all` + // too, and this arity leaves it defaulted) — so it bounds the names, + // rather than counting them. + .find(|row| row.args.len() == argc && row.named.len() >= argc - positional_count) + // No row of this arity, but the member may still be one that forwards + // to a method, and a method arm is matched on its argument list rather + // than on a row. `string.replace(s, p, w, all: false)` is that call: + // the positional spelling of it forwards and lowers, and only the + // named spelling landed here and fell back. Any row for the member + // carries the same declaration — the names are the stdlib export's, + // not the row's — so the permutation below can use it. + .or_else(|| { + forwards_to_method(module, name) + .and_then(|_| module_call_abi_rows(module, name).find(|row| !row.named.is_empty())) + }) + .ok_or_else(reject)?; + let mut arg_regs: Vec> = vec![None; argc]; + for (i, slot) in arg_regs.iter_mut().enumerate().take(positional_count) { + *slot = Some(base.wrapping_add(1).wrapping_add(i as u8)); + } + for pair in 0..named_count { + let name_reg = base + .wrapping_add(1) + .wrapping_add(positional_count as u8) + .wrapping_add((pair * 2) as u8); + let value_reg = name_reg.wrapping_add(1); + let arg_name = ssa.const_str_at(name_reg, block, pc).ok_or_else(reject)?; + // `row.leading`, not the call's positional count: a named-eligible + // parameter may be written either way, so a call that passes some of + // them positionally still needs the *declaration's* frame order. Adding + // the call's count instead pushed `string.slice(s, 1, end: 3)` past the + // end of a 3-argument frame — the mixed spelling fell back while both + // pure spellings lowered. + let slot = row + .named + .iter() + .position(|param| *param == arg_name.as_str()) + .ok_or_else(reject)? + + row.leading; + // A member may declare more names than this row's arity covers + // (`string.replace` declares `all` too, and the row is the arity that + // leaves it defaulted), so a name can land past the end. That is a call + // this row cannot serve, not an index to trust. The same check catches + // a name written for a slot an earlier *positional* argument already + // filled — `string.slice(s, 1, start: 2)` is that call, and the VM + // refuses it too. + if slot >= argc || arg_regs[slot].is_some() { + return Err(reject()); + } + arg_regs[slot] = Some(value_reg); + } + let arg_regs = arg_regs.into_iter().collect::>>().ok_or_else(reject)?; + // Only when no row serves this arity: a member that both forwards and has + // a row of the right shape keeps taking the row, so nothing that lowered + // before now takes a different path. + if row.args.len() != argc + && let Some(method) = forwards_to_method(module, name) + && let Some((&receiver_reg, rest)) = arg_regs.split_first() + { + let (receiver, receiver_ty) = ssa.read(receiver_reg, block, pc)?; + let args = rest + .iter() + .map(|reg| ssa.read(*reg, block, pc)) + .collect::, _>>()?; + let result = lower_method_dispatch(ssa, insts, globals, receiver, receiver_ty, method, &args, block, pc)?; + ssa.write(base, block, result); + return Ok(()); } + lower_module_abi_call(ssa, insts, module, name, base, &arg_regs, block, pc) +} + +/// The table-row half of [`lower_module_call`], with the argument registers +/// given explicitly rather than assumed consecutive. +/// +/// A named call (`regex.replace(s, pattern: p, replacement: r)`) supplies its +/// arguments out of frame order, so it permutes the registers and lands here. +/// Everything above this point — the shapes with defaults, dispatch on argument +/// type, or a variadic tail — stays positional-only: those read fixed register +/// offsets, and a permuted window would need each of them to agree separately. +#[allow(clippy::too_many_arguments)] +pub(crate) fn lower_module_abi_call( + ssa: &mut Ssa, + insts: &mut Vec, + module: &str, + name: &str, + base: u8, + arg_regs: &[u8], + block: usize, + pc: usize, +) -> Result<(), Unsupported> { + let argc = arg_regs.len(); + // A member may have one row per carrier (`hash.sha256` takes `Bytes | + // String`), so the arity filter comes first and the argument types choose + // among what is left. The peek is `ssa.read`, which is the same read the + // materialisation below performs — it adds no instruction, so a candidate + // that loses leaves nothing behind in the stream. + let candidates: Vec<&ModuleAbiRow> = module_call_abi_rows(module, name) + .filter(|row| row.args.len() == argc) + .collect(); + let Some(&first) = candidates.first() else { + return Err(Unsupported::CallShape { + pc, + reason: "no native lowering for this stdlib module function", + }); + }; + let row = if candidates.len() == 1 { + first + } else { + candidates + .iter() + .copied() + .find(|row| { + row.args.iter().enumerate().all(|(i, want)| { + ssa.read(arg_regs[i], block, pc) + .is_ok_and(|(_, got)| abi_param_accepts(*want, got)) + }) + }) + // No row matches: take the first and let the materialisation below + // report the mismatch, so the failure reads the same as it does for + // a single-row member. + .unwrap_or(first) + }; + let (callee, param_tys, ret_ty) = (row.abi, row.args, row.ret); let mut args = Vec::with_capacity(argc); for (i, want) in param_tys.iter().enumerate() { - let arg_reg = base.wrapping_add(1).wrapping_add(i as u8); + let arg_reg = arg_regs[i]; // `Number` parameters (schema type F64) accept an Int by promotion, // matching the stdlib module's `number_arg` coercion. if *want == Ty::F64 { @@ -398,6 +849,26 @@ pub(crate) fn lower_module_call( } continue; } + // A `Dyn` parameter is the schema's "any value", so it boxes whatever + // the register holds rather than demanding the caller already produced a + // `Dyn` — the strict read refused `chan.try_send(c, 7)` for no reason + // other than 7 being an unboxed Int. + if *want == Ty::Dyn { + let (v, ty) = ssa.read(arg_reg, block, pc)?; + args.push(to_dyn(ssa, insts, v, ty, pc)?); + continue; + } + // A channel or a task travels boxed (`DYN_CHAN` / `DYN_TASK`), and + // these members take the id behind it. The generic read would unbox + // through `dyn.as_i64`, which refuses a handle *on purpose* — a channel + // must not be usable wherever an `Int` is required. + // Every `I64` of theirs, not just the first: `task.join_all(a, b)` + // takes several. A capacity or a count passes through unchanged — an + // unboxed `I64` is returned as-is, and a boxed one unboxes either way. + if *want == Ty::I64 && matches!(module, "chan" | "task") { + args.push(crate::dyn_box::read_channel_id(ssa, insts, arg_reg, block, pc)?); + continue; + } args.push(ssa.read_typed(arg_reg, block, *want, pc)?); } let dst = match ret_ty { @@ -448,6 +919,13 @@ pub(crate) fn lower_module_call( (dst, ret_ty) } }; + // A stdlib member's `Map` result is an ordinary map — only + // `NewObject` and the struct-update desugar make a struct instance. Said + // here rather than per row, so a new row cannot forget it and lose its + // `len()` to the interpreter. + if dst.1 == Ty::MapStrDyn { + ssa.set_plain_map(dst.0); + } ssa.write(base, block, dst); Ok(()) } @@ -500,7 +978,18 @@ pub(crate) fn print_parts( } return Err(Unsupported::TypeMismatch { pc }); }; - let rest = &args[1..]; + format_parts(&fmt, &args[1..], pc) +} + +/// Expand one *constant* format template against its arguments. +/// +/// This is the half of `println`'s lowering that `"{} and {}".format(a, b)` +/// needs too: `format`'s receiver *is* the template, so the two spellings are +/// the same expansion producing the same pieces. `println` hands the pieces to +/// [`emit_print`]; `format` folds them into a value with [`fold_parts_to_str`]. +/// Sharing this is what keeps the two from drifting — the leftover-argument +/// rule below is subtle enough that a copy would. +pub(crate) fn format_parts(fmt: &str, rest: &[(ValueId, Ty)], pc: usize) -> Result, Unsupported> { let mut parts: Vec = Vec::new(); let mut lit = String::new(); let mut chars = fmt.chars().peekable(); @@ -560,6 +1049,25 @@ pub(crate) fn emit_print( newline: bool, pc: usize, ) -> Result<(), Unsupported> { + let (value, fresh) = fold_parts_to_str(ssa, insts, globals, parts, pc)?; + insts.push(Inst::PrintStr { value, newline }); + if fresh { + free_owned_str(insts, value); + } + Ok(()) +} + +/// Fold [`PrintPart`]s into one string value, answering whether the result is a +/// fresh allocation the caller now owns (as opposed to an interned constant). +/// +/// `println` frees it after printing; `format` keeps it as the method's result. +pub(crate) fn fold_parts_to_str( + ssa: &mut Ssa, + insts: &mut Vec, + globals: &mut Vec, + parts: Vec, + pc: usize, +) -> Result<(ValueId, bool), Unsupported> { pub(crate) fn lit_value(ssa: &mut Ssa, insts: &mut Vec, globals: &mut Vec, text: &str) -> ValueId { let gid = intern_global(globals, text); let dst = ssa.new_val(); @@ -614,9 +1122,5 @@ pub(crate) fn emit_print( (acc, acc_fresh) } }; - insts.push(Inst::PrintStr { value, newline }); - if fresh { - free_owned_str(insts, value); - } - Ok(()) + Ok((value, fresh)) } diff --git a/aot/lower/src/prescan.rs b/aot/lower/src/prescan.rs index b9f9ef6f..8018fffd 100644 --- a/aot/lower/src/prescan.rs +++ b/aot/lower/src/prescan.rs @@ -1,5 +1,63 @@ use super::*; +/// Which functions can read a global, directly or through a call. +/// +/// Conservative in the two ways that matter: a function whose instructions do +/// not decode counts as a reader, and so does one that calls anything this +/// cannot name — an indirect call, a closure, a method. The answer is only used +/// to *skip* a stop, so being wrong in the other direction would let the scan +/// prove an initialization that a callee could have observed first. +fn functions_reading_globals(module: &lk_core::vm::ModuleData) -> Vec { + let n = module.functions.len(); + let mut reads = vec![false; n]; + let mut calls: Vec> = vec![Vec::new(); n]; + let has_impls = !module.type_info.impls.is_empty(); + for (fi, func) in module.functions.iter().enumerate() { + for raw in &func.code { + let Ok(instr) = Instr::try_from_raw(*raw) else { + reads[fi] = true; + break; + }; + match instr.opcode() { + Opcode::GetGlobal => reads[fi] = true, + Opcode::CallDirect => calls[fi].push(instr.b() as usize), + // An unknown callee could read anything. + Opcode::Call | Opcode::CallNamed | Opcode::MakeClosure => { + reads[fi] = true; + } + // A method call reaches user code only if the module has an + // `impl` for it to dispatch to. Without one, `xs.push(1)` is a + // builtin on a container and cannot look at a global — and + // treating it as if it could was enough to keep every function + // that touches a list out of the answer, which is most of them. + Opcode::CallMethodK if has_impls => reads[fi] = true, + _ => {} + } + } + } + // Propagate along the call edges until nothing changes. Monotone — a + // function only ever becomes a reader — so it terminates. + loop { + let mut changed = false; + for fi in 0..n { + if reads[fi] { + continue; + } + if calls[fi] + .iter() + .any(|&callee| reads.get(callee).copied().unwrap_or(true)) + { + reads[fi] = true; + changed = true; + } + } + if !changed { + break; + } + } + reads +} + /// Reachability from the entry over `CallDirect`/`MakeClosure` edges that does /// **not** descend into VM-executed functions: their bodies (and everything /// only they reach) run on the embedded VM, so no native lowering is needed. @@ -8,6 +66,7 @@ pub(crate) fn native_reachable_functions( funcs: &[FunctionData], entry: u32, vm_functions: &std::collections::HashMap, + try_bodies: &std::collections::HashMap<(u32, usize), u32>, ) -> Vec { let n = funcs.len(); let mut reachable = vec![false; n]; @@ -34,6 +93,17 @@ pub(crate) fn native_reachable_functions( stack.push(callee); } } + // Outlined try bodies are AOT-only functions, so no CallDirect names + // them in the original artifact. A native parent reaches every body it + // owns; omitting these edges let a hybrid rerun remove a body while the + // parent's `TryRegionCall` still named it. + for (&(parent, _), &body) in try_bodies { + let body = body as usize; + if parent as usize == fi && body < n && !reachable[body] { + reachable[body] = true; + stack.push(body); + } + } } reachable } @@ -138,6 +208,36 @@ pub(crate) fn bridge_eligibility( /// of a zero-capture `MakeClosure`. Only such slots may resolve to /// [`GlobalRef::Lambda`] on `GetGlobal` — a slot with any other write could be /// observed with a different value at runtime. +/// Global slots the program **writes**, by slot index. +/// +/// A written slot is a user global, whatever it is called. Name resolution +/// (`inst::global`'s `GetGlobal`) otherwise answers "the stdlib module `time`" +/// for a program whose own `let time = …` shadows it — the same shadowing the +/// import path already respects, applied to the built-in names too. Before +/// this, fourteen ordinary variable names (`time`, `env`, `hash`, `iter`, +/// `os`, `io`, `net`, `math`, `fs`, `bytes`, `regex`, `task`, `process`, +/// `encoding`) made the whole program fall back the moment a function read one. +/// +/// Syntactic and whole-module on purpose: a read may lower before the write in +/// the same pass, so asking "has a write been *observed* yet" would answer +/// differently depending on pass order. +pub(crate) fn prescan_shadowed_globals(module: &lk_core::vm::ModuleData, global_count: usize) -> Vec { + let mut shadowed = vec![false; global_count]; + for func in &module.functions { + for raw in &func.code { + let Ok(instr) = Instr::try_from_raw(*raw) else { + break; + }; + if instr.opcode() == Opcode::SetGlobal + && let Some(flag) = shadowed.get_mut(instr.bx() as usize) + { + *flag = true; + } + } + } + shadowed +} + pub(crate) fn prescan_lambda_globals(module: &lk_core::vm::ModuleData, global_count: usize) -> Vec> { let mut candidates: Vec> = vec![None; global_count]; let mut write_counts = vec![0usize; global_count]; @@ -239,6 +339,7 @@ pub(crate) fn prescan_initialized_globals(module: &lk_core::vm::ModuleData, glob let Some(entry) = module.functions.get(module.entry as usize) else { return initialized; }; + let global_readers = functions_reading_globals(module); for raw in &entry.code { let Ok(instr) = Instr::try_from_raw(*raw) else { break; @@ -249,6 +350,23 @@ pub(crate) fn prescan_initialized_globals(module: &lk_core::vm::ModuleData, glob *flag = true; } } + // A call is a stop only if the callee could *look*. + // + // The scan is proving that a slot is written before any user code + // can read it, so a call in the entry prefix used to end it: the + // callee runs user code, and that code might read the slot while it + // is still native zero. But a function that reads no global — + // transitively — cannot observe one, so it is not the reader this is + // guarding against. + // + // What that recovers is `let g = make();` at the top level, which is + // an ordinary way to build a container and which the stop rule sent + // to the interpreter: the slot was never proven initialized, so it + // was widened to `Dyn`, a container in a `Dyn` slot has to be boxed, + // and boxing a container copies it — so the write was refused and + // the whole program fell back, for a call that never touched a + // global. + Opcode::CallDirect if !global_readers.get(instr.b() as usize).copied().unwrap_or(true) => {} Opcode::Jmp | Opcode::Test | Opcode::BrFalse @@ -306,7 +424,8 @@ pub(crate) fn reachable_functions(module: &lk_core::vm::ModuleData, extra_roots: } } while let Some(fi) = stack.pop() { - for raw in &module.functions[fi].code { + let code = &module.functions[fi].code; + for (pc, raw) in code.iter().enumerate() { let Ok(instr) = Instr::try_from_raw(*raw) else { continue; }; @@ -314,6 +433,31 @@ pub(crate) fn reachable_functions(module: &lk_core::vm::ModuleData, extra_roots: // refs), so it must be lowered/emitted too. let callee = match instr.opcode() { Opcode::CallDirect | Opcode::MakeClosure => instr.b() as usize, + // A function *value*, which is two different things. + // + // Almost always it is the compiler publishing a top-level `fn` + // to its global slot — `LoadFunction r; SetGlobal r, slot`, + // always adjacent — and that stores a value nothing calls. + // Following those would mark every declared function reachable + // and leave nothing for this pass to prune. + // + // Anything else loading a function value can call it, and one + // shape in particular does: a call to a function past index 255 + // cannot be a `CallDirect`, because that names its target in a + // byte, so the compiler spells it `LoadFunction` + `Call`. + // Missing that edge pruned the callee and then lowered a call + // to it — a function with no entry block, reported as failed + // MIR validation with nothing to say which function. + Opcode::LoadFunction => { + let published = code + .get(pc + 1) + .and_then(|next| Instr::try_from_raw(*next).ok()) + .is_some_and(|next| next.opcode() == Opcode::SetGlobal && next.a() == instr.a()); + if published { + continue; + } + instr.bx() as usize + } _ => continue, }; if callee < n && !reachable[callee] { @@ -430,7 +574,21 @@ pub(crate) fn empty_map_is_int_keyed(func: &FunctionData, start_pc: usize, dst_r strlist_regs.remove(&instr.a()); } regs.remove(&instr.a()); - str_regs.remove(&instr.a()); + // A string literal of **eight bytes or more** is a heap + // constant, not a `LoadString` — the inline/heap cut is at + // seven — so this is where a long one arrives, and clearing the + // mark for it made the key look like anything but a string. + // `let m = {}; m["averylongkey"] = 1;` guessed an integer-keyed + // map and the whole program fell back, while the same code with + // a seven-byte key lowered. + if matches!( + func.consts.heap_values.get(instr.bx() as usize), + Some(ConstHeapValueData::LongString(_)) + ) { + str_regs.insert(instr.a()); + } else { + str_regs.remove(&instr.a()); + } } Opcode::LoadString | Opcode::ConcatString | Opcode::ConcatN | Opcode::ToString => { str_regs.insert(instr.a()); @@ -532,6 +690,7 @@ pub(crate) fn empty_list_elem_guess(func: &FunctionData, start_pc: usize, dst_re | Opcode::SliceFrom | Opcode::ToIter | Opcode::NewList + | Opcode::NewMap | Opcode::NewObject => { indexed_regs.insert(instr.a()); str_regs.remove(&instr.a()); diff --git a/aot/lower/src/sig.rs b/aot/lower/src/sig.rs index 31a7a31e..9095be71 100644 --- a/aot/lower/src/sig.rs +++ b/aot/lower/src/sig.rs @@ -19,18 +19,305 @@ pub(crate) struct SigInfer { /// default as a real mismatch. pub(crate) ret_known: Vec, pub(crate) conflict: bool, + /// `(function, TryBegin pc)` → the function that region's body became. + /// + /// Filled before any function is lowered, because a region's body has to + /// exist as a function *before* the parent can call it — and because the + /// bodies are ordinary entries in the function table from then on, lowered + /// by the same loop as everything else. + pub(crate) try_bodies: std::collections::HashMap<(u32, usize), u32>, + /// A try body's parameters, as *registers of the enclosing function*. + /// + /// Discovered rather than declared: the body is lowered, and a read with no + /// definition inside it names the register that has to come in from + /// outside. Repeating that until it lowers gives exactly the set it needs — + /// no table of which operand each opcode reads, which is the kind of table + /// that is wrong in one entry and produces a wrong answer. + pub(crate) try_body_params: std::collections::HashMap>, + /// Registers a body actually **rebound**, as opposed to objects it mutated + /// through a handle it shares with the parent. + /// + /// Recorded while the body is lowered, by comparing the SSA's `current_def` + /// before and after each instruction — the same device the body already + /// uses to notice a cell's value changing, widened from the tracked cells + /// to every register. + /// + /// It replaces reading the instruction's `a` field as "the register this + /// writes", which is not true of every opcode: `log.push(2)` lowers to + /// `ListPush a=log`, where `a` is the *receiver*. Counting that as a + /// rebinding gave the register a cell, and the `dyn.from_list` / + /// `dyn.as_list` round trip a cell implies is what loses the mutation. + pub(crate) try_body_rebound: std::collections::HashMap>, + /// What type each of those inputs travels as, when it is not `I64`. + /// + /// The trampoline marshals a body's inputs as machine words in a stack + /// buffer, so anything a word can hold may cross: an integer, and a + /// container handle, which *is* a pointer. What may not are the carriers + /// that occupy two registers (`Dyn`, the `Maybe`s) and `F64`, which the ABI + /// passes in XMM while the trampoline passes integers. + /// + /// Recorded by the caller, which is where the register's real type is + /// known, and read by the body on the next pass — the same fixpoint that + /// discovers *which* registers are inputs at all. + pub(crate) try_body_param_tys: std::collections::HashMap<(u32, u8), Ty>, + /// Region inputs the enclosing function holds as a *closure reference* + /// rather than as a value. + /// + /// A lambda has no runtime representation natively — it is a compile-time + /// `GlobalRef`, which is why storing one in a list rejects — so a region + /// input that is one has no word to marshal. It crosses the same way an + /// erased lambda argument crosses an ordinary call instead: the *identity* + /// travels at compile time (the body seeds the register with the ref) and + /// only the environment travels at run time, as extra words in the same + /// argument buffer. + /// + /// Without it `try { r = inner(); }` rejected for any local `inner`, which + /// is a shape a `try` block is written around constantly. + pub(crate) try_body_lambdas: std::collections::HashMap<(u32, u8), LambdaIdentity>, + /// Region inputs the enclosing function holds as an *upvalue cell* — a + /// variable some closure in it captured. See [`cell_region_input`]. + /// Region inputs that are a **struct instance**, by the struct's name. + /// + /// `ssa.struct_facts` is the enclosing function's own SSA state and stops + /// at the boundary, so inside the body the input is an ordinary + /// `Map` — which is what a struct rides, and which `IsMap` + /// answers `true` for. `let {p: c} = p;` inside a `try` then matched a map + /// pattern against a struct, where the interpreter refuses. + pub(crate) try_body_struct_inputs: std::collections::HashMap<(u32, u8), crate::ssa::StructFact>, + /// Region inputs whose word is a **closure handle**. + /// + /// `ssa.closure_values` is the enclosing function's own SSA state and stops + /// at the boundary: the body is a separate lowering and the input arrives + /// as an ordinary `Dyn` word. What that costs is precision at the one place + /// the fact is load-bearing — a store whose *key* is a closure is provably + /// not a key and may lower to the runtime's refusal, while any other `Dyn` + /// key might be a valid key of the wrong kind and must not. + /// + /// Only for lambdas used as *values*; one still travelling as a compile-time + /// identity is [`SigInfer::try_body_lambdas`] and has no word at all. + pub(crate) try_body_closure_inputs: std::collections::HashSet<(u32, u8)>, + pub(crate) try_body_cell_inputs: std::collections::HashSet<(u32, u8)>, + /// What a cell input's *content* type is, as the caller saw it entering the + /// region. + /// + /// A cell is dynamically typed — reading one answers a `Dyn` — so without + /// this every use of a captured variable inside a region became `Dyn` + /// arithmetic, which has no lowering: `if (p0 % 5 == 0)` rejected for a `p0` + /// some lambda in the function happened to capture. The body unboxes to this + /// type instead, and a store of a *different* type joins the entry to `Dyn` + /// and retries, so the two ends cannot disagree about what the cell holds. + pub(crate) try_body_cell_input_tys: std::collections::HashMap<(u32, u8), Ty>, + /// What a runtime-cell capture *holds*, by `(callee, capture index)`. + /// + /// A cell is dynamically typed, so reading one answers `Dyn` — and `Dyn` + /// arithmetic has no lowering, so a closure that merely *adds* to what it + /// captured rejected the moment the capture became a cell (which is what + /// assigning to it, or handing it to a `try` region, does). The call site + /// seeds the cell and therefore knows the type; the callee unboxes reads to + /// it, and a store of a different type joins the entry to `Dyn` and retries, + /// so the two ends cannot hold two opinions about one object. + /// + /// [`SigInfer::try_body_cell_input_tys`] is the same notion for a region + /// input, keyed by *register* because that is what the caller has there. + pub(crate) cell_capture_tys: std::collections::HashMap<(u32, usize), Ty>, + /// Lambdas the program uses as **runtime values** — stored in a container, + /// put in a struct field, returned from a branch — mapped to the *clone* + /// that is that value. + /// + /// A clone, not the lambda itself. A closure value is called through one + /// arity switch in the runtime, so it must have an all-`Dyn` signature; + /// the same lambda's other uses are often the ones that resolve statically, + /// and the typed HOF path takes its address with the typed signature. + /// Pinning the original to `Dyn` cost `examples/syntax/closure.lk` its + /// lowering. So the original keeps its signature and the value form is a + /// second copy of the body — the mechanism lambda erasure already uses. + /// + /// The value is built **at the consumer that needs one** + /// (`lower_call::read_value`), never at the definition, so a register that + /// names a lambda keeps exactly one meaning and `Move`, a call window and + /// an iteration need to know nothing about any of this. + pub(crate) value_lambdas: std::collections::HashMap, + /// The clones themselves: what [`SigInfer::param_ty`] answers `Dyn` for. + pub(crate) value_lambda_bodies: std::collections::HashSet, + /// What type each of those environment words travels as, keyed by + /// `(body, register, capture index)` — the [`SigInfer::try_body_param_tys`] + /// of a lambda input, which needs one type per capture rather than one per + /// register. + pub(crate) try_body_lambda_env_tys: std::collections::HashMap<(u32, u8, u8), Ty>, + /// A try body's *outputs*: registers of the enclosing function that the + /// body assigns and the enclosing function goes on to read. + /// + /// They cannot travel in registers. The body runs in a frame of its own, so + /// a write there leaves the parent's copy alone — and on the raise path the + /// body never returns at all, while the VM still shows whatever it managed + /// to write. So each one becomes a cell: the parent makes it, the body + /// writes through it as it goes, and the parent reads it back on both + /// edges. + pub(crate) try_body_cells: std::collections::HashMap>, + /// Which of a body's cells the *caller* allocated as **raw** — parking a + /// typed container handle rather than a boxed value. + /// + /// The kind is one decision, and it belongs to whoever creates the cell. + /// Both sides used to decide it independently — the caller from the + /// register's type *entering* the region, the body from the type it + /// *stores* — and the two disagree exactly when a register that was `nil` + /// is assigned a container inside the body. `let out = nil; try { out = + /// b.take(1); } catch e { }` then wrote a raw handle into a value cell, and + /// the read raised "runtime type error" where the VM printed the bytes. + pub(crate) try_body_raw_cells: std::collections::HashSet<(u32, u8)>, + /// Registers a *later* read proved the body had to write back. + /// + /// `try_body_cells` is what the region's own scan could see: registers the + /// enclosing function had already defined. This is the other half — a + /// register first defined *inside* the body and read after it, which the + /// scan cannot know about because nothing in the parent defines it. The + /// read itself is the evidence, and it arrives as an `UndefinedOperand`. + pub(crate) try_body_extra_cells: std::collections::HashMap>, + /// Try bodies that `return` from the **enclosing** function. + /// + /// A body is outlined into a function of its own, so a `return` written in + /// it would return from *that* function — a different program. It used to be + /// refused, which made `try { return n * 2; } catch e { return -1; }` drop + /// the whole program to the VM while the value form + /// (`let v = try { n * 2 } catch e { -1 }; return v;`) lowered. The same + /// function, two spellings, one of them three times slower. + /// + /// So the body gets a third channel beside "the value" and "it raised": two + /// more output cells, a flag and the value. The body sets them and returns + /// normally; the caller checks the flag on the ok edge and returns. + pub(crate) try_body_returns: std::collections::HashSet, + /// What a try body's parked `return` *is*, by body index. + /// + /// The value is boxed into the outcome cell and read back out with the + /// enclosing function's return type — which is joined over the returns that + /// function makes *directly*, and a region's return is not one of those. So + /// + /// ```lk + /// fn f() -> Any { let r: Any = []; try { return "ok " + r; } catch e { return "E"; } } + /// ``` + /// + /// took `Str` from the `catch` arm, read a list back as a string, and + /// raised where the interpreter answered. Recording the parked type lets + /// the two be joined, and a disagreement takes the `dyn_rets` retry that + /// two disagreeing direct returns already take. + pub(crate) try_body_ret_tys: std::collections::HashMap, + /// How many escape trailers a try body ends with — one per distinct pc + /// outside the region that its `break`/`continue` jumps to + /// (`TryRegionShape::escape_targets`). + /// + /// The body cannot work this out for itself: the trailers are `Return0` + /// placeholders, and which of its instructions are trailers rather than code + /// is a fact about the *region*, which lives in the parent. The count is + /// enough — they are the last `n` instructions, and the outcome code of the + /// `k`th is `2 + k`. + /// + /// A body with any of these takes the outcome flag whether or not it also + /// `return`s; the value cell stays tied to [`SigInfer::try_body_returns`], + /// since a `break` carries nothing and the trampoline's argument budget is + /// eight. + pub(crate) try_body_escapes: std::collections::HashMap, + /// The *enclosing* function's captures, handed to a try body so its + /// `LoadCapture k` has somewhere to resolve. + /// + /// A body is outlined with `capture_count == 0`, so its own capture list + /// held only the region's cell inputs — and `LoadCapture 0` inside it either + /// found nothing (a refusal) or, with enough cell inputs, would have found + /// the wrong one. That made a `try` inside *any* capturing closure + /// unlowerable, which is most closures: `spawn(|| { try { … } catch e { … } + /// })` is the ordinary way to write a goroutine that handles its own errors. + /// + /// Passed positionally and always, not on demand: the body's capture indices + /// are the enclosing function's, so index `k` has to be index `k`. A + /// statically-known capture still occupies a slot and carries a dead word, + /// the same way `ClosureCapture::StaticRef` already does at an ordinary call. + pub(crate) try_body_outer_captures: std::collections::HashMap>, + /// What a `Cell`-typed [`SigInfer::try_body_outer_captures`] entry holds, + /// copied from what the enclosing function reads it as — so the body's + /// arithmetic on a captured variable is typed the same way the enclosing + /// closure's is, rather than falling back to `Dyn`. + pub(crate) try_body_outer_cell_tys: std::collections::HashMap<(u32, usize), Ty>, /// Empty-`[]` literals whose guessed element type a consumer /// contradicted (`(function, pc)`): the next fixpoint pass materializes /// them as Dyn lists. - pub(crate) dyn_empty_lists: std::collections::HashSet<(u32, usize)>, + pub(crate) dyn_literals: std::collections::HashSet<(u32, usize)>, + /// `(function, parameter register)` pairs whose list argument must be + /// built as a Dyn list by every caller, because the callee pushes an + /// element the typed carrier cannot hold. + /// + /// The demand travels *up*: a callee cannot fix its own parameter (the + /// allocation belongs to the caller, and the caller's other aliases read + /// it), so the carrier has to be decided at the literal. + pub(crate) dyn_params: std::collections::HashSet<(u32, u8)>, /// Loop-header phis discovered to merge heterogeneous boxable types /// (`(function, block, slot)`): the next fixpoint pass pre-types them /// `Dyn` so the loop body consumes them through the Dyn arms. + /// Loop-header phis forbidden from inheriting provenance, discovered by a + /// contradicting edge (`Unsupported::PhiProvenance`). + pub(crate) no_phi_provenance: std::collections::HashSet<(u32, usize, usize)>, pub(crate) dyn_loop_phis: std::collections::HashSet<(u32, usize, usize)>, /// Functions whose returns disagreed on a boxable type (or returned a /// nullable carrier): the next fixpoint pass boxes every return point, /// making the function return `Dyn` instead of rejecting the module. pub(crate) dyn_rets: std::collections::HashSet, + /// `(function, capture index)` pairs that must travel as a **runtime cell** + /// rather than by value, because the body assigns to them. + /// + /// A closure's captures are hidden trailing arguments holding the cell's + /// content at the call site — right for a capture the body reads, and with + /// nowhere to put a write. So `|v| { acc = acc + v; }`, which is most of + /// what a closure is for, dropped the whole program to the VM. + /// + /// Discovered the same way `dyn_rets` and `try_body_params` are: the body + /// is lowered, the assignment finds a by-value capture, records the pair + /// and asks for a retry. The next pass has the caller seed an `rt.cell_new` + /// and read it back — the same carrier a `try` body's outer assignment + /// already crosses on. Nothing guesses at the bytecode's register + /// provenance, and a read-only capture keeps passing as a plain value. + pub(crate) cell_captures: std::collections::HashSet<(u32, usize)>, + /// `(function, capture index)` → the callable that capture *is*. + /// + /// `let f = |x| x + 1; let g = |x| f(x) * 2;` — composing two lambdas, which + /// is most of what having them is for. `f` is captured, so the compiler puts + /// it in a cell, and what goes into that cell is a lowering-time reference, + /// not a value. The callee's `LoadCapture` + `LoadCellVal` then read a + /// parameter that holds nothing meaningful. + /// + /// A reference has no runtime representation here, so the capture still + /// occupies its ABI slot (a dead `0`) and the *meaning* travels through this + /// map instead. Discovered by the caller and retried, the same loop + /// `cell_captures` uses — so the callee never lowers before the fact exists; + /// if it somehow did, the `Call` on a plain integer refuses and the retry + /// fixes it. + pub(crate) ref_captures: std::collections::HashMap<(u32, usize), GlobalRef>, + /// Per function: the struct its returns are known to construct. + /// + /// A type's *name* only ever entered the lowering from a `NewObject` + /// (`ssa.struct_facts`), so it stopped at the function boundary: the + /// receiver of `make(3, 4).norm()` had no type and the method call fell out + /// of the devirtualizing path — in one module as much as across two. This + /// carries it out, and the fixpoint carries it to callers lowered before + /// their callee. + /// + /// `Some(None)` where the returns disagree or one of them is not a struct: + /// an answer that is sometimes wrong would devirtualize to the wrong impl. + pub(crate) ret_structs: std::collections::HashMap>, + /// `(callee, parameter slot)` → the struct every call site passes there. + /// + /// The parameter-side twin of [`Self::ret_structs`], and the same missing + /// provenance one step earlier: a struct arriving as an *argument* had no + /// type name, so `fn area(q: P) { return q.w * q.h; }` read fields fine + /// (the carrier is `MapStrDyn` either way) while `fn area(q: P) { return + /// q.norm(); }` could not devirtualize and dropped the module to the VM. + /// Passing a value to a function is at least as common as returning one. + /// + /// `Some(None)` where the call sites disagree, or one of them passes + /// something that is not a struct: a name that is right only sometimes + /// would devirtualize to the wrong impl, which is worse than not lowering. + /// [`Self::observe_param`] takes the argument's name as a parameter — not + /// as a separate call the caller might forget — because a site that + /// silently records nothing inherits another site's answer, and that is + /// exactly the wrong-impl case. + pub(crate) param_structs: std::collections::HashMap<(usize, usize), Option>, /// Per module-global slot: the scalar type every `SetGlobal` writes (a /// mixed-type global marks `conflict`, rejecting the module rather than /// miscompiling one of the writes). @@ -44,6 +331,10 @@ pub(crate) struct SigInfer { /// assigned exactly once, in the entry prefix, from a zero-capture /// `MakeClosure`. Reading such a slot yields [`GlobalRef::Lambda`]. pub(crate) lambda_globals: Vec>, + /// Global slots the program writes — see + /// [`crate::prescan::prescan_shadowed_globals`]. A read of one resolves to + /// the slot, never to the stdlib module or builtin of the same name. + pub(crate) shadowed_globals: Vec, /// `lambda_params[f][i]` — this function's i-th parameter is an *erased* /// lambda with a statically known identity: the callee seeds the register /// with a `GlobalRef::Lambda`/`Closure` instead of binding a value, so @@ -101,6 +392,46 @@ pub(crate) struct SigInfer { } impl SigInfer { + /// Appends one function's worth of state to **every** per-function table, + /// returning its index. + /// + /// These tables are parallel arrays indexed by function, and the working + /// function list grows in three places: `try`-body outlining, a + /// lambda-argument specialization, and a closure-value clone. Each pushed + /// to the subset it happened to care about, and the subsets differed — so + /// after a single outlined `try` body, `lambda_params.len()` was one short + /// of `param_obs.len()` and a specialization's entry landed under the + /// *previous* function's index. The visible symptom was that + /// `fn ap(xs, f) { return xs.map(f); }` stopped lowering as soon as the + /// module contained a `try` anywhere, because the erased lambda parameter + /// was recorded for somebody else. + pub(crate) fn push_function(&mut self, params: Vec>, ret: Ty) -> u32 { + let index = self.param_obs.len() as u32; + self.param_obs.push(params); + self.ret_types.push(ret); + self.ret_known.push(true); + self.lambda_params.push(Vec::new()); + self.specialized.push(false); + self.plain_called.push(false); + self.ret_closures.push(None); + self.ret_closure_poisoned.push(false); + debug_assert!( + [ + self.ret_types.len(), + self.ret_known.len(), + self.lambda_params.len(), + self.specialized.len(), + self.plain_called.len(), + self.ret_closures.len(), + self.ret_closure_poisoned.len(), + ] + .iter() + .all(|&len| len == self.param_obs.len()), + "per-function tables must stay parallel" + ); + index + } + /// The type a parameter is believed to hold. /// /// An unobserved parameter defaults to `I64` rather than `Dyn`. `Dyn` @@ -110,7 +441,27 @@ impl SigInfer { /// the live functions it happens to call. A function that cannot lower on /// the `I64` guess is dropped instead, provided nothing reaches it. pub(crate) fn param_ty(&self, func: usize, i: usize) -> Ty { - self.param_obs[func].get(i).copied().flatten().unwrap_or(Ty::I64) + // The value form of a lambda is called through one arity switch, so + // every one of them has the same signature: all `Dyn`, parameters and + // captures alike. Same pinning `spawn` does to the body it launches by + // address. + if self.value_lambda_bodies.contains(&(func as u32)) { + return Ty::Dyn; + } + if let Some(observed) = self.param_obs[func].get(i).copied().flatten() { + return observed; + } + // `self` in `impl T { … }` is a struct instance, whatever the call + // sites said — including when there are none. Every impl method is a + // lowering root (a trait's arms must all exist), so an *uncalled* one + // was lowered with the `I64` default and then failed reading a field: + // `an operand at pc 1 is a str where a i64 is required`, in a method + // nobody calls, killing the whole module. `t4`/`t6` in the trait notes + // are exactly that. + if i == 0 && self.traits.impl_owner(func as u32).is_some() { + return Ty::MapStrDyn; + } + Ty::I64 } /// Records one call-site observation of `callee`'s parameter `slot_idx` @@ -121,7 +472,23 @@ impl SigInfer { /// observe as `Dyn` directly. The join is monotonic on a two-level /// lattice, so the fixpoint still terminates; function-vs-value /// polymorphism keeps its own reject (`lambda_params`). - pub(crate) fn observe_param(&mut self, callee: usize, slot_idx: usize, arg_ty: Ty) -> Ty { + pub(crate) fn observe_param( + &mut self, + callee: usize, + slot_idx: usize, + arg_ty: Ty, + arg_fact: Option<&crate::ssa::StructFact>, + ) -> Ty { + match self.param_structs.entry((callee, slot_idx)) { + std::collections::hash_map::Entry::Vacant(slot) => { + slot.insert(arg_fact.cloned()); + } + std::collections::hash_map::Entry::Occupied(mut slot) => { + if slot.get().as_ref() != arg_fact { + slot.insert(None); + } + } + } let obs = match arg_ty { Ty::Nil | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr | Ty::MaybeBool => Ty::Dyn, other => other, @@ -140,6 +507,40 @@ impl SigInfer { } } + /// Whether *every* capture of `callee` is a static reference. + /// + /// Then the closure needs nothing at runtime — it is a plain function + /// reference — so the capture environment is erased entirely rather than + /// carried as dead slots. That is what lets `xs.map(|x| f(x))` reach the + /// typed `map_fn` fast path, which calls the callback with exactly the + /// element and nothing else. + /// + /// All-or-nothing on purpose: a *mixed* environment would need a hole at one + /// index, and every call site would have to agree on where the hole is. The + /// dead-slot form already handles that case correctly, just with one wasted + /// register. + pub(crate) fn captures_all_static(&self, callee: usize, capture_count: usize) -> bool { + capture_count > 0 && (0..capture_count).all(|k| self.ref_captures.contains_key(&(callee as u32, k))) + } + + /// Records that capture `k` of `callee` has to arrive as a runtime cell, + /// and **pins** its parameter slot to [`Ty::Cell`]. + /// + /// The pin is the point: `param_obs` accumulates across fixpoint passes and + /// never resets, so the by-value type observed before the body's assignment + /// was seen would join with `Cell` to `Dyn` and the call site would then + /// fail to coerce the cell pointer at all. Returns whether this is new + /// information (the caller retries when it is). + pub(crate) fn require_cell_capture(&mut self, callee: usize, param_count: usize, k: usize) -> bool { + let fresh = self.cell_captures.insert((callee as u32, k)); + if let Some(slot) = self.param_obs.get_mut(callee).and_then(|p| p.get_mut(param_count + k)) { + let changed = *slot != Some(Ty::Cell); + *slot = Some(Ty::Cell); + return fresh || changed; + } + fresh + } + pub(crate) fn gvar(&self, slot: u16) -> u32 { self.gvar_of.get(&slot).copied().unwrap_or(u32::from(slot)) } @@ -166,6 +567,9 @@ pub(crate) fn ret_closure_candidate( let slot = ssa.cell_slot(*cid); ssa.read_slot(slot, block, 0).ok()? } + // A capture taken onward from an enclosing closure is not one of + // *this* function's parameter values, so the summary does not apply. + ClosureCapture::CellParam(_) | ClosureCapture::StaticRef => return None, ClosureCapture::Value(v, ty) => (*v, *ty), }; let k = fn_params diff --git a/aot/lower/src/ssa.rs b/aot/lower/src/ssa.rs index 49deea66..fdcd0153 100644 --- a/aot/lower/src/ssa.rs +++ b/aot/lower/src/ssa.rs @@ -45,6 +45,10 @@ pub(crate) fn build_term( // and `Some` for a bare `return` in a Dyn-returning function (nil // crosses the call boundary boxed). Some(Exit::Ret(None)) | Some(Exit::Ret(Some(_))) => Term::Ret(ret_val), + // The outcome code was written into the flag cell by the block's own + // instructions; the body itself returns nothing, as it does on every + // other path. + Some(Exit::TryEscape { .. }) => Term::Ret(None), Some(Exit::Jump(pc)) => br(pc), Some(Exit::Cond { then_pc, else_pc, .. }) => { let cond = cond_val.expect("cond resolved"); @@ -58,6 +62,23 @@ pub(crate) fn build_term( else_args: args_to(ssa, bi, e as usize), } } + // The body already ran, inside the trampoline; what is branched on is + // its *outcome*. True is "returned normally", so true is the + // fallthrough and false is the handler. + Some(Exit::TryRegion { + handler, fallthrough, .. + }) => { + let ok = cond_val.expect("try outcome resolved"); + let f = block_id(fallthrough); + let h = block_id(handler); + Term::CondBr { + cond: ok, + then_blk: BlockId(f), + then_args: args_to(ssa, bi, f as usize), + else_blk: BlockId(h), + else_args: args_to(ssa, bi, h as usize), + } + } Some(Exit::FusedCmp { jump_when, taken, @@ -149,6 +170,20 @@ pub(crate) struct Phi { pub(crate) operands: Vec<(usize, ValueId)>, } +/// What a `Map` word is — the two carriers share one machine +/// representation, so the MIR type cannot tell them apart. +/// +/// The lattice has no "unknown" member on purpose: absence from +/// [`Ssa::struct_facts`] is unknown, so a site that forgets to record a fact +/// makes the lowering refuse rather than answer for the wrong one. +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) enum StructFact { + /// A `NewObject` instance of this declared struct. + Struct(String), + /// An ordinary map. + PlainMap, +} + pub(crate) struct Ssa { pub(crate) reg_count: usize, /// Register slots plus the virtual cell slots appended after them @@ -158,8 +193,38 @@ pub(crate) struct Ssa { /// This function runs as a spawned goroutine (isolate): cell-capture /// writes go to the thread-private slots. pub(crate) spawned_isolate: bool, + /// What a capture parameter's runtime cell is agreed to hold, by capture + /// index and by the register that names it. + /// + /// Only a `try`-region cell input has one: a closure's own mutable capture + /// answers `Dyn`, which is what every cell read answered before. Keyed both + /// ways because the body asks by index (`LoadCellVal` on a `CellParam`) and + /// the enclosing frame asks by register (passing the same pointer one frame + /// further in). + pub(crate) cellparam_content: std::collections::HashMap, + cellparam_content_by_reg: std::collections::HashMap, + cellparam_reg: std::collections::HashMap, pub(crate) preds: Vec>, pub(crate) current_def: Vec>>, + /// Slots a block ends with *no* value in, whatever its predecessors say. + /// + /// A `try` region's body runs in its own frame, so a register it wrote and + /// did not carry back through a cell holds, in the parent, neither the + /// body's value nor reliably the parent's old one. `current_def = None` + /// cannot express that: the read falls through to `read_recursive`, which + /// walks predecessors and finds the pre-region definition — the stale value + /// that would be a wrong answer. + /// + /// So the absence is recorded rather than inferred, and a read of it fails + /// with `UndefinedOperand` naming the register. That error is already how + /// the fixpoint discovers which registers need a cell, so poisoning turns + /// "is anything reading this after the region?" — a liveness question that + /// would otherwise need a table of every opcode's read operands — into a + /// question the SSA answers by being asked. + /// `Some(body)` names the try body whose write was left behind, so the + /// read's failure can say which region needs the cell rather than making + /// every region in the function guess. + pub(crate) poisoned: Vec>>, pub(crate) sealed: Vec, pub(crate) filled: Vec, pub(crate) phis: Vec>, @@ -173,11 +238,40 @@ pub(crate) struct Ssa { /// Loop-header phis pre-typed `Dyn` by a fixpoint retry (slots keyed by /// `(block, slot)`; see `Unsupported::DynLoopPhi`). pub(crate) dyn_loop_slots: std::collections::HashSet<(usize, usize)>, - /// Empty-`[]` literal pcs forced to Dyn by a fixpoint retry. - pub(crate) dyn_empty_pcs: std::collections::HashSet, - /// Guessed empty-list handles → their literal pc (a consumer that - /// contradicts the guess reports `EmptyListGuessWrong`). - pub(crate) empty_guess: std::collections::HashMap, + /// Loop-header phis a fixpoint retry has forbidden from inheriting + /// provenance (`(block, slot)`; see `Unsupported::PhiProvenance`). + pub(crate) no_provenance_slots: std::collections::HashSet<(usize, usize)>, + /// Container-literal pcs forced to a Dyn carrier by a fixpoint retry. + pub(crate) dyn_literal_pcs: std::collections::HashSet, + /// SSA values that hold a **closure**, built by + /// `lower_call::materialize_closure`. + /// + /// A closure is a `Dyn` like any other as far as the type lattice goes, and + /// that is not enough: `dyn.as_i64` is a legitimate lowering for a boxed + /// value the checker proved is an `Int`, and a nonsense one for a closure. + /// The consumers that unbox into a scalar ask this before they do + /// (`convert::read_typed_scalar`), so a lambda pushed into a guessed `[]` + /// widens the literal instead of compiling to an unbox that raises on the + /// one value the list was built to hold. + pub(crate) closure_values: std::collections::HashSet, + /// A closure value with an *empty* environment → the function it names. + /// + /// The same fact `GlobalRef::Lambda` carries, kept across the point where + /// the reference becomes a value. The list HOFs' typed fast paths ask which + /// function a callback register names, and a capture-free lambda still + /// answers that after it has been built — without this, a lambda used as a + /// value *anywhere* dropped every `xs.map(f)` in the module to the generic + /// path, which has no lowering for it at all. + pub(crate) closure_fidx: std::collections::HashMap, + /// A container literal's handle → `(its pc, the carrier it was built with)`. + /// + /// The carrier is a *judgement about what goes in*, and a later store can + /// contradict it: an empty `[]` guesses, a `[1, 2]` reads its own elements, + /// a `{"a": 1}` reads its own values — and all three are equally wrong when + /// the program then puts a String in. Whoever finds the contradiction + /// reports `LiteralElemTypeContradicted` naming this pc, and the fixpoint + /// rebuilds that literal with a Dyn carrier. + pub(crate) literal_carrier: std::collections::HashMap, /// Constant-range materializations (`NewRange` with all-const operands, /// step 1): handle → exclusive `(start, end)`. Lets `GetIndex` recognize /// a range key (`s[1..3]`) and emit a real slice. @@ -199,6 +293,19 @@ pub(crate) struct Ssa { /// propagated by `Move`. Block-local by construction; any write to the /// register clears it. pub(crate) builtin_regs: std::collections::HashMap<(usize, u8), GlobalRef>, + /// Cells whose whole content is a lowering-time reference — a lambda, a + /// closure, a named function. + /// + /// `let f = |x| x + 1; let g = |x| f(x) * 2;` is the shape: `f` is captured, + /// so the compiler puts it in a cell, and what goes *into* that cell is a + /// `GlobalRef`, not a value. `StoreCellVal` read the register for an SSA + /// value, found none, and the program fell back — composing two lambdas, + /// which is most of what having them is for. + /// + /// One ref per cell. A cell that is also assigned something else refuses + /// (`Unsupported`, so the program falls back) rather than guessing which + /// meaning a later read wanted. + pub(crate) cell_refs: std::collections::HashMap, /// Fresh ids for upvalue cells created by `LoadHeapConst`; each cell's /// content lives in virtual slot `reg_count + cid`, participating in the /// same Braun construction as registers (cross-block cell state gets @@ -211,11 +318,25 @@ pub(crate) struct Ssa { /// (`Maybe` ↔ scalar merges); appended after the block's own instructions /// when the MIR blocks are assembled. pub(crate) edge_insts: Vec>, - /// `NewObject` provenance: the struct type name behind a `MapStrDyn` - /// handle value (plan J1). Method calls and display contexts consult the - /// trait table through it; `Move` preserves the `ValueId`, so the entry - /// follows the value across registers for free. - pub(crate) struct_types: std::collections::HashMap, + /// What a `MapStrDyn` handle value actually is, when this function can + /// prove it (plan J1). Method calls and display contexts consult the trait + /// table through it; `Move` preserves the `ValueId`, so the entry follows + /// the value across registers for free. + /// + /// **Absence means unproven, not "a plain map".** A struct instance and a + /// map share the carrier, so a value with no fact might be either, and the + /// map *collection* operations refuse there — answering a struct's field + /// count for `len()` is a wrong answer, and the interpreter raises instead. + pub(crate) struct_facts: std::collections::HashMap, + /// A list handle → the declared struct **all** its elements are, when they + /// agree. + /// + /// The element-side twin of [`Self::struct_types`]. An array of records is + /// an ordinary shape, and without this the struct identity stopped at the + /// list: `nodes[i].next` read a field of something the lowering had no name + /// for, so the declared field type could not be applied and the read stayed + /// boxed. + pub(crate) list_elem_struct: std::collections::HashMap, } impl Ssa { @@ -235,8 +356,12 @@ impl Ssa { slot_count, capture_slots: capture_count, spawned_isolate: false, + cellparam_content: std::collections::HashMap::new(), + cellparam_content_by_reg: std::collections::HashMap::new(), + cellparam_reg: std::collections::HashMap::new(), preds, current_def: vec![vec![None; slot_count]; total_blocks], + poisoned: vec![vec![None; slot_count]; total_blocks], sealed: vec![false; total_blocks], filled: vec![false; total_blocks], phis: (0..total_blocks).map(|_| Vec::new()).collect(), @@ -245,16 +370,21 @@ impl Ssa { next_val: 0, const_int: std::collections::HashMap::new(), dyn_loop_slots: std::collections::HashSet::new(), - dyn_empty_pcs: std::collections::HashSet::new(), - empty_guess: std::collections::HashMap::new(), + no_provenance_slots: std::collections::HashSet::new(), + dyn_literal_pcs: std::collections::HashSet::new(), + closure_values: std::collections::HashSet::new(), + closure_fidx: std::collections::HashMap::new(), + literal_carrier: std::collections::HashMap::new(), range_def: std::collections::HashMap::new(), list_len: std::collections::HashMap::new(), list_base_len: std::collections::HashMap::new(), const_strs: std::collections::HashMap::new(), builtin_regs: std::collections::HashMap::new(), + cell_refs: std::collections::HashMap::new(), next_cell: 0, edge_insts: vec![Vec::new(); total_blocks], - struct_types: std::collections::HashMap::new(), + struct_facts: std::collections::HashMap::new(), + list_elem_struct: std::collections::HashMap::new(), } } @@ -272,6 +402,8 @@ impl Ssa { pub(crate) fn write_slot(&mut self, slot: usize, block: usize, value: Reg) { if slot < self.slot_count { + // A write is a definition, so it clears the absence. + self.poisoned[block][slot] = None; self.current_def[block][slot] = Some(value); if slot < self.reg_count { self.builtin_regs.remove(&(block, slot as u8)); @@ -279,17 +411,101 @@ impl Ssa { } } + /// Records that `reg` names a compile-time reference from here on. + /// + /// **Clears the register's SSA definition**, which is the half of the + /// invariant that was missing. [`Ssa::write`] clears the reference, so a + /// value shadows a ref; a bare `builtin_regs` insert did *not* clear the + /// definition, so a ref did not shadow a value — and `read_slot` consults + /// `current_def` first. A register recycled from a value to a reference + /// therefore read back the **stale value**. + /// + /// `GlobalRef::ArgList` is the exception, and the only one: it is a *view* + /// of a materialized handle rather than a name for something with no value, + /// so both halves are meant to be live at once (see `NewList`, and the + /// `Move` arm that propagates the pair). + pub(crate) fn bind_ref(&mut self, block: usize, reg: u8, reference: GlobalRef) { + if (reg as usize) < self.reg_count && !matches!(reference, GlobalRef::ArgList(_)) { + self.current_def[block][reg as usize] = None; + } + self.builtin_regs.insert((block, reg), reference); + } + + /// What `reg` holds, without recording a read or building a phi for it. + /// + /// For asking a *question* about a register — "is this already a value?" — + /// where reading it would commit to a definition the caller may not want. + pub(crate) fn peek(&self, reg: u8, block: usize) -> Option { + self.current_def[block][reg as usize] + } + pub(crate) fn read(&mut self, reg: u8, block: usize, pc: usize) -> Result { self.read_slot(reg as usize, block, pc) } pub(crate) fn read_slot(&mut self, slot: usize, block: usize, pc: usize) -> Result { + // Checked before `current_def`, and before the predecessor walk inside + // `read_recursive` — which is the whole point: the stale value is + // reachable through the predecessors, and it is exactly what must not + // be returned. + if let Some(body) = self.poisoned[block][slot] { + return Err(Unsupported::UndefinedOperand { + pc, + reg: slot, + body: Some(body), + }); + } if let Some(v) = self.current_def[block][slot] { return Ok(v); } + // A register the lowering tracks as a compile-time reference has no SSA + // value on purpose, so the generic "read before any definition" would be + // describing the bookkeeping rather than the program. Named here, once, + // rather than at each consumer: the consumers are every reader. + if let Some(reference) = self.builtin_regs.get(&(block, slot as u8)) { + return Err(Unsupported::ReferenceAsValue { + pc, + reg: slot, + what: reference.describe(), + lambda: match reference { + GlobalRef::Lambda(fidx) | GlobalRef::Closure(fidx, _) | GlobalRef::UserFn(fidx) => Some(*fidx), + _ => None, + }, + }); + } self.read_recursive(slot, block, pc) } + /// Marks `slot` as having no value at the end of `block`. + /// + /// See [`Ssa::poisoned`]. Applied after a `try` region's write-backs, so a + /// register the region *did* carry back keeps the definition it was just + /// given. + pub(crate) fn poison(&mut self, reg: u8, block: usize, body: u32) { + if (reg as usize) < self.reg_count { + self.current_def[block][reg as usize] = None; + self.poisoned[block][reg as usize] = Some(body); + } + } + + /// Records what capture parameter `k`, reached through `reg`, holds. + pub(crate) fn set_cellparam_content_ty(&mut self, k: usize, reg: u8, ty: Ty) { + self.cellparam_content.insert(k, ty); + self.cellparam_content_by_reg.insert(reg, ty); + self.cellparam_reg.insert(k, reg); + } + + /// Which register names capture parameter `k`, for a cell input — what the + /// agreement in [`SigInfer::try_body_cell_input_tys`] is keyed by. + pub(crate) fn cellparam_reg(&self, k: usize) -> Option { + self.cellparam_reg.get(&k).copied() + } + + /// What the cell `reg` names holds, when this function knows. + pub(crate) fn cellparam_content_ty(&self, reg: u8) -> Option { + self.cellparam_content_by_reg.get(®).copied() + } + /// The virtual slot holding cell `cid`'s content. pub(crate) fn cell_slot(&self, cid: u32) -> usize { self.reg_count + cid as usize @@ -363,6 +579,47 @@ impl Ssa { .all(|&pred| self.collect_builtin_ref(reg, pred, visited, found)) } + /// The compile-time string a **register** holds at `block`, by whichever + /// route answers. + /// + /// One accessor rather than the `const_strs.get(v).or_else(reg_const_str)` + /// pair that was written out at six call sites: a name a lowering needs at + /// compile time (a struct's type name, a named argument, a bundled + /// module's member, a map key) is the same question every time, and two of + /// the sites had only half of it. + pub(crate) fn const_str_at(&mut self, reg: u8, block: usize, pc: usize) -> Option { + self.read(reg, block, pc) + .ok() + .and_then(|(v, _)| self.const_str_value(v)) + .or_else(|| self.reg_const_str(reg, block)) + } + + /// The compile-time string a *value* is, looking through a phi. + /// + /// [`Self::reg_const_str`] answers the same question from a register; this + /// one starts from the SSA value, which is what a consumer holding an + /// already-read operand has. A phi param redirects to its register's + /// reaching definitions — the compiler's loop-literal cache hoists a + /// template out of the loop body, so inside the loop `"{}"` *is* a phi + /// param and the plain map lookup saw nothing. `"{} ".format(i)` in a loop + /// fell back for that reason alone. + pub(crate) fn const_str_value(&self, v: ValueId) -> Option { + if let Some(found) = self.const_strs.get(&v) { + return Some(found.clone()); + } + let (phi_block, phi_reg) = self + .phis + .iter() + .enumerate() + .find_map(|(block, phis)| phis.iter().find(|phi| phi.param == v).map(|phi| (block, phi.reg)))?; + let mut visited = std::collections::HashSet::new(); + let mut found: Option = None; + let agreed = self.preds[phi_block] + .iter() + .all(|&p| self.collect_reg_const_str(phi_reg, p, &mut visited, &mut found)); + agreed.then_some(found).flatten() + } + pub(crate) fn reg_const_str(&self, reg: u8, block: usize) -> Option { let mut visited = std::collections::HashSet::new(); let mut found: Option = None; @@ -431,7 +688,11 @@ impl Ssa { return Ok(self.read_slot(slot, p, pc)?.1); } } - Err(Unsupported::UndefinedOperand { pc, reg: slot }) + Err(Unsupported::UndefinedOperand { + pc, + reg: slot, + body: None, + }) } pub(crate) fn read_recursive(&mut self, slot: usize, block: usize, pc: usize) -> Result { @@ -452,13 +713,27 @@ impl Ssa { ty, operands: Vec::new(), }); + // Provenance seeded from the same filled predecessor the *type* + // came from, and for the same reason: a loop body is lowered + // before its header is sealed, so waiting for every edge means the + // body never sees the fact at all. The seed is optimistic and + // checked when the operands arrive — a back edge that disagrees + // reports `PhiProvenance`, and the retry lowers this slot without + // it (`no_provenance_slots`). The type does exactly this already. + if !self.no_provenance_slots.contains(&(block, slot)) { + self.seed_provenance(param, slot, block, pc); + } self.incomplete[block].push(idx); (param, ty) } else if self.preds[block].len() == 1 { let p = self.preds[block][0]; self.read_slot(slot, p, pc)? } else if self.preds[block].is_empty() { - return Err(Unsupported::UndefinedOperand { pc, reg: slot }); + return Err(Unsupported::UndefinedOperand { + pc, + reg: slot, + body: None, + }); } else { let ty = self.phi_ty(slot, block, pc)?; let ty = if self.dyn_loop_slots.contains(&(block, slot)) { @@ -519,26 +794,8 @@ impl Ssa { .iter() .all(|&(_, _, ty)| ty == phi_ty || maybe_pair(ty, phi_ty)) { - // A guessed empty-`[]` handle read through a phi (loop/branch) - // keeps its provenance: every non-self edge must carry the same - // literal pc for the param to inherit it. - let mut guess: Option<(usize, Ty)> = None; - let mut all_guessed = true; - for &(_, v, _) in &incoming { - if v == param { - continue; - } - match self.empty_guess.get(&v) { - Some(&g) if guess.is_none() || guess == Some(g) => guess = Some(g), - _ => { - all_guessed = false; - break; - } - } - } - if all_guessed && let Some(g) = guess { - self.empty_guess.insert(param, g); - } + self.verify_seeded_provenance(param, &incoming, block, slot)?; + self.inherit_provenance(param, &incoming); for (p, v, ty) in incoming { let v = if ty == phi_ty { v @@ -570,6 +827,9 @@ impl Ssa { | Ty::ListF64 | Ty::ListStr | Ty::MapStrDyn + | Ty::Set + | Ty::Bytes + | Ty::SliceI64 | Ty::MaybeI64 | Ty::MaybeF64 | Ty::MaybeStr @@ -595,6 +855,128 @@ impl Ssa { Ok(()) } + /// The declared struct behind a handle, when it is one. + pub(crate) fn struct_name(&self, v: ValueId) -> Option<&str> { + match self.struct_facts.get(&v) { + Some(StructFact::Struct(name)) => Some(name), + _ => None, + } + } + + /// Whether a handle is *provably* an ordinary map — the proof the map + /// collection operations need before they may answer from the carrier. + pub(crate) fn is_plain_map(&self, v: ValueId) -> bool { + self.struct_facts.get(&v) == Some(&StructFact::PlainMap) + } + + /// Records a handle as an instance of a declared struct. + pub(crate) fn set_struct(&mut self, v: ValueId, name: String) { + self.struct_facts.insert(v, StructFact::Struct(name)); + } + + /// Records a handle as an ordinary map: a map literal, or a runtime call + /// whose result is one. Only a `NewObject` produces a struct, so anything + /// built any other way is this. + pub(crate) fn set_plain_map(&mut self, v: ValueId) { + self.struct_facts.insert(v, StructFact::PlainMap); + } + + /// Copies provenance from the first filled predecessor's definition of + /// `slot` onto a not-yet-complete phi parameter. + fn seed_provenance(&mut self, param: ValueId, slot: usize, block: usize, pc: usize) { + let preds = self.preds[block].clone(); + for p in preds { + if !self.filled[p] { + continue; + } + let Ok((v, _)) = self.read_slot(slot, p, pc) else { + return; + }; + if let Some(&carrier) = self.literal_carrier.get(&v) { + self.literal_carrier.insert(param, carrier); + } + if let Some(fact) = self.struct_facts.get(&v).cloned() { + self.struct_facts.insert(param, fact); + } + if let Some(name) = self.list_elem_struct.get(&v).cloned() { + self.list_elem_struct.insert(param, name); + } + return; + } + } + + /// What a phi inherits from its operands: the facts that are about *which + /// value this is*, not about its type. + /// + /// A phi is a new `ValueId`, so every side table keyed by one loses its + /// entry at a merge unless it is carried across. All three are carried the + /// same way — every non-self edge must agree — and they are carried in one + /// place so a fourth table cannot be added and forgotten. The guessed-`[]` + /// carrier had this; the struct identity did not, which is why a loop over + /// an array of records (`while c >= 0 { c = nodes[c].next; }`) lost the + /// declared field type at the loop header and stopped lowering. + fn inherit_provenance(&mut self, param: ValueId, incoming: &[(usize, ValueId, Ty)]) { + fn agreed( + incoming: &[(usize, ValueId, Ty)], + param: ValueId, + get: impl Fn(ValueId) -> Option, + ) -> Option { + let mut agreed: Option = None; + for &(_, v, _) in incoming { + if v == param { + continue; + } + match get(v) { + Some(found) if agreed.is_none() || agreed.as_ref() == Some(&found) => agreed = Some(found), + _ => return None, + } + } + agreed + } + if let Some(carrier) = agreed(incoming, param, |v| self.literal_carrier.get(&v).copied()) { + self.literal_carrier.insert(param, carrier); + } + if let Some(fact) = agreed(incoming, param, |v| self.struct_facts.get(&v).cloned()) { + self.struct_facts.insert(param, fact); + } + if let Some(name) = agreed(incoming, param, |v| self.list_elem_struct.get(&v).cloned()) { + self.list_elem_struct.insert(param, name); + } + } + + /// Checks a seeded phi provenance against the operands that have now + /// arrived. + /// + /// An optimistic seed the back edge contradicts has already been used by + /// the loop body, so it cannot simply be dropped here — the pass is + /// reported as retriable and the next one lowers this slot without the + /// seed, which is what `dyn_loop_phis` does for an optimistic *type*. + fn verify_seeded_provenance( + &mut self, + param: ValueId, + incoming: &[(usize, ValueId, Ty)], + block: usize, + slot: usize, + ) -> Result<(), Unsupported> { + let seeded_struct = self.struct_facts.get(¶m).cloned(); + let seeded_elem = self.list_elem_struct.get(¶m).cloned(); + if seeded_struct.is_none() && seeded_elem.is_none() { + return Ok(()); + } + for &(_, v, _) in incoming { + if v == param { + continue; + } + if seeded_struct.is_some() && self.struct_facts.get(&v) != seeded_struct.as_ref() { + return Err(Unsupported::PhiProvenance { block, slot }); + } + if seeded_elem.is_some() && self.list_elem_struct.get(&v) != seeded_elem.as_ref() { + return Err(Unsupported::PhiProvenance { block, slot }); + } + } + Ok(()) + } + /// Emits the `dyn.from_*` boxing sequence for one phi edge into /// `edge_insts[pred]` (they land after the block body, before the /// terminator). Mirrors `to_dyn`, but targets an edge, not the body. @@ -606,6 +988,12 @@ impl Ssa { Ty::Str => Some("from_str"), Ty::ListDyn => Some("from_list"), Ty::MapStrDyn => Some("from_map"), + // The three that box by tagging the handle in place. Missing here, + // a phi merging one of them with `nil` — `let out = try { … } catch + // e { … };` is exactly that shape — rejected the whole function. + Ty::Set => Some("from_set"), + Ty::Bytes => Some("from_bytes"), + Ty::SliceI64 => Some("from_slice"), _ => None, }; if let Some(name) = simple { @@ -647,12 +1035,26 @@ impl Ssa { Ty::MaybeStr => "from_maybe_str", _ => "from_maybe_bool", }; - let value = self.new_val(); + let value_narrow = self.new_val(); self.edge_insts[pred].push(Inst::MaybeValue { - dst: value, + dst: value_narrow, src: v, maybe_ty: ty, }); + // A `MaybeBool`'s value half comes back as the `Bool` it is + // and the ABI entry takes the word — the same widening the + // present half gets just below. See `dyn_box::to_dyn`, which + // boxes the same carriers on the non-edge path. + let value = if ty == Ty::MaybeBool { + let wide = self.new_val(); + self.edge_insts[pred].push(Inst::ZextBool { + dst: wide, + src: value_narrow, + }); + wide + } else { + value_narrow + }; let present_b = self.new_val(); self.edge_insts[pred].push(Inst::MaybePresent { dst: present_b, @@ -672,23 +1074,20 @@ impl Ssa { }); Some(dst) } + // In place, under a tag naming the carrier — see `dyn_box`'s arm + // for the aliasing the old element-wise rebuild lost. Ty::ListI64 | Ty::ListF64 | Ty::ListStr => { - let converter = match ty { - Ty::ListI64 => "i64_to_dyn", - Ty::ListF64 => "f64_to_dyn", - _ => "str_to_dyn", - }; - let converted = self.new_val(); - self.edge_insts[pred].push(Inst::Call { - dst: Some(converted), - callee: AbiRef::new("list_h", converter), - args: vec![v], + let kind = crate::dyn_box::typed_list_kind(ty).expect("checked by the arm"); + let kind_v = self.new_val(); + self.edge_insts[pred].push(Inst::Const { + dst: kind_v, + value: Const::I64(kind), }); let dst = self.new_val(); self.edge_insts[pred].push(Inst::Call { dst: Some(dst), - callee: AbiRef::new("dyn", "from_list"), - args: vec![converted], + callee: AbiRef::new("dyn", "from_typed_list"), + args: vec![v, kind_v], }); Some(dst) } diff --git a/aot/lower/src/tables.rs b/aot/lower/src/tables.rs index 0ad8d4af..ad9aec7c 100644 --- a/aot/lower/src/tables.rs +++ b/aot/lower/src/tables.rs @@ -1,5 +1,41 @@ use super::*; +/// The ABI entry that answers how many elements a container holds, per +/// carrier. +/// +/// One table because there is one question. It used to be written twice — once +/// for the `Len` opcode and once for the `is_empty` method — and the copies +/// disagreed: `{"a": true}.len()` lowered while `{"a": true}.is_empty()` did +/// not, and the same for both integer-keyed maps, because the second table was +/// a subset someone extended once and not again. +/// +/// `Str` is deliberately absent. A string's length is its count of Unicode +/// scalar values, which is a string call rather than a container one, so its +/// caller spells it rather than sharing a row that is only half right. +pub(crate) fn container_len_abi(ty: Ty) -> Option { + let (module, name) = match ty { + Ty::ListI64 => ("list_h", "i64_len"), + Ty::ListF64 => ("list_h", "f64_len"), + Ty::ListStr => ("list_h", "str_len"), + Ty::ListDyn => ("list_h", "dyn_len"), + // A window's length is its own, not the source's. + Ty::SliceI64 => ("slice_h", "i64_len"), + Ty::MapStrI64 => ("map_h", "str_i64_len"), + // The bool map rides the `str_i64` carrier, so it is the same call. + Ty::MapStrBool => ("map_h", "str_i64_len"), + Ty::MapStrF64 => ("map_h", "str_f64_len"), + Ty::MapStrDyn => ("map_h", "str_dyn_len"), + Ty::MapI64I64 => ("map_h", "i64_i64_len"), + Ty::MapI64F64 => ("map_h", "i64_f64_len"), + Ty::Set => ("set", "len"), + Ty::Bytes => ("bytes_h", "len"), + // A boxed Dyn: length dispatches on the runtime tag. + Ty::Dyn => ("dyn", "len_of"), + _ => return None, + }; + Some(AbiRef::new(module, name)) +} + /// Module-object metadata: how one stdlib module name binds. Single source /// of truth — the bare-`GetGlobal` whitelist and the submodule import /// routing both derive from this table (adding a module is one row here @@ -51,10 +87,14 @@ pub(crate) const MODULE_TABLE: &[ModuleRow] = &[ bare_global: true, submodule_of: None, }, + // `std` is a **submodule of `io`** (`use { std } from io;`), not a bare + // global: a bare `std` does not resolve at all. This row claimed otherwise — + // harmlessly, because it has no members here, but this table is documented + // as the single source of truth for how a module name binds. ModuleRow { name: "std", - bare_global: true, - submodule_of: None, + bare_global: false, + submodule_of: Some("io"), }, ModuleRow { name: "iter", @@ -76,6 +116,17 @@ pub(crate) const MODULE_TABLE: &[ModuleRow] = &[ bare_global: true, submodule_of: None, }, + // `chan`'s other members arrive pre-flattened (`GetGlobal "chan::close"`), + // because they are registered under those names; `chan.new` is an ordinary + // module export, so it arrives as the module object plus a `GetIndex`. The + // module needed a row here for that shape to resolve at all — without it + // `use chan; chan.new(1)` dropped its module to the VM while the global + // `chan(1)` lowered. + ModuleRow { + name: "chan", + bare_global: true, + submodule_of: None, + }, ModuleRow { name: "stream", bare_global: true, @@ -86,8 +137,46 @@ pub(crate) const MODULE_TABLE: &[ModuleRow] = &[ bare_global: true, submodule_of: None, }, - // `encoding`/`net` submodules (the parents themselves have no typed - // members — only the submodule objects bind). + ModuleRow { + name: "hash", + bare_global: true, + submodule_of: None, + }, + ModuleRow { + name: "random", + bare_global: true, + submodule_of: None, + }, + ModuleRow { + name: "regex", + bare_global: true, + submodule_of: None, + }, + ModuleRow { + name: "uuid", + bare_global: true, + submodule_of: None, + }, + // The submodule *parents*. They have no typed members of their own, but the + // name has to bind for `encoding.json.parse(s)` to reach the submodule at + // all — without these rows the chain stopped at the first dot and the whole + // program fell back, while `use { json } from encoding;` lowered. + ModuleRow { + name: "encoding", + bare_global: true, + submodule_of: None, + }, + ModuleRow { + name: "net", + bare_global: true, + submodule_of: None, + }, + ModuleRow { + name: "io", + bare_global: true, + submodule_of: None, + }, + // `encoding`/`net`/`io` submodules. ModuleRow { name: "json", bare_global: false, @@ -103,6 +192,21 @@ pub(crate) const MODULE_TABLE: &[ModuleRow] = &[ bare_global: false, submodule_of: Some("encoding"), }, + ModuleRow { + name: "base64", + bare_global: false, + submodule_of: Some("encoding"), + }, + ModuleRow { + name: "hex", + bare_global: false, + submodule_of: Some("encoding"), + }, + ModuleRow { + name: "url", + bare_global: false, + submodule_of: Some("encoding"), + }, ModuleRow { name: "socket", bare_global: false, @@ -113,6 +217,16 @@ pub(crate) const MODULE_TABLE: &[ModuleRow] = &[ bare_global: false, submodule_of: Some("net"), }, + ModuleRow { + name: "udp", + bare_global: false, + submodule_of: Some("net"), + }, + ModuleRow { + name: "file", + bare_global: false, + submodule_of: Some("io"), + }, ]; /// A bare `GetGlobal` of this name is a stdlib module object. @@ -140,6 +254,27 @@ pub(crate) struct ModuleAbiRow { pub(crate) abi: AbiRef, pub(crate) args: &'static [Ty], pub(crate) ret: Ty, + /// How many leading parameters can only be passed positionally — the + /// declaration's parameter count minus its `named(...)` count. + /// + /// This is where the named block *starts* in frame order, and it is not the + /// same as how many arguments a given call happens to pass positionally: a + /// named-eligible parameter may be written either way. Reading the call's + /// count instead, `string.slice(s, 1, end: 3)` placed `end` at slot 3 of a + /// 3-argument frame and fell back — the mixed spelling, which the VM has + /// always accepted, was the one that could not lower. + pub(crate) leading: usize, + /// The names of the trailing parameters a caller may pass by name, in + /// frame order — the stdlib export's `named(...)` list. + /// + /// Empty means positional-only, which is most members. A member that + /// declares names can be *called* by name, and that call is a different + /// opcode (`CallNamed`) carrying its arguments in caller order; without + /// these the permutation is unknown and the whole program falls back. The + /// The stdlib marks every parameter after the subject `named`, so a caller + /// can label the ones a reader cannot tell apart — `regex.replace(s, + /// pattern: p, replacement: r)` is three strings otherwise. + pub(crate) named: &'static [&'static str], } pub(crate) const fn abi_row( @@ -155,6 +290,30 @@ pub(crate) const fn abi_row( abi, args, ret, + // Positional-only, so every parameter is a leading one. + leading: args.len(), + named: &[], + } +} + +/// [`abi_row`] for a member whose trailing parameters may be passed by name. +pub(crate) const fn abi_row_named( + module: &'static str, + member: &'static str, + abi: AbiRef, + args: &'static [Ty], + ret: Ty, + leading: usize, + named: &'static [&'static str], +) -> ModuleAbiRow { + ModuleAbiRow { + module, + member, + abi, + args, + ret, + leading, + named, } } @@ -163,6 +322,8 @@ pub(crate) const MODULE_ABI: &[ModuleAbiRow] = &[ abi_row("os", "clock", AbiRef::new("os", "clock"), &[], Ty::F64), // Unix epoch milliseconds. abi_row("os", "epoch", AbiRef::new("os", "epoch"), &[], Ty::I64), + // Unix *seconds*, where `epoch` is milliseconds. + abi_row("os", "time", AbiRef::new("os", "time"), &[], Ty::I64), // Monotonic milliseconds / sleep-for-milliseconds. abi_row("time", "now", AbiRef::new("time", "now"), &[], Ty::I64), abi_row("time", "sleep", AbiRef::new("time", "sleep"), &[Ty::I64], Ty::Nil), @@ -180,6 +341,133 @@ pub(crate) const MODULE_ABI: &[ModuleAbiRow] = &[ abi_row("os", "os", AbiRef::new("os", "name"), &[], Ty::Str), abi_row("process", "cwd", AbiRef::new("process", "cwd"), &[], Ty::Str), abi_row("fs", "temp_dir", AbiRef::new("fs", "temp_dir"), &[], Ty::Str), + // The rest of `fs`. Every one of these had an lkrt implementation and an ABI + // row already — and no row here, so nothing could reach them: the runtime + // was built, and three of its error messages had drifted from the VM's + // without anything noticing, because an unreachable path is an *unverified* + // path, not a spare one. + abi_row( + "fs", + "read_to_string", + AbiRef::new("fs", "read_to_string"), + &[Ty::Str], + Ty::Str, + ), + // `fs.write(path, data)` takes `Bytes | String` — one row per carrier. + abi_row( + "fs", + "write", + AbiRef::new("fs", "write_str"), + &[Ty::Str, Ty::Str], + Ty::Bool, + ), + abi_row( + "fs", + "write", + AbiRef::new("fs", "write_bytes"), + &[Ty::Str, Ty::Bytes], + Ty::Bool, + ), + abi_row( + "fs", + "read_dir", + AbiRef::new("fs", "read_dir_list"), + &[Ty::Str], + Ty::ListStr, + ), + // `String?`, so it arrives boxed: a resolved path that is not UTF-8 is nil. + abi_row( + "fs", + "canonicalize", + AbiRef::new("fs", "canonicalize"), + &[Ty::Str], + Ty::Dyn, + ), + // `fs.metadata` and `env.vars` answer string-keyed maps of mixed values — + // `Ty::MapStrDyn`, built on the lkrt side through the same two-stage + // construction the VM uses, because a map's iteration order is what + // `println` prints. + abi_row( + "fs", + "metadata", + AbiRef::new("fs", "metadata_map"), + &[Ty::Str], + Ty::MapStrDyn, + ), + abi_row("env", "vars", AbiRef::new("env", "vars_map"), &[], Ty::MapStrDyn), + abi_row("fs", "is_file", AbiRef::new("fs", "is_file"), &[Ty::Str], Ty::Bool), + abi_row("fs", "is_dir", AbiRef::new("fs", "is_dir"), &[Ty::Str], Ty::Bool), + abi_row( + "fs", + "append", + AbiRef::new("fs", "append_str"), + &[Ty::Str, Ty::Str], + Ty::Bool, + ), + abi_row( + "fs", + "append", + AbiRef::new("fs", "append_bytes"), + &[Ty::Str, Ty::Bytes], + Ty::Bool, + ), + abi_row( + "fs", + "create_dir", + AbiRef::new("fs", "create_dir"), + &[Ty::Str], + Ty::Bool, + ), + abi_row( + "fs", + "create_dir_all", + AbiRef::new("fs", "create_dir_all"), + &[Ty::Str], + Ty::Bool, + ), + // The `remove_*` trio answers `false` for a path that was not there, and + // raises for anything else. + abi_row( + "fs", + "remove_file", + AbiRef::new("fs", "remove_file"), + &[Ty::Str], + Ty::Bool, + ), + abi_row( + "fs", + "remove_dir", + AbiRef::new("fs", "remove_dir"), + &[Ty::Str], + Ty::Bool, + ), + abi_row( + "fs", + "remove_dir_all", + AbiRef::new("fs", "remove_dir_all"), + &[Ty::Str], + Ty::Bool, + ), + abi_row_named( + "fs", + "rename", + AbiRef::new("fs", "rename"), + &[Ty::Str, Ty::Str], + Ty::Bool, + 1, + &["to"], + ), + // `copy` answers the byte count, not a bool. + abi_row_named( + "fs", + "copy", + AbiRef::new("fs", "copy"), + &[Ty::Str, Ty::Str], + Ty::I64, + 1, + &["to"], + ), + abi_row("env", "has", AbiRef::new("env", "has"), &[Ty::Str], Ty::Bool), // Sorted entry names as List (the VM's exact shape). abi_row( "fs", @@ -189,6 +477,9 @@ pub(crate) const MODULE_ABI: &[ModuleAbiRow] = &[ Ty::ListStr, ), abi_row("fs", "exists", AbiRef::new("fs", "exists"), &[Ty::Str], Ty::Bool), + // Answers a `Bytes` value. It had an ABI entry and no row, because there was + // no type to give it. + abi_row("fs", "read", AbiRef::new("fs", "read"), &[Ty::Str], Ty::Bytes), // chrono-backed datetime (byte-identical to the stdlib module). abi_row("datetime", "now", AbiRef::new("datetime", "now"), &[], Ty::I64), abi_row( @@ -224,8 +515,46 @@ pub(crate) const MODULE_ABI: &[ModuleAbiRow] = &[ abi_row("math", "sqrt", AbiRef::new("math", "sqrt"), &[Ty::F64], Ty::F64), abi_row("math", "sin", AbiRef::new("math", "sin"), &[Ty::F64], Ty::F64), abi_row("math", "cos", AbiRef::new("math", "cos"), &[Ty::F64], Ty::F64), + abi_row("math", "tan", AbiRef::new("math", "tan"), &[Ty::F64], Ty::F64), + // `asin`/`acos` reject outside `-1..=1`, the log family rejects + // non-positive: the guards live in the helpers so both back ends raise the + // stdlib module's own sentence. + abi_row("math", "asin", AbiRef::new("math", "asin"), &[Ty::F64], Ty::F64), + abi_row("math", "acos", AbiRef::new("math", "acos"), &[Ty::F64], Ty::F64), + abi_row("math", "atan", AbiRef::new("math", "atan"), &[Ty::F64], Ty::F64), + abi_row_named( + "math", + "atan2", + AbiRef::new("math", "atan2"), + &[Ty::F64, Ty::F64], + Ty::F64, + 1, + &["x"], + ), + abi_row("math", "log", AbiRef::new("math", "log"), &[Ty::F64], Ty::F64), + abi_row("math", "log10", AbiRef::new("math", "log10"), &[Ty::F64], Ty::F64), + abi_row("math", "log2", AbiRef::new("math", "log2"), &[Ty::F64], Ty::F64), + // `clamp` is `Int`-only in the module schema, so no f64 promotion here. + abi_row_named( + "math", + "clamp", + AbiRef::new("math", "clamp_i64"), + &[Ty::I64, Ty::I64, Ty::I64], + Ty::I64, + // One leading positional-only parameter: the subject. + 1, + &["min", "max"], + ), abi_row("math", "exp", AbiRef::new("math", "exp"), &[Ty::F64], Ty::F64), - abi_row("math", "pow", AbiRef::new("math", "pow"), &[Ty::F64, Ty::F64], Ty::F64), + abi_row_named( + "math", + "pow", + AbiRef::new("math", "pow"), + &[Ty::F64, Ty::F64], + Ty::F64, + 1, + &["exponent"], + ), abi_row( "math", "hypot", @@ -237,43 +566,118 @@ pub(crate) const MODULE_ABI: &[ModuleAbiRow] = &[ // Only a Float NaN is true; an Int argument f64-promotes (never NaN), // exactly the module's `matches!(.., Float(v) if v.is_nan())`. abi_row("math", "is_nan", AbiRef::new("math", "is_nan"), &[Ty::F64], Ty::Bool), - abi_row("path", "sep", AbiRef::new("path", "sep"), &[], Ty::Str), - // String-or-nil results arrive boxed (`String?` in the module schema). + // `is_inf` is the same shape as `is_nan`: an Int argument promotes to a + // finite `f64` and answers false, which is what the module answers for it. + abi_row("math", "is_inf", AbiRef::new("math", "is_inf"), &[Ty::F64], Ty::Bool), + abi_row("math", "sinh", AbiRef::new("math", "sinh"), &[Ty::F64], Ty::F64), + abi_row("math", "cosh", AbiRef::new("math", "cosh"), &[Ty::F64], Ty::F64), + abi_row("math", "tanh", AbiRef::new("math", "tanh"), &[Ty::F64], Ty::F64), + // The `path` module's fixed-arity members. `parent`/`file_name`/`file_stem`/ + // `extension` answer `String?`, which arrives boxed — the convention + // `string.strip_prefix` established. abi_row( - "string", - "strip_prefix", - AbiRef::new("str", "strip_prefix"), - &[Ty::Str, Ty::Str], + "path", + "normalize", + AbiRef::new("path", "normalize"), + &[Ty::Str], + Ty::Str, + ), + abi_row("path", "parent", AbiRef::new("path", "parent"), &[Ty::Str], Ty::Dyn), + abi_row( + "path", + "file_name", + AbiRef::new("path", "file_name"), + &[Ty::Str], Ty::Dyn, ), abi_row( - "string", - "strip_suffix", - AbiRef::new("str", "strip_suffix"), - &[Ty::Str, Ty::Str], + "path", + "file_stem", + AbiRef::new("path", "file_stem"), + &[Ty::Str], Ty::Dyn, ), abi_row( - "string", - "count", - AbiRef::new("str", "count"), + "path", + "extension", + AbiRef::new("path", "extension"), + &[Ty::Str], + Ty::Dyn, + ), + abi_row( + "path", + "with_extension", + AbiRef::new("path", "with_extension"), &[Ty::Str, Ty::Str], - Ty::I64, + Ty::Str, ), - // The module spelling counts bytes (`str::len`), unlike `.len()`. - abi_row("string", "len", AbiRef::new("str", "byte_len"), &[Ty::Str], Ty::I64), abi_row( - "string", - "capitalize", - AbiRef::new("str", "capitalize"), + "path", + "is_absolute", + AbiRef::new("path", "is_absolute"), + &[Ty::Str], + Ty::Bool, + ), + abi_row( + "path", + "components", + AbiRef::new("path", "components"), &[Ty::Str], + Ty::ListStr, + ), + abi_row("path", "sep", AbiRef::new("path", "sep"), &[], Ty::Str), + abi_row("path", "delimiter", AbiRef::new("path", "delimiter"), &[], Ty::Str), + // The two `string` members that keep a row, because both declare + // `named(...)`: a named call is `CallNamed`, which never reaches the + // method forwarder, so without these `string.slice(s, start: 1)` falls + // back while `string.slice(s, 1)` lowers. Every other member of the module + // forwards — see `forwards_to_method` and the test next to it. + // + // `string.replace(text, pattern, with)` — the three-argument form. The + // fourth parameter `all` defaults to true, which is what `str::replace` + // does, so a call that omits it lowers here; a call that passes `all` has a + // different arity, finds no row, and reaches the `replace` *method* instead + // (the member forwards, and the method's three-argument arm selects the + // replacement limit). Both spellings of that call — positional and + // `all:` — lower. + abi_row_named( + "string", + "replace", + AbiRef::new("str", "replace"), + &[Ty::Str, Ty::Str, Ty::Str], Ty::Str, + // The stdlib declares three names and this row is the arity that leaves + // `all` at its default, which is why the *names* list stays whole while + // the `args` list does not: the named-call permutation reads the + // declaration from here even for the arity this row cannot serve. + // One leading positional-only parameter: the subject. + 1, + &["pattern", "with", "all"], + ), + abi_row_named( + "string", + "slice", + AbiRef::new("str", "slice_chars"), + &[Ty::Str, Ty::I64, Ty::I64], + Ty::Str, + // One leading positional-only parameter: the subject. + 1, + &["start", "end"], + ), + // Text → number. `to_int` is not here: its base is optional, so it is + // materialized in `lower_module` instead of split across two rows. + abi_row( + "string", + "to_float", + AbiRef::new("str", "to_float"), + &[Ty::Str], + Ty::Dyn, ), - abi_row("string", "title", AbiRef::new("str", "title"), &[Ty::Str], Ty::Str), // Native channels/goroutines (plan H): channel/task values are i64 // ids; blocking semantics + raises live in lkrt. abi_row("chan", "close", AbiRef::new("chan", "close"), &[Ty::I64], Ty::Nil), abi_row("chan", "len", AbiRef::new("chan", "len"), &[Ty::I64], Ty::I64), + abi_row("chan", "capacity", AbiRef::new("chan", "capacity"), &[Ty::I64], Ty::I64), abi_row( "chan", "is_closed", @@ -289,9 +693,319 @@ pub(crate) const MODULE_ABI: &[ModuleAbiRow] = &[ Ty::Bool, ), abi_row("chan", "try_recv", AbiRef::new("chan", "try_recv"), &[Ty::I64], Ty::Dyn), + // The blocking pair. Both were reachable only as bare globals until the + // module grew them, so neither had a row here either. + abi_row( + "chan", + "send", + AbiRef::new("chan", "send"), + &[Ty::I64, Ty::Dyn], + Ty::Nil, + ), + abi_row("chan", "recv", AbiRef::new("chan", "recv"), &[Ty::I64], Ty::Dyn), + // The module spelling of the global `chan(capacity)`. Same lkrt entry; the + // optional type-string argument is a checker hint the VM drops too, so only + // the one-argument form has a row (two args takes the generic path). + // `time.timeout` / `time.after` answer a capacity-1 `Channel`, which is an + // `I64` id in MIR — the same representation `chan.new` already uses. The + // `Float` millisecond spelling truncates (`ms as i64`), like the module's + // `numeric_millis`. + abi_row("time", "timeout", AbiRef::new("time", "timeout"), &[Ty::I64], Ty::I64), + abi_row("time", "after", AbiRef::new("time", "after"), &[Ty::I64], Ty::I64), + abi_row("chan", "new", AbiRef::new("chan", "new"), &[Ty::I64], Ty::I64), abi_row("task", "await", AbiRef::new("rt", "task_await"), &[Ty::I64], Ty::Dyn), + // `task.sleep(ms)` and `time.sleep(ms)` are the same operation — both take + // milliseconds through `duration_millis` and block — so they share the + // entry rather than growing a second one. + abi_row("task", "sleep", AbiRef::new("time", "sleep"), &[Ty::I64], Ty::Nil), // `encoding` submodules (VM `de.rs` mirrored in lkrt). abi_row("json", "parse", AbiRef::new("json", "parse"), &[Ty::Str], Ty::Dyn), + // The write direction. The argument is `Dyn`, so any carrier boxes into it, + // and the answer's object keys are sorted on both sides (a `serde_json::Map` + // is a `BTreeMap`) — this is the one encoding member a map's iteration order + // does not reach. + abi_row( + "json", + "stringify", + AbiRef::new("json", "stringify"), + &[Ty::Dyn], + Ty::Str, + ), + abi_row( + "yaml", + "stringify", + AbiRef::new("yaml", "stringify"), + &[Ty::Dyn], + Ty::Str, + ), + abi_row( + "toml", + "stringify", + AbiRef::new("toml", "stringify"), + &[Ty::Dyn], + Ty::Str, + ), + // `base64`/`hex`/`url`. `encode` takes `Bytes | String` in the language, so + // it is two rows — the second used to be missing, and + // `base64.encode(bytes.from_string("hi"))` therefore ran on the bridge while + // the same call on a string ran native. + abi_row("base64", "encode", AbiRef::new("base64", "encode"), &[Ty::Str], Ty::Str), + abi_row( + "base64", + "encode", + AbiRef::new("base64", "encode_bytes"), + &[Ty::Bytes], + Ty::Str, + ), + abi_row("hex", "encode", AbiRef::new("hex", "encode"), &[Ty::Str], Ty::Str), + abi_row( + "hex", + "encode", + AbiRef::new("hex", "encode_bytes"), + &[Ty::Bytes], + Ty::Str, + ), + // `hash`, both carriers of every member. The digests come from the same + // crates the stdlib module uses (`sha2`/`sha1`/`crc32fast`); `fnv64` is the + // one loop that exists twice, and `lkrt`'s `vm_mirror` conformance test is + // what keeps the two spellings equal. + // `process`. The child-process members take an optional argument list, and + // the no-list arity is its own lkrt entry point rather than a null handle + // invented at the call site. + abi_row("process", "id", AbiRef::new("process", "id"), &[], Ty::I64), + abi_row( + "process", + "set_cwd", + AbiRef::new("process", "set_cwd"), + &[Ty::Str], + Ty::Bool, + ), + abi_row("process", "exit", AbiRef::new("process", "exit"), &[Ty::I64], Ty::Nil), + abi_row( + "process", + "status", + AbiRef::new("process", "status_noargs"), + &[Ty::Str], + Ty::I64, + ), + abi_row( + "process", + "status", + AbiRef::new("process", "status"), + &[Ty::Str, Ty::ListStr], + Ty::I64, + ), + abi_row( + "process", + "output_string", + AbiRef::new("process", "output_string_noargs"), + &[Ty::Str], + Ty::Str, + ), + abi_row( + "process", + "output_string", + AbiRef::new("process", "output_string"), + &[Ty::Str, Ty::ListStr], + Ty::Str, + ), + abi_row( + "process", + "output", + AbiRef::new("process", "output_noargs"), + &[Ty::Str], + Ty::MapStrDyn, + ), + abi_row( + "process", + "output", + AbiRef::new("process", "output"), + &[Ty::Str, Ty::ListStr], + Ty::MapStrDyn, + ), + // `random`. `bool` is two arities (the probability defaults to 0.5), and + // `choice`/`shuffle` are one row per list carrier — `choice` answers the + // element, so it boxes; `shuffle` answers a list of the same carrier. + abi_row_named( + "random", + "int", + AbiRef::new("random", "int"), + &[Ty::I64, Ty::I64], + Ty::I64, + 1, + &["max"], + ), + abi_row("random", "float", AbiRef::new("random", "float"), &[], Ty::F64), + abi_row("random", "bool", AbiRef::new("random", "bool"), &[], Ty::Bool), + abi_row("random", "bool", AbiRef::new("random", "bool_p"), &[Ty::F64], Ty::Bool), + abi_row("random", "bytes", AbiRef::new("random", "bytes"), &[Ty::I64], Ty::Bytes), + abi_row( + "random", + "choice", + AbiRef::new("random", "choice_i64"), + &[Ty::ListI64], + Ty::Dyn, + ), + abi_row( + "random", + "choice", + AbiRef::new("random", "choice_f64"), + &[Ty::ListF64], + Ty::Dyn, + ), + abi_row( + "random", + "choice", + AbiRef::new("random", "choice_str"), + &[Ty::ListStr], + Ty::Dyn, + ), + abi_row( + "random", + "choice", + AbiRef::new("random", "choice_dyn"), + &[Ty::ListDyn], + Ty::Dyn, + ), + abi_row( + "random", + "shuffle", + AbiRef::new("random", "shuffle_i64"), + &[Ty::ListI64], + Ty::ListI64, + ), + abi_row( + "random", + "shuffle", + AbiRef::new("random", "shuffle_f64"), + &[Ty::ListF64], + Ty::ListF64, + ), + abi_row( + "random", + "shuffle", + AbiRef::new("random", "shuffle_str"), + &[Ty::ListStr], + Ty::ListStr, + ), + abi_row( + "random", + "shuffle", + AbiRef::new("random", "shuffle_dyn"), + &[Ty::ListDyn], + Ty::ListDyn, + ), + // `regex`. `find` answers `Map?` and `captures` answers `List?`, so both + // arrive boxed; `find_all` is a dyn list of match maps. Each map is built + // through the VM's own two-stage construction (`str_dyn_map_mirrored`) — + // its keys are `text`, `start`, `end`, and that insertion order is what + // `println` prints. + abi_row_named( + "regex", + "find", + AbiRef::new("regex", "find"), + &[Ty::Str, Ty::Str], + Ty::Dyn, + 1, + &["pattern"], + ), + abi_row_named( + "regex", + "find_all", + AbiRef::new("regex", "find_all"), + &[Ty::Str, Ty::Str], + Ty::ListDyn, + 1, + &["pattern"], + ), + abi_row_named( + "regex", + "captures", + AbiRef::new("regex", "captures"), + &[Ty::Str, Ty::Str], + Ty::Dyn, + 1, + &["pattern"], + ), + abi_row_named( + "regex", + "is_match", + AbiRef::new("regex", "is_match"), + &[Ty::Str, Ty::Str], + Ty::Bool, + 1, + &["pattern"], + ), + abi_row_named( + "regex", + "split", + AbiRef::new("regex", "split"), + &[Ty::Str, Ty::Str], + Ty::ListStr, + 1, + &["pattern"], + ), + abi_row_named( + "regex", + "replace", + AbiRef::new("regex", "replace"), + &[Ty::Str, Ty::Str, Ty::Str], + Ty::Str, + // One leading positional-only parameter: the subject. + 1, + &["pattern", "replacement"], + ), + // `uuid`. `v4` has no arguments and a different answer every call — see the + // ABI schema for why it must not be `Pure`. + abi_row("uuid", "v4", AbiRef::new("uuid", "v4"), &[], Ty::Str), + abi_row("uuid", "parse", AbiRef::new("uuid", "parse"), &[Ty::Str], Ty::Str), + abi_row( + "uuid", + "is_valid", + AbiRef::new("uuid", "is_valid"), + &[Ty::Str], + Ty::Bool, + ), + abi_row("hash", "sha256", AbiRef::new("hash", "sha256_str"), &[Ty::Str], Ty::Str), + abi_row( + "hash", + "sha256", + AbiRef::new("hash", "sha256_bytes"), + &[Ty::Bytes], + Ty::Str, + ), + abi_row("hash", "sha1", AbiRef::new("hash", "sha1_str"), &[Ty::Str], Ty::Str), + abi_row("hash", "sha1", AbiRef::new("hash", "sha1_bytes"), &[Ty::Bytes], Ty::Str), + abi_row("hash", "crc32", AbiRef::new("hash", "crc32_str"), &[Ty::Str], Ty::I64), + abi_row( + "hash", + "crc32", + AbiRef::new("hash", "crc32_bytes"), + &[Ty::Bytes], + Ty::I64, + ), + abi_row("hash", "fnv64", AbiRef::new("hash", "fnv64_str"), &[Ty::Str], Ty::I64), + abi_row( + "hash", + "fnv64", + AbiRef::new("hash", "fnv64_bytes"), + &[Ty::Bytes], + Ty::I64, + ), + abi_row( + "url", + "encode_component", + AbiRef::new("url", "encode_component"), + &[Ty::Str], + Ty::Str, + ), + abi_row( + "url", + "decode_component", + AbiRef::new("url", "decode_component"), + &[Ty::Str], + Ty::Str, + ), abi_row("yaml", "parse", AbiRef::new("yaml", "parse"), &[Ty::Str], Ty::Dyn), abi_row("toml", "parse", AbiRef::new("toml", "parse"), &[Ty::Str], Ty::Dyn), // `net` submodules + `bytes` (the lkrt tcp family predates this). @@ -310,36 +1024,109 @@ pub(crate) const MODULE_ABI: &[ModuleAbiRow] = &[ &[Ty::I64, Ty::Str], Ty::I64, ), - abi_row("tcp", "read", AbiRef::new("tcp", "read"), &[Ty::I64, Ty::I64], Ty::I64), - abi_row("tcp", "close", AbiRef::new("tcp", "close"), &[Ty::I64], Ty::I64), + // Answers a `Bytes` **value**, not the one-shot host handle it used to: a + // `Bytes` you can only read once is not the language's `Bytes`. abi_row( + "tcp", + "read", + AbiRef::new("tcp", "read"), + &[Ty::I64, Ty::I64], + Ty::Bytes, + ), + abi_row("tcp", "close", AbiRef::new("tcp", "close"), &[Ty::I64], Ty::I64), + // The `bytes` module forwards to the method arms (see + // `forwards_to_method`), so only the member that can be *called by name* + // keeps a row: a named call is `CallNamed`, which never reaches the + // forwarder. The other ten rows were unreachable code pointing at the same + // `bytes_h` symbols their method arms already call. + abi_row_named( "bytes", - "to_string_utf8", - AbiRef::new("bytes", "to_string_utf8"), - &[Ty::I64], - Ty::Str, + "slice", + AbiRef::new("bytes_h", "slice"), + &[Ty::Bytes, Ty::I64, Ty::I64], + Ty::Bytes, + // One leading positional-only parameter: the subject. + 1, + &["start", "end"], ), + abi_row( + "base64", + "decode", + AbiRef::new("base64", "decode"), + &[Ty::Str], + Ty::Bytes, + ), + abi_row("hex", "decode", AbiRef::new("hex", "decode"), &[Ty::Str], Ty::Bytes), ]; -pub(crate) fn module_call_abi(module: &str, name: &str) -> Option<(AbiRef, &'static [Ty], Ty)> { +/// Every row for one member, in table order. +/// +/// A stdlib member may accept more than one carrier — `hash.sha256(data)` and +/// `base64.encode(data)` each take `Bytes | String`, and a `Bytes` is a +/// different native argument than a `Str`, so it is a different row. The +/// caller ([`lower_module_call`]) picks by the argument types it actually has. +/// +/// Before this existed the table was keyed by name alone, so a two-carrier +/// member got whichever row was written first and the other carrier fell back +/// silently: `base64.encode(bytes.from_string("hi"))` ran on the bridge while +/// the same call on a string ran native. That is the same "one operation, N +/// carriers, only some of them finished" shape the list methods had. +pub(crate) fn module_call_abi_rows<'a>( + module: &'a str, + name: &'a str, +) -> impl Iterator + 'a { + MODULE_ABI + .iter() + .filter(move |row| row.module == module && row.member == name) +} + +/// Every member whose row carries a `named(...)` list, for the CLI test that +/// compares this table against the stdlib's own declaration. +/// +/// The list is a copy — `aot/lower` cannot read the stdlib signature registry, +/// which is populated at run time by whoever links the standard library, and a +/// lowering that silently degrades when that has not happened yet is worse than +/// a copy with a test on it. +pub fn named_parameter_rows() -> impl Iterator { MODULE_ABI .iter() - .find(|row| row.module == module && row.member == name) - .map(|row| (row.abi, row.args, row.ret)) + .filter(|row| !row.named.is_empty()) + .map(|row| (row.module, row.member, row.leading, row.named)) +} + +/// Every ABI row's `(module, member)`, named or not. +/// +/// [`named_parameter_rows`] only reports rows that already carry names, so it +/// cannot see the opposite mistake: a member the stdlib declares `named(...)` +/// whose row here has none. That one is silent — the named spelling simply +/// stops lowering and the whole program falls back — so the conformance test +/// needs the full list to check both directions. +pub fn module_abi_row_paths() -> impl Iterator { + MODULE_ABI.iter().map(|row| (row.module, row.member)) +} + +/// Whether a row's declared parameter type accepts an argument the lowering +/// actually holds — the same three rules [`lower_module_call`] then applies +/// when it materialises the argument: exact, `Dyn` takes anything (it boxes), +/// and `F64` takes an `I64` (the stdlib's `number_arg` promotion). +pub(crate) fn abi_param_accepts(want: Ty, got: Ty) -> bool { + want == got || want == Ty::Dyn || (want == Ty::F64 && got == Ty::I64) } /// Method-name roles across the lowering — the single source of truth the -/// `Dyn`-receiver unbox guards, the string-list lookahead, and the +/// `Dyn`-receiver unbox guard, the string-list lookahead, and the /// `iter`/`stream` module-spelling forwarders all derive from. Adding a /// stdlib method with any of these behaviours is one row here. +/// +/// There used to be a second unbox column, for map-only names. A boxed map's +/// carrier is named by its tag and not by any static type, so those names now +/// dispatch inside the runtime (`dyn.map_*`) instead of unboxing to a +/// `str_dyn` handle that only one of the six carriers has. pub(crate) struct MethodRow { pub(crate) name: &'static str, /// A `Dyn` receiver unboxes through `dyn.as_list` (list-only name; a /// non-list tag aborts, the VM's method-on-wrong-type loud error). pub(crate) unbox_list: bool, - /// A `Dyn` receiver unboxes through `dyn.as_map` (map-only name). - /// Names shared with other receivers (`get`) stay boxed and reject. - pub(crate) unbox_map: bool, /// A string-list receiver's result is still a string list (the /// `strlist_regs` lookahead keeps tracking through the call). pub(crate) strlist: bool, @@ -348,17 +1135,10 @@ pub(crate) struct MethodRow { pub(crate) forward: bool, } -pub(crate) const fn method_row( - name: &'static str, - unbox_list: bool, - unbox_map: bool, - strlist: bool, - forward: bool, -) -> MethodRow { +pub(crate) const fn method_row(name: &'static str, unbox_list: bool, strlist: bool, forward: bool) -> MethodRow { MethodRow { name, unbox_list, - unbox_map, strlist, forward, } @@ -366,29 +1146,147 @@ pub(crate) const fn method_row( #[rustfmt::skip] pub(crate) const METHOD_TABLE: &[MethodRow] = &[ - // name unbox_list unbox_map strlist forward - method_row("map", true, false, true, true), - method_row("filter", true, false, true, true), - method_row("reduce", true, false, false, true), - method_row("take", true, false, true, true), - method_row("skip", true, false, true, true), - method_row("concat", true, false, true, false), - method_row("unique", true, false, true, true), - method_row("sort", true, false, true, false), - method_row("reverse", true, false, true, false), - method_row("slice", false, false, true, false), - method_row("enumerate", false, false, false, true), - method_row("zip", false, false, false, true), - method_row("chain", false, false, false, true), - method_row("flatten", false, false, false, true), - method_row("chunk", false, false, false, true), - method_row("has", false, true, false, false), - method_row("keys", false, true, false, false), - method_row("values", false, true, false, false), - method_row("delete", false, true, false, false), - method_row("remove", false, true, false, false), + // name unbox_list strlist forward + method_row("map", true, true, true), + method_row("filter", true, true, true), + method_row("reduce", true, false, true), + method_row("take", true, true, true), + method_row("skip", true, true, true), + method_row("concat", true, true, false), + method_row("unique", true, true, true), + method_row("sort", true, true, false), + method_row("to_bytes", true, false, false), + method_row("sum", true, false, true), + method_row("min", true, false, true), + method_row("max", true, false, true), + method_row("reverse", true, true, false), + // NOT `unbox_list`, and the exception is worth naming: `slice` answers a + // *window* over the receiver, and `dyn.as_list` materializes a plain list + // for three of the four carriers — so unboxing changes the answer's kind. + // `"" + xs.slice(0, 1)` raises "object cannot be converted to string" in + // the VM, which is what a window does, and answered a list when this row + // said `true`. + method_row("slice", false, true, false), + method_row("enumerate", true, false, true), + method_row("zip", true, false, true), + method_row("chain", true, true, true), + method_row("flatten", true, false, true), + method_row("chunk", true, false, true), + // Answer an element, not a list, so `strlist` says nothing about them; they + // are here for `unbox_list` alone. Their arms already accept `ListDyn` — + // only the row was missing, so a boxed receiver refused at the tag guard it + // was entitled to pass. `pop` shares the `first`/`last` arm and is *not* + // here: it mutates, which `no_unbox_list_name_mutates_its_receiver` forbids. + method_row("first", true, false, true), + method_row("last", true, false, false), + method_row("index_of", true, false, false), + method_row("count", true, false, false), + method_row("has", false, false, false), + method_row("keys", false, false, false), + method_row("values", false, false, false), + method_row("delete", false, false, false), + method_row("remove", false, false, false), ]; +/// The method `module.name(receiver, …)` is a spelling of, if it is one. +/// +/// The VM routes both spellings through the same `core_methods`, so the +/// lowering has one job: put the receiver where the method arm expects it. This +/// used to be spelled `matches!(module, "iter" | "stream")` at the call site — +/// a list of two, not a rule — so every `string` module function fell back +/// while its method spelling lowered. `string.trim(s)` and `s.trim()` are the +/// same call; which one a program wrote decided whether it stayed native. +/// +/// The names are listed rather than "anything the method table knows", because +/// deciding by *trying* `lower_method_dispatch` would emit instructions before +/// finding out — the mistake `lower_conditional` made and paid for. +/// +/// The answer is the *method's* name, not the member's, because two of them +/// differ: `bytes.from_string(s)` is `s.bytes()` and `bytes.from_list(xs)` is +/// `xs.to_bytes()`. Returning a bool assumed the two names were always equal, +/// and forwarded `from_string` to a method nobody defines. +pub(crate) fn forwards_to_method(module: &str, name: &str) -> Option<&'static str> { + match module { + "iter" | "stream" => method_role(name).filter(|role| role.forward).map(|role| role.name), + // Every `bytes` member: the module is a forwarder now, and each + // member's method arm calls the same `bytes_h` symbol its row used to. + "bytes" => match name { + // The two constructors, whose receiver is the List or the String + // and whose method therefore has another name. + "from_list" => Some("to_bytes"), + "from_string" => Some("bytes"), + "len" | "is_empty" | "get" | "first" | "last" | "contains" | "index_of" | "sum" | "min" | "max" + | "take" | "skip" | "slice" | "to_list" | "to_string_utf8" | "to_string_lossy" | "concat" => { + Some(name_of(name)) + } + _ => None, + }, + // Every `string` member that is a `Str` method with the receiver first. + // Checked against the VM: each `string.f(s, …) == s.f(…)`. + "string" => match name { + "len" | "is_empty" | "lower" | "upper" | "trim" | "reverse" | "repeat" | "starts_with" | "ends_with" + | "contains" | "slice" | "index_of" | "get" | "first" | "last" | "take" | "skip" | "replace" | "split" + | "chars" | "bytes" | "byte_at" | "capitalize" | "title" | "count" | "strip" | "strip_prefix" + | "strip_suffix" | "pad_left" | "pad_right" | "format" => Some(name_of(name)), + _ => None, + }, + _ => None, + } +} + +/// The `&'static str` for a member name that spells its own method. +/// +/// The table's names are literals, so this is a lookup that cannot fail — but +/// `name` arrives borrowed from the caller's `String`, and the answer has to +/// outlive it. +fn name_of(name: &str) -> &'static str { + const NAMES: &[&str] = &[ + "len", + "is_empty", + "get", + "first", + "last", + "contains", + "index_of", + "sum", + "min", + "max", + "take", + "skip", + "slice", + "to_list", + "to_string_utf8", + "to_string_lossy", + "concat", + "lower", + "upper", + "trim", + "reverse", + "repeat", + "starts_with", + "ends_with", + "replace", + "split", + "chars", + "bytes", + "byte_at", + "capitalize", + "title", + "count", + "strip", + "strip_prefix", + "strip_suffix", + "pad_left", + "pad_right", + "format", + ]; + NAMES + .iter() + .copied() + .find(|candidate| *candidate == name) + .expect("every forwarded member name is in this list") +} + pub(crate) fn method_role(name: &str) -> Option<&'static MethodRow> { METHOD_TABLE.iter().find(|row| row.name == name) } @@ -409,3 +1307,249 @@ pub(crate) fn module_const(module: &str, name: &str) -> Option<(Const, Ty)> { _ => None, } } + +#[cfg(test)] +mod tests { + use super::*; + + /// The `path` members that lower natively, and the two that deliberately do + /// not. + /// + /// A table test rather than a differential, and the reason needs stating + /// carefully — I got it wrong once and wrote the wrong reason down. + /// + /// The differential *does* catch a lost row for anything **in its corpus**: + /// its harness compiles with `LK_AOT_HYBRID=0` and `LK_AOT_NO_FALLBACK=1`, + /// so a member that stops lowering fails the compile rather than quietly + /// bridging. (The experiment that seemed to show otherwise had edited + /// nothing — the row was multi-line after `cargo fmt`, the patch silently + /// matched nothing, and an unchanged build passed.) + /// + /// What this test adds is the members *no program in the corpus mentions*, + /// and a failure that names the missing member instead of a `pc` in a + /// compile error. Its other half — the two that must **not** be here — is + /// something no differential can express at all. + /// + /// `join` and `normalize` are absent on purpose. `join` is variadic, which + /// the fixed-arity ABI cannot express. `normalize` is lexical path + /// cleaning, which `std::path` does not do — lowering it means *copying* + /// the VM's component loop into `lkrt`, giving one rule that has already + /// carried two bugs a second place to drift. lkrt's discipline is to share + /// the crate underneath (`std::path`, as it already shares base64/hex/ + /// chrono), never to re-type LK-level logic. Both stay on the bridge until + /// there is a shared implementation to point at. + /// A member that forwards to a method has no row of its own — unless it + /// can be called by name. + /// + /// Two carriers for one operation, and only one of them reachable: + /// `forwards_to_method` is consulted *first*, so a `string` row for a + /// forwarded member is code no call arrives at. Six of them sat here, and + /// one was worse than dead — `string.len` pointed at `str::byte_len` under + /// a comment claiming the module spelling counts bytes. It does not: the + /// module forwards to `s.len()`, which counts characters. Had the forward + /// list ever lost `len`, `string.len("中文abc")` would have started + /// answering 9 compiled and 5 interpreted. + /// + /// The exception is real: `slice` and `replace` declare `named(...)`, and a + /// named call is a different opcode that never reaches the forwarder, so + /// their rows are the only thing that lowers `string.slice(s, start: 1)`. + #[test] + fn a_forwarded_member_has_no_row_unless_it_can_be_called_by_name() { + for row in MODULE_ABI { + if forwards_to_method(row.module, row.member).is_none() { + continue; + } + assert!( + !row.named.is_empty(), + "`{}.{}` forwards to the method arm, so this row is unreachable — delete it, \ + or give the member a `named(...)` list if a named call needs it", + row.module, + row.member + ); + } + } + + /// Every carrier of a `Bytes | String` member has a row. + /// + /// One member, two argument types, and for a long time only the first one + /// written had a row — so `base64.encode(text)` ran native and + /// `base64.encode(bytes)` ran on the bridge, which nothing reported. Same + /// shape the list methods had across their carriers. + #[test] + fn both_carriers_of_every_bytes_or_string_member_lower() { + for (module, member) in [ + ("hash", "sha256"), + ("hash", "sha1"), + ("hash", "crc32"), + ("hash", "fnv64"), + ("base64", "encode"), + ("hex", "encode"), + ] { + for want in [Ty::Str, Ty::Bytes] { + assert!( + module_call_abi_rows(module, member).any(|row| row.args == [want]), + "{module}.{member} has no row for its {want:?} carrier — that carrier \ + silently falls back to the hybrid bridge" + ); + } + } + } + + /// Every stdlib member wired natively in this round still has a row. + /// + /// One test for many modules because they share one question: is the + /// member still lowered at all? The differential corpora answer it for the + /// calls they contain — they forbid fallback, so a lost row fails their + /// compile — but only for those calls, and only after building a native + /// binary. This answers it for the whole list, immediately, and names the + /// member that went missing. + /// No `unbox_list` name may mutate its receiver. + /// + /// `dyn.as_list` is read-only, and has to be: a `DYN_LIST` hands back its + /// own handle while a typed carrier has to materialize one, so a write + /// through the guard lands on a copy for three of the four list + /// representations. The names that reach it — `map`, `filter`, `sort`, + /// `reverse`, … — all answer new lists in this language and leave the + /// receiver alone. `push` is the mutating one and goes to `dyn.list_push`. + /// + /// Nothing about `unbox_list = true` says "read-only", so a mutating name + /// given that flag would compile, run, and drop writes. This is what says + /// it. The mutating list methods were read off the VM: `a.push(9)`, + /// `a.set(0, 9)`, `a.insert(0, 9)`, `a.clear()` and `a.pop()` all change + /// `a`; every other list method answers a new value. + #[test] + fn no_unbox_list_name_mutates_its_receiver() { + const MUTATING: &[&str] = &["push", "set", "insert", "clear", "pop"]; + let offenders: Vec<&str> = METHOD_TABLE + .iter() + .filter(|row| row.unbox_list && MUTATING.contains(&row.name)) + .map(|row| row.name) + .collect(); + assert!( + offenders.is_empty(), + "these mutate the receiver but unbox through the read-only `dyn.as_list` guard, \ + so the write would land on a materialized copy: {offenders:?}" + ); + } + + #[test] + fn the_natively_lowered_stdlib_surface_stays_lowered() { + for (module, member) in [ + ("hash", "sha256"), + ("hash", "sha1"), + ("hash", "crc32"), + ("hash", "fnv64"), + ("uuid", "v4"), + ("uuid", "parse"), + ("uuid", "is_valid"), + ("regex", "is_match"), + ("regex", "split"), + ("regex", "replace"), + ("regex", "find"), + ("regex", "find_all"), + ("regex", "captures"), + ("random", "int"), + ("random", "float"), + ("random", "bool"), + ("random", "bytes"), + ("random", "choice"), + ("random", "shuffle"), + ("process", "id"), + ("process", "set_cwd"), + ("process", "exit"), + ("process", "status"), + ("process", "output"), + ("process", "output_string"), + ("env", "vars"), + ("json", "stringify"), + ("yaml", "stringify"), + ("toml", "stringify"), + ("time", "timeout"), + ("time", "after"), + ] { + assert!( + module_call_abi_rows(module, member).next().is_some(), + "{module}.{member} lost its native lowering" + ); + } + } + + /// The `fs` surface lowers, and `metadata` is the one member that does not. + /// + /// The differential covers the ones its corpus exercises (it forbids + /// fallback, so a lost row fails that compile). This covers the whole + /// declared surface at once, including members no differential program + /// mentions — which is how half of these came to be missing while the lkrt + /// side was already written. + /// + /// `fs.metadata` answers a four-key `Map`, which needed the map carrier + /// (`Ty::MapStrDyn`) and the mirrored construction — a map's iteration + /// order is what `println` prints, so handing back a natively-built map is + /// only correct if it rehashes the way the VM's does. + #[test] + fn the_fs_module_lowers_its_scalar_members() { + for member in [ + "read_to_string", + "write", + "append", + "read_dir", + "canonicalize", + "exists", + "is_file", + "is_dir", + "create_dir", + "create_dir_all", + "remove_file", + "remove_dir", + "remove_dir_all", + "rename", + "copy", + "temp_dir", + "metadata", + ] { + assert!( + module_call_abi_rows("fs", member).next().is_some(), + "fs.{member} lost its native lowering" + ); + } + } + + #[test] + fn the_path_module_lowers_exactly_its_fixed_arity_members() { + for member in [ + "parent", + "file_name", + "file_stem", + "extension", + "with_extension", + "is_absolute", + "components", + "sep", + "delimiter", + // `normalize` is a *copy* of the module's component walk rather + // than a shared implementation — the same `std::path::Component` + // loop, including the rule that `..` cancels only a named + // component and is dropped above a root. That makes it the one + // path member whose two sides can drift in silence, which is why + // `examples/stdlib/path_normalize.lk` walks the cases the loop + // distinguishes: the VM/native sweep compares its output. + "normalize", + ] { + assert!( + module_call_abi_rows("path", member).next().is_some(), + "path.{member} lost its native lowering" + ); + } + // A list of one, deliberately: it is the counterpart of the list above + // and a member moves between them when its lowering changes. Written as + // a loop so that move is an edit to the data, not to the shape. + #[allow(clippy::single_element_loop)] + for member in ["join"] { + assert!( + module_call_abi_rows("path", member).next().is_none(), + "path.{member} gained a native lowering; if that is intended, say here what \ + it shares its implementation with" + ); + } + } +} diff --git a/aot/lower/src/tests.rs b/aot/lower/src/tests.rs index 90bea3cf..96791c9b 100644 --- a/aot/lower/src/tests.rs +++ b/aot/lower/src/tests.rs @@ -7,7 +7,7 @@ fn artifact(consts: ConstPoolData, code: Vec, register_count: u16) -> Modul version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: Vec::new(), @@ -72,7 +72,7 @@ fn lowers_zero_capture_lambda_global_call() { version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: vec!["inc".to_string()], @@ -117,7 +117,7 @@ fn lowers_local_lambda_call() { version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: Vec::new(), @@ -161,7 +161,7 @@ fn rejects_capturing_closure() { version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: Vec::new(), @@ -203,7 +203,7 @@ fn rejects_reassigned_lambda_global() { version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: vec!["f".to_string()], @@ -240,7 +240,7 @@ fn lowers_direct_call() { version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: vec!["add".to_string()], @@ -349,7 +349,7 @@ fn dead_function_is_skipped() { version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: vec!["dead".to_string()], @@ -395,7 +395,7 @@ fn monomorphizes_f64_parameter() { version: MODULE_ARTIFACT_VERSION, imports: Vec::new(), module: ModuleData { - type_scope: lk_core::vm::TypeScope::anonymous(), + type_scope: lk_core::val::TypeScope::anonymous(), entry: 0, type_info: Default::default(), globals: vec!["f".to_string()], @@ -1286,3 +1286,107 @@ fn int_add_coerces_mixed_operands() { let mir = lower(&art).expect("lowers"); assert_eq!(mir.functions[0].ret, Ty::F64); } + +/// A container-typed module global keeps its own type. +/// +/// `container_ty` decides two things at once — which globals keep their type, +/// and which are *refused* when a slot joins to `Dyn`. A container missing from +/// it is therefore both boxed and not refused, which is the definition of +/// miscompiled: `Ty::Bytes`, `Ty::Set`, `Ty::MapStrDyn` and `Ty::SliceI64` were +/// missing, and +/// +/// ```lk +/// let b = "abc".bytes(); +/// fn f(n: Int) -> Int { return b[n] ?? -1; } +/// ``` +/// +/// printed `98` interpreted and died with `runtime type error` compiled, for +/// any index at all. The same value as a parameter or a local was fine, and so +/// were `List` and `String` globals — which is why no example, no differential +/// case and no fuzz seed had ever shown it. +/// +/// Asserted on the classification rather than on a compiled program, because +/// the property is "every handle type is listed" and a program can only ever +/// show one of them at a time. +#[test] +fn every_handle_type_counts_as_a_container_global() { + use lk_aot_mir::Ty; + + for ty in [ + Ty::ListDyn, + Ty::ListI64, + Ty::ListF64, + Ty::ListStr, + Ty::SliceI64, + Ty::MapStrDyn, + Ty::MapStrI64, + Ty::MapI64I64, + Ty::MapStrF64, + Ty::MapI64F64, + Ty::MapStrBool, + Ty::Set, + Ty::Bytes, + ] { + assert!( + crate::inst::global::container_ty(ty), + "{ty:?} is a handle: boxing it into a `Dyn` global makes a second container" + ); + } + + for ty in [Ty::I64, Ty::F64, Ty::Bool, Ty::Str, Ty::Nil, Ty::Dyn] { + assert!( + !crate::inst::global::container_ty(ty), + "{ty:?} copies into a slot with nothing lost" + ); + } +} + +/// Recording a compile-time reference clears the register's SSA definition — +/// the other half of the invariant [`Ssa::write`] already keeps in the opposite +/// direction, and the half that was missing. +/// +/// The bytecode reuses registers, so a lambda's `MakeClosure` lands on the slot +/// a container literal was loaded into a moment earlier: +/// +/// ```text +/// 0000 LoadHeapConst r1 #0 ; [] +/// 0001 Move r0 r1 ; fs = r0 +/// 0002 MakeClosure r1 … ; the lambda, into the slot the list was in +/// 0003 ListPush r0 r1 +/// ``` +/// +/// `read_slot` consults `current_def` before `builtin_regs`, so with the +/// definition left in place the push read the **list** back and pushed it into +/// itself. `let fs = []; fs.push(|x| x + 1);` compiled, answered `List` for +/// `typeof(fs[0])` where the interpreter answers `Function`, and made +/// `println(fs)` recurse until the stack ran out. +#[test] +fn binding_a_reference_clears_the_registers_value() { + let mut ssa = Ssa::new(4, 0, 0, vec![Vec::new()], 1); + let value = ssa.new_val(); + ssa.write(1, 0, (value, Ty::ListI64)); + assert!(ssa.read(1, 0, 0).is_ok(), "the register holds a value to begin with"); + + ssa.bind_ref(0, 1, GlobalRef::Lambda(7)); + let err = ssa.read(1, 0, 0).expect_err("a reference is not a value"); + assert!( + matches!(err, Unsupported::ReferenceAsValue { reg: 1, .. }), + "the read must report the reference, not hand back the stale value: {err:?}" + ); +} + +/// `GlobalRef::ArgList` is the exception, and deliberately: it is a *view* of a +/// materialized handle rather than a name for something with no value, so both +/// halves stay live. Clearing it too takes an argument pack out of reach. +#[test] +fn an_argument_pack_keeps_both_views() { + let mut ssa = Ssa::new(4, 0, 0, vec![Vec::new()], 1); + let handle = ssa.new_val(); + ssa.write(1, 0, (handle, Ty::ListDyn)); + ssa.bind_ref(0, 1, GlobalRef::ArgList(vec![])); + assert_eq!( + ssa.read(1, 0, 0).expect("the handle is still readable").0, + handle, + "an ArgList names a handle that exists; the value half is not stale" + ); +} diff --git a/aot/lower/src/trait_env.rs b/aot/lower/src/trait_env.rs index 6b79cada..ba0202ce 100644 --- a/aot/lower/src/trait_env.rs +++ b/aot/lower/src/trait_env.rs @@ -6,6 +6,11 @@ /// a runtime registration call, so every consumer had to decode them again. /// The declarations now travel structurally in the artifact and the /// registration calls are gone, so this is a direct read. +/// One declared struct as the entry prologue describes it to the runtime: its +/// type id, its name, and `(field name, declared-type code)` in declaration +/// order. +pub(crate) type StructTypeDecl = (i64, String, Vec<(String, i64)>); + #[derive(Debug, Clone, Default)] pub(crate) struct TraitEnv { /// `(type name, method name)` → impl function index. @@ -14,15 +19,152 @@ pub(crate) struct TraitEnv { pub(crate) type_ids: std::collections::HashMap, /// Method name → dispatch arms `(type id, impl fn)`, declaration order. pub(crate) methods: std::collections::HashMap>, + /// Type name → its field names in **declaration order**, which is the order + /// `display` prints them in (see `val::DeclaredType::fields`). + /// + /// Emitted into the entry prologue as `obj_ty.begin`/`obj_ty.field` calls so + /// the runtime can render a marked instance. Ordered by type id, so the + /// emission order is fixed. + pub(crate) struct_fields: Vec, + /// `(struct name, field name)` → the field's position in the declaration. + /// + /// A declared struct's fields are a fixed, ordered list, so a field read + /// can be a positional one rather than a hash lookup (`map_h.str_dyn_get_at`). + pub(crate) struct_field_index: std::collections::HashMap<(String, String), usize>, + /// `(struct name, field name)` → the declared-type code a store is measured + /// against (`DECLARED_*`). + pub(crate) struct_field_codes: std::collections::HashMap<(String, String), i64>, +} + +/// Method names the lowering may call **without** a `CallMethodK` naming them. +/// +/// `show` is reached from a display site (`"${value}"`), not from a method call +/// — see `lower_method::apply_show`. Anything added there has to be added here +/// too, or its impl stops being a lowering root and the module fails MIR +/// validation with a dangling callee. One list, named at both ends. +pub(crate) const IMPLICIT_METHOD_HOOKS: &[&str] = &["show"]; + +/// Every method name some `CallMethodK` in the module names. +/// +/// `CallMethodK` is the only method-call opcode and it takes its name from the +/// constant pool, so this set is exact — there is no dynamic-name form to be +/// conservative about. +pub(crate) fn called_method_names(module: &lk_core::vm::ModuleData) -> std::collections::HashSet { + let mut names = std::collections::HashSet::new(); + for func in &module.functions { + for raw in &func.code { + let Ok(instr) = lk_core::vm::Instr::try_from_raw(*raw) else { + break; + }; + if instr.opcode() == lk_core::vm::Opcode::CallMethodK + && let Some(name) = func.consts.strings.get(instr.b() as usize) + { + names.insert(name.to_string()); + } + } + } + names +} + +/// The dispatch id of a built-in impl target, or `None` for a struct name. +/// +/// Mirrors `lkrt::lkdyn::dispatch_builtin_code` and its base — the runtime +/// computes the same number from the value's tag, and the two have to agree or +/// no arm matches. `examples/syntax/trait_builtin.lk` is the conformance check: +/// it dispatches through a trait parameter on every built-in kind, so a +/// disagreement is a wrong answer there rather than a silent miss. +/// +/// The name is the impl target's type *text*, so a container's is written out +/// (`List`, `Map`) and only its base names the type. +fn dispatch_builtin_type_id(type_name: &str) -> Option { + const BASE: i64 = 1 << 40; + let code = match type_name.split('<').next().unwrap_or(type_name) { + "Nil" => 1, + "Bool" => 2, + "Int" => 3, + "Float" => 4, + "String" => 5, + "List" => 6, + "Set" => 7, + "Bytes" => 8, + "Map" => 9, + "Slice" => 10, + "Channel" => 11, + "Task" => 12, + "Stream" => 13, + _ => return None, + }; + Some(BASE + code) +} + +impl TraitEnv { + /// The type whose `impl` block defines function `fidx`, if any. + /// + /// The inverse of [`Self::impls`], and what tells the lowering that `self` + /// inside an impl method is that type — provenance a parameter cannot get + /// from a `NewObject` because it never sees one. + /// + /// Linear over the table: impl blocks are counted in the dozens, and this + /// runs once per lowered function. + pub(crate) fn impl_owner(&self, fidx: u32) -> Option { + let mut found: Option<&String> = None; + for ((type_name, _), &f) in &self.impls { + if f != fidx { + continue; + } + match found { + // One function registered under two types — the compiler is + // free to share a body, and a *default* method copied into two + // impls is exactly two identical bodies. Answering either type + // would devirtualize `self.other()` to the wrong impl, which is + // a wrong answer rather than a refusal. So: no answer. + Some(previous) if previous != type_name => return None, + Some(_) => {} + None => found = Some(type_name), + } + } + found.cloned() + } } pub(crate) fn trait_env_prescan(module: &lk_core::vm::ModuleData) -> TraitEnv { let mut env = TraitEnv::default(); // Declaration order fixes the runtime type ids, so the ordering here is // load-bearing. + // Every declared struct gets a type id, not only the ones with impls: the + // id is also how `display` finds a type's name and field order, and a + // struct with no methods still prints. + for decl in &module.type_info.structs { + let next_id = env.type_ids.len() as i64 + 1; + let tid = *env.type_ids.entry(decl.name.clone()).or_insert(next_id); + env.struct_fields.push(( + tid, + decl.name.clone(), + decl.fields + .iter() + .map(|f| (f.name.clone(), declared_field_code(f.ty.as_deref()))) + .collect(), + )); + for (index, field) in decl.fields.iter().enumerate() { + env.struct_field_index + .insert((decl.name.clone(), field.name.clone()), index); + env.struct_field_codes.insert( + (decl.name.clone(), field.name.clone()), + declared_field_code(field.ty.as_deref()), + ); + } + } for decl in &module.type_info.impls { let next_id = env.type_ids.len() as i64 + 1; - let tid = *env.type_ids.entry(decl.type_name.clone()).or_insert(next_id); + // `impl S for Int` names a *built-in* type, whose values carry no arena + // mark for a sequential id to be compared against. Those arms take the + // fixed code the runtime answers for the kind + // (`lkrt::lkdyn::dispatch_builtin_code`); a struct keeps the sequential + // id, which is also what `display` looks its name up by. + let tid = match dispatch_builtin_type_id(&decl.type_name) { + Some(fixed) => *env.type_ids.entry(decl.type_name.clone()).or_insert(fixed), + None => *env.type_ids.entry(decl.type_name.clone()).or_insert(next_id), + }; for method in &decl.methods { env.impls .insert((decl.type_name.clone(), method.name.clone()), method.function); @@ -33,3 +175,32 @@ pub(crate) fn trait_env_prescan(module: &lk_core::vm::ModuleData) -> TraitEnv { } env } + +/// The declared-type code `obj_ty.field` carries to the runtime, mirroring +/// `lkrt::lkdyn`'s constants. Scalars only, and `Any` for everything else — see +/// `lkrt::lkdyn::check_declared_field`. +pub(crate) const DECLARED_ANY: i64 = 0; +pub(crate) const DECLARED_INT: i64 = 1; +pub(crate) const DECLARED_FLOAT: i64 = 2; +pub(crate) const DECLARED_BOOL: i64 = 3; +pub(crate) const DECLARED_STR: i64 = 4; +pub(crate) const DECLARED_NULLABLE: i64 = 16; + +fn declared_field_code(text: Option<&str>) -> i64 { + use lk_core::val::Type; + let Some(ty) = text.and_then(Type::parse) else { + return DECLARED_ANY; + }; + let (ty, nullable) = match &ty { + Type::Optional(inner) => ((**inner).clone(), DECLARED_NULLABLE), + other => (other.clone(), 0), + }; + let base = match ty { + Type::Int => DECLARED_INT, + Type::Float => DECLARED_FLOAT, + Type::Bool => DECLARED_BOOL, + Type::String => DECLARED_STR, + _ => return DECLARED_ANY, + }; + base | nullable +} diff --git a/aot/lower/src/try_region.rs b/aot/lower/src/try_region.rs new file mode 100644 index 00000000..bf4edaf5 --- /dev/null +++ b/aot/lower/src/try_region.rs @@ -0,0 +1,434 @@ +//! Protected regions (`try { … } catch e { … }`), turned into a call. +//! +//! Cranelift cannot emit `setjmp`. A call that returns twice has no place in +//! its SSA or its register allocator, so the shape the VM uses — open a +//! handler, run the body *here*, and longjmp back into the middle of this +//! function — cannot be lowered as written. What can be lowered is a call: the +//! body becomes a function of its own, `lkrt`'s trampoline does the `setjmp` +//! in a C frame that outlives it, and the caller branches on whether the body +//! returned or raised. +//! +//! So this module answers one question: *can this region be outlined, and if +//! so, into what function?* The rejections are as important as the acceptances +//! — each one is a shape whose meaning would change if it were called instead +//! of inlined, and every one of them names itself rather than falling out as +//! "opcode TryBegin is not natively lowerable yet". +//! +//! # Nothing is rejected here any more, and what it took +//! +//! The three `try`/`catch` files that sat in `AOT_COVERAGE_ALLOW` all lower +//! natively now, and each came off by a different fix. The reason to write that +//! down is the two that did *not* work, because both looked right: +//! +//! - **A placeholder on the edge that has no definition.** In the VM that +//! register holds whatever it held before the region, which is a real value a +//! program could read. +//! +//! - **Reading a container back out of a cell as the register's own type.** The +//! type is not a guess — it comes from the SSA, and the type checker refuses +//! `let a = 0; try { a = "s"; }` — and it makes `try_catch.lk` compile. It +//! also makes it print `Assertion failed` where the VM prints `try/catch: ok`. +//! A container does not need a cell: the parent and the body hold the *same +//! handle*, so a mutation is already visible, and the `dyn.from_list` / +//! `dyn.as_list` round trip is what loses it. +//! +//! What did work, in order: +//! +//! 1. **Cells are discovered, not predicted.** A region carries nothing back to +//! begin with; every register its body rebound and did not carry back is +//! poisoned at the region's exit, and a later read of one fails naming +//! itself. That error is how the fixpoint already finds a cell, so the set +//! ends up containing exactly the registers something reads. `Ssa::poisoned` +//! exists because `current_def = None` is not an absence — the read falls +//! through to the predecessors and finds the stale value. +//! +//! 2. **A body may be handed a container.** The trampoline marshals inputs as +//! machine words, and a handle *is* a machine word; declaring them all `I64` +//! rejected a body that merely looked at a list the parent owned. +//! +//! Same for a **`Bool`** (2026-07-30): 0/1 is a machine word too, and its +//! absence from `crosses_as_word` meant a `try` inside *any* function taking +//! a bool dropped the module to the VM — while the identical function with an +//! `Int` parameter lowered. That is what the note on file as "the `try` +//! expression's value cannot lower" actually was. +//! +//! **`F64` needed one more step.** The trampoline's signature is all +//! `long long` (`lkrt/src/try_trampoline.c`), so a float arrives in an +//! *integer* register. Adding `F64` to the list and declaring the body's +//! parameter `F64` made Cranelift read a float register instead: it compiled +//! and **segfaulted**. The body now declares the parameter `I64` and reads +//! the float back out of those bits (`Inst::BitsToFloat`) before its first +//! instruction. The differential case does arithmetic on it, because a +//! bit-cast in the wrong direction still runs and answers *something*. +//! +//! 3. **What a body rebound is reported by the body.** Reading the `a` field as +//! "the register this instruction writes" is not true of every opcode — +//! `log.push(2)` is `ListPush a=log`, where `a` is the receiver. The body +//! already compares the SSA's `current_def` before and after each +//! instruction to notice a cell changing; widening that to every register +//! answers the question without a table of operand roles. +//! +//! 4. **A retriable discovery made in the final pass has somewhere to go.** The +//! fixpoint converges, `refine_signatures` runs once, and the final pass +//! lowers against refined signatures — where a function that was clean every +//! pass can fail, with nothing after it to retry. +//! +//! 5. **An already-boxed value comes back as itself.** `Dyn` needs no unboxing: +//! what the cell holds *is* the register's value. Unlike (the refuted) +//! container case, nothing is reinterpreted. +//! +//! The through-line: every one of these replaced an inference about what a +//! value *must be* with a question put to the SSA — and the two that were +//! refuted were the two that inferred. Compiling was never the test; agreeing +//! with the VM was, and the refutations were found by running the program. +//! +//! # Leaving the region +//! +//! A body can end four ways, and three of them are not "it finished". `return` +//! belongs to the enclosing function; `break` and `continue` belong to a loop +//! that encloses the region. All three are the same problem — a jump whose +//! destination is in a frame the outlined body does not have — so they share one +//! answer: an **outcome flag** cell the body writes on its way out (0 fell +//! through, 1 returned, `2 + k` took the `k`th escape) and a check block behind +//! the region's ok edge that reads it and takes the edge the body named. +//! +//! Two decisions in that are worth keeping: +//! +//! - **The escape is still a jump, right up to the end.** Each destination gets +//! a one-instruction trailer past the body's `Return0`, and the jumps that +//! took it are rewritten to point there. So a `break` stays an ordinary jump +//! through leader-finding, the CFG, and SSA construction, and only becomes a +//! flag write at the trailer. Intercepting the jump *instruction* instead +//! would have meant one rewrite per terminator shape, and would have been +//! silently wrong for the shape it forgot. +//! +//! - **The parent's edges are real edges.** The escape destinations are recorded +//! as successors of the region's block, so the phi operands they need are +//! built by the same machinery every other edge uses; the check block reads +//! them back with `args_to`. Nothing about a `break` out of a `try` is special +//! in the parent — it is one block's terminator having four successors instead +//! of two. +//! +//! What this cost, and what caught it: the region was looked up by the *block's* +//! leader rather than the `TryBegin`'s pc, so `while c { i = i + 1; try { … } }` +//! found no escapes, built a body declaring five parameters, and called it with +//! four. That is not a link error — the trampoline takes the body's address and +//! casts it — so it ran and dereferenced whatever the fifth register held. +//! `clif.rs` now checks the call against the body's declared arity, and the +//! generated corpus that found it compares against the VM rather than merely +//! compiling. +//! +use lk_core::vm::{FunctionData, Instr, Opcode}; + +use crate::Unsupported; + +/// A `try` region found in a function's bytecode. +pub(crate) struct TryRegionShape { + /// The `TryBegin` itself: what a rejection names, and where the parent's + /// block ends. + pub(crate) begin_pc: usize, + /// The body, `[start, end)` — everything between `TryBegin` and `TryEnd`. + pub(crate) body_start: usize, + pub(crate) body_end: usize, + /// Where the handler begins, and where control resumes after the region. + pub(crate) handler: usize, + pub(crate) fallthrough: usize, + /// The register the handler reads the caught value from. + pub(crate) catch_reg: u8, + /// The body `return`s from the **enclosing** function. + /// + /// Outlined, such a `return` would return from the body instead — a + /// different program — so it used to be a rejection. It is a third outcome + /// now: two more output cells (a flag and the value), set by the body and + /// checked by the caller on the ok edge. + pub(crate) body_returns: bool, + /// Where the body jumps to *outside* the region, distinct and in the order + /// first seen: a `break` or `continue` belonging to a loop that encloses the + /// `try`. + /// + /// Outlined, the body has no such loop, so each of these is one more + /// outcome the flag reports (code `2 + index`) and one more edge out of the + /// region's block in the parent. + pub(crate) escape_targets: Vec, + /// The jumps themselves: the parent pc of each, and which + /// `escape_targets` entry it takes. + pub(crate) escapes: Vec, +} + +/// One jump out of a region's body, and where it lands. +pub(crate) struct TryEscape { + /// The `Jmp` itself, in the *parent's* pc space. + pub(crate) pc: usize, + /// An index into [`TryRegionShape::escape_targets`], which is also the + /// outcome code the flag carries, offset by 2. + pub(crate) target: usize, +} + +/// Every register the body might write. +/// +/// Over-approximated on purpose: it is `a` for every instruction in the body, +/// whether or not that opcode writes a register at all. `a` is the destination +/// by convention throughout this instruction set, so this misses nothing; what +/// it adds are registers an instruction only *read*, and the cost of that is a +/// region rejected that could have been lowered. The cost of the opposite +/// mistake is a program that computes a different answer, which is why the +/// approximation goes this way. +pub(crate) fn written_registers(instrs: &[Instr], start: usize, end: usize) -> Vec { + let mut written: Vec = instrs[start..end].iter().map(|instr| instr.a()).collect(); + written.sort_unstable(); + written.dedup(); + written +} + +/// Builds the function a region's body becomes. +/// +/// The body's instructions verbatim, with a `Return0` appended: it produces no +/// value, and the only thing the caller wants back is whether it finished. +/// Register numbering is left alone — the body uses the enclosing function's +/// registers, so the synthesized function simply declares as many. +/// +/// `performance` is carried over **rebased**, and only the two tables this +/// pipeline actually reads: `for_loops` (`cfg::exit_of`) and `key_ops` +/// (`inst::container`). Both are keyed by pc, and the body's pcs are the +/// parent's shifted by `body_start`, so the rebase is a slice — a fact read at +/// the wrong pc is worse than a missing one, and slicing cannot produce one. +/// +/// Dropping them wholesale is what it used to do, and the cost was concrete: a +/// `for` loop *requires* its fact, so a region with an ordinary `for i in 0..n` +/// in it rejected. That was the single most common blocker left in a generated +/// corpus of `try` programs. Neither fact names a pc — `PerfForLoopFact`'s jump +/// is an offset — so nothing inside them needs adjusting. +/// +/// The rest of the tables stay default. They are the VM executor's, and an +/// outlined body is never executed by the VM: it exists only in this crate's +/// own function table. +/// +/// # Escape trailers +/// +/// A `break` or `continue` belonging to a loop outside the `try` jumps to a pc +/// the body does not contain. Copied verbatim, that jump's *offset* would be +/// resolved against the body's own code — landing on some unrelated instruction +/// when the distance happens to fit, which is a wrong answer rather than a +/// refusal. So each distinct destination gets a one-instruction trailer past the +/// body's `Return0`, and every jump that took it is rewritten to point there. +/// +/// The trailer's opcode is never executed: the parent's lowering replaces its +/// exit with [`crate::Exit::TryEscape`], which writes the outcome code into the +/// flag cell and returns. What the trailer buys is a *block* — so a jump out +/// stays an ordinary jump, and every terminator shape (fused compare, `for` +/// latch, plain `Jmp`) keeps working without a second mechanism. +pub(crate) fn outline(parent: &FunctionData, region: &TryRegionShape) -> FunctionData { + let mut code: Vec = parent.code[region.body_start..region.body_end].to_vec(); + code.push(Instr::abc(Opcode::Return0, 0, 0, 0).raw()); + let trailer_base = code.len(); + for _ in ®ion.escape_targets { + code.push(Instr::abc(Opcode::Return0, 0, 0, 0).raw()); + } + for escape in ®ion.escapes { + let from = escape.pc - region.body_start; + let to = trailer_base + escape.target; + // `cfg::rel` reads `pc + 1 + offset`, so this is the inverse. Both fit + // an `i32` comfortably: `to` is bounded by the body's length. + code[from] = Instr::sj(Opcode::Jmp, (to as i64 - from as i64 - 1) as i32).raw(); + } + fn rebase(table: &[Option], start: usize, end: usize) -> Vec> { + (start..end).map(|pc| table.get(pc).cloned().flatten()).collect() + } + let (start, end) = (region.body_start, region.body_end); + let performance = lk_core::vm::analysis::PerformanceFacts { + for_loops: rebase(&parent.performance.for_loops, start, end), + key_ops: rebase(&parent.performance.key_ops, start, end), + ..Default::default() + }; + FunctionData { + consts: parent.consts.clone(), + code, + performance, + register_count: parent.register_count, + param_count: 0, + positional_param_count: 0, + param_names: Vec::new(), + capture_count: 0, + // Named even when the parent is not — the top-level entry has no name, + // so its outlined bodies had none either and a blocker in one arrived + // bare. `try@12` is not a made-up id like `fn41`: the pc is where the + // region begins, which is the one thing a reader can look up. + debug_name: Some(match parent.debug_name.as_ref() { + Some(name) => format!("{name}$try{}", region.begin_pc), + None => format!("try@{}", region.begin_pc), + }), + export_name: None, + extern_name: None, + } +} + +/// Finds every `try` region in a function, in `TryBegin` order. +/// +/// Returns `Err` for a region that cannot be outlined, because falling back +/// silently is what made this feature invisible for so long: a program with a +/// `try` in it simply ran three times slower, with nothing said. +pub(crate) fn scan(func: &FunctionData, instrs: &[Instr]) -> Result, Unsupported> { + let mut regions: Vec = Vec::new(); + // Only this function's *own* regions. A `try` written inside another one's + // body belongs to the body — which becomes a function of its own, scanned + // in turn — so outlining it here as well would give one `TryBegin` two + // owners and consume the same instructions twice. + let mut inner_until = 0usize; + for (pc, instr) in instrs.iter().enumerate() { + if instr.opcode() != Opcode::TryBegin || pc < inner_until { + continue; + } + let shape = shape_at(func, instrs, pc)?; + inner_until = shape.body_end; + regions.push(shape); + } + // An escape has to land somewhere the parent still *has* a block. Every + // region's body is consumed there — its instructions belong to the outlined + // function — so a jump into one would resolve to the block that region's + // `TryBegin` ends, and run it from the top. The spans are only all known + // once the scan is done, which is why this is here and not in `shape_at`. + let spans: Vec<(usize, usize)> = regions + .iter() + .map(|region| { + // Plus the `Jmp` over the handler, which the parent consumes too. + let last = match instrs.get(region.body_end + 1) { + Some(instr) if instr.opcode() == Opcode::Jmp => region.body_end + 1, + _ => region.body_end, + }; + (region.body_start, last) + }) + .collect(); + for region in ®ions { + for &target in ®ion.escape_targets { + if spans.iter().any(|&(start, end)| target >= start && target <= end) { + return Err(Unsupported::TryRegion { + pc: region.begin_pc, + reason: "the body jumps into another `try` region's body, which is a function of its own", + }); + } + } + } + Ok(regions) +} + +fn shape_at(func: &FunctionData, instrs: &[Instr], begin_pc: usize) -> Result { + let code_len = instrs.len(); + + // The body runs from the next instruction to the matching `TryEnd`. + // Nested regions are counted rather than assumed away: an inner `try` + // inside the body has its own `TryEnd`, and taking the first one would cut + // the outer body in half. + let mut depth = 0usize; + let mut body_end = None; + for (pc, instr) in instrs.iter().enumerate().skip(begin_pc + 1) { + match instr.opcode() { + Opcode::TryBegin => depth += 1, + Opcode::TryEnd => { + if depth == 0 { + body_end = Some(pc); + break; + } + depth -= 1; + } + _ => {} + } + } + let body_end = body_end.ok_or(Unsupported::TryRegion { + pc: begin_pc, + reason: "no matching TryEnd", + })?; + + // `TryBegin catch_reg, →handler`, the offset relative to the next pc. + let begin = instrs[begin_pc]; + let handler = + crate::cfg::rel(begin_pc, i32::from(begin.sbx()), code_len).ok_or(Unsupported::BadTarget { pc: begin_pc })?; + // What follows `TryEnd` is the jump over the handler, when the body can + // fall through at all. + // + // A body that *always* returns has none, and then there is no ok edge to + // give the region: `after_end` is the handler itself, and the `TryEnd` + // block has no successor for the CFG to record. The return channel + // (`body_returns`) answers a body that returns on *some* path, which is the + // general shape; this degenerate one — the whole body is a `return` — stays + // a rejection, because the fix for it is a region with no ok edge rather + // than one more cell. + let after_end = body_end + 1; + let has_fallthrough = after_end < code_len && instrs[after_end].opcode() == Opcode::Jmp; + let fallthrough = if has_fallthrough { + crate::cfg::rel(after_end, instrs[after_end].sj_arg(), code_len) + .ok_or(Unsupported::BadTarget { pc: after_end })? + } else { + after_end + }; + + let mut body_returns = false; + for instr in instrs.iter().take(body_end).skip(begin_pc + 1) { + let op = instr.opcode(); + // A `return` inside the body returns from the *enclosing* function — + // recorded, and answered by the return channel (`body_returns`). + if matches!(op, Opcode::Return | Opcode::Return0 | Opcode::Return1) { + body_returns = true; + } + } + + // A jump that leaves the body is a `break` or `continue` belonging to a loop + // that *encloses* the `try`. Outlined, the body has no such loop, so the + // jump becomes one more outcome the body reports and one more edge out of + // the region in the parent — the same channel a `return` already travels on, + // with the flag widened from "did it return" to "which way did it leave". + // + // Only an unconditional `Jmp` qualifies, and it is the only shape the + // compiler produces: a `break` is a statement, so it is emitted as a jump to + // a patched label, and the condition in front of it becomes a fused branch + // that skips *over* that jump. Probed rather than assumed — + // `if c { break; }`, `if c { … } else { break; }`, `if !(c) { … } else { + // continue; }`, a `||` condition, a divisibility test, a nil test, and a + // `while` written inside the region all put the escape in a `Jmp` of its own. + // + // The rejection below is what makes that a safe thing to rely on rather than + // a thing to hope for: a conditional whose taken edge left would need one + // edge rewritten and the other kept, and it says so instead of being + // approximated. If the compiler ever does fuse one, the coverage gate turns + // red on a refusal — not on a wrong answer. + let mut consumed = vec![false; code_len]; + let mut escape_targets: Vec = Vec::new(); + let mut escapes: Vec = Vec::new(); + for pc in begin_pc + 1..body_end { + let exit = crate::cfg::exit_of(pc, instrs, code_len, &mut consumed, &func.performance)?; + let leaves = |target: usize| target <= begin_pc || target > body_end; + if let Some(crate::Exit::Jump(target)) = exit + && leaves(target) + { + let index = match escape_targets.iter().position(|&t| t == target) { + Some(index) => index, + None => { + escape_targets.push(target); + escape_targets.len() - 1 + } + }; + escapes.push(TryEscape { pc, target: index }); + continue; + } + for target in crate::cfg::exit_successors(exit, pc + 1) { + if leaves(target) { + return Err(Unsupported::TryRegion { + pc, + reason: "a branch here leaves the `try` on one edge only, and the body becomes a \ + function of its own — an unconditional `break` or `continue` lowers", + }); + } + } + } + + Ok(TryRegionShape { + begin_pc, + body_start: begin_pc + 1, + body_end, + handler, + fallthrough, + catch_reg: begin.a(), + body_returns, + escape_targets, + escapes, + }) +} diff --git a/aot/lower/src/unsupported.rs b/aot/lower/src/unsupported.rs index 97573cbb..64e660b5 100644 --- a/aot/lower/src/unsupported.rs +++ b/aot/lower/src/unsupported.rs @@ -1,11 +1,44 @@ use super::*; /// Why a bytecode artifact cannot (yet) be lowered to MIR. +/// +/// Every variant here is a public claim about what the language cannot compile +/// natively, and `reason()` prints it to the user. A variant nothing constructs +/// therefore states a limitation that does not exist — and rustc does not catch +/// it, because a `pub` enum's variants count as reachable. Two were found this +/// way (2026-08-21): `NoReturn` ("the entry function never returns") and +/// `NonBoolCondition`, whose doc read "int-truthiness not yet lowered" while +/// `if 0`, `if "s"`, `if []` and `n ? a : b` all lower and agree with the VM. +/// When a lowering path is removed, remove its variant with it. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Unsupported { NoEntry, EntryHasParams(u16), EntryHasCaptures(u16), + /// Which function the blocker below is in. + /// + /// Every other variant names a `pc`, which is an index into one function's + /// code — and a program that is a kernel has hundreds. A pc alone sends the + /// reader looking through every one of them; the name turns the same + /// diagnostic into a line to open. Wrapped rather than a field on each + /// variant because the name is known at one place — the failure list, which + /// already carries the function index — and not at the dozens of places + /// that construct a blocker. + In { + function: String, + inner: Box, + }, + /// A container written to a global slot the lowering had to widen to `Dyn`. + /// + /// Boxing a container re-represents it — a `List` and a `List` are + /// different memory — so the slot ends up holding a *second* container and + /// the writer's own register goes on referring to the first. Nothing after + /// that is wrong in a way anything can see: the program runs and prints a + /// number. Refusing is what turns it into a fallback. + ContainerGlobalBoxed { + pc: usize, + name: String, + }, BadInstr { pc: usize, }, @@ -13,6 +46,44 @@ pub enum Unsupported { pc: usize, op: Opcode, }, + /// A **call shape** the lowering does not cover, carrying *why*. + /// + /// These sites used to say `Unsupported::Opcode { pc, op: Opcode::Call }` + /// with the opcode written in by hand, because the helper that refuses does + /// not have the instruction. The message then named an opcode the program + /// need not contain: eight of the twelve blockers in the x86 bare-metal + /// kernel reported `opcode Call (at pc N)` where pc N held a + /// `CallMethodK`. A reader who went to look found something else there. + /// + /// Same shape and same reason as [`Unsupported::TryRegion`]: the answer is + /// always a specific property of one call, never "calls are unsupported". + CallShape { + pc: usize, + reason: &'static str, + }, + /// A method call the dispatch table has no arm for, naming *which* method + /// and *which* receiver. + /// + /// `CallShape`'s reason is a `&'static str`, so the one this replaces could + /// only say "no native lowering for this method on this receiver type" — + /// true of every unlowered call, and useless for finding the one in front of + /// you. The `bare-metal-x86` kernel refused on a call this way and the + /// message named neither half; the argument types are here too, because a + /// receiver's arm often matches on them. + UnsupportedMethod { + pc: usize, + method: String, + receiver: &'static str, + args: Vec<&'static str>, + }, + /// A `try` region whose shape would change meaning if the body were called + /// instead of run in place. Carries *why*, because "opcode TryBegin is not + /// natively lowerable" is what this replaces: it named a feature where the + /// answer is always a specific property of one region. + TryRegion { + pc: usize, + reason: &'static str, + }, /// Two bundled modules define the same top-level name. Reported rather than /// resolved: the bundle flattens them into one namespace, so one would /// silently shadow the other for every nested read. @@ -35,16 +106,62 @@ pub enum Unsupported { }, /// A register (or virtual cell slot) was read with no reaching definition /// on any predecessor path. + /// + /// `body` names the try body whose poison caused it, when one did — the + /// register held a value that body wrote in its own frame and did not carry + /// back. That is the body that needs a cell for it, and *only* that body: + /// attributing the read to every region in the function gave a cell to + /// regions whose bodies merely reused the register as a scratch, and the + /// parent then had to seed a cell from a register it had never defined. + /// `None` is an ordinary undefined read, which no cell can fix. UndefinedOperand { pc: usize, reg: usize, + body: Option, + }, + /// A register the lowering tracks as a *compile-time reference* (a lambda, + /// a module object, an argument pack) was read where a runtime value is + /// required. + /// + /// This is not an undefined read, and reporting it as one — "register r1 is + /// read at pc 2 before any definition" — described the lowering's + /// bookkeeping instead of the program. `let fs = [|x| x + 1];` says nothing + /// about registers; what it does is put a closure in a container, which has + /// no native representation yet. + ReferenceAsValue { + pc: usize, + reg: usize, + what: &'static str, + /// The lambda the register named, when it named one. A closure *can* + /// become a runtime value (`SigInfer::value_lambdas`); which one it was + /// is what the fixpoint needs in order to make one, and the register + /// number does not survive the return out of `Ssa`. + lambda: Option, + }, + /// A loop-header phi's optimistically seeded *provenance* (which struct a + /// value is, which literal a container came from) is contradicted by an + /// edge that arrived later. Retriable: the next pass lowers the slot with + /// no seed. + PhiProvenance { + block: usize, + slot: usize, }, /// An empty `[]` literal's guessed element type was contradicted by a - /// later consumer: retriable — the fixpoint re-lowers with the literal - /// materialized as a Dyn list (`pc` identifies the `LoadHeapConst`). - EmptyListGuessWrong { + /// later consumer: retriable — the fixpoint re-lowers with the literals + /// materialized as Dyn lists (`pcs` identify the `LoadHeapConst`s). + LiteralElemTypeContradicted { pcs: Vec, }, + /// A push into a *parameter* widened its carrier, and this function built + /// no literal to blame: retriable — the fixpoint records the parameter and + /// every call site then builds its list argument as a Dyn list. + /// + /// The caller is where the fix has to land: a `Vec` cannot become a + /// `Vec` after the fact, so the carrier has to be decided at the + /// literal. `param` is the parameter's register (`r0..r(n-1)` at entry). + ParamCarrierContradicted { + param: u8, + }, /// A loop-header phi merged heterogeneous boxable types: retriable — /// the fixpoint re-lowers the function with this phi pre-typed `Dyn` /// (its body then consumes it through the Dyn arms from the start). @@ -56,10 +173,21 @@ pub enum Unsupported { TypeMismatch { pc: usize, }, - NoReturn, - /// A branch condition register was not a `Bool` (int-truthiness not yet lowered). - NonBoolCondition { + /// The same, from a site that knows both types. + /// + /// Worth a second variant rather than fields on the first: `TypeMismatch` + /// is constructed in ninety-odd places, most of them a `_ =>` arm that has + /// nothing to say beyond "not this". The handful that *do* know — anything + /// reading an operand it requires a specific type for — can say it, and + /// that is the difference between "an operand at pc 88 has a type outside + /// the natively lowerable subset" and "wanted I64, found Dyn". + /// + /// The first of those cost a round of patching every construction site with + /// a print to find out which one had fired. + OperandType { pc: usize, + want: &'static str, + got: &'static str, }, /// Two returns disagree on the value type. ReturnTypeConflict, @@ -72,7 +200,12 @@ pub enum Unsupported { /// now-bridged callee) produced structurally-invalid MIR. Rather than emit /// it (codegen would reject it as an internal error), the module is treated /// as not-natively-lowerable so the caller falls back to the VM. - InvalidMir, + /// + /// The validator's error is carried along: this is a whole-module fallback, + /// so without naming the cause the only way to see it was an env var nobody + /// sets — which is how an arm that named a nonexistent ABI function went + /// unnoticed (see §45). + InvalidMir(String), } impl Unsupported { @@ -84,10 +217,34 @@ impl Unsupported { Unsupported::NoEntry => "the module has no entry function".to_string(), Unsupported::EntryHasParams(n) => format!("the entry function takes {n} parameter(s)"), Unsupported::EntryHasCaptures(n) => format!("the entry function captures {n} value(s)"), + Unsupported::In { function, inner } => format!("in `{function}`: {inner}"), + Unsupported::ContainerGlobalBoxed { pc, name } => format!( + "the container written to global `{name}` (at pc {pc}) would have to be boxed, \ + which copies it — the slot and the writer would stop being the same container" + ), Unsupported::BadInstr { pc } => format!("undecodable instruction at pc {pc}"), Unsupported::Opcode { pc, op } => { format!("opcode {op:?} (at pc {pc}) is not natively lowerable yet") } + Unsupported::UnsupportedMethod { + pc, + method, + receiver, + args, + } => { + let args = if args.is_empty() { + String::new() + } else { + format!(" with ({})", args.join(", ")) + }; + format!("no native lowering for `{receiver}.{method}(){args}` (at pc {pc})") + } + Unsupported::CallShape { pc, reason } => { + format!("the call at pc {pc} is not natively lowerable: {reason}") + } + Unsupported::TryRegion { pc, reason } => { + format!("the try region at pc {pc} cannot be outlined: {reason}") + } Unsupported::BundledNameCollision { name } => format!( "two bundled modules both define `{name}`. Bundling flattens them into one namespace, \ so one would silently shadow the other — rename one of them" @@ -96,25 +253,42 @@ impl Unsupported { format!("global `{name}` (read at pc {pc}) does not resolve to anything natively lowerable") } Unsupported::BadConst { pc } => format!("unsupported constant operand at pc {pc}"), - Unsupported::UndefinedOperand { pc, reg } => { + Unsupported::UndefinedOperand { pc, reg, .. } => { format!("register r{reg} is read at pc {pc} before any definition") } + Unsupported::ReferenceAsValue { pc, reg, what, .. } => format!( + "the {what} in r{reg} at pc {pc} is a compile-time reference, not a runtime value \ + — storing one in a container, or otherwise using it where a value is required, \ + has no native form yet" + ), + Unsupported::ParamCarrierContradicted { param } => { + format!("a push into parameter r{param} widens its carrier, which only the caller can build") + } + // `want` and `got` are carried in the value and used to be dropped + // here, so every investigation of this refusal began by guessing + // which of its construction sites it came from. + Unsupported::OperandType { pc, want, got } => { + format!("an operand at pc {pc} is a {got} where a {want} is required") + } Unsupported::TypeMismatch { pc } => { format!("an operand at pc {pc} has a type outside the natively lowerable subset") } - Unsupported::EmptyListGuessWrong { pcs } => { + Unsupported::PhiProvenance { block, slot } => { + format!( + "the loop-header phi for r{slot} in block {block} was seeded with a provenance an edge contradicts" + ) + } + Unsupported::LiteralElemTypeContradicted { pcs } => { format!("empty list literal(s) at pc {pcs:?} were mis-guessed (retried as Dyn)") } Unsupported::DynLoopPhi { block, slot } => { format!("a loop-header phi (block {block}, slot {slot}) merges heterogeneous types") } - Unsupported::NoReturn => "the entry function never returns".to_string(), - Unsupported::NonBoolCondition { pc } => { - format!("the branch condition at pc {pc} is not a bool") - } Unsupported::ReturnTypeConflict => "returns disagree on the value type".to_string(), Unsupported::BadTarget { pc } => format!("a branch at pc {pc} targets an out-of-range pc"), - Unsupported::InvalidMir => "the lowered module did not pass MIR validation".to_string(), + Unsupported::InvalidMir(error) => { + format!("the lowered module did not pass MIR validation: {error}") + } } } } diff --git a/aot/lower/src/vocab.rs b/aot/lower/src/vocab.rs index 421e7a36..c49bad8a 100644 --- a/aot/lower/src/vocab.rs +++ b/aot/lower/src/vocab.rs @@ -10,9 +10,19 @@ pub(crate) enum Builtin { /// Width rides in the variant because that is where the source puts it — /// the compiler cannot ask the type checker for a pointee type, which is /// why these are intrinsics rather than `*p` syntax. - /// `cpu_*` — barriers, interrupt masking, wait-for-interrupt. The payload - /// is the ABI entry name under the `cpu` module. - Cpu(&'static str, u8), + /// `cpu_*` — barriers, interrupt masking, wait-for-interrupt, and the + /// system-control instructions. The payload is the ABI entry name under the + /// `cpu` module, and *only* that: how many arguments the entry takes and + /// whether it produces a value are read back out of the ABI table at + /// lowering time. + /// + /// Carrying the arity here as well is what this used to do, alongside a + /// hard-coded list of the entries that return something. Both were copies + /// of what the table already says, and a copy of a signature is the shape + /// this repo has been bitten by: an entry whose arity disagreed would lower + /// a call with the wrong number of arguments, and one missing from the + /// returns-a-value list would have its result overwritten with nil. + Cpu(&'static str), VolatileRead(u8), VolatileWrite(u8), /// `port_in_uN(port)` / `port_out_uN(port, value)` — x86 port I/O. @@ -30,8 +40,6 @@ pub(crate) enum Builtin { CallMethod, /// `Set()` / `Set(list)` — the VM's set constructor builtin. SetCtor, - /// `try$call(closure)` — the try/catch desugar's protected call. - TryCall, /// `error(v)` — raises a first-class error value (`rt.raise_dyn`). ErrorRaise, /// `chan(capacity[, type])` — a native channel (its `i64` id). @@ -48,11 +56,12 @@ pub(crate) enum Builtin { /// `__lk_make_struct(name, fields)` — the struct-update desugar's /// object constructor: a fresh field copy + struct provenance. MakeStruct, - /// `__lk_bit_and(l, r)` / `__lk_bit_or(l, r)` / `__lk_bit_not(v)` — the + /// `__lk_bit_and(l, r)` / `__lk_bit_or(l, r)` / `__lk_bit_xor(l, r)` / `__lk_bit_not(v)` — the /// `&`/`|`/`~` operator desugars (Int-only in the VM; other argument /// types reject and fall back to its loud error). BitAnd, BitOr, + BitXor, BitNot, /// `__lk_shl(l, r)` / `__lk_shr(l, r)` — the `<<`/`>>` desugars. Unlike the /// other bitwise operators these do not lower to a machine instruction: @@ -63,6 +72,22 @@ pub(crate) enum Builtin { // build blocks mid-instruction; a call per shift is the price of the check. Shl, Shr, + /// `__lk_shr_u(l, r)` — the compiler picks this when the left operand is a + /// `u64`, where an arithmetic shift would replicate a bit that is part of + /// the value rather than its sign. + ShrU, + /// `__lk_lt_u` / `__lk_div_u` / `__lk_mod_u` — the unsigned forms the + /// compiler picks when both operands are proven `u64`. + LtU, + DivU, + ModU, + /// `__lk_u64_to_float(x)` — the unsigned read of the carrier as a float. + U64ToFloat, + /// `__lk_u64_str(x)` — the unsigned read of the carrier as its decimal + /// string. Inserted by the compiler at the sites that *render* a value + /// (a `println` argument, a template-string part) rather than compute with + /// it, because that is the last place the width is still known. + U64Str, /// `symbol_address("name")` — the address of an `#[export]`ed function, and /// `call_address_2(addr, a, b)` — a call through one. Together they are /// what a driver table is made of: an array of function pointers, indexed @@ -86,17 +111,25 @@ pub(crate) enum GlobalRef { /// constant string key), which produces [`GlobalRef::ModuleFn`]. Module(String), /// A member function resolved from `module.name`, callable when - /// [`module_call_abi`] maps it to a typed lkrt ABI entry. + /// [`module_call_abi_rows`] maps it to a typed lkrt ABI entry. ModuleFn(String, String), /// A compile-time-bundled file module (`use "path"` → `GetGlobal` of the /// file-stem binding); the payload indexes `SigInfer::imports.bundles`. /// Its only consumer is a constant-name member read, which resolves to /// [`GlobalRef::Lambda`] of the merged function. UserModule(usize), - /// A user function value (`LoadFunction`); its only supported consumer is - /// the compiler's `SetGlobal` storage of top-level `fn` declarations - /// (direct calls address the callee by index instead). - UserFn, + /// A user function value (`LoadFunction`), with the function it names. + /// + /// Two consumers. The compiler's `SetGlobal` storage of a top-level `fn` + /// declaration, which is a no-op natively. And a `Call` through the + /// register, which is a direct call the bytecode could not spell that way: + /// `CallDirect` names its target in a byte, so a module whose 256th + /// function calls its 257th gets `LoadFunction` + `Call` instead. That used + /// to reject, which made 256 functions a *native* ceiling as well as a + /// bytecode one — reached the ordinary way, by a program with a lot of + /// drivers. The index is what makes the call lowerable; it is the same + /// devirtualization `Lambda` already gets. + UserFn(u32), /// A capture-free closure (`MakeClosure` with `capture_count == 0`) — a /// statically known function reference. Supported consumers: an indirect /// `Call` through the register (lowered as a direct call) and the entry @@ -124,6 +157,25 @@ pub(crate) enum GlobalRef { ArgList(Vec<(ValueId, Ty)>), } +impl GlobalRef { + /// What this reference is, in the program's words — for the diagnostic that + /// fires when one is read where a runtime value is required. + pub(crate) fn describe(&self) -> &'static str { + match self { + Self::Builtin(_) => "builtin", + Self::Module(_) => "stdlib module object", + Self::ModuleFn(_, _) => "stdlib module function", + Self::UserModule(_) => "bundled module object", + Self::UserFn(_) => "function reference", + Self::Lambda(_) => "closure", + Self::Closure(_, _) => "closure", + Self::Cell(_) => "captured variable cell", + Self::CellParam(_) => "captured variable", + Self::ArgList(_) => "argument pack", + } + } +} + /// The statically known identity of a lambda passed as an argument: the /// target function plus its capture count (a capturing closure's *environment /// values* are runtime data — hidden trailing arguments — and stay out of the @@ -149,6 +201,23 @@ pub(crate) enum RetCaptureSrc { pub(crate) enum ClosureCapture { /// A shared mutable cell, resolved at each call site. Cell(u32), + /// The *enclosing* function's `k`th capture, captured onward. + /// + /// A closure nested in a closure (`|v| { let inner = |w| { total = total + + /// w; }; … }`) captures what its parent captured. The parent holds it as a + /// capture parameter, not as a cell of its own, so there was nothing for + /// `Cell(cid)` to name and the whole program fell back. + /// + /// When the parent's capture is already a runtime cell (`Ty::Cell`) the + /// pointer passes straight through — parent and child share one cell, which + /// is exactly the VM's semantics. When it is not, the child's need for one + /// propagates up: the call site records it against the parent and retries, + /// so `SigInfer::cell_captures` reaches a fixpoint over the whole chain. + CellParam(usize), + /// A capture whose whole meaning is a lowering-time reference (a lambda, a + /// named function): nothing to pass, so the slot carries a dead `0` and the + /// callee reads [`SigInfer::ref_captures`]. + StaticRef, /// A direct by-value capture. Value(ValueId, Ty), } @@ -200,6 +269,32 @@ pub(crate) enum Exit { taken: usize, fallthrough: usize, }, + /// A `try` region, collapsed into one exit. + /// + /// The body's instructions are not part of this function: they were + /// outlined into a function of their own, because Cranelift cannot emit + /// `setjmp` — a call that returns twice has no place in its SSA or its + /// register allocator. What is left here is a call whose *outcome* is a + /// flag, and this exit is the branch on it: fall through when the body + /// returned, into the handler when it raised. + TryRegion { + /// The function the body became. + body: u32, + /// The register the handler reads the caught value from. + catch_reg: u8, + handler: usize, + fallthrough: usize, + }, + /// A `try` body leaving through a jump that belonged to the enclosing + /// function — a `break` or `continue` whose loop is outside the region. + /// + /// Only ever the exit of an escape trailer (`try_region::outline`), and only + /// inside an outlined body. It writes `code` into the outcome flag and + /// returns normally, so the trampoline still reports "did not raise"; the + /// caller's check block reads the code and takes the edge the jump named. + TryEscape { + code: i64, + }, /// Fused `TestEqIntI2` + trailing `Jmp`: `r_a == imm_a && r_b == imm_b` /// falls through, anything else branches to `taken`. Consumes the `Jmp`. FusedCmp2 { diff --git a/aot/lower/tests/abi_names.rs b/aot/lower/tests/abi_names.rs new file mode 100644 index 00000000..04e0af1c --- /dev/null +++ b/aot/lower/tests/abi_names.rs @@ -0,0 +1,119 @@ +//! Every ABI name the lowering can emit has a row in the schema. +//! +//! A `Call` naming a function the schema does not have fails MIR validation — +//! which rejects the *whole module*, not the one call. That is a clean +//! fallback, and it is also invisible: the program simply does not lower, with +//! no sign that a lowering arm is at fault rather than the program. One such +//! arm (`dyn.map_get`, the callable-property path for a boxed receiver) was +//! written, shipped, and never ran once. +//! +//! So the names are checked here, against the source. A literal in the second +//! position of `AbiRef::new` has to exist in the schema; a literal in the first +//! has to be a module the schema knows. Both positions are read regardless of +//! the expression shape around them, because the arm that got this wrong wrote +//! `AbiRef::new(if … { "dyn" } else { "map_h" }, if … { "map_get" } else { … })` +//! — a form that reads a name out of a conditional. + +use std::collections::HashSet; + +/// The lowering's sources, by the path `include_str!` resolves from this file. +const SOURCES: &[(&str, &str)] = &[ + ("lib.rs", include_str!("../src/lib.rs")), + ("function.rs", include_str!("../src/function.rs")), + ("lower_call.rs", include_str!("../src/lower_call.rs")), + ("lower_method.rs", include_str!("../src/lower_method.rs")), + ("lower_module.rs", include_str!("../src/lower_module.rs")), + ("lower_builtin.rs", include_str!("../src/lower_builtin.rs")), + ("convert.rs", include_str!("../src/convert.rs")), + ("dyn_box.rs", include_str!("../src/dyn_box.rs")), + ("capture.rs", include_str!("../src/capture.rs")), + ("try_region.rs", include_str!("../src/try_region.rs")), + ("inst/container.rs", include_str!("../src/inst/container.rs")), + ("inst/call.rs", include_str!("../src/inst/call.rs")), + ("inst/global.rs", include_str!("../src/inst/global.rs")), + ("inst/scalar.rs", include_str!("../src/inst/scalar.rs")), + ("inst/string.rs", include_str!("../src/inst/string.rs")), + ("inst/control.rs", include_str!("../src/inst/control.rs")), +]; + +/// Every string literal inside each `AbiRef::new( … )` call in `source` that is +/// used as a *name* rather than compared against one. +/// +/// A name may be chosen by a conditional — `AbiRef::new("dyn", if name == +/// "keys" { "map_keys" } else { "map_values" })` — so the literals inside the +/// call are a mix of names and of the thing being tested. An operand of `==` +/// or `!=` is the latter. +fn abi_ref_literals(source: &str) -> Vec { + let mut found = Vec::new(); + let mut rest = source; + while let Some(at) = rest.find("AbiRef::new(") { + let after = &rest[at + "AbiRef::new(".len()..]; + let mut depth = 1usize; + let mut end = after.len(); + for (index, ch) in after.char_indices() { + match ch { + '(' => depth += 1, + ')' => { + depth -= 1; + if depth == 0 { + end = index; + break; + } + } + _ => {} + } + } + let args = &after[..end]; + let mut chars = args.char_indices().peekable(); + while let Some((index, ch)) = chars.next() { + if ch != '"' { + continue; + } + let start = index + 1; + let mut close = start; + for (at, ch) in args[start..].char_indices() { + if ch == '"' { + close = start + at; + break; + } + } + let before = args[..index].trim_end(); + let compared = before.ends_with("==") || before.ends_with("!="); + if !compared { + found.push(args[start..close].to_string()); + } + while let Some(&(at, _)) = chars.peek() { + if at <= close { + chars.next(); + } else { + break; + } + } + } + rest = &after[end..]; + } + found +} + +#[test] +fn every_emitted_abi_name_exists_in_the_schema() { + let schema = lk_aot_abi::ABI_FUNCTIONS; + let modules: HashSet<&str> = schema.iter().map(|row| row.module).collect(); + let names: HashSet<&str> = schema.iter().map(|row| row.name).collect(); + + let mut unknown: Vec = Vec::new(); + for (file, source) in SOURCES { + for literal in abi_ref_literals(source) { + if modules.contains(literal.as_str()) || names.contains(literal.as_str()) { + continue; + } + unknown.push(format!("{file}: \"{literal}\"")); + } + } + assert!( + unknown.is_empty(), + "these `AbiRef::new` literals name neither an ABI module nor an ABI function, \ + so any program reaching them fails MIR validation and falls back whole:\n {}", + unknown.join("\n ") + ); +} diff --git a/aot/lower/tests/hybrid_lowering.rs b/aot/lower/tests/hybrid_lowering.rs index e56a108d..4964c964 100644 --- a/aot/lower/tests/hybrid_lowering.rs +++ b/aot/lower/tests/hybrid_lowering.rs @@ -11,11 +11,8 @@ fn artifact(source: &str) -> ModuleArtifact { let program = parse_program_source(source, ParseOptions::default()).expect("parse"); // The try/catch desugar references runtime builtins; declare them as // external globals the way a CLI compile (full stdlib context) would. - let externals: Vec = ["try$call", "assert", "error", "println"] - .iter() - .map(|s| s.to_string()) - .collect(); - let module = Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), externals).expect("compile"); + let externals: Vec = ["assert", "error", "println"].iter().map(|s| s.to_string()).collect(); + let module = Compiler::compile_module_with_globals(&program, externals).expect("compile"); ModuleArtifact::new(Vec::new(), &module).expect("artifact") } @@ -111,6 +108,26 @@ fn hybrid_degrades_a_discarded_bridge_result_to_the_void_call() { ); } +#[test] +fn hybrid_bridges_the_real_owner_of_an_unlowerable_try_body() { + let artifact = artifact( + "fn guarded() -> String {\n\ + try { let f = \"v={}\".trim(); println(f, 1); return \"ok\"; }\n\ + catch e { return \"caught\"; }\n\ + }\n\ + println(guarded());\n\ + return 0;\n", + ); + let artifact_function_count = artifact.module.functions.len() as u32; + let mir = lk_aot_lower::lower_with_hybrid(&artifact, true).expect("the try owner bridges"); + lk_aot_mir::validate(&mir).expect("hybrid module validates"); + assert_eq!(mir.vm_functions.len(), 1, "only the real `guarded` function bridges"); + assert!( + mir.vm_functions[0].id.0 < artifact_function_count, + "an outlined try body has no function in the embedded VM artifact" + ); +} + #[test] fn hybrid_rejects_a_global_touching_callee() { // The callee's VM-side subtree writes a module global: the bridge VM's diff --git a/aot/lower/tests/mir_snapshots.rs b/aot/lower/tests/mir_snapshots.rs index 67bf8343..5390878c 100644 --- a/aot/lower/tests/mir_snapshots.rs +++ b/aot/lower/tests/mir_snapshots.rs @@ -29,18 +29,27 @@ fn assert_snapshot(source: &str, expected: &str) { ); } +/// `/` yields a `Float`, so two `Int`s widen and divide as `f64`. +/// +/// This snapshot used to hold `int.div … -> i64`, pinning the one place that +/// disagreed with the rest of the language: the checker, the constant folder +/// and the `dyn.*` helpers all said `/` produces a `Float`, and only the typed +/// lowering (and the VM's typed fast path) divided as integers. A native +/// `7 / 2` answered `3` where the VM answered `3.5`. #[test] fn straightline_division() { assert_snapshot( "let x = 20;\nlet y = 4;\nreturn x / y;\n", r#" mir module (abi v1) -fn f0() -> i64 entry { +fn f0() -> f64 entry { bb0(): v0 = const.i64 20 v1 = const.i64 4 - v2 = int.div v0, v1 - ret v2 + v2 = sitofp v0 + v3 = sitofp v1 + v4 = float.div v2, v3 + ret v4 } "#, ); @@ -124,6 +133,7 @@ fn list_literal_and_dynamic_index() { "let xs = [10, 20, 30];\nlet i = 0;\nlet s = 0;\nwhile (i < 3) { s = s + xs[i]; i = i + 1; }\nreturn s;\n", r#" mir module (abi v1) +global g0 = "Add expected numbers or strings, got Int and Nil" fn f0() -> i64 entry { bb0(): v0 = call list_h.i64_new() @@ -144,11 +154,15 @@ bb1(v8: i64, v11: list, v13: i64): condbr v10, bb2(), bb3() bb2(): v12 = list.i64.get_maybe v11, v8 - v14 = maybe.i64.unwrap v12 - v15 = int.add v13, v14 - v16 = const.i64 1 - v17 = int.add v8, v16 - br bb1(v17, v11, v15) + v14 = maybe.present> v12 + v15 = zext.bool v14 + v16 = const.str g0 + call rt.maybe_guard(v15, v16) + v17 = maybe.value> v12 + v18 = int.add v13, v17 + v19 = const.i64 1 + v20 = int.add v8, v19 + br bb1(v20, v11, v18) bb3(): ret v13 } @@ -166,21 +180,17 @@ global g0 = "a" global g1 = "b" fn f0() -> maybe entry { bb0(): - v0 = call map_h.lit_new() - v1 = const.str g0 - v2 = call dyn.from_str(v1) - v4 = const.i64 1 - v3 = call dyn.from_i64(v4) - call map_h.lit_set(v0, v2, v3) - v5 = const.str g1 - v6 = call dyn.from_str(v5) - v8 = const.i64 2 - v7 = call dyn.from_i64(v8) - call map_h.lit_set(v0, v6, v7) - v9 = call map_h.lit_finish_str_i64(v0) - v10 = const.str g1 - v11 = map.str_i64.get_maybe v9, v10 - ret v11 + v1 = const.i64 2 + v0 = call map_h.str_i64_new_sized(v1) + v2 = const.str g0 + v3 = const.i64 1 + call map_h.str_i64_set_const(v0, v2, v3) + v4 = const.str g1 + v5 = const.i64 2 + call map_h.str_i64_set_const(v0, v4, v5) + v6 = const.str g1 + v7 = map.str_i64.get_maybe v0, v6 + ret v7 } "#, ); diff --git a/aot/mir/src/lib.rs b/aot/mir/src/lib.rs index fc626a43..442f5c45 100644 --- a/aot/mir/src/lib.rs +++ b/aot/mir/src/lib.rs @@ -52,6 +52,10 @@ pub enum Ty { /// A growable `List` handle (opaque `ptr` at the ABI). Phase 2 container /// handle-ification; more element types follow. ListI64, + /// A window over a `List` (opaque `ptr`): what `xs.slice(a, b)` + /// returns. Not a list — it borrows one, and the distinction is the point + /// (`lkrt::lkslice`, the VM's `HeapValue::Slice`). + SliceI64, /// A growable `List` handle (opaque `ptr` at the ABI). ListF64, /// A growable `List` handle (elements are `Str` pointers; opaque `ptr`). @@ -68,6 +72,13 @@ pub enum Ty { /// ABI as `0`/`1`; the type keeps bool display/compare semantics exact. MapStrBool, /// The result of a dynamic (not provably in-range) `List` index: a + /// A native `Bytes` handle (`*mut c_void` → an arena-owned `Vec`), + /// mirroring the VM's `HeapValue::Bytes`. Opaque pointer. + /// + /// A distinct type rather than a bare handle integer because *display* and + /// *equality* depend on knowing it is bytes: `println(b)` is + /// `Bytes([104,105])`, not a pointer, and `==` compares content. + Bytes, /// A mutable capture cell (`rt.cell_*`, the VM's `UpvalCell`): an /// arena-owned boxed-Dyn slot passed by pointer, so a `try` body's /// assignment to an outer local writes through. Opaque pointer. @@ -154,6 +165,22 @@ pub enum Const { Nil, } +/// Which register of a two-register carrier [`Inst::CarrierWord`] extracts. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CarrierHalf { + Lo, + Hi, +} + +impl CarrierHalf { + pub fn name(self) -> &'static str { + match self { + Self::Lo => "lo", + Self::Hi => "hi", + } + } +} + /// A single SSA instruction: it defines at most one value (`dst`) from its inputs. #[derive(Debug, Clone, PartialEq)] pub enum Inst { @@ -181,6 +208,15 @@ pub enum Inst { lhs: ValueId, rhs: ValueId, }, + /// `dst = bitcast(src)` — the same eight bytes read as an `F64`. + /// + /// Not a conversion: `1` becomes `5e-324`, not `1.0`. The one use is the + /// `try`-region trampoline, which calls a body through a + /// `(long long, …)` signature (`lkrt/src/try_trampoline.c`) — so every + /// input arrives in an integer register, and a float one has to be read + /// back out of those bits. Passing it *as* a float without this is what + /// segfaulted. + BitsToFloat { dst: ValueId, src: ValueId }, /// `dst = sitofp(src)` — widen an `I64` value to `F64`. IntToFloat { dst: ValueId, src: ValueId }, /// `dst = fptosi(src)` — an `F64` truncated toward zero into `I64`. @@ -211,6 +247,12 @@ pub enum Inst { ZextBool { dst: ValueId, src: ValueId }, /// `dst = !src` — boolean negation (`xor i1 src, true`). Not { dst: ValueId, src: ValueId }, + /// `dst = -src` — float negation (`fneg`). + /// + /// Not `0.0 - src`: IEEE has two zeros, and `0.0 - 0.0` is `+0.0` where + /// `-(0.0)` is `-0.0`. The VM does a real negation, so this does too, or + /// the two backends disagree on a value a program can print. + FloatNeg { dst: ValueId, src: ValueId }, /// `dst = lhs & rhs` on `Bool` (`and i1`). Used by fused conjunction /// branches (`TestEqIntI2`). BoolAnd { dst: ValueId, lhs: ValueId, rhs: ValueId }, @@ -242,6 +284,30 @@ pub enum Inst { /// VM equivalent: an interpreter has no code addresses to hand out, so the /// builtin refuses there rather than inventing one. SymbolAddr { dst: ValueId, symbol: String }, + /// `dst = volatile load.uN [addr]` — a device read, zero-extended to `I64`. + /// + /// A real machine load, not a call into the runtime. What made this a call + /// before was that Cranelift has no volatile flag and its alias analysis + /// will collapse two loads of one address into one — for a device register, + /// whose two reads can legitimately differ and whose reads can have side + /// effects, a miscompile. The way out is not a flag but a *`sequence_point` + /// before each access*: it emits no machine code at all, and the alias pass + /// treats it as a fence, so the second load's "last store" differs from the + /// first's and the two are no longer the same memory location to it. + /// + /// `bits` is the width of the *access*, which is not a property of the + /// value: the VM carries every machine integer in an `i64`, so the width + /// has to ride on the instruction. That is the distinction this enum's + /// `IntTruncate` doc anticipated. + VolatileLoad { dst: ValueId, addr: ValueId, bits: u8 }, + /// `volatile store.uN [addr], value` — a device write. + /// + /// As [`Inst::VolatileLoad`], and the elimination it has to survive is the + /// mirror image: the alias pass drops a store of a value a location is + /// already known to hold. Two identical writes to one port — a command + /// register that counts them, say — are not one write, and the preceding + /// `sequence_point` is what keeps them two. + VolatileStore { addr: ValueId, value: ValueId, bits: u8 }, /// Calls through an address held in a value, with a fixed integer /// signature. The other half of a driver table. CallIndirect { @@ -261,25 +327,31 @@ pub enum Inst { arg_tys: Vec, ret: Ty, }, - /// `dst = try.call f{func}(args)` — a native protected call (`try$call`, - /// plan G): codegen expands to `rt.try_push` + `_setjmp` + a conditional - /// call of the try-body function (which returns `Dyn`), joining into the - /// `[ok, value]` dyn-list the desugared destructuring consumes. A raise - /// inside the body longjmps back to the `_setjmp`. - TryCall { + /// `dst = try.region f{func}` — run a `try` body under a fresh handler. + /// + /// The statement form of `try`, where the body produces no value: `dst` is + /// 1 when it returned and 0 when it raised, and the caught value is read + /// separately (`rt.current_error`) on the path that wants it. Codegen calls + /// `lkrt`'s trampoline, which does the `setjmp` in a C frame — Cranelift + /// cannot emit one. + TryRegionCall { dst: ValueId, func: FuncId, + /// The enclosing function's registers the body reads, as machine words. args: Vec, }, - /// `dst = trait.dispatch(self, arms)` — a runtime trait-method dispatch - /// over a boxed struct instance (plan J1): codegen reads the receiver's - /// arena type mark (`lkrt_dyn_obj_type_id`) and expands an `icmp` chain - /// calling the matching impl. Every arm takes the boxed `self` and - /// returns `Dyn` (the lowering forces `dyn_rets`), so one rendered + /// `dst = trait.dispatch(self, args, arms)` — a runtime trait-method + /// dispatch over a boxed struct instance (plan J1): codegen reads the + /// receiver's arena type mark (`lkrt_dyn_obj_type_id`) and expands an + /// `icmp` chain calling the matching impl. Every arm takes the boxed `self` + /// followed by the boxed arguments and returns `Dyn` (the lowering forces + /// `dyn_rets` and observes every parameter as `Dyn`), so one rendered /// signature serves all arms; no matching mark raises. TraitDispatch { dst: ValueId, self_arg: ValueId, + /// The method's own arguments, each already boxed to `Dyn`. + args: Vec, /// `(runtime type id, impl function)` in registration order. arms: Vec<(i64, FuncId)>, }, @@ -312,6 +384,21 @@ pub enum Inst { handle: ValueId, index: ValueId, }, + /// `dst = lkrt_str_byte_at(s, index)` — one byte of a string as a + /// [`Ty::MaybeI64`]: absent past either end, the same nil the VM answers. + StrByteAtMaybe { + dst: ValueId, + handle: ValueId, + index: ValueId, + }, + /// `dst = lkrt_lkslice_i64_get_pair(handle, index)` — the [`Ty::SliceI64`] + /// analogue of [`Inst::ListGetMaybe`], and a dedicated instruction for the + /// same reason: the `{i64, i64}` return is outside the scalar ABI. + SliceGetMaybe { + dst: ValueId, + handle: ValueId, + index: ValueId, + }, /// `dst = lkrt_maybe_i64_unwrap(src.value, src.present)` — narrows a /// [`Ty::MaybeI64`] to an `I64` in a scalar context, aborting if the element was /// absent (matching the VM's halt on `nil` arithmetic). Emitted when a dynamic @@ -383,6 +470,34 @@ pub enum Inst { /// `dst = {src, 1}` — wraps a plain scalar into a present `Maybe` carrier /// (the dual of [`Inst::MaybeValue`] for mixed phi edges). MaybeWrap { dst: ValueId, src: ValueId, maybe_ty: Ty }, + /// One half of a two-register carrier (`Dyn`, the four `Maybe`s), as a raw + /// `I64` word — `half` selects which. + /// + /// What such a value needs to cross a boundary that carries *machine + /// words*: it occupies two registers, so it travels as two and is put back + /// together by [`Inst::CarrierFromParts`]. The `try`-region trampoline is + /// that boundary, and without this a `try` inside `for x in ` did + /// not compile at all — a list's loop variable is a carrier — and there was + /// no correct way to make it one word. Unwrapping a `Maybe` aborts when + /// absent, and the body may only have asked `x ?? default`. + /// + /// Raw halves rather than the typed accessors (`MaybeValue`/`MaybePresent` + /// and the `dyn.as_*` family): those *interpret*, and what has to survive a + /// round trip is the bits. Deliberately blind to which half means what — + /// taking both and putting them back in the same order cannot get the + /// convention wrong, and there is no convention to keep in step. + CarrierWord { + dst: ValueId, + src: ValueId, + half: CarrierHalf, + }, + /// The inverse: rebuild a `ty`-typed carrier from its two words. + CarrierFromParts { + dst: ValueId, + lo: ValueId, + hi: ValueId, + ty: Ty, + }, /// `dst = select cond, then_v, else_v` over values of type `ty`. Select { dst: ValueId, @@ -521,11 +636,15 @@ pub enum MirError { UnknownAbi { module: &'static str, name: &'static str }, /// A `GlobalGet`/`GlobalSet` names a mutable global outside the module table. UnknownGlobal { func: FuncId, gvar: u32 }, - /// The module/function references a missing entry block/function. + /// The module references no entry, or a function has no entry block. MissingEntry, + /// An instruction references a function absent from the MIR module. + UnknownFunction { func: FuncId, callee: FuncId }, /// A call or branch passes a different number of arguments than the /// callee's parameters / the target block's params expect. ArityMismatch { func: FuncId }, + /// The entry function returns a type its top-level printer cannot consume. + UnsupportedEntryReturn { ty: Ty }, } /// Validates structural well-formedness: single-assignment, define-before-use @@ -535,8 +654,11 @@ pub enum MirError { /// topological-ish order for the simple straightline/if shapes we lower first); /// it is a cheap guard that catches lowering bugs long before LLVM would. pub fn validate(module: &MirModule) -> Result<(), MirError> { - if module.function(module.entry).is_none() { + let Some(entry) = module.function(module.entry) else { return Err(MirError::MissingEntry); + }; + if matches!(entry.ret, Ty::Cell) { + return Err(MirError::UnsupportedEntryReturn { ty: entry.ret }); } for func in &module.functions { if func.block(func.entry).is_none() { @@ -590,9 +712,12 @@ pub fn validate(module: &MirModule) -> Result<(), MirError> { gvar: *gvar, }); } - if let Inst::CallFn { func: callee, args, .. } = inst { + if let Inst::CallFn { func: callee, args, .. } | Inst::TryRegionCall { func: callee, args, .. } = inst { let Some(target) = module.function(*callee) else { - return Err(MirError::MissingEntry); + return Err(MirError::UnknownFunction { + func: func.id, + callee: *callee, + }); }; if args.len() != target.params.len() { return Err(MirError::ArityMismatch { func: func.id }); @@ -612,14 +737,14 @@ pub fn validate(module: &MirModule) -> Result<(), MirError> { return Err(MirError::ArityMismatch { func: func.id }); } } - if let Inst::TraitDispatch { arms, .. } = inst { + if let Inst::TraitDispatch { args, arms, .. } = inst { for (_, callee) in arms { let Some(target) = module.function(*callee) else { return Err(MirError::MissingEntry); }; - // One boxed `self` parameter — the rendered arm call - // shape. - if target.params.len() != 1 { + // Boxed `self` plus the method's own boxed arguments — + // the rendered arm call shape, the same for every arm. + if target.params.len() != 1 + args.len() { return Err(MirError::ArityMismatch { func: func.id }); } } @@ -718,7 +843,9 @@ pub fn render(module: &MirModule) -> String { out } -fn ty_name(ty: Ty) -> &'static str { +/// A type's name, for a diagnostic. Public because the lowering's own errors +/// name types too, and one spelling beats two. +pub fn ty_name(ty: Ty) -> &'static str { match ty { Ty::I64 => "i64", Ty::F64 => "f64", @@ -726,6 +853,7 @@ fn ty_name(ty: Ty) -> &'static str { Ty::Str => "str", Ty::Nil => "nil", Ty::ListI64 => "list", + Ty::SliceI64 => "slice", Ty::ListF64 => "list", Ty::ListStr => "list", Ty::MapStrI64 => "map", @@ -741,6 +869,7 @@ fn ty_name(ty: Ty) -> &'static str { Ty::ListDyn => "list", Ty::MapStrDyn => "map", Ty::Set => "set", + Ty::Bytes => "bytes", Ty::Cell => "cell", } } @@ -798,6 +927,7 @@ fn render_inst(inst: &Inst) -> String { v(*rhs) ) } + Inst::BitsToFloat { dst, src } => format!("{} = bitcast.f64 {}", v(*dst), v(*src)), Inst::IntToFloat { dst, src } => format!("{} = sitofp {}", v(*dst), v(*src)), Inst::FloatToInt { dst, src } => format!("{} = fptosi {}", v(*dst), v(*src)), Inst::ZextBool { dst, src } => format!("{} = zext.bool {}", v(*dst), v(*src)), @@ -809,6 +939,7 @@ fn render_inst(inst: &Inst) -> String { if *signed { "signed" } else { "unsigned" } ), Inst::Not { dst, src } => format!("{} = not {}", v(*dst), v(*src)), + Inst::FloatNeg { dst, src } => format!("{} = fneg {}", v(*dst), v(*src)), Inst::BoolAnd { dst, lhs, rhs } => format!("{} = bool.and {}, {}", v(*dst), v(*lhs), v(*rhs)), Inst::MaybePresent { dst, src, maybe_ty } => { format!("{} = maybe.present<{}> {}", v(*dst), ty_name(*maybe_ty), v(*src)) @@ -821,6 +952,11 @@ fn render_inst(inst: &Inst) -> String { } } Inst::SymbolAddr { dst, symbol } => format!("{} = symbol.addr {symbol}", v(*dst)), + Inst::VolatileLoad { dst, addr, bits } => format!("{} = volatile.load.u{bits} [{}]", v(*dst), v(*addr)), + Inst::VolatileStore { addr, value, bits } => format!("volatile.store.u{bits} [{}], {}", v(*addr), v(*value)), + Inst::TryRegionCall { dst, func, args: a } => { + format!("{} = try.region f{}({})", v(*dst), func.0, args(a)) + } Inst::CallIndirect { dst, callee, args: a } => { let call = format!("call.indirect v{}({})", callee.0, args(a)); match dst { @@ -862,18 +998,33 @@ fn render_inst(inst: &Inst) -> String { None => call, } } - Inst::TryCall { dst, func, args: a } => format!("{} = try.call f{}({})", v(*dst), func.0, args(a)), - Inst::TraitDispatch { dst, self_arg, arms } => { + Inst::TraitDispatch { + dst, + self_arg, + args: a, + arms, + } => { let arm_list = arms .iter() .map(|(tid, f)| format!("{tid} => f{}", f.0)) .collect::>() .join(", "); - format!("{} = trait.dispatch {}, [{arm_list}]", v(*dst), v(*self_arg)) + format!( + "{} = trait.dispatch {}({}), [{arm_list}]", + v(*dst), + v(*self_arg), + args(a) + ) } Inst::ListGetMaybe { dst, handle, index } => { format!("{} = list.i64.get_maybe {}, {}", v(*dst), v(*handle), v(*index)) } + Inst::SliceGetMaybe { dst, handle, index } => { + format!("{} = slice.i64.get_maybe {}, {}", v(*dst), v(*handle), v(*index)) + } + Inst::StrByteAtMaybe { dst, handle, index } => { + format!("{} = str.byte_at_maybe {}, {}", v(*dst), v(*handle), v(*index)) + } Inst::UnwrapMaybeI64 { dst, src } => format!("{} = maybe.i64.unwrap {}", v(*dst), v(*src)), Inst::ListGetMaybeF64 { dst, handle, index } => { format!("{} = list.f64.get_maybe {}, {}", v(*dst), v(*handle), v(*index)) @@ -908,6 +1059,12 @@ fn render_inst(inst: &Inst) -> String { Inst::MaybeValue { dst, src, maybe_ty } => { format!("{} = maybe.value<{}> {}", v(*dst), ty_name(*maybe_ty), v(*src)) } + Inst::CarrierWord { dst, src, half } => { + format!("{} = carrier.{} {}", v(*dst), half.name(), v(*src)) + } + Inst::CarrierFromParts { dst, lo, hi, ty } => { + format!("{} = carrier.parts.{} {}, {}", v(*dst), ty_name(*ty), v(*lo), v(*hi)) + } Inst::MaybeWrap { dst, src, maybe_ty } => { format!("{} = maybe.wrap<{}> {}", v(*dst), ty_name(*maybe_ty), v(*src)) } @@ -948,14 +1105,18 @@ pub(crate) fn inst_def(inst: &Inst) -> Option { | Inst::IntBin { dst, .. } | Inst::FloatBin { dst, .. } | Inst::Cmp { dst, .. } + | Inst::BitsToFloat { dst, .. } | Inst::IntToFloat { dst, .. } | Inst::FloatToInt { dst, .. } | Inst::ZextBool { dst, .. } | Inst::IntTruncate { dst, .. } | Inst::Not { dst, .. } + | Inst::FloatNeg { dst, .. } | Inst::BoolAnd { dst, .. } | Inst::MaybePresent { dst, .. } | Inst::ListGetMaybe { dst, .. } + | Inst::SliceGetMaybe { dst, .. } + | Inst::StrByteAtMaybe { dst, .. } | Inst::UnwrapMaybeI64 { dst, .. } | Inst::ListGetMaybeF64 { dst, .. } | Inst::UnwrapMaybeF64 { dst, .. } @@ -967,15 +1128,17 @@ pub(crate) fn inst_def(inst: &Inst) -> Option { | Inst::MapGetMaybeI64F64 { dst, .. } | Inst::MaybeValue { dst, .. } | Inst::MaybeWrap { dst, .. } + | Inst::CarrierWord { dst, .. } + | Inst::CarrierFromParts { dst, .. } | Inst::Select { dst, .. } | Inst::GlobalGet { dst, .. } => Some(*dst), - Inst::SymbolAddr { dst, .. } => Some(*dst), + Inst::SymbolAddr { dst, .. } | Inst::TryRegionCall { dst, .. } | Inst::VolatileLoad { dst, .. } => Some(*dst), Inst::CallIndirect { dst, .. } => *dst, Inst::Call { dst, .. } | Inst::CallFn { dst, .. } | Inst::CallExtern { dst, .. } | Inst::CallVm { dst, .. } => { *dst } - Inst::PrintStr { .. } | Inst::GlobalSet { .. } => None, - Inst::TryCall { dst, .. } | Inst::TraitDispatch { dst, .. } => Some(*dst), + Inst::PrintStr { .. } | Inst::GlobalSet { .. } | Inst::VolatileStore { .. } => None, + Inst::TraitDispatch { dst, .. } => Some(*dst), } } @@ -988,11 +1151,13 @@ fn inst_uses(inst: &Inst) -> Vec { | Inst::BoolAnd { lhs, rhs, .. } => { vec![*lhs, *rhs] } - Inst::IntToFloat { src, .. } + Inst::BitsToFloat { src, .. } + | Inst::IntToFloat { src, .. } | Inst::FloatToInt { src, .. } | Inst::ZextBool { src, .. } | Inst::IntTruncate { src, .. } | Inst::Not { src, .. } + | Inst::FloatNeg { src, .. } | Inst::MaybePresent { src, .. } | Inst::UnwrapMaybeI64 { src, .. } | Inst::UnwrapMaybeF64 { src, .. } @@ -1002,6 +1167,8 @@ fn inst_uses(inst: &Inst) -> Vec { vec![*src] } Inst::ListGetMaybe { handle, index, .. } + | Inst::SliceGetMaybe { handle, index, .. } + | Inst::StrByteAtMaybe { handle, index, .. } | Inst::ListGetMaybeF64 { handle, index, .. } | Inst::ListGetMaybeStr { handle, index, .. } => { vec![*handle, *index] @@ -1013,6 +1180,9 @@ fn inst_uses(inst: &Inst) -> Vec { vec![*handle, *key] } Inst::SymbolAddr { .. } => vec![], + Inst::VolatileLoad { addr, .. } => vec![*addr], + Inst::VolatileStore { addr, value, .. } => vec![*addr, *value], + Inst::TryRegionCall { args, .. } => args.clone(), Inst::CallIndirect { callee, args, .. } => { let mut values = vec![*callee]; values.extend(args.iter().copied()); @@ -1021,9 +1191,14 @@ fn inst_uses(inst: &Inst) -> Vec { Inst::Call { args, .. } | Inst::CallFn { args, .. } | Inst::CallExtern { args, .. } - | Inst::CallVm { args, .. } - | Inst::TryCall { args, .. } => args.clone(), - Inst::TraitDispatch { self_arg, .. } => vec![*self_arg], + | Inst::CallVm { args, .. } => args.clone(), + Inst::CarrierWord { src, .. } => vec![*src], + Inst::CarrierFromParts { lo, hi, .. } => vec![*lo, *hi], + Inst::TraitDispatch { self_arg, args, .. } => { + let mut operands = vec![*self_arg]; + operands.extend(args.iter().copied()); + operands + } Inst::PrintStr { value, .. } => vec![*value], Inst::Select { cond, then_v, else_v, .. @@ -1153,6 +1328,30 @@ mod tests { ); } + #[test] + fn unsupported_entry_return_is_rejected_before_codegen() { + let mut m = div_module(); + m.functions[0].ret = Ty::Cell; + assert_eq!(validate(&m), Err(MirError::UnsupportedEntryReturn { ty: Ty::Cell })); + } + + #[test] + fn unknown_try_region_body_is_rejected() { + let mut m = div_module(); + m.functions[0].blocks[0].insts.push(Inst::TryRegionCall { + dst: ValueId(3), + func: FuncId(7), + args: vec![], + }); + assert_eq!( + validate(&m), + Err(MirError::UnknownFunction { + func: FuncId(0), + callee: FuncId(7), + }) + ); + } + #[test] fn div_lowers_to_a_known_abi_helper() { // The MIR div op is expected to resolve to the guarded lkrt helper. diff --git a/aot/mir/src/opt.rs b/aot/mir/src/opt.rs index aa185bed..1054abff 100644 --- a/aot/mir/src/opt.rs +++ b/aot/mir/src/opt.rs @@ -585,15 +585,24 @@ fn is_removable(inst: &Inst) -> bool { // follows the ordinary rule and may be dropped when nothing reads it. Inst::CallIndirect { .. } => false, Inst::SymbolAddr { .. } => true, + // A device access is the effect. A read whose result nothing uses is + // still a read — of a UART's receive register it is what empties the + // FIFO — so neither of these is ever dead. + Inst::VolatileLoad { .. } | Inst::VolatileStore { .. } => false, + // Running the body is the point; its outcome flag being unread does not + // make the call dead. + Inst::TryRegionCall { .. } => false, Inst::IntBin { op, .. } => !matches!(op, IntBinOp::Div | IntBinOp::Mod), Inst::FloatBin { op, .. } => !matches!(op, FloatBinOp::Div | FloatBinOp::Mod), Inst::Const { .. } | Inst::Cmp { .. } + | Inst::BitsToFloat { .. } | Inst::IntToFloat { .. } | Inst::FloatToInt { .. } | Inst::ZextBool { .. } | Inst::IntTruncate { .. } | Inst::Not { .. } + | Inst::FloatNeg { .. } | Inst::BoolAnd { .. } | Inst::MaybePresent { .. } | Inst::MaybeValue { .. } @@ -602,6 +611,8 @@ fn is_removable(inst: &Inst) -> bool { // Container reads with `Maybe` semantics never abort (a missing // element is `present = 0`), so a dead read is genuinely dead. | Inst::ListGetMaybe { .. } + | Inst::SliceGetMaybe { .. } + | Inst::StrByteAtMaybe { .. } | Inst::ListGetMaybeF64 { .. } | Inst::ListGetMaybeStr { .. } | Inst::MapGetMaybe { .. } @@ -612,11 +623,13 @@ fn is_removable(inst: &Inst) -> bool { // A `Maybe` unwrap aborts when the element was absent — dropping it // would turn the VM's halt into a silent continue. Inst::UnwrapMaybeI64 { .. } | Inst::UnwrapMaybeF64 { .. } | Inst::UnwrapMaybeStr { .. } => false, + // Pure: they only take apart and put back together values they were + // handed. + Inst::CarrierWord { .. } | Inst::CarrierFromParts { .. } => true, // Calls stay even when `Pure`: an unused aborting call (`socket.addr` // with a bad port) is observable precisely by aborting. Inst::Call { .. } | Inst::CallFn { .. } - | Inst::TryCall { .. } | Inst::TraitDispatch { .. } | Inst::CallVm { .. } | Inst::PrintStr { .. } @@ -630,6 +643,9 @@ fn uses_mut(inst: &mut Inst) -> Vec<&mut ValueId> { Inst::Const { .. } | Inst::GlobalGet { .. } => vec![], Inst::CallExtern { args, .. } => args.iter_mut().collect(), Inst::SymbolAddr { .. } => vec![], + Inst::VolatileLoad { addr, .. } => vec![addr], + Inst::VolatileStore { addr, value, .. } => vec![addr, value], + Inst::TryRegionCall { args, .. } => args.iter_mut().collect(), Inst::CallIndirect { callee, args, .. } => { let mut values: Vec<&mut ValueId> = vec![callee]; values.extend(args.iter_mut()); @@ -639,11 +655,13 @@ fn uses_mut(inst: &mut Inst) -> Vec<&mut ValueId> { | Inst::FloatBin { lhs, rhs, .. } | Inst::Cmp { lhs, rhs, .. } | Inst::BoolAnd { lhs, rhs, .. } => vec![lhs, rhs], - Inst::IntToFloat { src, .. } + Inst::BitsToFloat { src, .. } + | Inst::IntToFloat { src, .. } | Inst::FloatToInt { src, .. } | Inst::ZextBool { src, .. } | Inst::IntTruncate { src, .. } | Inst::Not { src, .. } + | Inst::FloatNeg { src, .. } | Inst::MaybePresent { src, .. } | Inst::MaybeValue { src, .. } | Inst::MaybeWrap { src, .. } @@ -651,12 +669,13 @@ fn uses_mut(inst: &mut Inst) -> Vec<&mut ValueId> { | Inst::UnwrapMaybeF64 { src, .. } | Inst::UnwrapMaybeStr { src, .. } | Inst::GlobalSet { src, .. } => vec![src], - Inst::Call { args, .. } - | Inst::CallFn { args, .. } - | Inst::TryCall { args, .. } - | Inst::CallVm { args, .. } => args.iter_mut().collect(), + Inst::Call { args, .. } | Inst::CallFn { args, .. } | Inst::CallVm { args, .. } => args.iter_mut().collect(), + Inst::CarrierWord { src, .. } => vec![src], + Inst::CarrierFromParts { lo, hi, .. } => vec![lo, hi], Inst::TraitDispatch { self_arg, .. } => vec![self_arg], Inst::ListGetMaybe { handle, index, .. } + | Inst::SliceGetMaybe { handle, index, .. } + | Inst::StrByteAtMaybe { handle, index, .. } | Inst::ListGetMaybeF64 { handle, index, .. } | Inst::ListGetMaybeStr { handle, index, .. } => vec![handle, index], Inst::MapGetMaybe { handle, key, .. } diff --git a/aot/mir/src/opt/tests.rs b/aot/mir/src/opt/tests.rs index d1e0af89..66812a0c 100644 --- a/aot/mir/src/opt/tests.rs +++ b/aot/mir/src/opt/tests.rs @@ -383,6 +383,41 @@ fn scope_drop_releases_a_loop_local_container() { )); } +#[test] +fn scope_drop_keeps_a_list_that_a_window_still_points_at() { + // `let xs = []; xs.push(1); let w = xs.slice(0, 1); w.len()` inside a loop + // body. Both handles look block-local, and the window may indeed be + // released — but the list may not: the window addresses it on every read, + // so freeing it here is a use-after-free that would only misbehave once the + // allocator reused the block. + // + // The one thing standing between this program and that bug is + // `slice_h.i64_new` being declared `ConstructsView` rather than + // `Constructs`; this test is what notices if it ever changes back. + let mut func = loop_func( + vec![ + call(10, "list_h", "i64_new", &[]), + konst(11, 1), + Inst::Call { + dst: None, + callee: AbiRef::new("list_h", "i64_push"), + args: vec![ValueId(10), ValueId(11)], + }, + konst(12, 0), + call(13, "slice_h", "i64_new", &[10, 12, 11]), + call(14, "slice_h", "i64_len", &[13]), + ], + ValueId(0), + Vec::new(), + ); + assert_eq!(scope_drop_block_locals(&mut func), 1); + assert_eq!( + released_handles(&func), + vec![ValueId(13)], + "the window is releasable; the list it windows is not" + ); +} + #[test] fn scope_drop_skips_a_handle_escaping_through_the_terminator() { // The handle is passed as a block argument to the next iteration — it diff --git a/api-cabi/Cargo.toml b/api-cabi/Cargo.toml new file mode 100644 index 00000000..d535f52e --- /dev/null +++ b/api-cabi/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "lk-api-cabi" +description = "The C-ABI static library form of lk-api, for the AOT linker" +version = "0.1.3" +edition = "2024" +authors = ["lollipopkit "] +license = "Apache-2.0" +publish = false + +[lib] +name = "lk_api_cabi" +# staticlib only, and that is the whole point of the crate — the same split +# `lkrt-cabi` made, for a different reason. +# +# `crate-type` is unconditional: while `lk-api` declared `["lib", "staticlib"]`, +# *every* build of it emitted the archive too, because `lk-cli` depends on the +# lib. That archive is the entire VM plus stdlib with debuginfo — 172MB — and +# only a Tier 1 hybrid link ever reads it. Here nothing depends on this crate, +# so it is built only when named. +crate-type = ["staticlib"] + +[dependencies] +# `ffi` is the reason the archive exists: without it the C entry points +# (`lk_vm_*`, `lk_hybrid_*`) are not compiled at all. +lk-api = { path = "../api", features = ["ffi"] } diff --git a/api-cabi/src/lib.rs b/api-cabi/src/lib.rs new file mode 100644 index 00000000..1cbbb93b --- /dev/null +++ b/api-cabi/src/lib.rs @@ -0,0 +1,16 @@ +//! `lk-api` packaged as a C-ABI static library. +//! +//! The AOT driver links this archive into a Tier 0 VM bundle and a Tier 1 +//! hybrid binary — both need the embedded interpreter and the `lk_hybrid_*` +//! bridge. It is a separate crate because `crate-type` cannot be conditional: +//! `lk-api` also declaring `staticlib` meant that building `lk-cli`, or running +//! `cargo build --workspace`, emitted a 172MB archive that only the linker path +//! ever opens. +//! +//! Nothing depends on this crate, so it is built only when named +//! (`cargo build -p lk-api-cabi --release`), which is what +//! `ensure_lk_api_staticlib` does. + +// The re-export is what pulls `lk-api`'s `#[no_mangle]` symbols into the +// archive. Without a reference the linker has no reason to keep them. +pub use lk_api::*; diff --git a/api/Cargo.toml b/api/Cargo.toml index 875249b9..1de2867f 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -5,7 +5,10 @@ edition = "2024" [lib] name = "lk_api" -crate-type = ["lib", "staticlib"] +# `lib` only — the C-ABI archive is `lk-api-cabi`'s job. Declaring `staticlib` +# here made every build that reached `lk-api` (i.e. every build of `lk-cli`) +# also emit a 172MB archive that only the AOT linker path opens. +crate-type = ["lib"] [features] ffi = [] diff --git a/api/include/lk.h b/api/include/lk.h index 050cf994..60dce4c7 100644 --- a/api/include/lk.h +++ b/api/include/lk.h @@ -1,7 +1,9 @@ /* lk.h — C ABI for embedding the LK virtual machine (lk-api `ffi` feature). * - * Build lk-api with `--features ffi` and link the produced static/dynamic - * library. Each `LkVm` is an isolated instance (no shared global state). + * Build `lk-api-cabi` (which turns on lk-api's `ffi` feature) and link the + * produced `liblk_api_cabi.a`. The archive is a separate crate so that an + * ordinary workspace build does not emit it. + * Each `LkVm` is an isolated instance (no shared global state). * A cbindgen config could regenerate this; kept hand-written as the surface * is tiny and stable. */ @@ -26,6 +28,11 @@ char *lk_vm_eval(LkVm *vm, const char *src); /* Free a VM created by lk_vm_new. */ void lk_vm_free(LkVm *vm); +/* The message behind the last lk_vm_eval that returned NULL, or NULL if the last + * call succeeded. Borrowed from the VM: valid until the next lk_vm_eval or + * lk_vm_free, and must NOT be passed to lk_string_free. */ +const char *lk_vm_last_error(LkVm *vm); + /* Free a string returned by lk_vm_eval. */ void lk_string_free(char *s); diff --git a/api/src/lib.rs b/api/src/lib.rs index 2ed2ae0b..3c9c5e99 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -3,7 +3,7 @@ //! A minimal, safe surface for embedding the LK VM in a Rust host. Each [`Vm`] //! is an **isolated instance**: it owns its own `VmContext` (heap, globals, //! async runtime handle), so multiple VMs are fully independent with no shared -//! global state — this is exactly what the M0 "去全局状态" work enabled. Add a +//! global state — this is exactly what the M0 global-state removal enabled. Add a //! fuel budget to sandbox execution (the instruction-budget knob of M2.6). use lk_core::vm::ModuleResolver; @@ -32,6 +32,17 @@ pub struct Vm { ctx: Option, fuel: Option, heap_limit: Option, + /// The last `eval` failure, kept so the C ABI can *say* what went wrong. + /// + /// `lk_vm_eval` answers NULL on error and used to drop the message on the + /// floor, so every embedder — including this project's own Tier 0 bundle — + /// could only print "execution failed". A missing import, a type error and + /// a divide by zero were the same sentence. + last_error: Option, + /// NUL-terminated copy handed to C by `lk_vm_last_error`; owned here so the + /// caller needs no free. + #[cfg(feature = "ffi")] + last_error_c: Option, } impl Vm { @@ -45,6 +56,9 @@ impl Vm { ctx: None, fuel: None, heap_limit: None, + last_error: None, + #[cfg(feature = "ffi")] + last_error_c: None, } } @@ -65,6 +79,9 @@ impl Vm { ctx: None, fuel: None, heap_limit: None, + last_error: None, + #[cfg(feature = "ffi")] + last_error_c: None, } } @@ -359,7 +376,6 @@ fn map_key_to_string(key: &lk_core::val::RuntimeMapKey) -> String { RuntimeMapKey::Int(value) => value.to_string(), RuntimeMapKey::ShortStr(value) => value.as_str().to_string(), RuntimeMapKey::String(value) => value.to_string(), - RuntimeMapKey::Obj(handle) => format!("", handle.index()), } } @@ -676,7 +692,7 @@ mod tests { #[test] fn instances_are_isolated() { - // Two independent VMs share no global state (M0 去全局状态). + // Two independent VMs share no global state (M0 global-state removal). let mut a = Vm::new(); let mut b = Vm::new(); assert_eq!(a.eval("let x = 10; return x;").unwrap(), "10"); @@ -841,13 +857,48 @@ pub mod ffi { return core::ptr::null_mut(); }; match vm.eval(src) { - Ok(out) => CString::new(out) - .map(CString::into_raw) - .unwrap_or(core::ptr::null_mut()), - Err(_) => core::ptr::null_mut(), + Ok(out) => { + vm.last_error = None; + CString::new(out) + .map(CString::into_raw) + .unwrap_or(core::ptr::null_mut()) + } + Err(error) => { + // Kept rather than dropped: NULL alone made "missing import", + // "type error" and "divide by zero" the same answer, and the + // one embedder this project ships (the Tier 0 bundle) could + // only print "lk: execution failed". + vm.last_error = Some(format!("{error:#}")); + core::ptr::null_mut() + } } } + /// The message behind the last [`lk_vm_eval`] that answered NULL, or NULL if + /// the last call succeeded. Borrowed from the VM — valid until the next + /// `lk_vm_eval` or [`lk_vm_free`], and **not** to be passed to + /// [`lk_string_free`]. + /// + /// # Safety + /// `vm` must come from [`lk_vm_new`] and not be freed. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn lk_vm_last_error(vm: *mut Vm) -> *const c_char { + if vm.is_null() { + return core::ptr::null(); + } + let vm = unsafe { &mut *vm }; + let Some(message) = vm.last_error.as_deref() else { + return core::ptr::null(); + }; + // Re-encoded into a NUL-terminated buffer the VM owns, so the pointer + // stays valid for the caller without a free. + let Ok(encoded) = CString::new(message) else { + return core::ptr::null(); + }; + vm.last_error_c = Some(encoded); + vm.last_error_c.as_ref().map_or(core::ptr::null(), |s| s.as_ptr()) + } + /// Free a VM created by [`lk_vm_new`]. /// /// # Safety @@ -1086,12 +1137,16 @@ pub mod ffi { type ListDynPush = unsafe extern "C" fn(*mut c_void, LkHybridDyn); type MapStrDynNew = unsafe extern "C" fn() -> *mut c_void; type MapStrDynSet = unsafe extern "C" fn(*mut c_void, *const c_char, LkHybridDyn); + /// Marks a field map as an instance of the struct of this name. See + /// `marshal_object`. + type ObjMarkByName = unsafe extern "C" fn(*mut c_void, *const c_char) -> i64; type RaiseDyn = unsafe extern "C" fn(LkHybridDyn); static RT_LIST_DYN_NEW: AtomicUsize = AtomicUsize::new(0); static RT_LIST_DYN_PUSH: AtomicUsize = AtomicUsize::new(0); static RT_MAP_STR_DYN_NEW: AtomicUsize = AtomicUsize::new(0); static RT_MAP_STR_DYN_SET: AtomicUsize = AtomicUsize::new(0); + static RT_OBJ_MARK_BY_NAME: AtomicUsize = AtomicUsize::new(0); static RT_RAISE_DYN: AtomicUsize = AtomicUsize::new(0); /// Register the lkrt runtime table (hybrid wrapper C constructor): @@ -1104,12 +1159,14 @@ pub mod ffi { list_dyn_push: ListDynPush, map_str_dyn_new: MapStrDynNew, map_str_dyn_set: MapStrDynSet, + obj_mark_by_name: ObjMarkByName, raise_dyn: RaiseDyn, ) { RT_LIST_DYN_NEW.store(list_dyn_new as usize, Ordering::Release); RT_LIST_DYN_PUSH.store(list_dyn_push as usize, Ordering::Release); RT_MAP_STR_DYN_NEW.store(map_str_dyn_new as usize, Ordering::Release); RT_MAP_STR_DYN_SET.store(map_str_dyn_set as usize, Ordering::Release); + RT_OBJ_MARK_BY_NAME.store(obj_mark_by_name as usize, Ordering::Release); RT_RAISE_DYN.store(raise_dyn as usize, Ordering::Release); } @@ -1118,6 +1175,7 @@ pub mod ffi { list_dyn_push: ListDynPush, map_str_dyn_new: MapStrDynNew, map_str_dyn_set: MapStrDynSet, + obj_mark_by_name: ObjMarkByName, } fn hybrid_rt() -> HybridRt { @@ -1125,7 +1183,13 @@ pub mod ffi { let list_dyn_push = RT_LIST_DYN_PUSH.load(Ordering::Acquire); let map_str_dyn_new = RT_MAP_STR_DYN_NEW.load(Ordering::Acquire); let map_str_dyn_set = RT_MAP_STR_DYN_SET.load(Ordering::Acquire); - if list_dyn_new == 0 || list_dyn_push == 0 || map_str_dyn_new == 0 || map_str_dyn_set == 0 { + let obj_mark_by_name = RT_OBJ_MARK_BY_NAME.load(Ordering::Acquire); + if list_dyn_new == 0 + || list_dyn_push == 0 + || map_str_dyn_new == 0 + || map_str_dyn_set == 0 + || obj_mark_by_name == 0 + { hybrid_die(format_args!( "container return needs the lkrt constructor table (lk_hybrid_register_rt)" )); @@ -1138,6 +1202,7 @@ pub mod ffi { list_dyn_push: core::mem::transmute::(list_dyn_push), map_str_dyn_new: core::mem::transmute::(map_str_dyn_new), map_str_dyn_set: core::mem::transmute::(map_str_dyn_set), + obj_mark_by_name: core::mem::transmute::(obj_mark_by_name), } } } @@ -1205,6 +1270,7 @@ pub mod ffi { Some(HeapValue::String(value)) => leaked_c_string(value.as_ref()), Some(HeapValue::List(list)) => marshal_list(list, state, depth), Some(HeapValue::Map(map)) => marshal_map(map, state, depth), + Some(HeapValue::Object(object)) => marshal_object(object, state, depth), Some(other) => hybrid_die(format_args!( "bridged return kind not yet marshalable: {}", other.type_name() @@ -1219,22 +1285,33 @@ pub mod ffi { state: &lk_core::vm::RuntimeModuleState, depth: usize, ) -> LkHybridDyn { - // A typed string list displays *quoted* in the VM while `ListDyn` - // displays bare (the Mixed-list quirk) — converting would silently - // change program output, so it stays unmarshalable for now. - if matches!(list, lk_core::val::TypedList::String(_)) { - hybrid_die(format_args!( - "bridged return kind not yet marshalable: List (quoted typed display)" - )); - } + // A typed string list used to be refused here, on the grounds that it + // displays *quoted* in the VM while a `ListDyn` displays bare, so + // converting would change the program's output. Both display quoted + // now — `println(["a", "b"])` and the same list typed `List` agree + // on either engine — and the branch below already knew how to convert + // one, which the refusal above it made dead code. let rt = hybrid_rt(); // SAFETY: the constructor table points at lkrt's no-mangle builders // (registered by the wrapper); handles stay arena-owned. unsafe { let handle = (rt.list_dyn_new)(); - for item in list.collect_owned() { - let element = marshal_value(item, state, depth + 1); - (rt.list_dyn_push)(handle, element); + // A `TypedList::String` element past `ShortStr`'s inline limit + // cannot become a `RuntimeVal` without a heap allocation, and this + // side only has the heap immutably. It does not need one: marshaling + // a string only *reads* it. + if let lk_core::val::TypedList::String(values) = list { + for text in values { + (rt.list_dyn_push)(handle, leaked_c_string(text.as_ref())); + } + } else { + let items = list + .collect_owned() + .expect("only a string list can decline, and that case is handled above"); + for item in items { + let element = marshal_value(item, state, depth + 1); + (rt.list_dyn_push)(handle, element); + } } LkHybridDyn { tag: LK_HYBRID_DYN_LIST, @@ -1243,6 +1320,44 @@ pub mod ffi { } } + /// A struct instance: its fields as a `str -> Dyn` map, marked with the + /// declared name so `typeof` and `println` answer `P` rather than `Map`. + /// + /// This arm did not exist, so a bridged function that *returned* a struct + /// aborted the program — `bridged return kind not yet marshalable: P`. The + /// hybrid test had one and discarded the result, which is why nothing saw + /// it: a value that is never used is never marshalled. + /// + /// A name the native side does not know (a struct declared only inside the + /// bridged module) leaves the map unmarked, which is the same answer the + /// native side gives for a struct whose declaration is out of reach. + fn marshal_object( + object: &lk_core::val::RuntimeObject, + state: &lk_core::vm::RuntimeModuleState, + depth: usize, + ) -> LkHybridDyn { + let rt = hybrid_rt(); + // SAFETY: as in `marshal_map`. + unsafe { + let handle = (rt.map_str_dyn_new)(); + for (key, value) in object.fields_iter() { + let Ok(key_c) = std::ffi::CString::new(key) else { + hybrid_die(format_args!("bridged struct field name contains an embedded NUL")); + }; + let element = marshal_value(value, state, depth + 1); + (rt.map_str_dyn_set)(handle, key_c.into_raw(), element); + } + let Ok(name) = std::ffi::CString::new(object.type_name().as_ref()) else { + hybrid_die(format_args!("bridged struct name contains an embedded NUL")); + }; + (rt.obj_mark_by_name)(handle, name.as_ptr()); + LkHybridDyn { + tag: LK_HYBRID_DYN_MAP, + payload: handle as i64, + } + } + } + fn marshal_map(map: &lk_core::val::TypedMap, state: &lk_core::vm::RuntimeModuleState, depth: usize) -> LkHybridDyn { use lk_core::val::RuntimeMapKey; diff --git a/bare-metal-native/Cargo.lock b/bare-metal-native/Cargo.lock index 703e63fa..f7de5d91 100644 --- a/bare-metal-native/Cargo.lock +++ b/bare-metal-native/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "cc" version = "1.4.0" @@ -12,6 +27,50 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -24,6 +83,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -33,12 +102,24 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + [[package]] name = "lk-aot-abi" version = "0.1.0" @@ -54,11 +135,16 @@ dependencies = [ name = "lkrt" version = "0.1.3" dependencies = [ + "base64", "cc", + "crc32fast", "hashbrown", + "hex", "lk-aot-abi", "rustc-hash", "serde_json", + "sha1", + "sha2", "spin", ] @@ -134,6 +220,28 @@ dependencies = [ "zmij", ] +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "2.0.1" @@ -157,12 +265,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "zmij" version = "1.0.23" diff --git a/bare-metal-native/program.lk b/bare-metal-native/program.lk index fab924b3..c5d804cd 100644 --- a/bare-metal-native/program.lk +++ b/bare-metal-native/program.lk @@ -46,10 +46,14 @@ fn uart_putc(byte: Int) { // Spin while the transmit FIFO is full. This read has to happen on every // iteration — it is the canonical access a non-volatile load would let the // compiler hoist, hanging the loop. - let full = 1; + // `u32`, because that is what the register answers in and what the mask is + // applied at. Written as `let full = 1;` this was an `Int` that the loop + // then assigned a `u32` to — which the checker refuses, correctly: machine + // integers do not mix, and the width of a status flag is the register's. + let full: u32 = 1; while (full != 0) { let flags = unsafe { volatile_read_u32((UART0_BASE + REG_FR) as *mut u32) }; - full = flags & FR_TX_FULL; + full = flags & (FR_TX_FULL as u32); } unsafe { volatile_write_u32((UART0_BASE + REG_DR) as *mut u32, byte as u32); }; } @@ -115,8 +119,8 @@ for i in 0..10 { let irq = unsafe { cpu_irq_save() }; // "native LK drives the PL011: sum(fib(0..9)) = " uart_write([110, 97, 116, 105, 118, 101, 32, 76, 75, 32, 100, 114, 105, 118, 101, 115, - 32, 116, 104, 101, 32, 80, 76, 48, 49, 49, 58, 32, 115, 117, 109, 40, - 102, 105, 98, 40, 48, 46, 46, 57, 41, 41, 32, 61, 32]); + 32, 116, 104, 101, 32, 80, 76, 48, 49, 49, 58, 32, 115, 117, 109, 40, + 102, 105, 98, 40, 48, 46, 46, 57, 41, 41, 32, 61, 32]); uart_put_int(total); uart_putc(10); unsafe { cpu_irq_restore(irq); }; diff --git a/bare-metal-x86/Cargo.lock b/bare-metal-x86/Cargo.lock index a57bf27a..5714869f 100644 --- a/bare-metal-x86/Cargo.lock +++ b/bare-metal-x86/Cargo.lock @@ -132,12 +132,30 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + [[package]] name = "itoa" version = "1.0.18" @@ -175,7 +193,8 @@ version = "0.2.0" dependencies = [ "anyhow", "arcstr", - "hashbrown", + "hashbrown 0.15.5", + "indexmap", "itoa", "libm", "lk-values", @@ -198,7 +217,6 @@ dependencies = [ "lk-stdlib-hash", "lk-stdlib-iter", "lk-stdlib-math", - "lk-stdlib-slice", "lk-stdlib-string", ] @@ -274,15 +292,6 @@ dependencies = [ "lk-stdlib-common", ] -[[package]] -name = "lk-stdlib-slice" -version = "0.1.3" -dependencies = [ - "anyhow", - "lk-core", - "lk-stdlib-common", -] - [[package]] name = "lk-stdlib-string" version = "0.1.3" @@ -297,7 +306,7 @@ name = "lk-values" version = "0.1.0" dependencies = [ "arcstr", - "hashbrown", + "hashbrown 0.15.5", "serde", ] @@ -305,11 +314,18 @@ dependencies = [ name = "lkrt" version = "0.1.3" dependencies = [ + "base64", "cc", - "hashbrown", + "crc32fast", + "hashbrown 0.15.5", + "hex", + "indexmap", "lk-aot-abi", "rustc-hash", + "serde", "serde_json", + "sha1", + "sha2", "spin", ] diff --git a/bare-metal-x86/Cargo.toml b/bare-metal-x86/Cargo.toml index c53e8a3c..dfef9032 100644 --- a/bare-metal-x86/Cargo.toml +++ b/bare-metal-x86/Cargo.toml @@ -27,7 +27,7 @@ lkrt = { path = "../lkrt", default-features = false } # no_std build the Cortex-M demo uses; `stdlib/bare` supplies the module surface # that works without an OS, with this kernel's own console as the output sink. lk-core = { path = "../core", default-features = false } -lk-stdlib-bare = { path = "../stdlib/bare", default-features = false, features = ["math","string","bytes","iter","slice","hash","encoding"] } +lk-stdlib-bare = { path = "../stdlib/bare", default-features = false, features = ["math","string","bytes","iter","hash","encoding"] } [profile.release] panic = "abort" diff --git a/bare-metal-x86/README.md b/bare-metal-x86/README.md index 35c576ca..67fa8006 100644 --- a/bare-metal-x86/README.md +++ b/bare-metal-x86/README.md @@ -11,15 +11,18 @@ configuration space and draw to its framebuffer. ```bash rustup target add x86_64-unknown-none cargo build -p lk-cli --features aot # from the repo root -LK_BIN=../target/debug/lk ./run.sh +./run.sh # builds the image and boots it +python3 check_pci.py # any check builds its own image too ``` ``` -.display at pci slot 2 +half 44 +display at pci 2.0 framebuffer 0xfd000000 pixels 00001428 00ffc040 -....lkos -keys 4 last 115 +ABBA.BAB.ABA.BABA.BABA.BABAB.ABAB.ABABA. ... +keys 0 last 0 +[lk returned to the board] ``` ...and on the screen, a shell: @@ -61,6 +64,107 @@ than setting a flag someone has to remember to check. ## Tasks +The table is the program's, and so is everything that decides with it. +`drivers/tasks.lk` holds what a task *is* — three words: where its stack pointer +is while it is not running, which address space it runs in, and which ring-0 +stack an interrupt from it lands on. `program.lk` holds spawning, the starting +frame, and the switch bookkeeping: + +```lk +#[export("lk_schedule_from_interrupt")] +fn schedule_from_interrupt(rsp: Int) -> Int { + let current = task_word(TASK_TABLE_BASE + TASK_CURRENT_OFFSET); + task_set_field(TASK_TABLE_BASE, current, TASK_RSP_OFFSET, rsp); + … +} +``` + +What is left in `src/tasks.rs` is 154 lines: the register spill either side of +that call, and the software interrupt a task uses to ask for it. Those are the +one thing a language cannot say — *return on a different stack* — and the +trampoline is where it is said. + +Task stacks come from the page allocator too, so `TASK_CAPACITY` is now a +property of the table's fixed region rather than of five static arrays. + +### The one interrupt path that did not save SSE + +Found by reading, not by a check: `SAVE_TASK` — the timer's — saved fifteen +integer registers and **no** XMM registers, and it was the only interrupt path +here that did not. Every other one says why beside itself: a compiled LK handler +may clobber any XMM register under the System V ABI, LK numbers are `f64`, and +the interrupted computation may hold one. + +The timer path calls two compiled LK functions a thousand times a second, and it +is also the one that switches tasks — so a task's SSE state was not part of what +travelled with it either. Nothing had gone wrong because today's tick and +scheduler do integer work only, which is a property of the *handlers*, not of +the boundary. + +The alignment question that made this look hard dissolves once stated properly. +`sub rsp, 256` is a multiple of sixteen, so it *preserves* whatever alignment +the pushes above produced — and that alignment already works, because this path +calls compiled LK today. The device path's 264 is 256 plus the eight its nine +pushes need; neither number has to be derived from first principles, and trying +to derive them was the whole difference between "hard" and "ten minutes". + +The half that is easy to forget is the other one: a task that has never run +needs the same area reserved on its starting frame, because the board restores +from that frame before it `iretq`s and restores the SSE registers *first*, from +the lowest addresses. So `task_prepare_frame` now asks the board how many words +its save sequence leaves (`lk_task_saved_words`) rather than counting them again +in LK. That is the one number that must not drift: a frame short by a word is +not an error anything reports, it is a resume that reads its RIP out of whatever +the next slot held. + +### The ceiling this hit, and what was actually behind it + +Adding `drivers/tasks.lk` did not compile: + +``` +bundled import 'drivers/serial': function index overflow +``` + +`CallDirect` and `MakeClosure` name their target in the instruction's `b` field, +which is a byte. With the new driver, `program.lk` and its drivers came to 260 +functions. + +It turned out to be **three** limits wearing one error message, and none of them +is the one the message named. + +**The merge numbered functions as they arrived.** A dep's instructions are +already emitted by the time it renumbers them — rewriting one into two would +move every jump offset after it — so anything a dep calls directly has to land +below 256. But most functions are not called that way: of the 159 driver +functions here, **52** are, and the rest are reached by name from the importing +program, which the lowering resolves through a `u32`. So directly-called +functions are numbered first, and what is bounded is now "the importing file's +functions, plus the ones a dep calls directly" — 144 here, against 256. + +**A call past index 255 did not lower natively.** The compiler handles it +correctly: past 255 it emits `LoadFunction` + `Call` instead of `CallDirect`. +The native lowering rejected that shape, so 256 functions was a ceiling on the +native path too, by a completely different mechanism. The function value in the +register now carries the function it names, which is the same devirtualization a +capture-free closure already got. + +**Reachability did not follow `LoadFunction`.** With the call lowering fixed, +the callee turned out never to have been lowered at all: the prescan followed +`CallDirect` and `MakeClosure` and nothing else, so a function reached only the +new way was pruned, and the module then failed MIR validation with a function +that had no entry block. The edge is there now — except for the one shape that +is *not* a call, `LoadFunction` immediately followed by `SetGlobal`, which is +how the compiler publishes a top-level `fn`. Following those would mark every +declared function reachable and leave the pass nothing to prune. + +Three tests pin the three, each isolating one: a bundle past 256 with no direct +calls at all, a bundle whose dep-local indices all fit but whose *merged* ones +do not, and a single file with no bundling where the 300th function is called. +Each compares the native build against the VM, because a numbering invented +wrongly computes a different answer without failing anything. + + + Two tasks, preempted by the timer. The shell is one; the other spins a glyph in the top-right corner and never yields — the CPU is taken away from it. @@ -123,8 +227,9 @@ draws as it goes. Same arithmetic, no intermediate list. and a native build calls that symbol. ```lk -#[extern("kernel_yield")] -fn task_yield() { +#[extern("kernel_run")] +fn kernel_run(address: Int, length: Int) -> Int { + return 0 - 1; } ``` @@ -133,11 +238,27 @@ implementation — so a fallback goes there. That also makes this the one construct whose two back ends are not checked against each other: the thing being called is not in the program. -`kernel_yield` is a software interrupt (`int 0x30`) rather than a plain call. -The switch needs a complete interrupt frame on the stack, because that is what -the resume path expects to find; `int` builds one and a `call` does not. The -vector is past the PIC's remapped range, so nothing but an `int` can raise it -— there is no device to acknowledge. +**Yield used to be one of these, and is not any more.** It was +`#[extern("kernel_yield")]` calling a Rust function containing `int 0x30`, +because `int` takes its vector as an *immediate* — there is no operand to pass +one in through, so the number lived twice: once where this file installs the +gate, once inside that stub, in two languages, with nothing checking they agreed. + +The runtime answers that with 256 stubs, each `int n` and a return, so the vector +becomes an index (`lkrt/src/isr.rs`, which now holds both directions of the same +obstacle). `task_yield` is one line: + +```lk +fn task_yield() { + unsafe { cpu_raise_interrupt(VECTOR_YIELD); }; +} +``` + +A kernel that can *handle* an interrupt but not raise one can answer a syscall +and not define one. The switch needs a complete interrupt frame on the stack, +because that is what the resume path expects to find; `int` builds one and a +`call` does not. The vector is past the PIC's remapped range, so nothing but an +`int` can raise it — there is no device to acknowledge. There is no privilege boundary here to cross: LK and the kernel are one binary at ring 0. What this is, is the direction `#[export]` did not cover — the @@ -162,6 +283,24 @@ to still be background. Both halves of that check are load-bearing, and both have been seen to fail: replacing `window_put` with a direct `put_pixel` lights 35 of them. +### Shift, and what a modifier is + +A modifier key is a key like any other: the controller has no notion of one, and +reports a press and a release for `Shift` exactly as it does for `A`. What makes +it a modifier is that the *program* keeps its state instead of translating it — +which is why `drivers/keyboard.lk` only names the three scancodes and the +handler does the rest. + +Both edges matter for `Shift`, and only one for `CapsLock`: the release is what +ends a shift, while a lock that ended when you let go would be a shift key. And +they compose differently — caps lock affects letters only (a keyboard where it +turned `1` into `!` is one nobody could type on), so a letter asks "is exactly +one of them in effect" while everything else asks only about shift. + +The shifted punctuation is a table of literals rather than arithmetic. There is +no relation between `1` and `!` beyond a convention, and writing the convention +down is the honest way to say so. + ### Who gets the keyboard Tab moves the focus between the two windows. The key handler does not know what @@ -422,6 +561,378 @@ stating: show the difference. `check_disk.py` therefore makes its third assertion from *outside*, after QEMU has exited: the image file must hold what was written. +## The descriptor table, and how to tell whose it is + +`program.lk` builds the GDT the machine runs on — null, ring-0 code and data, +ring-3 code and data, and the sixteen-byte TSS descriptor — loads it with +`lgdt`, reloads CS through a far return, and points the task register at the +TSS it just built. + +The boot stub still has a table, and always will: entering long mode takes a +`lgdt` and a far jump through a 64-bit code descriptor, both before any compiled +code exists to do them. What changed is that it is now **three entries** — null, +ring-0 code, ring-0 data — and nothing else. Enough to reach the code that +builds the real one. + +That shrink is what turns the ring-3 test from a demonstration into a proof. +There is no ring-3 descriptor anywhere in the image except the one the program +writes at run time; a user task that runs at all is a user task running on the +program's table. `check_user.py` passes unchanged, which is the claim. + +The TSS is `drivers/tss.lk`, and it is one field wearing a hundred bytes of +history: `rsp0`, the stack the CPU switches to when an interrupt takes the +machine from ring 3 back to ring 0. The scheduler writes it on every switch — +two user tasks sharing one kernel stack would have the second one's interrupt +frame land on the first one's — so the board calls back into the program for +that one word. + +The selectors travel the same way. `program.lk` defines the table, so it is the +one place that knows what is at 0x18 and 0x20; `src/user.rs` asks it rather than +naming them again. Two answers to that question that disagreed would mean an +`iretq` into a segment other than the one intended, and if that segment happened +to be a ring-0 descriptor there would be no ring boundary at all. + +### The second wall, which was also the compiler's + +The first was constants; this one was declarations. Publishing a top-level `fn` +is `LoadFunction r; SetGlobal r, slot`, and the register is dead the instant the +store lands — but it used to be a fresh register every time. `program.lk` with +its drivers bundled in declares **236 functions**, out of the 256 a `u8` +register field allows, so the top level had about twenty registers left for +everything else. Adding the syscall constants used them up, and the error named +a constant. + +The fix is one register shared by every declaration, rather than a register +recycled after each. The difference is not stylistic. Recycling — handing it +back for anything to use next — is wrong here for a reason outside the bytecode +compiler entirely: **the AOT lowering tracks what a register means keyed by +`(block, register)`, with no notion of time.** A register that once held a +function value keeps that meaning, so a later `SetGlobal` from it reads as +declaration bookkeeping and gets elided — a global write silently dropped. + +That was not hypothetical. The recycling version was written first, and it made +`program.lk` stop lowering natively with "returns disagree on the value type" — +the same stale-meaning problem surfacing as a signature conflict instead. A +register that only ever holds a function value being published cannot have any +of that happen to it, because its meaning never changes. There is a test for the +shape, not just the outcome, and a TODO on the lowering. + +### The first wall, which was the compiler's + +Adding the GDT and TSS constants made `program.lk` stop compiling: + +``` +Compiler global dst register 256 exceeds u8 encoding +``` + +Registers are `u8` in the instruction encoding, so a function has 256 of them +and the top level is a function. Every global-backed top-level binding kept one +register permanently, as a *cache* of the global slot it had just been written +to — worth having, and unconditional. `program.lk` plus the drivers bundled into +it declare 256 constants between them; none of the files is anywhere near +unusual, and the error named whichever constant was added last. + +The cache now has an eviction rule, which a cache should have had. Past 128 +registers a top-level binding is only a global: reads cost a `GetGlobal` and the +register goes back. Nothing about the meaning changes — the value was already in +the global slot, which is the one place a *function* could ever see it from. + +Two details are load-bearing. The question is asked *before* the initializer is +lowered, because the register file runs out on the temporaries of the statement +after the last binding rather than on the binding itself, and a check that comes +afterwards still overflows — which is how the first attempt failed. And the +limit is half the file rather than all of it, so what is left is one statement's +working set. Below 128 nothing changes at all, which is why the benchmark +workloads (five top-level bindings) emit byte-identical code. + +## What the board still answers, and what it stopped answering + +A row of small Rust functions used to exist only to tell the program where the +linker had put something: the user section's bounds, the kernel's page +directories, each ring-3 task's entry and stack. Every one of them was a +question with an address for an answer, and `symbol_address` — which the window +manager already used for its painter table — turns out to answer it directly: + +```lk +let kernel_directories: Int = unsafe { symbol_address("__pd") }; +``` + +It works on a *data* symbol as well as a function, which is the whole point: +"where did the linker put this" is a question a systems language can ask. The +two ring-3 task stacks moved from `static mut [u8; N]` in Rust into linker +reservations for the same reason — a static array and a reserved range are the +same thing, and only one of them has a name every language can say. + +Moving them tightened something, too. The task stacks now sit *outside* +`__user_start..__user_end`, because each task maps its own at +`USER_STACK_VIRTUAL` in its own address space and needs no U bit in the kernel's +tables. That range is what `write(ptr, len)` checks against, so one ring-3 task +can no longer hand the kernel a pointer into the other one's stack. + +`lk_enter_user` takes all four numbers now — what runs, on which stack, through +which two descriptors — instead of asking the program back for the selectors. +What the board contributes is the one thing that cannot be said in LK: `iretq`, +which is the only way *into* ring 3, because no instruction lowers privilege +directly. + +### A stack task 0 never had + +Task 0 pre-exists the table — it is the one already running when the first +interrupt lands — so nothing ever published a ring-0 stack for it, and a switch +*back* to it left `rsp0` pointing at whichever task ran last. The `user` command +enters ring 3 from task 0, and an interrupt during that excursion would have +pushed its frame onto another task's kernel stack. + +Now the table's slot 0 carries the boot ring-0 stack like any other, so every +switch sets `rsp0` to a stack that belongs to the task being switched to. Found +by asking what `lk_boot_kernel_stack` was still for once every other accessor +had gone. + +## Ring 3 + +Everything else here runs at ring 0, where a wrong address is a fault and a +right one is whatever the hardware does. That was fine while all the code was +the kernel's own — and stopped being fine the moment the kernel started running +*programs*, because "the program cannot touch the framebuffer" was a fact about +the program. + +``` +>user +ring3 +USER +!! exception #PF page fault vector=…e error=…7 rip=…10062e cr2=0000000000300000 +``` + +`USER` is printed a byte at a time by ring-3 code through `int 0x80`, the only +gate with DPL 3. What follows is the same program writing to `0x300000` — the +shared page every interrupt handler uses — and the CPU refusing, with an error +code whose bit 2 says the access came from ring 3. + +Three structures had to exist first, and two of them fail as a triple fault +rather than an error when they do not: + +- a **TSS**, because the CPU needs a ring-0 stack to switch to when an interrupt + arrives during ring 3. Without `rsp0` it pushes the interrupt frame onto the + *user* stack, which the user can then rewrite. Its `io_map_base` points past + the segment on purpose: a bitmap that starts beyond the limit means "no ports + at all", where zero would point at the TSS's own fields and make a permission + map out of whatever was there. +- **ring-3 descriptors**, because privilege is a property of the segment. +- a **user-accessible page**. This one produced the first failure: the ring-3 + program could not execute at all, faulting on its own first instruction with + error code 5 — a user-mode access to a page the tables do not mark user. The U + bit has to be set at *every* level of the walk, because the CPU takes their + conjunction. + +What ring 3 is granted is **its own pages, and nothing else**. The first 2 MiB +has 4 KiB granularity — one page table instead of one big page — and the U bit +is set only on the pages between `__user_start` and `__user_end`, a section the +linker script page-aligns at both ends. Everything else in that range, which is +most of the kernel, stays kernel-only. + +That granularity is what the check is aimed at. The forbidden access is a *read* +of `0x100010` — the kernel's first instruction, in the same 2 MiB as the user +program. While the range was one user-accessible page, that read succeeded and +told nobody; now it faults. "Ring 3 cannot reach the shared page two megabytes +away" is a much weaker claim than "ring 3 cannot reach the kernel", and only the +second one is worth making. + +Two mistakes on the way, both instructive. An early version set the U bit on the +second directory entry as well, and the forbidden write simply succeeded — which +is why the check asserts the *error code*, not just the address: a fault there +from ring 0 would be a kernel bug with the same `cr2`. And the U bit has to be +set at every level of the walk, because the CPU takes their conjunction; a user +page under a kernel-only directory is still kernel-only. + +### An address space of its own, built by the program + +`program.lk` builds it, out of pages from its own allocator: + +```lk +fn build_user_space(stack_physical: Int) -> Int { + let pml4 = page_alloc(SHARED_PAGES); + … + table_set(pdpt, index_of(stack_virtual, SHIFT_PDPT), directory, shared); +``` + +Every index is *computed from the virtual address* rather than written down. The +old version had `pdpt.add(1)`, `pd.write(…)`, `pt.write(…)` — 1, 0 and 0, all +correct, and correct only because the stack happens to sit at the bottom of the +second gigabyte. Numbers like that go on looking right after they stop being +true. + +Two things follow from the pages coming out of the allocator. The linker script +no longer reserves four sets of tables, so the ceiling on how many address +spaces there can be is "how much memory is left" rather than a number nobody +justified — the previous one was four, and four was only there because two had +been. And `check_shell.py`'s expected page addresses moved by eight pages, which +is the two spaces being allocated like anything else; that check asserts the +*addresses* precisely so that a counter pretending to be an allocator would not +pass it. + +### The failure that was not one + +Removing the linker reservations made `check_spawn.py` report both the spinner +and the clock windows frozen — three runs in a row, deterministic-looking, and +the reservation being the only difference. Restoring it passed. Doubling the +kernel stack instead also passed. The obvious story was a stack that had been +overflowing into 64 KiB of unused reservations and now overflowed into `__pt0`. + +It was none of that. The same build passes 4/4 on an idle machine. Every one of +those failures happened with a `cargo build` running alongside, and +`check_spawn` is the one check here that is wall-clock: four screenshots 1.4 s +apart, asking whether a window that changes once a slice ever changed. A starved +guest makes them all land in one phase. + +Recorded here and in the check itself because roughly forty minutes went into a +hypothesis that a second look would have killed — and because the next person +reading a red `check_spawn` should re-run it on a quiet host before believing +it. + +### An address space of its own + +The ring-3 task's stack is at `0x4000_0000` — *in its own address space*. In the +kernel's, that address is identity-mapped RAM that does not exist on this +machine. Two spaces, not one with extra permissions, and the difference between +a thread and a process is the one word per task that says which. + +Almost all of it is shared, and shared by *pointing* rather than copying: the +task's PML4 names the kernel's own page directories for three of the four +gigabytes. The kernel has to be mapped in every space — an interrupt during +ring 3 lands in kernel code, and there would otherwise be nowhere for it to go — +and copying the entries would work today and drift the first time a mapping is +added to one and not the other. Only the second gigabyte is the task's own, and +it holds one page: its stack. + +There are two of them, which is what makes it a claim rather than a permission: +both tasks keep a stack at *the same* virtual address, each writes one letter +into it, and each prints what it reads back — for ever, interleaved by the +timer. `ABABAB…`. One address space would mean the second write landed on the +first's page and both letters were the same from then on. + +That it works at all is the other half of the evidence. QEMU's default machine +has 128 MiB, so `0x4000_0000` is backed by nothing in the kernel's identity map; +a task running there means the tables that give it meaning are the ones in +force. + +CR3 changes before the stack pointer is handed back, not after: the value the +switch returns is read by the CPU *after* this returns, and it has to mean the +same thing in whichever space is current by then. It does, because the kernel is +mapped identically in both — which makes the order safe rather than lucky. + +### Whose list of holes it is + +The syscall dispatcher is `program.lk`'s. What a user task may ask for is a list +of holes in the wall the ring boundary just built, and deciding what goes on +that list is not the board's business — nor is deciding whether to believe a +pointer: + +```lk +fn user_range_is_valid(address: Int, length: Int) -> Bool { + if (length <= 0 || length > MAX_WRITE) { return false; } + let end = address + length; + if (end < address) { return false; } + return address >= user_section_start() && end <= user_section_end(); +} +``` + +An `Int` is signed, which does half the work for free: an address with its top +bit set arrives negative and fails the lower bound. The other end still needs +the wrap check — an address just under the maximum wraps `address + length` to a +negative number, which would then pass `end <= limit`. LK's addition wraps +rather than trapping, so the wrap is what that comparison looks for. + +The board answers where the user section is, because the linker is the only +thing that knows and the board is the only side that can ask it. + +**The syscall trampoline now saves the SSE registers**, which it did not before. +The handler is compiled LK, LK numbers are `f64`, and the System V ABI lets a +called function clobber every XMM register — which the ring-3 caller never +agreed to. Nothing would have gone wrong yet: the user programs here are +assembly that touches no XMM at all. That is exactly why it is worth writing +down rather than waiting for the first one that does. + +### A pointer the kernel does not believe + +The first syscall took a byte per call, which was slow and deliberate: a pointer +from ring 3 is a *number*, and following one without checking is the shape of +every "the kernel read out its own memory on request" bug there has ever been. + +There is a checked one now. `write(ptr, len)` verifies the range lies inside the +user section — the only memory ring 3 can reach — before reading a byte of it, +and the check is the kernel's, not a promise the caller makes. The arithmetic is +checked too, because a length near `u64::MAX` wraps the end back below the start +and makes any address look contained. + +The user program does both: it prints `str` through the checked call, then hands +the same call the kernel's own address and prints what came back. `N` means +refused. A `Y` there would mean the boundary is decoration. + +The kernel copies each byte out before using it rather than printing from user +memory in place. One instruction shorter would leave a window between the check +and the use — on one CPU a small one, on two a race. + +### And back again + +`user` has no way back — its only exits are a syscall (which returns *into* ring +3) and a fault. A ring-3 *task* does: one is spawned at boot, prints `3` for +ever, and never yields. The shell answers a command while it runs, which is the +claim: the timer took the CPU away from ring 3 and gave it back. + +What that needed was one line in the scheduler and a stack per task. The frame a +task starts on is the same shape either way — fifteen saved registers under the +frame the CPU pushes — and the only difference between a kernel task and a user +one is the four numbers in it. What is *not* the same is where an interrupt from +ring 3 lands: the CPU takes that from the TSS, so `rsp0` is set to the next +task's own kernel stack on every switch. Two user tasks sharing one would have +the second's interrupt frame land on the first's, and the first would resume +into whatever was left. + +### What a third one would need + +The tables are there for four address spaces and the task table holds six tasks, +so a third user process costs nothing structural. What it needs is a *claim*: two +tasks printing `A` and `B` prove they cannot see each other, and a third printing +`C` proves nothing further unless it is arranged to fail differently — sharing a +space with exactly one of the others, say, so the check can tell "isolated from +everyone" from "isolated from the last one spawned". + +Adding the task is half an hour. Deciding what it would demonstrate is the part +worth doing first. + +## The memory map, in one place + +Three things want RAM and none of them can ask: the kernel image, the Rust heap +the interpreter allocates from, and the page allocator the LK program hands out. +So the map has to be written down — and the place it is written down has to be +one both languages can read. `link.ld` is that place: + +``` +__shared_base = 0x00300000; +__source_base = 0x00380000; +__heap_base = 0x00400000; +__heap_size = 0x00400000; +__run_heap_base = __heap_base + __heap_size; +__page_arena_base = __run_heap_base + __run_heap_size; +``` + +Rust takes the address of an `extern static`; LK asks `symbol_address`. The +relations are written as relations, so "the page arena starts where the run heap +ends" is a statement rather than an arithmetic coincidence between two constants +nobody recomputed. + +It used to be a table in a doc comment plus a literal in each language. +`0x00380000` was written twice, and `kernel_run` cross-checked them by refusing +any address but its own — which notices that the two have drifted rather than +preventing it. + +The note that had to go with it was this file's own: that `program.lk` and the +interrupt handlers agree on `0x300000` "by writing the number down, not by +asking the linker — an interrupt handler cannot look anything up". That was true +of a *run-time* lookup and never true of this one. A symbol's address is +resolved when the image is linked, so what the handler executes is an immediate +either way. + ## Memory that comes back `drivers/pages.lk` never reclaims, which was honest while nothing freed. @@ -621,9 +1132,51 @@ framebuffer back proves the writes reached the device's memory, but an unconfigured card accepts those too. Only what QEMU scans out shows that the mode was actually set. +## What is Rust, and why — the whole list + +The point of this demo is what LK can be made to do, so the interesting number +is what it still cannot. Every line below is here because of a specific thing +the language cannot say, and the reason is the entry, not the file: + +| file | lines | why it is not LK | +| --- | --- | --- | +| `main.rs` | 346 | Hosts the *interpreter*: a Rust bump allocator, and the parse-and-run entry the `run` command calls. A language cannot be its own host. | +| `boot.rs` | 205 | The multiboot header, and 32-bit code that reaches long mode. It runs before there is a stack, a GDT, or paging — before anything compiled could. | +| `user.rs` | 182 | `iretq`, the syscall trampoline's register spill, and `ltr`'s frame. No instruction lowers privilege directly, and an interrupt is not a call. | +| `interrupts.rs` | 144 | The 32 exception stubs, and a fault reporter whose whole design is to depend on as little as possible. | +| `user_programs.rs` | 121 | Three ring-3 programs. Assembly because a ring-3 program must not reach for a runtime, and `int` takes its vector as an immediate. | +| `tasks.rs` | 110 | Returning on a *different* stack, which is what a task switch is. | + +Against roughly 5,000 lines of LK: every driver, the interrupt table, the +descriptor table, the task state segment, the page tables and address spaces, +the task table, the scheduler, the syscall dispatcher, the window manager, the +shell. + +Two of those rows are worth reading twice, because they are the ones that stopped +being about the language: + +**`interrupts.rs` no longer has a trampoline per device.** `lkrt` carries 256 +stubs and a handler table, so installing a driver's interrupt is two stores from +LK. What is left here is the *exception* stubs, which differ — each pushes a +dummy error code where the CPU does not — and the reporter. + +**`user.rs` no longer answers questions.** It used to hold a row of small +functions telling the program where the linker had put things. `symbol_address` +works on data symbols, so the program asks the linker itself; and the memory map +moved into `link.ld`, which is the one file both languages read. + ## The drivers are modules ``` +drivers/idt.lk the interrupt descriptor table: gates, and `lidt` +drivers/pic.lk the 8259 pair: remap, mask, end-of-interrupt +drivers/gdt.lk segment descriptors, `lgdt`, and reloading CS +drivers/paging.lk four-level page tables, CR3, and the TLB +drivers/tasks.lk the task table, and the frame a task starts life on + +`lkrt` supplies one thing no driver can: 256 interrupt stubs and a handler +table, so a gate can reach a compiled LK function at all. +drivers/tss.lk the one field long mode kept: the ring-0 stack drivers/serial.lk a 16550 UART drivers/pci.lk configuration space drivers/vbe.lk the Bochs VBE display interface, including panning @@ -674,6 +1227,71 @@ Refusing the shape is what stops a native build computing something the VM would not. A container that is built inside a function is fine — it is fresh per call, so there is nothing to share. +## The width that was only real if you typed it + +Giving `unsafe` blocks a type made a second thing visible, and it was worse than +the first: + +```lk +fn read() -> u32 { return 4000000000 as u32; } +let a = read(); let b = read(); println(a + b); // 8000000000 +let c: u32 = 4000000000; let d: u32 = 4000000000; +println(c + d); // 3705032704 +``` + +Same types, same values, two answers — and which one you got depended on whether +the width had been *written down*. Machine-int arithmetic wraps to its width, +and the wrap is emitted where the compiler can prove the width; proof came from +exactly two places, an annotation and an `as` cast. Everything else was left +unproven, on the stated grounds that not wrapping is the safe answer. It is not: +it is a different answer, and the type checker had already decided which one is +right. + +Proof now also comes from what the initializer *produces*: a call to a function +that declares a machine return, a builtin whose name is a width +(`volatile_read_u32`), and a read of a local already known to hold one. Nothing +else — this widens what can be proven, it does not change what proof means. + +The first attempt looked correct and changed nothing, which is the part worth +keeping. It matched `Expr::Call`, and by the time the compiler sees a call to a +plain function, name resolution has rewritten it to `CallExpr(Var(name), …)`. +The test that caught it is the one that compares the two spellings against each +other rather than against a number. + +## `unsafe { … }` has a type now + +It used to have none — every `unsafe` block type-checked to `Any`, with a note +in the checker saying a block that evaluates to a typed value was "a separate +change". The executor had never agreed: it already evaluates a block to its last +statement's value, trailing semicolon included, so `unsafe { 7; }` is 7. The +type has caught up with the value. + +`Any` spreads, and where it spread was exactly the kind of code this demo is +made of. Measuring the stride of an array of interrupt stubs is two addresses +subtracted; with both of them `Any`, the result had no `as Int` out of it and +the error named the cast rather than the missing type. It cost real time twice. + +The other half of that fix: `symbol_address` and `call_address_2` had no entry +in the type checker at all, so a call to either produced `Any` and neither its +arity nor its arguments were checked. They do now, and `symbol_address` also +insists its name is a *literal* — a relocation is a name resolved when the image +is linked, and there is nothing to look one up in at run time. Passing a +variable used to type-check, run under the VM (which refuses), and fail to lower +natively with a message about an opcode. + +Closing the hole immediately found two real errors, both in this repository's +own test fixtures: + +```lk +let a = unsafe { volatile_read_u32(reg) }; +let b = unsafe { volatile_read_u32(reg) }; +return a + b; // from a function returning Int +``` + +A 32-bit read is a `u32`, and two of them added is not an `Int` until something +says so. The drivers here had always written the cast; the fixtures had not, +because `Any` did not make them. + ## Port I/O ```lk @@ -749,11 +1367,131 @@ fn on_tick() { } ``` -The board's share is an IDT, remapping the 8259 PIC away from the vectors the -CPU reserves for exceptions, acknowledging the interrupt, and spilling every -caller-saved register. What a tick *means* is the program's, and that part is -LK — including programming the PIT's divisor, which `program.lk` does with the -same `port_out_u8` its UART driver uses. +**The table those interrupts come through is LK's too.** `drivers/idt.lk` +builds all 256 gates and loads them with `cpu_load_idt`; `program.lk` decides +which vector means what: + +```lk +idt_set_gate(IDT_BASE, VECTOR_KEYBOARD, + unsafe { symbol_address("__keyboard_trampoline") }, KERNEL_CODE_SELECTOR, 0); +``` + +That is not a rewrite for its own sake. A gate is sixteen bytes of *decision* — +which vector, which handler, and which privilege level may raise it — and the +only reason it used to be Rust is that there was no way to say `lidt` in LK. +There is now, so the decisions live where the rest of the program's decisions +do. The board's remaining share is the thing this genuinely cannot be: the +trampolines. An interrupt is not a call — the code it lands in never agreed to +lose its caller-saved registers — so a compiled handler has to be entered +through a stub that spills all of them and leaves with `iretq`. That is +assembly in any language. + +**### Adding an interrupt stopped being a Rust edit + +A gate points at a stub, not at a handler, and that will always be true: the +code an interrupt lands in never agreed to lose its caller-saved registers, so +something has to spill them and leave with `iretq`. What was *not* inevitable is +that every vector needed its own hand-written stub in the board's Rust — adding +a device meant editing a file the driver has nothing to do with. + +`lkrt` now carries 256 of them and a handler table, so installing one is two +stores from LK: + +```lk +fn install_device_handler(vector: Int, handler: Int) { + … + idt_set_gate(IDT_BASE, vector, stubs + vector * stride, KERNEL_CODE_SELECTOR, 0); + unsafe { volatile_write_u64((table + vector * 8) as *mut u64, handler as u64); }; +} +``` + +The keyboard's and the mouse's trampolines are gone from `src/interrupts.rs`. +The timer's is not, and cannot be: returning on a *different* stack is what a +task switch is, and no shared tail does that. Nor is the syscall's, which has to +put a value back in `rax`. + +Two details in the shared tail are worth the words. Each stub pushes its vector +as `push imm32`, not `imm8` — the byte form sign-extends, so vector 200 would +arrive as −56, on exactly the vectors nobody tests. And a handler of zero is +checked for rather than called: a vector arriving with nothing installed is a +spurious interrupt, and answering it with a call to address zero turns a +diagnosable event into a fault inside a fault. + +The 8259 is LK's too**, both halves of it — `drivers/pic.lk` holds the +four-write initialisation sequence, the mask register, and the end-of-interrupt. +Those had to move together: bringing the chip up decides which vector each line +lands on, acknowledging decides which chip is told the handler is done, and +split across the boundary they become two files naming one command port. + +The acknowledgement is a *wrapper* around each handler rather than its last +line: + +```lk +#[export("lk_key_isr")] +fn isr_key() { + on_key(); + pic_eoi_master(); +} +``` + +`on_key` returns early from four places. An end-of-interrupt a `return` can skip +is one that will be skipped — and the symptom is a device that works once and +then goes silent, which is what an unacknowledged 8259 line does. Written this +way there is nowhere for it not to happen. + +Stopping is the program's as well. `program.lk` masks the flag and then the +chip as its last act, in that order: the flag stops the CPU taking anything, the +chip stops it raising anything, and a tick landing between the two would splice +a `.` into the line the board prints afterwards. + +What is left in `src/interrupts.rs` is the trampolines and the exception +reporter — the reporter because it runs *after* something has gone wrong, and +the two things a handler must never do (allocate, take a lock) are exactly what +formatting a report in LK would need. + +What a tick *means* has always been the program's, including programming the +PIT's divisor, which `program.lk` does with the same `port_out_u8` its UART +driver uses. + +### The stride that was derived wrong + +The 32 exception stubs are a `.rept` in the board's assembly, padded to a fixed +stride, and `program.lk` computes a stub's address from it. It asks the +assembler for the stride rather than naming 16 — `(ISR_STUBS_END - ISR_STUBS) / +32` — because a copy of that number on the LK side is a copy nothing checks. + +Deriving it was right and the derivation was wrong. `.align 16` sits at the +*top* of each iteration, so the array ended nine bytes into its final slot: +span 505, stride 15, and every gate but the first pointed into the middle of the +stub before it. The `fault-probe` build reported a page fault as **vector 2**, +with the faulting address sitting in the error-code field. + +Two fixes, and the second is the useful one. The assembly now pads after the +last stub as well. And `program.lk` checks that the span divides by 32 before +dividing, which costs one modulo at boot and is the thing that would have said +so out loud: + +```lk +if ((span % EXCEPTION_COUNT) != 0) { + uart_write(/* "bad isr stride" */); + halt(); +} +``` + +### Which comes first + +The program installs its table before anything can fault, and the board no +longer touches interrupts at boot at all — `kernel_main` calls `main()` and the +program asks for the PIC when it is ready. Between the two there is a window +with no gate for any vector, and a fault with no gate is a triple fault, which +on this machine is a silent reset. + +That window is why the deliberate fault moved. It used to be a `write_volatile` +in `kernel_main`, which is now *before* the table exists — the probe stopped +reporting anything and the machine simply reset, which is exactly the failure +the probe exists to make impossible. It is an `#[extern]` the program calls +immediately after installing the table, so what it lands in is the table the +program actually built. The handler and the main program share a device, so `program.lk` masks interrupts around the lines it does not want spliced: @@ -782,7 +1520,7 @@ live below 2 MiB, so 0x0030_0000 is untouched. That is crude, and deliberately so — a kernel with no memory manager yet has exactly this much to work with, and pretending otherwise would hide what the program is actually doing. -`check_keyboard.py` types at the machine through QEMU's monitor. `sendkey` +`check_shell.py` types at the machine through QEMU's monitor. `sendkey` puts a real scancode into the emulated controller, so the test covers IRQ1, the LK handler, the scancode table and the echo — the one part memory inspection cannot show. @@ -870,3 +1608,40 @@ kernels do. position independent — a bare-metal image is loaded at a fixed address and has no dynamic loader — so the link rejects its absolute relocations. Setting the relocation model in `.cargo/config.toml` is what makes the two agree. + +## What the checks cover + +Seventeen scripts, each *building* the image and booting it under QEMU, driving +it through the monitor. They are listed here because a check nobody runs is a +claim nobody holds, and five of these were written after the sections above. + +Building is `kernel.py`'s job and it is not a convenience: the image path used +to be a bare default, so a script tested whatever happened to be on disk — +including the kernel that `CARGO_FLAGS=--features=fault-probe ./run.sh` builds +to page-fault deliberately, which made every check report a fault it had not +caused, on every revision. + +| | what fails if it is wrong | +| --- | --- | +| `check_shell.py` | IRQ1 through the scancode table to the echo, a command answered, the scroll, and the page allocator's *addresses* — a counter would print two numbers just as happily | +| `check_screen.py` | the framebuffer, by reading pixels back | +| `check_mouse.py` · `check_focus.py` · `check_drag.py` · `check_stack.py` | the PS/2 mouse, and windows that own their pixels, their focus and their order | +| `check_tasks.py` · `check_spawn.py` | two windows changing while the shell sits idle: a task that was never started and a scheduler that stopped reaching it are the same failure from outside | +| `check_user.py` | ring 3 spoke through a syscall, was preempted while never yielding, and was refused the kernel page next to its own | +| `check_disk.py` | a sector read, one written, and the medium checked *after* QEMU exits — a drive acknowledges a write long before it is on the platter | +| `check_run.py` | a program loaded off that disk and interpreted, on the same machine that compiled the kernel | +| `check_interpreted_driver.py` | a *driver* loaded off that disk and interpreted: it drives the CMOS clock through port I/O, on a kernel that was compiled before it existed. Everything else here proves LK can be compiled into this layer; this proves it can drive hardware without being compiled at all | +| `check_pci.py` | a device found by walking configuration space, reached through its BAR, made to compute, made to DMA into RAM, and made to interrupt on a gate the driver installed for itself | +| `check_net.py` | an Intel NIC: its MAC out of the EEPROM, two descriptor rings fed twelve exchanges past their wrap, a well-formed ARP request on the wire (checked from a pcap, not from the guest), the gateway's reply, every page given back, and the card's own interrupt reaching a handler | +| `check_exit.py` | a task that *ends* — twenty of them through sixteen slots, every slot and every stack page returning — and one that sleeps for the time it asks for with the CPU halted throughout | +| `check_clock.py` | the CMOS clock, against a base time QEMU was *told*, at a moment where a missing BCD conversion turns November into month 17 | +| `check_hpet.py` | the high precision timer: its rate computed from the period the chip states about itself, and its 64-bit counter watched to advance — the one clock here that is neither counted nor coarse | + +The two that are not scripts: + +* **`CARGO_FLAGS=--features=fault-probe`** makes the kernel touch an address + 36 bits wide right after installing its interrupt table. A reporter that is + never made to report is indistinguishable from one that cannot. +* **`cargo run --release --bin lk-bare-metal`** in `../bare-metal` boots the + Cortex-M demo. It is the only thing that proves the no_std VM *works* rather + than merely compiles, and it is on a different architecture. diff --git a/bare-metal-x86/build.rs b/bare-metal-x86/build.rs index fccb1c7d..4b11e610 100644 --- a/bare-metal-x86/build.rs +++ b/bare-metal-x86/build.rs @@ -30,6 +30,20 @@ fn main() { // An explicit path in CI, otherwise whatever is installed. let lk = std::env::var("LK_BIN").unwrap_or_else(|_| "lk".to_string()); + // The compiler's *contents*, not just which one was named. Tracking only + // `LK_BIN` left a gap the size of the ordinary development loop: rebuild + // `lk`, rebuild the image, and cargo sees the same env var and the same + // sources and keeps the object the *previous* compiler emitted. That is the + // failure the note above says must not happen, and it happened — a codegen + // change appeared to leave the image working right up until a stale object + // failed to link against a runtime that had moved on. + // + // Only when it names a file that exists: `lk` resolved through `PATH` is + // not a path cargo can stat, and pointing `rerun-if-changed` at a missing + // one makes every build a rebuild. + if std::path::Path::new(&lk).is_file() { + println!("cargo:rerun-if-changed={lk}"); + } let status = Command::new(&lk) .arg("compile") .arg(format!("object:{target}")) diff --git a/bare-metal-x86/check_clock.py b/bare-metal-x86/check_clock.py new file mode 100644 index 00000000..37ef19f3 --- /dev/null +++ b/bare-metal-x86/check_clock.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""The battery-backed clock, read by a driver written in LK. + +Every other clock in this kernel counts. The PIT counts down and raises an +interrupt, the kernel counts those, and what that gives is elapsed time: a +machine that has been up for four seconds cannot say whether it is Tuesday. The +RTC is the one thing on the board that kept running while the power was off. + +QEMU is told a *fixed* base time, so this is not "close to the host clock" but an +exact answer with a known value. The moment is chosen to make the two format +questions fail loudly rather than plausibly: + +* **BCD.** The registers hold binary-coded decimal unless status B says + otherwise, so 59 arrives as `0x59`. A driver that skips the conversion reads + it as 89 — and reads month 11 as 17, and hour 19 as 25. The base below is all + such values, so a missing conversion is not a near miss. +* **The update window.** The chip copies its counters into the registers once a + second, and a read that straddles that gives a mixture of before and after: + 01:59:59 becomes 01:00:59, which is a clock that is perfect except for one + second in every hour. Waiting for the in-progress flag is not enough on its + own — an update can begin between the check and the reads — so the driver + reads twice and requires the two to agree. This check runs `clock` several + times across several seconds, which is the only way to give that window a + chance to be hit. + +The whole driver is `drivers/rtc.lk`; the board contributes nothing. +""" + +import os +import re +import socket +import subprocess +import sys +import tempfile +import time + +from kernel import kernel_image + +# Every field is one a missing BCD conversion would mangle: 11 → 17, 19 → 25, +# 59 → 89, 58 → 88. +BASE = "2019-11-19T19:59:58" +BASE_YEAR, BASE_MONTH, BASE_DAY = 2019, 11, 19 +BASE_HOUR, BASE_MINUTE, BASE_SECOND = 19, 59, 58 +# How far the guest's clock may have advanced by the time a reading is taken. +# Generous: the run types several commands through the monitor, each with a +# deliberate pause. +MAX_DRIFT_SECONDS = 120 + + +def send_line(connection, text): + """Types `text` and Enter through the monitor, a key at a time.""" + names = {" ": "spc", "-": "minus", ".": "dot", "/": "slash"} + for character in text: + connection.sendall(f"sendkey {names.get(character, character)}\n".encode()) + time.sleep(0.25) + connection.sendall(b"sendkey ret\n") + time.sleep(2.5) + + +def as_seconds(year, month, day, hour, minute, second): + """Seconds since the base date, for comparing two readings. + + Not a real calendar: the run spans seconds, so days and months only have to + be *equal* to the base, which is checked separately. + """ + return ((day * 24 + hour) * 60 + minute) * 60 + second + + +def main(): + image = kernel_image() + with tempfile.TemporaryDirectory() as workdir: + monitor = os.path.join(workdir, "monitor") + serial = os.path.join(workdir, "serial.txt") + qemu = subprocess.Popen( + [ + "qemu-system-x86_64", + "-kernel", image, + "-display", "none", + # The whole point: a known answer, not the host's clock. + "-rtc", f"base={BASE}", + "-serial", "file:" + serial, + "-monitor", f"unix:{monitor},server,nowait", + ] + ) + try: + for _ in range(100): + if os.path.exists(monitor): + break + time.sleep(0.1) + time.sleep(2) + connection = socket.socket(socket.AF_UNIX) + connection.connect(monitor) + time.sleep(0.3) + connection.recv(65536) + # Several readings across several seconds. One reading proves the + # registers were decoded; a sequence proves the clock *runs*, and + # gives the update window a chance to be straddled. + for _ in range(4): + send_line(connection, "clock") + connection.sendall(b"quit\n") + connection.close() + finally: + qemu.terminate() + qemu.wait(timeout=10) + + with open(serial, errors="replace") as handle: + transcript = handle.read() + + failures = [] + # Unanchored: the spinner writes to the same serial line from a timer + # interrupt, so a shell line can arrive with another task's byte stuck + # to either end of it. + readings = re.findall( + r"clock (\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})", transcript + ) + if len(readings) < 4: + unsettled = transcript.count("clock: unsettled") + failures.append( + f"expected 4 readings, got {len(readings)}" + + (f" ({unsettled} unsettled)" if unsettled else "") + ) + + stamps = [] + for year, month, day, hour, minute, second in readings: + year, month, day = int(year), int(month), int(day) + hour, minute, second = int(hour), int(minute), int(second) + # The date is the base's, exactly. A missing BCD conversion turns + # November into month 17, which is not a date at all. + if (year, month) != (BASE_YEAR, BASE_MONTH): + failures.append(f"read {year}-{month:02d}, not {BASE_YEAR}-{BASE_MONTH:02d}") + if day not in (BASE_DAY, BASE_DAY + 1): + failures.append(f"read day {day}, not {BASE_DAY}") + if not (0 <= hour < 24 and 0 <= minute < 60 and 0 <= second < 60): + failures.append(f"read {hour}:{minute}:{second}, which is not a time") + stamps.append(as_seconds(year, month, day, hour, minute, second)) + + if stamps: + start = as_seconds(BASE_YEAR, BASE_MONTH, BASE_DAY, BASE_HOUR, BASE_MINUTE, BASE_SECOND) + drift = stamps[0] - start + if not 0 <= drift <= MAX_DRIFT_SECONDS: + failures.append( + f"the first reading is {drift}s from the base time, which is not the " + f"clock QEMU was told to keep" + ) + # And it *runs*: never backwards, and it moved at all across four + # readings taken seconds apart. A driver that read a constant would + # pass every other check here. + for earlier, later in zip(stamps, stamps[1:]): + if later < earlier: + failures.append(f"the clock went backwards: {earlier} then {later}") + if len(stamps) > 1 and stamps[-1] == stamps[0]: + failures.append("the clock did not advance across four readings seconds apart") + + if "exception #" in transcript: + fault = re.search(r"!! exception .*", transcript) + failures.append(f"the machine faulted: {fault.group(0)!r}") + + if failures: + print("\n".join(failures)) + print("--- transcript ---") + print(transcript) + return 1 + print(f"OK: read {BASE} out of the CMOS registers, decoded from BCD, and watched it run") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bare-metal-x86/check_disk.py b/bare-metal-x86/check_disk.py index e53bcd4a..0b3482b2 100644 --- a/bare-metal-x86/check_disk.py +++ b/bare-metal-x86/check_disk.py @@ -25,10 +25,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + SECTOR = 512 # What this script puts in sector 0, and what the program writes into sector 1. PLANTED = b"hello.txt" @@ -60,9 +61,7 @@ def send_line(connection, text): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: disk = os.path.join(workdir, "disk.img") # A real tar archive, written by Python's `tarfile`. Sector 0 is its @@ -107,6 +106,11 @@ def main(): connection.connect(monitor) time.sleep(0.3) connection.recv(65536) + # `ls` walks the archive: an entry's length is in its own header, + # so where the next one starts is not known until this one is read. + # Both entries and both sizes, because a walk that stops after the + # first would still print something. + send_line(connection, "ls") send_line(connection, "disk") send_line(connection, "cat hello.txt") send_line(connection, "disk w") @@ -119,6 +123,9 @@ def main(): with open(serial, errors="replace") as handle: transcript = handle.read() failures = [] + for expected in (f"{FILE_NAME} {len(FILE_BODY)}", "motd 6"): + if expected not in transcript: + failures.append(f"`ls` did not list {expected!r}") expected_read = f"{SECTORS} {PLANTED.decode()}" if expected_read not in transcript: failures.append(f"`disk` did not report {expected_read!r}") diff --git a/bare-metal-x86/check_drag.py b/bare-metal-x86/check_drag.py index c2dff77a..78850025 100644 --- a/bare-metal-x86/check_drag.py +++ b/bare-metal-x86/check_drag.py @@ -20,10 +20,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + FOREGROUND = (0x40, 0xFF, 0x90) FRAME_FOCUSED = (0xFF, 0xC0, 0x40) # The pane window, as `program.lk` defines it: 62x18 at the top right. @@ -67,9 +68,7 @@ def count_colour(path, colour, left, top, width, height): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") qemu = subprocess.Popen( diff --git a/bare-metal-x86/check_exit.py b/bare-metal-x86/check_exit.py new file mode 100644 index 00000000..2cf70039 --- /dev/null +++ b/bare-metal-x86/check_exit.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +"""A task's whole life: it waits without spending anything, it ends, and +everything it held comes back. + +`check_spawn.py` next door proves tasks *run*: a spinner and a clock, both +`while (true)`, both preempted by the timer. That is half a scheduler. Every task +in this kernel was a loop that was started, and a table of them was a watermark — +a slot could be born and never die, so nothing showed whether a slot or a stack +could come back. + +The awkward part is not the marking, it is the timing: **a task cannot release +the stack it is standing on**. The release would hand back the pages holding the +frame that is about to return, and the next interrupt would land on memory the +allocator had given away. So an exiting task marks itself dead and stops, and the +scheduler gives the pages back a tick later — when that task is neither the one +running nor the one about to run. + +Four numbers, and each one fails differently: + +* **20 of 20 started.** The table holds sixteen slots. Twenty tasks fit only if + slots come back; a kernel that leaked them stops at whatever was spare. +* **The watermark barely moves.** It is one past the highest slot ever taken, so + running twenty tasks one at a time should raise it by one and leave it there — + the same slot, reused. A watermark that climbed with each task would mean each + one took a fresh slot and nothing was reclaimed. +* **The free-page count is the number it started at.** Not close to it. Each task + takes a stack; a reclaim that marked the slot free without releasing the pages + looks exactly like success from every other angle. +* **A sleep takes as long as it says.** Ten sleeps of fifty ticks have to take + about five hundred ticks — not none, which is what a `task_sleep` that + returned immediately would take, and which is exactly what the first version + did: the scheduler's slice shortcut handed back the current task on seven + ticks out of eight without asking whether it was still runnable, so a task + that had just marked itself blocked was resumed anyway. Every wake was + counted, every number looked right, and only the elapsed time gave it away. +* **And costs nothing while it waits.** The idle task counts the turns it is + given, so "the machine had nothing to do" is a number: it has to be about the + same as the elapsed ticks, which is to say the CPU was halted for essentially + the whole wait. A spin would score zero here — a spinning task is runnable, + and the rotation never falls through to idle while anything is runnable. + + Three tasks are suspended for the measurement, because this demonstration + deliberately runs tasks that never block: a spinner that exists to be + interrupted mid-update, and two ring-3 tasks that never yield. Suspending + those is itself worth something — two of them are in ring 3, and a ring-3 + task has no say in it. That is the difference between a kernel and a + cooperative loop. +* **The work actually happened.** Each task bumps a counter 200,000 times, so + `ran` has to be twenty times that. Without it, "every task ended" is also what + a kernel that never scheduled them would report — which is what the first + version of this did, because the shell runs with interrupts masked and nothing + schedules without the timer. +""" + +import os +import re +import socket +import subprocess +import sys +import tempfile +import time + +from kernel import kernel_image + +# Must match `TASK_BRIEF_ROUNDS` and the loop inside `lk_task_brief`. +ROUNDS = 20 +STEPS_PER_TASK = 200000 +# `TASK_CAPACITY` in the program. Fewer than the rounds, on purpose. +CAPACITY = 16 +# The scheduler's slice, in ticks: a woken task runs at the next decision, so +# each wake may be up to this late. +SLICE_TICKS = 8 +SLEEP_SLACK = 60 +# How much of the wait the CPU has to have been halted for, as a percentage. +# Below this and something was runnable throughout, which is a spin. +IDLE_SHARE = 80 + + +def send_line(connection, text): + """Types `text` and Enter through the monitor, a key at a time.""" + names = {" ": "spc", "-": "minus", ".": "dot", "/": "slash"} + for character in text: + connection.sendall(f"sendkey {names.get(character, character)}\n".encode()) + time.sleep(0.25) + connection.sendall(b"sendkey ret\n") + time.sleep(6) + + +def main(): + image = kernel_image() + with tempfile.TemporaryDirectory() as workdir: + monitor = os.path.join(workdir, "monitor") + serial = os.path.join(workdir, "serial.txt") + qemu = subprocess.Popen( + [ + "qemu-system-x86_64", + "-kernel", image, + "-display", "none", + "-serial", "file:" + serial, + "-monitor", f"unix:{monitor},server,nowait", + ] + ) + try: + for _ in range(100): + if os.path.exists(monitor): + break + time.sleep(0.1) + time.sleep(2) + connection = socket.socket(socket.AF_UNIX) + connection.connect(monitor) + time.sleep(0.3) + connection.recv(65536) + send_line(connection, "task") + # Twice: the second cycle runs on slots and pages the first one gave + # back, so anything the reclaim got subtly wrong shows here rather + # than in a first run that had untouched memory to draw on. + send_line(connection, "task") + send_line(connection, "sleep") + connection.sendall(b"quit\n") + connection.close() + finally: + qemu.terminate() + qemu.wait(timeout=10) + + with open(serial, errors="replace") as handle: + transcript = handle.read() + + failures = [] + # Unanchored: the spinner writes to the same serial line from a timer + # interrupt, so a shell line can arrive with another task's byte stuck to + # either end of it. + cycles = re.findall( + r"task (\d+)/(\d+) slots (\d+)->(\d+) pages (\d+)->(\d+) ran (\d+)", transcript + ) + if len(cycles) < 2: + failures.append(f"expected two cycles, got {len(cycles)}: {cycles}") + + for index, (started, wanted, slots_before, slots_after, pages_before, pages_after, ran) in enumerate(cycles): + if int(wanted) != ROUNDS: + failures.append(f"cycle {index}: the program ran {wanted} rounds, not {ROUNDS}") + if int(wanted) <= CAPACITY: + failures.append( + f"cycle {index}: {wanted} rounds fits in {CAPACITY} slots, so it would pass " + f"without any slot coming back" + ) + if started != wanted: + failures.append(f"cycle {index}: only {started} of {wanted} tasks started") + if int(slots_after) - int(slots_before) > 1: + failures.append( + f"cycle {index}: the watermark went {slots_before}->{slots_after}; " + f"slots are not being reused" + ) + if pages_before != pages_after: + failures.append( + f"cycle {index}: the pages did not come back: {pages_before}->{pages_after}" + ) + if int(ran) != ROUNDS * STEPS_PER_TASK: + failures.append( + f"cycle {index}: the tasks did {ran} steps, not {ROUNDS * STEPS_PER_TASK} — " + f"they were not all scheduled" + ) + + # The waiting half. The lower bound is what separates sleeping from + # returning immediately; the upper bound is what catches a wake that was + # missed and had to wait out another whole period. The slack is the + # scheduling granularity — a woken task runs at the next decision, which + # is up to one slice away, once per wake. + slept = re.search(r"sleep wakes (\d+)/(\d+) ticks (\d+) want (\d+) idle (\d+)", transcript) + if not slept: + step = re.search(r"sleep: .*", transcript) + failures.append(f"`sleep` did not report: {step.group(0) if step else 'nothing'}") + else: + woke, rounds, ticks, want, idle = (int(g) for g in slept.groups()) + if woke != rounds: + failures.append(f"the sleeper woke {woke} times, not {rounds}") + if ticks < want: + failures.append(f"{rounds} sleeps took {ticks} ticks, less than the {want} asked for") + if ticks > want + rounds * SLICE_TICKS + SLEEP_SLACK: + failures.append(f"{rounds} sleeps took {ticks} ticks, far more than the {want} asked for") + # Essentially every tick of the wait. Not all of them: the sleeper + # runs for a moment at each wake, and the shell wakes to check on it. + if idle < ticks * IDLE_SHARE // 100: + failures.append( + f"the machine was idle for {idle} of {ticks} ticks — a task that waits " + f"should not be costing anything" + ) + + if "exception #" in transcript: + fault = re.search(r"!! exception .*", transcript) + failures.append(f"the machine faulted: {fault.group(0)!r}") + + if failures: + print("\n".join(failures)) + print("--- transcript ---") + print(transcript) + return 1 + print(f"OK: a task slept for the time it asked for with the CPU halted throughout, " + f"and {ROUNDS} tasks ran to completion and returned through {CAPACITY} slots, " + f"giving back every slot and every stack page") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bare-metal-x86/check_focus.py b/bare-metal-x86/check_focus.py index 36e5137f..bd062c30 100755 --- a/bare-metal-x86/check_focus.py +++ b/bare-metal-x86/check_focus.py @@ -17,10 +17,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + # The top-left pixel of each window's frame. SHELL = (0, 0) PANE = (42 * 6, 0) @@ -39,9 +40,7 @@ def pixel(path, x, y): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") qemu = subprocess.Popen( diff --git a/bare-metal-x86/check_hpet.py b/bare-metal-x86/check_hpet.py new file mode 100644 index 00000000..3612c813 --- /dev/null +++ b/bare-metal-x86/check_hpet.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""The high precision timer, read by a driver written in LK. + +Every other clock here is counted or coarse. The PIT raises an interrupt and the +kernel counts them, so what it knows is "about a thousand of something per +second"; the RTC knows the date and nothing finer. The HPET states its own tick +period, in femtoseconds, in a register — so a reading becomes real time by +arithmetic instead of by a constant somebody measured once. + +What that makes this a test of, besides the timer: + +* **64-bit registers.** The capability word's period is its *high* 32 bits, so + `capabilities >> 32` has to be a logical shift; on an `i64` carrier the same + expression sign-extends as soon as the chip sets its own bit 63, and the + period comes back with no relation to time. +* **Unsigned division.** Ticks per second is `10^15 / period`, and ticks per + microsecond `10^9 / period`. Both operands are `u64`. +* **Subtraction across readings.** `later - earlier` at 64 bits is a distance + whatever the origin, including across the counter's own wrap. + +So the assertions below are not only "there is a timer". They are that the rate +the driver computed from the chip's own statement agrees with the ticks it +counted — two numbers that come apart if either is derived wrongly. + +One thing this cannot claim: that it would catch the *signed* forms. QEMU's +capability word has bit 63 clear, so an arithmetic `>> 32` answers what a +logical one does, and swapping them here changes nothing. What makes the driver +unsigned is the register, not this test; what this test catches is a rate that +disagrees with the chip, a counter that does not advance, and the two numbers +disagreeing with each other. +""" + +import os +import re +import socket +import subprocess +import sys +import tempfile +import time + +from kernel import kernel_image + +# What QEMU's HPET runs at: a 10 ns period, stated as 10,000,000 femtoseconds, +# so 10^15 / 10^7. Checked exactly rather than as a range — it is a fixed +# property of the emulated chip, so any other number means the driver read the +# wrong register or divided by the wrong thing. Verified to fire: pointing +# `hpet_period_femtoseconds` at the counter instead of the capability word +# reports 0 Hz here. +EXPECTED_HZ = 100_000_000 +# How far the driver's microsecond figure may be from the one recomputed here +# from its own tick count. Integer division truncates at both ends, so one is +# the floor of the disagreement rather than a tolerance for being wrong. +MAX_MICROSECOND_SLACK = 2 + + +def send_line(connection, text): + """Types `text` and Enter through the monitor, a key at a time.""" + names = {" ": "spc", "-": "minus", ".": "dot", "/": "slash"} + for character in text: + connection.sendall(f"sendkey {names.get(character, character)}\n".encode()) + time.sleep(0.25) + connection.sendall(b"sendkey ret\n") + time.sleep(3) + + +def main(): + image = kernel_image() + with tempfile.TemporaryDirectory() as workdir: + monitor = os.path.join(workdir, "monitor") + serial = os.path.join(workdir, "serial.txt") + qemu = subprocess.Popen( + [ + "qemu-system-x86_64", + "-kernel", image, + "-display", "none", + "-serial", "file:" + serial, + "-monitor", f"unix:{monitor},server,nowait", + ] + ) + try: + for _ in range(100): + if os.path.exists(monitor): + break + time.sleep(0.1) + time.sleep(2) + connection = socket.socket(socket.AF_UNIX) + connection.connect(monitor) + time.sleep(0.3) + connection.recv(65536) + # Twice: one reading proves the registers were decoded, two prove + # the counter *runs*. A stopped counter — an enable bit never set, + # or an address nothing decodes — gives the same tick delta both + # times, and usually zero. + for _ in range(2): + send_line(connection, "hpet") + connection.sendall(b"quit\n") + connection.close() + finally: + qemu.terminate() + qemu.wait(timeout=10) + + with open(serial, errors="replace") as handle: + transcript = handle.read() + + failures = [] + # Unanchored: the spinner writes to the same serial line from a timer + # interrupt, so a shell line can arrive with another task's byte stuck + # to either end of it. + readings = re.findall(r"hpet (\d+)Hz ticks (\d+) us (\d+)", transcript) + if len(readings) < 2: + note = "" + if "no timer" in transcript: + note = " (the driver reported no timer at the standard address)" + elif "would not start" in transcript: + note = " (the driver could not start the counter)" + failures.append(f"expected 2 readings, got {len(readings)}{note}") + + for hertz, ticks, micros in readings: + hertz, ticks, micros = int(hertz), int(ticks), int(micros) + if hertz != EXPECTED_HZ: + failures.append( + f"the driver computed {hertz} Hz from the chip's period, not {EXPECTED_HZ}" + ) + if ticks == 0: + failures.append("the counter did not advance between two reads of it") + continue + # The driver's own two numbers, checked against each other: the + # rate came from the chip's stated period, the microseconds from + # the ticks and that same period, so a mistake in either shows up + # as the two disagreeing while each still looks like a number. + expected_micros = ticks // (hertz // 1_000_000) if hertz >= 1_000_000 else 0 + if abs(micros - expected_micros) > MAX_MICROSECOND_SLACK: + failures.append( + f"{ticks} ticks at {hertz} Hz is {expected_micros} us, " + f"but the driver said {micros}" + ) + + if len(readings) >= 2 and readings[0][1] == readings[1][1]: + failures.append( + f"both readings counted exactly {readings[0][1]} ticks, which is a " + f"counter that is not running" + ) + + if "exception #" in transcript: + fault = re.search(r"!! exception .*", transcript) + failures.append(f"the machine faulted: {fault.group(0)!r}") + + if failures: + print("\n".join(failures)) + print("--- transcript ---") + print(transcript) + return 1 + print( + f"OK: found the HPET at its standard address, computed {EXPECTED_HZ} Hz from the " + f"period it states, and watched its 64-bit counter advance" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bare-metal-x86/check_interpreted_driver.py b/bare-metal-x86/check_interpreted_driver.py new file mode 100644 index 00000000..0c6c8725 --- /dev/null +++ b/bare-metal-x86/check_interpreted_driver.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""A driver written in LK, read off the disk, and run *interpreted*. + +`check_run.py` shows the kernel hosting an interpreter: a program it had never +seen computes an answer and prints it. This shows that the same interpreter +reaches the *hardware* — the program below drives the CMOS clock through port +I/O, on a machine whose kernel was compiled hours earlier and never told about +it. + +Why that is worth its own check: everything else here proves LK can be +*compiled* into the layer that drives devices. This proves the language can +drive them without being compiled at all — a driver you can edit on the disk and +re-run, on the machine, with no toolchain in sight. The `port_*` and `volatile_*` +intrinsics live in `VmContext`, not in the AOT lowering, so the executor has +them; nothing else asserts that. + +The answer is checkable rather than plausible, for the reason `check_clock.py` +gives: QEMU is told a fixed base time, and every field of it is a value a +missing BCD conversion would mangle (59 → 89, 19 → 25). The program does its own +BCD conversion — that is part of what is being tested — so a wrong reading is a +wrong number rather than a crash. +""" + +import io +import os +import re +import socket +import subprocess +import sys +import tarfile +import tempfile +import time + +from kernel import kernel_image + +SECTOR = 512 +SECTORS = 64 + +# Fixed, so the answer is known. Chosen as `check_clock.py` chooses it: every +# field is one a missing BCD conversion turns into a different number. +BASE = "2019-11-19T19:59:58" +BASE_HOUR, BASE_MINUTE = 19, 59 +# How far the guest's clock may have moved by the time the program runs: a boot, +# a command typed one key at a time, and the interpreter's own parse and run. +MAX_DRIFT_SECONDS = 300 + +# The driver. Deliberately the same job `drivers/rtc.lk` does compiled, so the +# comparison is between *how it runs*, not between two different programs. +DRIVER = b"""fn cmos(reg: Int) -> Int { + unsafe { port_out_u8(0x70, (reg | 0x80) as u8); }; + return unsafe { port_in_u8(0x71) } as Int; +} + +fn from_bcd(v: Int) -> Int { + return ((v / 16) as Int) * 10 + (v & 0x0f); +} + +let guard = 0; +while (guard < 100000 && (cmos(0x0a) & 0x80) != 0) { + guard = guard + 1; +} +let second = from_bcd(cmos(0x00)); +let minute = from_bcd(cmos(0x02)); +let hour = from_bcd(cmos(0x04)); +println("INTERPRETED RTC " + hour + " " + minute + " " + second); +""" + + +def send_line(connection, text): + names = {" ": "spc", "-": "minus", ".": "dot", "/": "slash"} + for character in text: + connection.sendall(f"sendkey {names.get(character, character)}\n".encode()) + time.sleep(0.25) + connection.sendall(b"sendkey ret\n") + # The whole front end runs — parse, type-check, execute — out of a bump + # allocator in a kernel built for size. + time.sleep(12) + + +def main(): + image = kernel_image() + with tempfile.TemporaryDirectory() as workdir: + disk = os.path.join(workdir, "disk.img") + monitor = os.path.join(workdir, "monitor") + serial = os.path.join(workdir, "serial.txt") + + archive = io.BytesIO() + with tarfile.open(fileobj=archive, mode="w", format=tarfile.USTAR_FORMAT) as tar: + info = tarfile.TarInfo("rtc.lk") + info.size = len(DRIVER) + info.mtime = 0 + tar.addfile(info, io.BytesIO(DRIVER)) + image_bytes = archive.getvalue() + with open(disk, "wb") as handle: + handle.write(image_bytes) + handle.write(b"\0" * (SECTOR * SECTORS - len(image_bytes) % (SECTOR * SECTORS))) + + qemu = subprocess.Popen( + [ + "qemu-system-x86_64", + "-kernel", image, + "-display", "none", + "-rtc", f"base={BASE}", + "-drive", f"file={disk},format=raw,if=ide", + "-serial", "file:" + serial, + "-monitor", f"unix:{monitor},server,nowait", + ] + ) + try: + for _ in range(100): + if os.path.exists(monitor): + break + time.sleep(0.1) + time.sleep(2) + connection = socket.socket(socket.AF_UNIX) + connection.connect(monitor) + time.sleep(0.3) + connection.recv(65536) + send_line(connection, "run rtc.lk") + connection.sendall(b"quit\n") + connection.close() + finally: + qemu.terminate() + qemu.wait(timeout=10) + + with open(serial, errors="replace") as handle: + transcript = handle.read() + # The spinner writes to the same serial line from a timer interrupt, so + # a line can arrive with another task's byte stuck into it. + cleaned = re.sub(r"[AB]", "", transcript) + + failures = [] + reading = re.search(r"INTERPRETED RTC (\d+) (\d+) (\d+)", cleaned) + if reading is None: + note = "" + stage = re.search(r"failed (\d+)", transcript) + if stage: + # -5 is "it ran and raised" (see `kernel_run` in src/main.rs). + note = f" (the interpreter reported stage {stage.group(1)})" + failures.append(f"the interpreted driver printed nothing{note}") + else: + hour, minute, second = (int(part) for part in reading.groups()) + if not (0 <= hour < 24 and 0 <= minute < 60 and 0 <= second < 60): + failures.append(f"read {hour}:{minute}:{second}, which is not a time") + else: + base = BASE_HOUR * 3600 + BASE_MINUTE * 60 + 58 + drift = hour * 3600 + minute * 60 + second - base + if not 0 <= drift <= MAX_DRIFT_SECONDS: + failures.append( + f"read {hour}:{minute:02d}:{second:02d}, which is {drift}s from the " + f"{BASE} QEMU was told to keep — the registers were decoded wrongly, or " + f"not read at all" + ) + + if "exception #" in transcript: + fault = re.search(r"!! exception .*", transcript) + failures.append(f"the machine faulted: {fault.group(0)!r}") + + if failures: + print("\n".join(failures)) + print("--- transcript ---") + print(transcript) + return 1 + print( + "OK: a driver read off the disk and run interpreted drove the CMOS clock through " + "port I/O, and read the time QEMU was told to keep" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bare-metal-x86/check_mouse.py b/bare-metal-x86/check_mouse.py index 613ad792..d9fca65d 100644 --- a/bare-metal-x86/check_mouse.py +++ b/bare-metal-x86/check_mouse.py @@ -24,10 +24,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + WIDTH, HEIGHT = 320, 200 POINTER = (0xFF, 0x40, 0x60) FRAME_IDLE = (0x20, 0x30, 0x40) @@ -64,9 +65,7 @@ def pointer_pixels(path): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") qemu = subprocess.Popen( diff --git a/bare-metal-x86/check_net.py b/bare-metal-x86/check_net.py new file mode 100644 index 00000000..180f8f8e --- /dev/null +++ b/bare-metal-x86/check_net.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +"""Real hardware: an Intel gigabit NIC, driven by LK, asking a question on the +wire and hearing the answer. + +The `edu` device in `check_pci.py` proves a driver can find a device, reach its +registers, make it compute, take its interrupt, and make it write RAM. It proves +it against a device invented to make that easy: a handful of registers and a DMA +engine that does one transfer when told to. + +A NIC is not commanded, it is *fed*. The driver and the card share two rings of +descriptors in memory, each side owning a moving index, and the whole protocol is +which one may advance and when — nothing is ever started by writing a start bit. +That is the shape of every driver for hardware built in the last thirty years, +and it is the thing the educational device cannot show. + +Two independent witnesses, which is the point of this script: + +* **The guest says so.** `net` prints the card's own MAC — read out of the + card's serial EEPROM, not a constant — and the hardware address that answered + for 10.0.2.2. Every field of the reply is checked inside the kernel: the + ethertype rules out other traffic, the operation code rules out the card + hearing its own request, the sender address rules out a reply about another + host, and the destination rules out a broadcast meant for someone else. + +* **The wire says so.** QEMU dumps the segment to a pcap, and this script parses + it. A driver that convinced itself would still have to put a well-formed frame + on the wire for the host's stack to answer it, and the capture is where that is + visible independently of anything the guest believes. + +The whole driver is `drivers/e1000.lk` and `drivers/arp.lk`. The board +contributes nothing to this path. +""" + +import os +import re +import socket +import struct +import subprocess +import sys +import tempfile +import time + +from kernel import kernel_image + +# QEMU's user-mode network: the guest is .15 and the gateway is .2. The +# gateway's hardware address is derived from its IP by slirp, which is why it can +# be written down here. +GATEWAY_IP = bytes([10, 0, 2, 2]) +GUEST_IP = bytes([10, 0, 2, 15]) +GATEWAY_MAC = "52550a000202" + +ETH_TYPE_ARP = 0x0806 +ARP_REQUEST = 1 +ARP_REPLY = 2 + + +def send_line(connection, text): + """Types `text` and Enter through the monitor, a key at a time.""" + names = {" ": "spc", "-": "minus", ".": "dot", "/": "slash"} + for character in text: + connection.sendall(f"sendkey {names.get(character, character)}\n".encode()) + time.sleep(0.25) + connection.sendall(b"sendkey ret\n") + time.sleep(4) + + +def frames(path): + """Every frame in a pcap, as bytes.""" + with open(path, "rb") as handle: + data = handle.read() + if len(data) < 24: + return [] + out = [] + offset = 24 + while offset + 16 <= len(data): + _, _, captured, _ = struct.unpack(" ([0-9a-f]{12}) idle (\d+) irqs (\d+)/(\d+)", transcript + ) + if len(answers) < 2: + step = re.search(r"net: (?!ok)\S+", transcript) + failures.append( + f"`net` did not complete: {step.group(0)!r}" if step + else f"`net` completed {len(answers)} of 2 exchanges" + ) + else: + for done, wanted, card_mac, gateway_mac, idle, irqs, waits in answers: + if done != wanted: + failures.append(f"only {done} of {wanted} exchanges completed") + if int(wanted) <= 8: + failures.append( + f"{wanted} exchanges does not reach either ring's wrap (8 entries)" + ) + if gateway_mac != GATEWAY_MAC: + failures.append( + f"the reply came from {gateway_mac}, not the gateway's {GATEWAY_MAC}" + ) + # The card's own interrupt reached a handler the driver + # installed. Not once per exchange — see below — but a driver + # that scored zero here would be a polling driver with an + # interrupt driver's comments, which is what this one was until + # the count was added. + # `irqs/waits`: how many of the waits the card itself ended. + # Not all of them — see below — but a driver that scored zero + # would be one whose handler is never reached, with the deadline + # carrying the whole thing, which is what this was until the + # count was added. + if int(waits) < 1: + failures.append(f"the driver never blocked: {waits} waits in {wanted} exchanges") + if int(irqs) < 1: + failures.append( + f"the card ended {irqs} of {waits} waits: the handler is never reached, " + f"and the deadline is carrying the whole driver" + ) + # And the machine was asleep while it waited. A poll scores zero: + # a polling task is runnable, and the rotation never falls + # through to idle while anything is runnable. + if int(idle) < 1: + failures.append( + f"the machine idled {idle} times during the exchange — the driver is " + f"spinning somewhere" + ) + # Only the parts that must not vary. The idle and interrupt counts + # are measurements of a running machine and differ run to run; the + # addresses and the exchange count are claims and must not. + stable = {(done, wanted, card, gw) for done, wanted, card, gw, _, _, _ in answers} + if len(stable) != 1: + failures.append(f"the two sessions disagreed: {stable}") + + # The pages come back. Two sessions of five pages each, and the count + # has to be the number it started at — not close to it. A free list that + # loses a page on a failure path, or a caller that releases in the wrong + # order and then cannot re-take its run, both show up as a number that + # drifts. + counts = re.findall(r"pages (\d+)/(\d+)", transcript) + if len(counts) < 2: + failures.append(f"expected a page count before and after, got {counts}") + elif len(set(counts)) != 1: + failures.append(f"the page count did not come back: {counts}") + + if "exception #" in transcript: + fault = re.search(r"!! exception .*", transcript) + failures.append(f"the machine faulted: {fault.group(0)!r}") + + # (2) What the wire says. The guest's own MAC comes from the transcript, + # so this checks the *card* put the address it reported into the frame — + # a driver that read the EEPROM wrong and framed consistently would pass + # the guest-side check on its own. + captured = frames(capture) + card_mac = bytes.fromhex(answers[0][2]) if answers else None + requests = [ + frame for frame in captured + if (parsed := arp_of(frame)) + and parsed[0] == ARP_REQUEST + and parsed[3] == GATEWAY_IP + and parsed[2] == GUEST_IP + and (card_mac is None or parsed[1] == card_mac) + ] + replies = [ + frame for frame in captured + if (parsed := arp_of(frame)) + and parsed[0] == ARP_REPLY + and parsed[2] == GATEWAY_IP + ] + # Two sessions of twelve. The wire is where a driver that reported + # success without transmitting would be caught. + wanted = 2 * int(answers[0][1]) if answers else 2 + if len(requests) < wanted: + failures.append( + f"the wire carried {len(requests)} well-formed ARP requests from the card, " + f"not {wanted} (of {len(captured)} frames)" + ) + if len(replies) < wanted: + failures.append( + f"the wire carried {len(replies)} ARP replies for the gateway, not {wanted}" + ) + # Ethernet's minimum is 60 bytes and an ARP request is 42. The card pads, + # but only because `TCTL_PSP` is set — without it the frame that leaves + # is a runt, which some paths carry and some discard. + for frame in requests: + if len(frame) < 60: + failures.append(f"the card transmitted a {len(frame)}-byte runt") + break + + if failures: + print("\n".join(failures)) + print("--- transcript ---") + print(transcript) + return 1 + print("OK: read the card's MAC out of its EEPROM, fed two descriptor rings " + "twelve exchanges past their wrap, put well-formed ARP requests on the " + "wire, received the gateway's replies, and gave every page back") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bare-metal-x86/check_pci.py b/bare-metal-x86/check_pci.py new file mode 100644 index 00000000..90fb4e16 --- /dev/null +++ b/bare-metal-x86/check_pci.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""A PCI device, driven by LK: found on the bus, reached through its BAR, made +to compute, made to write RAM by itself, and made to interrupt. + +Every other device this kernel drives is a fixed-port ISA relic. They are found +by knowing their address, spoken to with `in`/`out`, and they never touch memory +on their own — so none of them shows whether LK can write the kind of driver a +modern machine actually needs. This one does, in five claims: + +1. `pci` lists what is on the bus. The list has to *contain* the device this + script attached and not be a fixed set of lines: the script passes + `-device edu` and looks for `1234:11e8` in the output, at a slot QEMU chose. +2. The device's registers answer at the address configuration space gave. The + identification register is a known constant, so a driver that computed the + BAR wrong reads zeros or all-ones instead — and `edu` reports which step + failed, so "the BAR is wrong" and "the DMA is wrong" are different lines. +3. It computes. 5! comes back as 120 through the busy-bit protocol, which is + the shape of every real offload: write the operand, poll the device's own + status, read the answer back out of the same register. +4. It writes RAM. A pattern goes out to the device's internal memory and comes + back to a *different* address, which is the only one of the four that no + amount of port I/O could have done — the bytes at the second address can + only be there if the device's DMA engine put them there. +5. It interrupts, and the driver installs the gate for it *itself*, at the line + configuration space named — a kernel that installed every handler at startup + could not drive a device it had not been told about. `edu` is run twice: a + handler that acknowledged the device but not the chip, or the chip but not + the device, passes the first run and hangs or refaults on the second. + +The whole driver is `drivers/pci.lk` and `drivers/edu.lk`; the board contributes +nothing to this path. +""" + +import os +import re +import socket +import subprocess +import sys +import tempfile +import time + +from kernel import kernel_image + +# What QEMU's educational PCI device answers to. +EDU_ID = "1234:11e8" + + +def send_line(connection, text): + """Types `text` and Enter through the monitor, a key at a time.""" + names = {" ": "spc", "-": "minus", ".": "dot", "/": "slash"} + for character in text: + connection.sendall(f"sendkey {names.get(character, character)}\n".encode()) + time.sleep(0.25) + connection.sendall(b"sendkey ret\n") + time.sleep(2.5) + + +def main(): + image = kernel_image() + with tempfile.TemporaryDirectory() as workdir: + monitor = os.path.join(workdir, "monitor") + serial = os.path.join(workdir, "serial.txt") + qemu = subprocess.Popen( + [ + "qemu-system-x86_64", + "-kernel", image, + "-display", "none", + # The device under test. Nothing else in this run needs it, and + # `pci` has to find it rather than be told where it is. + "-device", "edu", + "-serial", "file:" + serial, + "-monitor", f"unix:{monitor},server,nowait", + ] + ) + try: + for _ in range(100): + if os.path.exists(monitor): + break + time.sleep(0.1) + time.sleep(2) + connection = socket.socket(socket.AF_UNIX) + connection.connect(monitor) + time.sleep(0.3) + connection.recv(65536) + send_line(connection, "pci") + send_line(connection, "edu") + # Twice. An interrupt path that leaves either end still asserting + # looks identical to a working one until it is asked again. + send_line(connection, "edu") + connection.sendall(b"quit\n") + connection.close() + finally: + qemu.terminate() + qemu.wait(timeout=10) + + with open(serial, errors="replace") as handle: + transcript = handle.read() + + failures = [] + + # (1) The listing found it, at whatever slot QEMU picked. + listing = [line for line in transcript.splitlines() if EDU_ID in line] + if not listing: + failures.append(f"`pci` did not list {EDU_ID}") + else: + # A memory BAR, of the size the device documents: 1 MiB. Checked + # because the size comes from *probing* the BAR — writing all ones + # and reading back which bits stuck — and a driver that skipped the + # restore afterwards would have unmapped the device it just sized. + if "@" not in listing[0]: + failures.append(f"`pci` listed {EDU_ID} with no memory BAR: {listing[0]!r}") + elif not re.search(r"\+00100000\b", listing[0]): + failures.append(f"`pci` sized the BAR wrong: {listing[0]!r}") + + # A bus with only the device under test on it would mean enumeration + # found one thing and stopped. QEMU's default machine has a host bridge + # and an ISA bridge before anything is attached. + # Unanchored: the spinner task writes to the same serial line from a + # timer interrupt, so a shell line can arrive with another task's byte + # stuck to either end of it. + counted = re.search(r"(\d+) devices", transcript) + if not counted: + failures.append("`pci` did not report how many devices it found") + elif int(counted.group(1)) < 3: + failures.append(f"`pci` found only {counted.group(1)} devices") + + # (2)(3)(4)(5) The driver reports which step failed, so this can too. + completed = re.findall(r"edu: ok irq \d+ bar [0-9a-f]{8}", transcript) + if len(completed) < 2: + step = re.search(r"edu: (?!ok)\S+", transcript) + failures.append( + f"`edu` did not complete: {step.group(0)!r}" if step + else f"`edu` completed {len(completed)} of 2 runs" + ) + + # An unclaimed vector is a general protection fault raised from inside an + # interrupt, and the 8259 delivers one on a line nothing is using + # whenever a request disappears before the CPU acknowledges it. Masking + # a line while its request is pending — which is what giving up on a + # wait does — is enough. Checked here because the machine goes on + # running afterwards and the transcript still looks plausible. + if "exception #" in transcript: + fault = re.search(r"!! exception .*", transcript) + failures.append(f"the machine faulted: {fault.group(0)!r}") + + if failures: + print("\n".join(failures)) + print("--- transcript ---") + print(transcript) + return 1 + print("OK: found a PCI device by enumeration, reached it through its BAR, " + "made it compute 5!, made it DMA a pattern into RAM, and took its " + "interrupt on a gate the driver installed for itself") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bare-metal-x86/check_run.py b/bare-metal-x86/check_run.py index 453a1761..d2125015 100644 --- a/bare-metal-x86/check_run.py +++ b/bare-metal-x86/check_run.py @@ -19,11 +19,12 @@ import os import socket import subprocess -import sys import tarfile import tempfile import time +from kernel import kernel_image + SECTOR = 512 SECTORS = 64 @@ -38,6 +39,34 @@ # `fn` with no body and no closing brace: a parse failure, not a runtime one, so # the kernel has to survive the stage that runs *before* any of the program does. BROKEN = b"fn (((\n" +# The language, not the library. +# +# `try`/`catch` is syntax: the parser desugars it into a call to a hidden +# `try$call` primitive, and the host has to have registered one. This kernel's +# did not — its global list was written before try/catch existed — so every +# program using it parsed, type-checked, and then failed at run time with +# "undefined function". The stage code said `failed 5`, which is "it ran and +# raised", and nothing said which name was missing. +# +# Here rather than in a Rust test because this is the only place the *bare* host +# is asked to run a program at all. +LANGUAGE = b"""try { + error("boom"); +} catch e { + println("CAUGHT"); +} +let squares = []; +for i in 1..=3 { + squares = squares.chain([i * i]); +} +println("SQUARES " + squares.len()); +// And one that is *not* caught, last, so the two assertions above still print. +// `RAISED HERE` is a string no part of the kernel contains, so seeing it come +// back proves the message travelled from the interpreter rather than being a +// stage code the kernel already knew how to print. +error("RAISED HERE"); +""" +LANGUAGE_ANSWERS = ["CAUGHT", "SQUARES 3"] def send_line(connection, text): @@ -53,14 +82,12 @@ def send_line(connection, text): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: disk = os.path.join(workdir, "disk.img") archive = io.BytesIO() with tarfile.open(fileobj=archive, mode="w", format=tarfile.USTAR_FORMAT) as tar: - for name, body in [("sq.lk", PROGRAM), ("bad.lk", BROKEN)]: + for name, body in [("sq.lk", PROGRAM), ("bad.lk", BROKEN), ("lang.lk", LANGUAGE)]: info = tarfile.TarInfo(name) info.size = len(body) info.mtime = 0 @@ -99,6 +126,7 @@ def main(): send_line(connection, "run sq.lk") send_line(connection, "run nope.lk") send_line(connection, "run bad.lk") + send_line(connection, "run lang.lk") # The shell must still be answering afterwards. send_line(connection, "keys") connection.sendall(b"quit\n") @@ -114,6 +142,25 @@ def main(): failures.append("the program's own output is missing") if ANSWER not in transcript: failures.append(f"the interpreter did not compute {ANSWER}") + # A raise says *what* raised, not only that something did. + # + # `bad.lk` fails to parse, so it reports stage 3 and nothing else — a + # parse failure has no message to carry. A program that runs and raises + # does, and the kernel prints it through the same console the program + # would have printed through. Without that, "failed 5" reads the same + # whether the fault is the program's or the host's, which is exactly the + # confusion that hid a missing `error` global for a whole round. + if "RAISED HERE" not in transcript: + failures.append( + "no `run: …` line — a program that raises must say what raised, not only that " + "it did" + ) + for answer in LANGUAGE_ANSWERS: + if answer not in transcript: + failures.append( + f"the interpreter did not print {answer!r} — the bare host is missing a " + f"language primitive, not a module" + ) if "no file" not in transcript: failures.append("`run nope.lk` did not report a missing file") # -3 is the parse stage (see `kernel_run` in src/main.rs). diff --git a/bare-metal-x86/check_screen.py b/bare-metal-x86/check_screen.py index 3ec2233d..88679128 100755 --- a/bare-metal-x86/check_screen.py +++ b/bare-metal-x86/check_screen.py @@ -16,10 +16,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + WIDTH, HEIGHT = 320, 200 # What `program.lk` draws: a dark background, a title in amber at cell (1,1), # and a prompt in green at cell (1,3). The checked points are the background @@ -45,9 +46,7 @@ def read_ppm(path): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") shot = os.path.join(workdir, "screen.ppm") diff --git a/bare-metal-x86/check_shell.py b/bare-metal-x86/check_shell.py index d54dc71e..4eb3724f 100755 --- a/bare-metal-x86/check_shell.py +++ b/bare-metal-x86/check_shell.py @@ -15,10 +15,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + # `x` then backspace, so the echo shows the correction; then enough newlines # to push the title off the top, which is what proves scrolling rather than # wrapping. @@ -28,6 +29,11 @@ KEYS = ( ["h", "e", "l", "x", "backspace", "p", "ret"] + ["e", "c", "h", "o", "spc", "l", "k", "ret"] + # Shift is a *state*, not a character: the handler tracks its press and + # release, and applies it to whatever key arrives between them. Upper case + # and shifted punctuation take different rules — caps lock affects letters + # only — so both are typed here. + + ["e", "c", "h", "o", "spc", "shift-a", "shift-1", "shift-minus", "ret"] + ["p", "a", "g", "e", "ret"] + ["p", "a", "g", "e", "ret"] + ["m", "e", "m", "ret"] @@ -43,17 +49,51 @@ + ["ret"] * 22 + ["e", "x", "i", "t", "ret"] ) +# What the *other* writers on this serial line leave behind. +# +# Three tasks share it: the timer prints a '.' every half-second, and the two +# ring-3 tasks print 'A' and 'B' forever. None of them ends a line, so their +# marks land in the middle of whatever the shell is saying — including in the +# middle of a number. Removing them is lossless for a decimal counter, because +# none of them is a digit. +# +# The 'A'/'B' half was added after this check started failing on a build that +# only changed *timing*: a device access became an instruction instead of a +# call, the shell got faster, and the ring-3 tasks landed a byte inside the +# counter line for the first time. The counters agreed — `B5106885/5106885` — +# and the check said they had not been printed at all. A check whose answer +# depends on which task wins a race is not checking what it claims to. +OTHER_WRITERS = ".AB" # Lines the shell must answer with. `help` lists the commands it knows, `echo` # repeats its argument, `exit` says goodbye — each proving a different part: # the byte-wise command match, the argument tail, and the loop ending. EXPECTED_LINES = [ - "help clear echo keys mem page sync yield win time disk cat run heap exit", + "help clear echo keys mem page sync yield win time disk cat run heap user ls exit", "lk", + "A!_", # Two pages handed out in order, from the range the loader reported. The - # addresses are what proves the allocator rather than a counter. They start - # past the kernel heap's sixteen pages, which startup took first. - "02010000", - "02011000", + # addresses are what proves the allocator rather than a counter — a counter + # would print two numbers just as happily. + # + # They start past what startup already took, which as of now is: + # + # 16 pages the kernel heap + # 8 pages two user address spaces, four page tables each + # 40 pages five task stacks, eight pages each + # ------ + # 64 pages = 0x40000, so the first free page is 0x02040000 + # + # Spelled out because these two numbers have moved four times, once per + # thing that stopped being reserved somewhere fixed and started being + # allocated like anything else. Recomputing them should be arithmetic, not + # archaeology. + # + # The fifth stack is the idle task's. It is spawned before anything else so + # that there is somewhere to go the moment a task can block — a scheduler + # with every task waiting and no idle task would resume one of the waiting + # ones, which is running a task it has just been told is not runnable. + "02040000", + "02041000", # The heap allocates three blocks, frees the middle one, allocates one that # only fits the hole, then frees everything. All three numbers are claims: # the block count must come *back* to what it was (holes joined on both @@ -71,9 +111,7 @@ def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") serial = os.path.join(workdir, "serial.txt") @@ -121,13 +159,20 @@ def main(): # both bump, under one critical section. They can only differ if an # increment read a stale value — which is exactly what happens without the # section, and what nothing else in the system can cause. - pair = next((line for line in output.splitlines() if "/" in line and line.strip("./0123456789") == ""), None) + def counters(line): + """The line with the other tasks' marks removed, if it is a counter pair.""" + stripped = "".join(c for c in line if c not in OTHER_WRITERS) + parts = stripped.split("/") + return parts if len(parts) == 2 and all(p.isdigit() and p for p in parts) else None + + pair = next((c for c in map(counters, output.splitlines()) if c is not None), None) if pair is None: raise SystemExit("shell: `sync` printed no counter pair") - # Every dot, not just the ends: the timer prints one on whatever line is - # current, and `31.710/31712` would otherwise split into two counters that - # differ — a passing run reported as a lost update. - left, right = pair.replace(".", "").split("/") + # The marks are removed everywhere in the line, not just at its ends: they + # land wherever the writer happened to be, and `31.710/31712` would + # otherwise split into two counters that differ — a passing run reported as + # a lost update. + left, right = pair if left != right: raise SystemExit(f"shared counters diverged ({left} != {right}): an update was lost") if int(left) == 0: diff --git a/bare-metal-x86/check_spawn.py b/bare-metal-x86/check_spawn.py index 6880f809..b8588112 100644 --- a/bare-metal-x86/check_spawn.py +++ b/bare-metal-x86/check_spawn.py @@ -20,10 +20,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + # The two windows, as `program.lk` places them: the spinner at the top right and # the clock three rows below it. Sampled inside their frames. SPINNER = (254, 2, 30, 8) @@ -51,9 +52,7 @@ def region(path, rect): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") serial = os.path.join(workdir, "serial.txt") @@ -88,6 +87,14 @@ def screenshot(name): # can land on the same phase and show no difference on a machine # where everything is working. Asking "did it ever change" over a # series is the same claim without the coin flip. + # + # It is still wall-clock, though, and that makes it the one check + # here sensitive to what else the *host* is doing: with a compile + # running alongside, four samples 1.4s apart have all landed inside + # one spinner phase and reported both windows frozen. Three such + # failures in a row once looked exactly like a real regression and + # were not — the same build passed 4/4 on an idle machine. Re-run a + # failure here on a quiet host before believing it. shots = [screenshot(f"sample{i}.ppm") for i in range(4)] connection.sendall(b"quit\n") connection.close() diff --git a/bare-metal-x86/check_stack.py b/bare-metal-x86/check_stack.py index 517ebcde..8cf5ad7d 100644 --- a/bare-metal-x86/check_stack.py +++ b/bare-metal-x86/check_stack.py @@ -22,10 +22,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + FRAME_FOCUSED = (0xFF, 0xC0, 0x40) FRAME_IDLE = (0x20, 0x30, 0x40) # The clock window's title bar, as `program.lk` places it, and where it is @@ -57,9 +58,7 @@ def frame_pixels_on_row(path, y): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") qemu = subprocess.Popen( diff --git a/bare-metal-x86/check_tasks.py b/bare-metal-x86/check_tasks.py index 4ddbda82..0685cdad 100755 --- a/bare-metal-x86/check_tasks.py +++ b/bare-metal-x86/check_tasks.py @@ -14,10 +14,11 @@ import os import socket import subprocess -import sys import tempfile import time +from kernel import kernel_image + # The spinner's cell: the top-left of its window, which starts at # `text_columns() - 11` of 53 columns. CELL_X, CELL_Y = 42 * 6, 0 @@ -60,9 +61,7 @@ def glyph_at(path): def main(): - image = sys.argv[1] if len(sys.argv) > 1 else ( - "target/x86_64-unknown-none/release/lk-bare-metal-x86.multiboot" - ) + image = kernel_image() with tempfile.TemporaryDirectory() as workdir: monitor = os.path.join(workdir, "monitor") qemu = subprocess.Popen( diff --git a/bare-metal-x86/check_user.py b/bare-metal-x86/check_user.py new file mode 100644 index 00000000..429fb7bd --- /dev/null +++ b/bare-metal-x86/check_user.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Boot, drop to ring 3, and check that the boundary is the machine's, not the +program's good manners. + +Three claims, and the second is the one that makes the first mean anything: + +1. **A ring-3 program can talk to the kernel**, and the kernel does not believe + what it is told. `USER` comes out a byte at a time through `int 0x80` — the + only vector whose gate has DPL 3. `str` comes out through a call that takes a + *pointer*, which the kernel checks against the bounds of the user section + before following. `N` is the same call handed the kernel's own address and + refusing it; a `Y` would mean the kernel printed its own memory on request. +2. **It cannot touch the kernel's memory** — including the kernel *next to it*. + Immediately after, it reads `0x100010`: the kernel's first instruction, in + the same 2 MiB as the program itself. The CPU faults with a user-mode error + code. That address is the point: while the first 2 MiB was one + user-accessible page, this read succeeded and told nobody, and only a + 4 KiB-granular table makes it the fault it should be. + +3. **Two ring-3 tasks, preempted, in address spaces of their own.** Both are + spawned at boot, both never yield, and both keep a stack at the *same* + virtual address — `0x4000_0000`, which the kernel's own space maps to memory + this machine does not have. Each writes one letter into its stack and prints + what it reads back for ever: `A` and `B`. Sharing a space would mean the + second write landed on the first's page and both printed the same letter. + That the shell answers in the middle of it is the preemption claim; the + one-shot program above cannot show it, having no way back at all. + +The error code is checked, not just the address: bit 2 is what says the access +came from ring 3. A fault at that address from ring 0 would be a kernel bug +with the same `cr2` and a different meaning. +""" + +import os +import socket +import subprocess +import tempfile +import time + +from kernel import kernel_image + +# What the ring-3 program prints through the syscall, and where it then tries to +# write. Both are in `src/user.rs`. +# `USER` is the byte-at-a-time call; `str` is the same text through the call +# that takes a *pointer*, which the kernel checked before following; `N` is that +# call refusing a kernel pointer. A `Y` there would mean the kernel read its own +# memory because a user task asked it to. +GREETING = "USERstrN" +# The kernel's first instruction — in the same 2 MiB as the user program, which +# is what makes it the interesting address to be refused. +FORBIDDEN = "cr2=0000000000100010" +# present | user: a read, from ring 3, of a page that is there but not marked +# user-accessible. Bit 2 is the whole claim — the same fault from ring 0 would +# be a kernel bug with the same `cr2`. +USER_WRITE_FAULT = "error=0000000000000005" + + +def main(): + image = kernel_image() + with tempfile.TemporaryDirectory() as workdir: + monitor = os.path.join(workdir, "monitor") + serial = os.path.join(workdir, "serial.txt") + qemu = subprocess.Popen( + [ + "qemu-system-x86_64", + "-kernel", image, + "-display", "none", + "-serial", "file:" + serial, + "-monitor", f"unix:{monitor},server,nowait", + ] + ) + try: + for _ in range(100): + if os.path.exists(monitor): + break + time.sleep(0.1) + time.sleep(2.5) + connection = socket.socket(socket.AF_UNIX) + connection.connect(monitor) + time.sleep(0.3) + connection.recv(65536) + # First: does the shell still answer while a ring-3 task runs? + for key in ["k", "e", "y", "s", "ret"]: + connection.sendall(f"sendkey {key}\n".encode()) + time.sleep(0.3) + time.sleep(2) + with open(serial, errors="replace") as handle: + while_running = handle.read() + + for key in ["u", "s", "e", "r", "ret"]: + connection.sendall(f"sendkey {key}\n".encode()) + time.sleep(0.3) + # The fault ends the excursion; the reporter prints and halts. + time.sleep(4) + connection.sendall(b"quit\n") + connection.close() + finally: + qemu.terminate() + qemu.wait(timeout=10) + + with open(serial, errors="replace") as handle: + transcript = handle.read() + + failures = [] + # The ring-3 task's own output, and the shell answering in the middle + # of it: either alone proves nothing. + if while_running.count("A") < 3 or while_running.count("B") < 3: + failures.append("both ring-3 tasks did not run") + # Interleaved, not one after the other: a task that ran to completion + # before the other started would say nothing about preemption. + if "AB" not in while_running and "BA" not in while_running: + failures.append("the two ring-3 tasks never interleaved") + if "\n5\n" not in while_running and "\n6\n" not in while_running: + failures.append("the shell did not answer while the ring-3 task was running") + if GREETING not in transcript: + failures.append( + f"expected {GREETING!r}: the syscalls, the checked pointer, and the refusal of a kernel one" + ) + if "#PF page fault" not in transcript: + failures.append("the forbidden write did not fault: the ring boundary is not enforced") + if FORBIDDEN not in transcript: + failures.append(f"the fault was not at the kernel's own code ({FORBIDDEN})") + if USER_WRITE_FAULT not in transcript: + failures.append(f"the fault was not a ring-3 access ({USER_WRITE_FAULT})") + if failures: + raise SystemExit( + "ring 3 wrong:\n " + "\n ".join(failures) + "\n--- serial ---\n" + transcript[-400:] + ) + print("OK: ring 3 spoke through a syscall, was preempted while never yielding, " + "and was refused the kernel page next to its own") + + +if __name__ == "__main__": + main() diff --git a/bare-metal-x86/drivers/arp.lk b/bare-metal-x86/drivers/arp.lk new file mode 100644 index 00000000..9d177806 --- /dev/null +++ b/bare-metal-x86/drivers/arp.lk @@ -0,0 +1,180 @@ +use { mmio_layout } from "layout"; + +// Ethernet framing and ARP, which is the smallest exchange that proves a NIC +// works in both directions. +// +// Smallest, and also the only one available: a machine with no IP stack cannot +// send anything that expects a reply, except this. ARP is one frame out, one +// frame back, answered by anything on the segment that owns the address asked +// for — including the host side of an emulated network, which is what makes it +// checkable without a second machine. +// +// It is also the one exchange where a *wrong* answer is distinguishable from no +// answer. A reply carries the address that was asked for and the hardware +// address that owns it, so a driver that received a frame at random cannot pass: +// the operation code has to be 2, the sender address has to be the one asked +// about, and the target has to be the card that asked. + +// Ethernet: destination, source, type. Everything after is the payload. +// +// The offsets are the widths added up, by the compiler. Written out by hand they +// are four numbers that have to agree, and the one that goes wrong is the header +// size — because it is the only one nothing else checks. +mmio_layout! { + ETH_DEST: 6, + ETH_SOURCE: 6, + ETH_TYPE: 2, + => ETH_HEADER_SIZE +} + +const ETH_TYPE_ARP = 0x0806; + +// ARP over Ethernet and IPv4, which fixes every length in the packet. +// +// Ten fields, of five different widths, and the two `1`s in the middle are what +// makes writing these by hand a mistake waiting to happen: every offset after +// them is odd-looking and correct, which is exactly the shape a reader checking +// by eye will "fix". +mmio_layout! { + ARP_HTYPE: 2, + ARP_PTYPE: 2, + ARP_HLEN: 1, + ARP_PLEN: 1, + ARP_OPER: 2, + ARP_SENDER_MAC: 6, + ARP_SENDER_IP: 4, + ARP_TARGET_MAC: 6, + ARP_TARGET_IP: 4, + => ARP_SIZE +} + +const ARP_HTYPE_ETHERNET = 1; +const ARP_PTYPE_IPV4 = 0x0800; +const ARP_OPER_REQUEST = 1; +const ARP_OPER_REPLY = 2; + +const MAC_SIZE = 6; +const IP_SIZE = 4; + +// A whole ARP request is 42 bytes. +const ARP_FRAME_SIZE = ETH_HEADER_SIZE + ARP_SIZE; + +// And Ethernet's minimum frame is 60, so an ARP request goes out padded. +// +// Padded *here*, by the sender, and not left to the card. The controller has a +// pad-short-packets bit and this driver sets it, but that is a property of one +// controller: QEMU's model of this card does not implement it, and a driver that +// depended on it would put 42-byte runts on the wire — carried by some paths, +// discarded by others, and passing every test written against the emulator. +// A frame is the frame layer's business. +const ETH_MIN_FRAME = 60; + +// Named for the frame rather than for the operation: `byte_at` is what a +// buffer-walking helper wants to be called, and the tar reader next door got +// there first. Bundling flattens every module into one namespace, so the second +// one to use a name is the one that has to say what it is about. +fn frame_byte(address: Int) -> Int { + let value = unsafe { volatile_read_u8(address as *mut u8) }; + return value as Int; +} + +fn frame_set_byte(address: Int, value: Int) { + unsafe { volatile_write_u8(address as *mut u8, value as u8); }; +} + +// Network byte order is big-endian, which is the opposite of everything else on +// this machine. Written out rather than done inline at each of the six places a +// 16-bit field appears, because getting one of them backwards produces a packet +// that is *almost* right and is answered by nobody. +fn set_be16(address: Int, value: Int) { + frame_set_byte(address, ((value / 0x100) as Int) & 0xff); + frame_set_byte(address + 1, value & 0xff); +} + +fn be16_at(address: Int) -> Int { + return frame_byte(address) * 0x100 + frame_byte(address + 1); +} + +fn copy_bytes(destination: Int, source: Int, count: Int) { + for index in 0..count { + frame_set_byte(destination + index, frame_byte(source + index)); + } +} + +fn bytes_equal(a: Int, b: Int, count: Int) -> Bool { + for index in 0..count { + if (frame_byte(a + index) != frame_byte(b + index)) { + return false; + } + } + return true; +} + +fn fill_bytes(address: Int, value: Int, count: Int) { + for index in 0..count { + frame_set_byte(address + index, value); + } +} + +// Builds an ARP request into `frame` and answers its length. +// +// `sender_mac` and the two IPs are addresses of the bytes, not the values: a MAC +// is six bytes and an IPv4 address is four, and neither fits the machine word +// this language counts in without a packing convention that would then have to +// be undone at every use. +fn arp_build_request(frame: Int, sender_mac: Int, sender_ip: Int, target_ip: Int) -> Int { + // Broadcast: the whole point of the request is that the driver does not know + // which hardware address to send it to. + fill_bytes(frame + ETH_DEST, 0xff, MAC_SIZE); + copy_bytes(frame + ETH_SOURCE, sender_mac, MAC_SIZE); + set_be16(frame + ETH_TYPE, ETH_TYPE_ARP); + + let arp = frame + ETH_HEADER_SIZE; + set_be16(arp + ARP_HTYPE, ARP_HTYPE_ETHERNET); + set_be16(arp + ARP_PTYPE, ARP_PTYPE_IPV4); + frame_set_byte(arp + ARP_HLEN, MAC_SIZE); + frame_set_byte(arp + ARP_PLEN, IP_SIZE); + set_be16(arp + ARP_OPER, ARP_OPER_REQUEST); + copy_bytes(arp + ARP_SENDER_MAC, sender_mac, MAC_SIZE); + copy_bytes(arp + ARP_SENDER_IP, sender_ip, IP_SIZE); + // The field being asked about, so zero rather than broadcast: this is not + // where the frame is going, it is the question. + fill_bytes(arp + ARP_TARGET_MAC, 0, MAC_SIZE); + copy_bytes(arp + ARP_TARGET_IP, target_ip, IP_SIZE); + + // Zeroed rather than merely reserved: the padding goes on the wire, and a + // frame that carried whatever was in the buffer would leak it to the + // segment. Ethernet's own trailer conventions are why a receiver ignores + // these bytes, not a reason to leave them undefined. + fill_bytes(frame + ARP_FRAME_SIZE, 0, ETH_MIN_FRAME - ARP_FRAME_SIZE); + return ETH_MIN_FRAME; +} + +// Whether `frame` is a reply to a request for `target_ip`, addressed to +// `our_mac`. If so, the answering hardware address is copied into `out`. +// +// Four things checked, and each rules out a different way of passing without +// having worked: the ethertype rules out any other traffic on the segment, the +// operation rules out this card hearing its *own* request looped back, the +// sender address rules out a reply about some other host, and the destination +// rules out a broadcast that was never meant for this card. +fn arp_match_reply(frame: Int, length: Int, our_mac: Int, target_ip: Int, out: Int) -> Bool { + if (length < ARP_FRAME_SIZE) { + return false; + } + if (be16_at(frame + ETH_TYPE) != ETH_TYPE_ARP) { + return false; + } + let arp = frame + ETH_HEADER_SIZE; + if (be16_at(arp + ARP_OPER) != ARP_OPER_REPLY) { + return false; + } + if (!bytes_equal(arp + ARP_SENDER_IP, target_ip, IP_SIZE)) { + return false; + } + if (!bytes_equal(frame + ETH_DEST, our_mac, MAC_SIZE)) { + return false; + } + copy_bytes(out, arp + ARP_SENDER_MAC, MAC_SIZE); + return true; +} diff --git a/bare-metal-x86/drivers/e1000.lk b/bare-metal-x86/drivers/e1000.lk new file mode 100644 index 00000000..ce4a1529 --- /dev/null +++ b/bare-metal-x86/drivers/e1000.lk @@ -0,0 +1,568 @@ +// An Intel 82540EM gigabit controller, which is real hardware. +// +// The `edu` device next door proves a driver can find a device, reach its +// registers, and make it touch memory. It proves it against a device invented to +// make that easy: four registers, one buffer, and a DMA engine that does one +// transfer at a time when told to. +// +// This is what the same four things look like on a card people actually shipped, +// and the difference is not size. A NIC is not commanded, it is *fed*: the +// driver and the device share two rings of descriptors in memory, each side +// owning a moving part of the ring, and the whole protocol is which index each +// one may advance and when. Nothing is ever waited on by writing a start bit — +// the card is running continuously, and a driver that thinks in transfers +// instead of rings will produce something that works once. +// +// Three things here have no counterpart in the educational device, and each is a +// thing the driver layer has to be able to express: +// +// * A ring of descriptors in physical memory, where the *device* writes back +// into structures the driver laid out, and ownership is a bit in each entry. +// * The card's own MAC address, which is not in a register but in a serial +// EEPROM reached through a register — a protocol inside a protocol. +// * Link state, which the driver has to ask for and then wait for; a frame +// handed to a card whose link is down is accepted and dropped. +// +// And it interrupts, which is the fourth. That was left out at first on the +// grounds that a NIC driven by polling its own rings is a complete driver — true, +// and also the reason nobody ships one: a poll is a CPU held at full tilt for a +// wait measured in microseconds, and a driver that does it has taken the machine +// away from everything else to look at a ring that has not changed. +// +// What makes it worth doing here rather than being a repeat of `edu.lk` is that +// this card's interrupt is not a doorbell. `ICR` is read-to-clear and holds +// *why*; the mask is a separate register from the cause; and the receive +// interrupt is on a timer the driver has to set to zero or the card sits on a +// packet waiting for company. Each of those is a way to write a handler that +// works once. + +use { mmio_layout } from "layout"; +use { pci_find, pci_bar, pci_command_set, + PCI_COMMAND_MEMORY, PCI_COMMAND_BUS_MASTER } from "pci"; + +const E1000_VENDOR = 0x8086; +const E1000_DEVICE = 0x100e; + +// ------------------------------------------------------------------ registers + +// The offsets stay `Int`: an offset is added to an address, and an address is +// not a register value. What follows them — the *contents* of those registers — +// says `u32`, because that is what the hardware defines and because a constant +// that has to be cast at every use is a constant that will be cast wrongly once. +const REG_CTRL = 0x0000; +const REG_STATUS = 0x0008; +const REG_EERD = 0x0014; +// Cause, mask-set and mask-clear are three registers, not one. `ICR` is +// read-to-clear: reading it says what happened *and* acknowledges it, so a +// handler that forgets the read leaves the card asserting its line and the +// machine re-enters the handler for ever. +const REG_ICR = 0x00c0; +const REG_IMS = 0x00d0; +const REG_IMC = 0x00d8; + +// The receive delay timer. Zero means "tell me about a packet when it arrives" +// — anything else is the card waiting to see whether another one turns up so it +// can report them together. Sensible for throughput, and for a driver waiting on +// one reply it is a delay with no upper bound that looks exactly like a dropped +// packet. +const REG_RDTR = 0x2820; + +// The other three delays, which is the part a datasheet buries. `RDTR` is the +// one everybody names; the *absolute* receive delay and the throttle apply on +// top of it, and a card with any of them set holds an interrupt back for a +// window the driver never asked for. A driver waiting on a single reply then +// waits out a timer it did not set — which is indistinguishable from a lost +// packet, and is what a poll-with-a-deadline quietly papers over. +const REG_RADV = 0x282c; +const REG_ITR = 0x00c4; +const REG_TIDV = 0x3820; + +// The causes worth being told about: a packet arrived, the ring is running low, +// and the ring overflowed. The last two are not needed to receive, but a driver +// that unmasks only the good news finds out about the ring being full by never +// being told anything again. +const ICR_RXT0: u32 = 0x00000080; +const ICR_RXDMT0: u32 = 0x00000010; +const ICR_RXO: u32 = 0x00000040; +const REG_RCTL = 0x0100; +const REG_TCTL = 0x0400; +const REG_TIPG = 0x0410; + +const REG_RDBAL = 0x2800; +const REG_RDBAH = 0x2804; +const REG_RDLEN = 0x2808; +const REG_RDH = 0x2810; +const REG_RDT = 0x2818; + +const REG_TDBAL = 0x3800; +const REG_TDBAH = 0x3804; +const REG_TDLEN = 0x3808; +const REG_TDH = 0x3810; +const REG_TDT = 0x3818; + +// The multicast table: 128 dwords the card indexes by a hash of the destination +// address. Left zero, but it has to be *written* zero — the card powers on with +// whatever was there, and a stale table is a card that accepts multicast traffic +// nobody asked for. +const REG_MTA = 0x5200; +const MTA_ENTRIES = 128; + +// The card's own address, as the receive filter uses it. Two registers: four +// bytes and then two, with the valid bit on top. +const REG_RAL = 0x5400; +const REG_RAH = 0x5404; +const RAH_VALID: u32 = 0x80000000; + +// CTRL: bring the link up, and let the card negotiate speed for itself. +const CTRL_SLU: u32 = 0x00000040; +const CTRL_ASDE: u32 = 0x00000020; +// STATUS: the link is up. Asked for, not assumed — the card accepts frames on a +// down link and drops them, which is a driver that works in every way except +// that nothing arrives. +const STATUS_LINK_UP: u32 = 0x00000002; + +// EERD: an EEPROM read is a register write and a poll. The address goes in the +// high half of the low word, the data comes back in the high word. +const EERD_START: u32 = 0x00000001; +const EERD_DONE: u32 = 0x00000010; + +// TCTL: enable, pad short packets, and the two timing constants the datasheet +// gives for this part. `PSP` matters more than it looks — Ethernet has a 60-byte +// minimum and an ARP frame is 42, so without it the card transmits a runt that +// every switch on the path discards. +const TCTL_EN: u32 = 0x00000002; +const TCTL_PSP: u32 = 0x00000008; +const TCTL_CT: u32 = 0x000000f0; // collision threshold 15, at bit 4 +const TCTL_COLD: u32 = 0x00040000; // collision distance 64, at bit 12 +// Inter-packet gap, as the datasheet's copper values: 10, 8, 6. +const TIPG_VALUE: u32 = 0x0060200a; + +// RCTL: enable, accept broadcast, and strip the CRC so a received length is the +// frame's own length. Unicast and multicast promiscuous as well, because this +// kernel has no address filter to configure and a driver that silently dropped +// what it was not expecting would be indistinguishable from one that received +// nothing. +const RCTL_EN: u32 = 0x00000002; +const RCTL_UPE: u32 = 0x00000008; +const RCTL_MPE: u32 = 0x00000010; +const RCTL_BAM: u32 = 0x00008000; +const RCTL_SECRC: u32 = 0x04000000; +// Buffer size 2048, which is bits 17:16 = 00. Named as zero on purpose: a +// constant that is zero still has to appear in the value written, or the next +// person adds a size and cannot find where the old one was. +const RCTL_BSIZE_2048: u32 = 0x00000000; + +// ---------------------------------------------------------------- descriptors + +// Both rings are arrays of 16-byte descriptors. The counts are small and powers +// of two because the card wraps the index itself and requires the ring length in +// bytes to be a multiple of 128. +const DESC_SIZE = 16; +const TX_COUNT = 8; +const RX_COUNT = 8; +const RX_BUFFER_SIZE = 2048; + +// Transmit descriptor: address, then length, then the command byte that says +// what the card should do with it. +const TX_LENGTH_OFFSET = 8; +const TX_CMD_OFFSET = 11; +const TX_STATUS_OFFSET = 12; + +const TX_CMD_EOP = 0x01; // this descriptor ends the packet +const TX_CMD_IFCS = 0x02; // insert the frame check sequence +const TX_CMD_RS = 0x08; // report status, which is what makes DD appear +const TX_STATUS_DD = 0x01; // the card is done with this descriptor + +// Receive descriptor: address, then the length the card wrote, then its status. +const RX_LENGTH_OFFSET = 8; +const RX_STATUS_OFFSET = 12; + +const RX_STATUS_DD = 0x01; +const RX_STATUS_EOP = 0x02; + +// ---------------------------------------------------------------- driver state + +// Everything the driver remembers, in a block the caller supplies. +// +// A block rather than globals, the same as every other table in this kernel: +// a driver that picks its own address is a driver that collides with the next +// one, and a driver with globals cannot be instantiated twice for a machine with +// two cards. +mmio_layout! { + STATE_MMIO: 8, + STATE_TX_RING: 8, + STATE_RX_RING: 8, + STATE_TX_TAIL: 8, + STATE_RX_HEAD: 8, + STATE_RX_BUFFERS: 8, + => STATE_SIZE +} + +fn state_get(state: Int, offset: Int) -> Int { + let value = unsafe { volatile_read_u64((state + offset) as *mut u64) }; + return value as Int; +} + +fn state_set(state: Int, offset: Int, value: Int) { + unsafe { volatile_write_u64((state + offset) as *mut u64, value as u64); }; +} + +// --------------------------------------------------------------------- access + +// Registers are 32 bits and volatile, for the same reason the educational +// device's are: a read is a request to the card, not a load of something already +// known, and a poll the compiler hoisted is a wait that never ends. +// A register is 32 bits wide, and now says so. +// +// The address stays an `Int` — it is an address, and arithmetic on it is +// ordinary — while the *value* is a `u32`, which is what the hardware defines +// it as. That is the split worth making: a register value that has picked up a +// 33rd bit is a bug the width catches, and a driver that reads one into an +// `Int` has agreed to find out later. +fn reg_read(mmio: Int, offset: Int) -> u32 { + return unsafe { volatile_read_u32((mmio + offset) as *mut u32) }; +} + +fn reg_write(mmio: Int, offset: Int, value: u32) { + unsafe { volatile_write_u32((mmio + offset) as *mut u32, value); }; +} + +// Descriptor fields. In RAM rather than MMIO, but volatile all the same: the +// *card* reads and writes these, so they are shared memory with something the +// compiler cannot see, and the ordering against the tail-register write below is +// the entire transmit protocol. +fn desc_u64(address: Int) -> Int { + let value = unsafe { volatile_read_u64(address as *mut u64) }; + return value as Int; +} + +fn desc_set_u64(address: Int, value: Int) { + unsafe { volatile_write_u64(address as *mut u64, value as u64); }; +} + +fn desc_u16(address: Int) -> Int { + let value = unsafe { volatile_read_u16(address as *mut u16) }; + return value as Int; +} + +fn desc_set_u16(address: Int, value: Int) { + unsafe { volatile_write_u16(address as *mut u16, value as u16); }; +} + +fn desc_u8(address: Int) -> Int { + let value = unsafe { volatile_read_u8(address as *mut u8) }; + return value as Int; +} + +fn desc_set_u8(address: Int, value: Int) { + unsafe { volatile_write_u8(address as *mut u8, value as u8); }; +} + +// -------------------------------------------------------------------- finding + +fn e1000_find() -> Int { + return pci_find(E1000_VENDOR, E1000_DEVICE); +} + +fn e1000_open(bdf: Int) -> Int { + // Bus master before anything: every ring access the card makes is a bus + // cycle it issues itself, so without this the rings are simply never read. + pci_command_set(bdf, PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER); + return pci_bar(bdf, 0); +} + +// --------------------------------------------------------------------- EEPROM + +// One 16-bit word from the card's serial EEPROM. +// +// A protocol inside a protocol: the register is a doorway, not the data. Write +// the word address with a start bit, poll a done bit, take the value out of the +// high half. Returns -1 if the card never answers, which is a real outcome — +// some variants of this part have no EEPROM at all and the driver then has to +// find the address elsewhere. +fn eeprom_read(mmio: Int, word: Int) -> Int { + reg_write(mmio, REG_EERD, EERD_START | ((word * 0x100) as u32)); + for _attempt in 0..100000 { + let value = reg_read(mmio, REG_EERD); + if ((value & EERD_DONE) != 0) { + return ((value / 0x10000) as Int) & 0xffff; + } + } + return 0 - 1; +} + +// The card's MAC address, six bytes, written into `out`. +// +// Three EEPROM words, little-endian within each. Returns false if the EEPROM +// does not answer. +fn e1000_mac(mmio: Int, out: Int) -> Bool { + for index in 0..3 { + let word = eeprom_read(mmio, index); + if (word < 0) { + return false; + } + desc_set_u8(out + index * 2, word & 0xff); + desc_set_u8(out + index * 2 + 1, ((word / 0x100) as Int) & 0xff); + } + return true; +} + +// ------------------------------------------------------------------ bring-up + +// Prepares the card and both rings. +// +// `state` is a block of `STATE_SIZE` bytes; `tx_ring`, `rx_ring` and `buffers` +// are physical addresses the caller allocated. Physical, because the card's own +// bus cycles land in that address space — the identity map on this machine makes +// them the same number as the virtual ones, which is a coincidence and not a +// rule. +// +// `buffers` must be `RX_COUNT * RX_BUFFER_SIZE` bytes of contiguous memory. +fn e1000_init(state: Int, mmio: Int, tx_ring: Int, rx_ring: Int, buffers: Int) { + state_set(state, STATE_MMIO, mmio); + state_set(state, STATE_TX_RING, tx_ring); + state_set(state, STATE_RX_RING, rx_ring); + state_set(state, STATE_TX_TAIL, 0); + state_set(state, STATE_RX_HEAD, 0); + state_set(state, STATE_RX_BUFFERS, buffers); + + // Interrupts off, and the pending cause register read to clear it. This + // driver polls; a card left able to interrupt would raise on a line with no + // gate behind it. + reg_write(mmio, REG_IMC, 0xffffffff); + reg_read(mmio, REG_ICR); + + // Ask for the link, and let the card work out the speed. Without this the + // card comes up with its link down and transmits into nothing. + reg_write(mmio, REG_CTRL, reg_read(mmio, REG_CTRL) | CTRL_SLU | CTRL_ASDE); + + // The multicast table, explicitly zero. See the constant. + for index in 0..MTA_ENTRIES { + reg_write(mmio, REG_MTA + index * 4, 0); + } + + setup_transmit(state, mmio, tx_ring); + setup_receive(state, mmio, rx_ring, buffers); +} + +// Installs the card's own address into the receive filter. +// +// Separate from `init` because it needs the address, and where the address comes +// from is not the ring setup's business: on this part it is in the EEPROM, on +// others the firmware has already left it in these very registers. +fn e1000_set_mac(mmio: Int, mac: Int) { + let low = desc_u8(mac) + desc_u8(mac + 1) * 0x100 + + desc_u8(mac + 2) * 0x10000 + desc_u8(mac + 3) * 0x1000000; + let high = desc_u8(mac + 4) + desc_u8(mac + 5) * 0x100; + reg_write(mmio, REG_RAL, low as u32); + // The valid bit, without which the filter ignores the entry and the card + // receives nothing addressed to it — visible only as silence. + reg_write(mmio, REG_RAH, (high as u32) | RAH_VALID); +} + +fn setup_transmit(state: Int, mmio: Int, ring: Int) { + for index in 0..TX_COUNT { + let descriptor = ring + index * DESC_SIZE; + desc_set_u64(descriptor, 0); + desc_set_u64(descriptor + 8, 0); + } + // The low half of a physical address. `as u32` *is* the truncation — + // the mask was standing in for it. + reg_write(mmio, REG_TDBAL, ring as u32); + reg_write(mmio, REG_TDBAH, ((ring / 0x100000000) as Int) as u32); + reg_write(mmio, REG_TDLEN, (TX_COUNT * DESC_SIZE) as u32); + // Head and tail both zero: the ring is empty, and the card advances head as + // it consumes what the driver puts between them. + reg_write(mmio, REG_TDH, 0); + reg_write(mmio, REG_TDT, 0); + reg_write(mmio, REG_TIPG, TIPG_VALUE); + reg_write(mmio, REG_TCTL, TCTL_EN | TCTL_PSP | TCTL_CT | TCTL_COLD); +} + +fn setup_receive(state: Int, mmio: Int, ring: Int, buffers: Int) { + for index in 0..RX_COUNT { + let descriptor = ring + index * DESC_SIZE; + desc_set_u64(descriptor, buffers + index * RX_BUFFER_SIZE); + desc_set_u64(descriptor + 8, 0); + } + // The low half of a physical address. `as u32` *is* the truncation — + // the mask was standing in for it. + reg_write(mmio, REG_RDBAL, ring as u32); + reg_write(mmio, REG_RDBAH, ((ring / 0x100000000) as Int) as u32); + reg_write(mmio, REG_RDLEN, (RX_COUNT * DESC_SIZE) as u32); + reg_write(mmio, REG_RDH, 0); + // The tail is the last descriptor the *driver* has given the card, so an + // empty-looking ring is in fact a full one: every descriptor from head to + // tail is the card's to fill. Setting it to `RX_COUNT - 1` hands over all + // but one, which is the most a ring of this shape can hold — head meeting + // tail is how "full" is spelled, so one slot always stays back. + reg_write(mmio, REG_RDT, (RX_COUNT - 1) as u32); + reg_write(mmio, REG_RCTL, RCTL_EN | RCTL_UPE | RCTL_MPE | RCTL_BAM + | RCTL_SECRC | RCTL_BSIZE_2048); +} + +// Whether the card has a link. +// +// Polled with a bound rather than assumed: auto-negotiation takes time on real +// hardware, and a frame handed to a card whose link is down is accepted and +// discarded — a driver that works in every visible way while nothing arrives. +// Lets the card raise an interrupt when a frame arrives. +// +// Separate from `init` because it is a decision rather than a setup: a driver +// that polls is a complete driver, and the same rings work either way. What this +// changes is who notices. +// +// The delay timer goes to zero first. Left at its power-on value the card holds +// a received frame back for a while in case another arrives — and a driver +// waiting on a single reply then waits out a timer it never set, which is +// indistinguishable from the reply being lost. +fn e1000_enable_receive_interrupt(mmio: Int) { + // Every delay to zero, not just the one with the obvious name. Anything left + // set is the card deciding when the driver hears about a frame. + reg_write(mmio, REG_RDTR, 0); + reg_write(mmio, REG_RADV, 0); + reg_write(mmio, REG_TIDV, 0); + reg_write(mmio, REG_ITR, 0); + // Read the cause register first: whatever it holds is from before this + // driver existed, and an unmasked cause that was already pending is an + // interrupt on the first unmask with nothing behind it. + reg_read(mmio, REG_ICR); + reg_write(mmio, REG_IMS, ICR_RXT0 | ICR_RXDMT0 | ICR_RXO); +} + +// Turns them off again, and says what was pending. +// +// A driver that is done listening has to mask *and* clear: a cause left set is a +// line left asserted, and the next thing to unmask that IRQ inherits it. +fn e1000_disable_interrupts(mmio: Int) -> u32 { + reg_write(mmio, REG_IMC, 0xffffffff); + return reg_read(mmio, REG_ICR); +} + +// What the card is reporting, and an acknowledgement in the same breath. +// +// One function because it is one register access: `ICR` clears on read, so +// asking twice gets the answer once. A handler that reads it into a variable and +// then reads it again to check a second bit has already thrown the second bit +// away. +fn e1000_interrupt_cause(mmio: Int) -> u32 { + return reg_read(mmio, REG_ICR); +} + +fn e1000_link_up(mmio: Int) -> Bool { + for _attempt in 0..1000000 { + if ((reg_read(mmio, REG_STATUS) & STATUS_LINK_UP) != 0) { + return true; + } + } + return false; +} + +// ------------------------------------------------------------------- transmit + +// Hands one frame to the card and waits for it to be taken. +// +// The ordering is the protocol, and it is the reverse of the educational +// device's: there the driver wrote a start bit last, and here it writes an +// *index* last. Everything about the descriptor has to be in memory before the +// tail moves, because moving the tail is what tells the card the descriptor +// exists — a card that reads a descriptor the compiler had not finished writing +// transmits whatever was there. +fn e1000_transmit(state: Int, frame: Int, length: Int) -> Bool { + let mmio = state_get(state, STATE_MMIO); + let ring = state_get(state, STATE_TX_RING); + let tail = state_get(state, STATE_TX_TAIL); + let descriptor = ring + tail * DESC_SIZE; + + desc_set_u64(descriptor, frame); + desc_set_u16(descriptor + TX_LENGTH_OFFSET, length); + desc_set_u8(descriptor + TX_CMD_OFFSET, TX_CMD_EOP | TX_CMD_IFCS | TX_CMD_RS); + // The status byte is cleared by the driver and set by the card. Clearing it + // here rather than trusting it to be clear is what makes the wait below mean + // "this transmission finished" instead of "some earlier one did". + desc_set_u8(descriptor + TX_STATUS_OFFSET, 0); + + let next = (tail + 1) % TX_COUNT; + state_set(state, STATE_TX_TAIL, next); + reg_write(mmio, REG_TDT, next as u32); + + for _attempt in 0..1000000 { + if ((desc_u8(descriptor + TX_STATUS_OFFSET) & TX_STATUS_DD) != 0) { + return true; + } + } + return false; +} + +// -------------------------------------------------------------------- receive + +// Takes one frame if the card has left one, copying it into `out`. +// +// Returns its length, 0 if nothing has arrived, and -1 for a frame too long for +// the buffer offered. Copied out rather than handed over in place, because the +// descriptor has to go back to the card immediately: a ring the driver holds +// entries out of is a ring that fills, and a full ring is a card dropping +// everything with nowhere to say so. +fn e1000_receive(state: Int, out: Int, capacity: Int) -> Int { + let mmio = state_get(state, STATE_MMIO); + let ring = state_get(state, STATE_RX_RING); + let head = state_get(state, STATE_RX_HEAD); + let descriptor = ring + head * DESC_SIZE; + + let status = desc_u8(descriptor + RX_STATUS_OFFSET); + if ((status & RX_STATUS_DD) == 0) { + return 0; + } + // The length is the *card's* word for how much it wrote, and the only thing + // that bounds where it wrote is the buffer this driver gave it. Clamped to + // that before it is used as a count, because the check below is the + // caller's bound and not this one's: with a caller offering more than + // `RX_BUFFER_SIZE`, a card reporting a longer frame would have the copy read + // out of the next descriptor's buffer, or past the region entirely on the + // last one. + // + // Not reachable from this kernel today — its caller offers 256 bytes, well + // under the 2048 each buffer has — which is exactly why it is worth writing + // down: what stops it is a number at the call site, not anything this + // function knows. + let length = desc_u16(descriptor + RX_LENGTH_OFFSET); + if (length > RX_BUFFER_SIZE) { + length = RX_BUFFER_SIZE; + } + let source = state_get(state, STATE_RX_BUFFERS) + head * RX_BUFFER_SIZE; + + let taken = length; + if (taken > capacity) { + taken = 0 - 1; + } else { + for index in 0..taken { + desc_set_u8(out + index, desc_u8(source + index)); + } + } + + // The descriptor goes back: status cleared, then the tail advanced to it. + // In that order — the tail write is what gives the entry to the card, and an + // entry given back with its status still set is one the driver will read as + // a frame that never arrived. + desc_set_u8(descriptor + RX_STATUS_OFFSET, 0); + state_set(state, STATE_RX_HEAD, (head + 1) % RX_COUNT); + reg_write(mmio, REG_RDT, head as u32); + + return taken; +} + +// Waits for a frame, bounded. +// +// A bound rather than a loop, and the caller's number rather than one here: how +// long it is reasonable to wait is a property of what was sent, and a driver +// that decided it for the caller would be deciding whether a reply is late or +// absent. +fn e1000_receive_within(state: Int, out: Int, capacity: Int, attempts: Int) -> Int { + for _attempt in 0..attempts { + let length = e1000_receive(state, out, capacity); + if (length != 0) { + return length; + } + } + return 0; +} diff --git a/bare-metal-x86/drivers/edu.lk b/bare-metal-x86/drivers/edu.lk new file mode 100644 index 00000000..4b809b25 --- /dev/null +++ b/bare-metal-x86/drivers/edu.lk @@ -0,0 +1,253 @@ +// The QEMU `edu` device: a PCI device that does the three things a PCI device +// does. +// +// Every other device this kernel drives is a fixed-port ISA relic — the UART, +// the PIC, the PIT, the keyboard controller, the ATA ports. They are found by +// knowing their address, they are spoken to with `in`/`out`, and they never +// touch memory on their own. None of them exercises what a driver on this +// architecture actually has to do: +// +// * be *found*, by walking configuration space rather than by being known +// * be reached through a BAR the firmware assigned, with volatile loads and +// stores rather than port cycles +// * write into RAM by itself, at a physical address the driver hands it +// * and say it is finished by raising an interrupt +// +// `edu` is a device QEMU ships for exactly this: it has a handful of MMIO +// registers, a DMA engine with 4 KiB of its own memory, and an interrupt, and +// every one of them has an answer that can be checked. A driver that computes +// 5! = 120 through it has done a full MMIO round trip; a driver that DMAs a +// pattern out and back has proved the device wrote RAM. +// +// It is not a device anyone ships. That is the point: it is the smallest thing +// that tells the difference between a language that can drive PCI hardware and +// one that can only toggle ports. + +use { pci_find, pci_bar, pci_bar_size, pci_command_set, pci_interrupt_line, + PCI_COMMAND_MEMORY, PCI_COMMAND_BUS_MASTER } from "pci"; + +const EDU_VENDOR = 0x1234; +const EDU_DEVICE = 0x11e8; + +// What the identification register answers for version 1.0. Checked rather +// than printed: a driver that found *a* device at 1234:11e8 and got something +// else back has found a device whose registers are somewhere else. +const EDU_IDENT = 0x010000ed; + +const EDU_REG_IDENT = 0x00; +const EDU_REG_LIVENESS = 0x04; +const EDU_REG_FACTORIAL = 0x08; +const EDU_REG_STATUS = 0x20; +const EDU_REG_IRQ_STATUS = 0x24; +const EDU_REG_IRQ_RAISE = 0x60; +const EDU_REG_IRQ_ACK = 0x64; +const EDU_REG_DMA_SOURCE = 0x80; +const EDU_REG_DMA_DEST = 0x88; +const EDU_REG_DMA_COUNT = 0x90; +const EDU_REG_DMA_COMMAND = 0x98; + +const EDU_STATUS_COMPUTING: u32 = 0x01; +const EDU_STATUS_IRQ_ON_DONE: u32 = 0x80; + +const EDU_DMA_START: u64 = 0x01; +const EDU_DMA_TO_RAM: u64 = 0x02; +const EDU_DMA_IRQ: u64 = 0x04; + +// The device's own memory, in the address space its DMA engine uses. A transfer +// names both ends in that space, and RAM appears in it at its physical address +// — so a driver hands over the *physical* address of its buffer, not a virtual +// one. On this machine the identity map makes those the same number, which is a +// coincidence worth naming: a kernel with a real address space has to translate +// here, and the place to do it is this driver. +const EDU_DMA_BASE = 0x40000; +const EDU_DMA_SIZE = 4096; + +// The interrupt the DMA engine raises when asked, and the bit it sets in the +// interrupt status register. +const EDU_IRQ_DMA = 0x100; + +// A bit with no other meaning to the device, for a driver that wants to check +// its handler works without waiting for a transfer to be slow. The raise +// register puts whatever it is given into the status register, so what comes +// back out is traceable to the request that asked for it. +const EDU_IRQ_TEST = 0x40; + +// ----------------------------------------------------------------- registers +// +// The offsets stay `Int` — an offset is added to an address — while the +// *contents* say their width. The DMA registers are the reason it matters here: +// they carry a 64-bit physical address, and a `u64` whose top bit is set is a +// negative `i64` carrier. Everything done with one has to be unsigned, and +// saying `u64` is how that gets chosen. +// +// The width stops at this file's edge. What the driver *exports* speaks the +// kernel's language, because that is where the values go — into shared words and +// into a line the shell prints, both of which are `Int`. Pushing `u32` outwards +// makes every caller say `as Int` at a boundary the driver already knows about. + +// A register, by its offset from the BAR. +// +// Every access is volatile: an MMIO read is a *request to the device*, not a +// load of a value that was already there, and a compiler that hoisted the +// status poll out of the loop below would spin for ever on the first answer it +// got. This is the same reason every table in this kernel goes through +// `volatile_*`, arrived at from the other direction. +fn edu_read(base: Int, offset: Int) -> u32 { + return unsafe { volatile_read_u32((base + offset) as *mut u32) }; +} + +fn edu_write(base: Int, offset: Int, value: u32) { + unsafe { volatile_write_u32((base + offset) as *mut u32, value); }; +} + +// The DMA registers are 64 bits wide and must be written as one access — the +// device latches the whole register, so two dword writes would start a transfer +// against a half-updated address. +fn edu_read64(base: Int, offset: Int) -> u64 { + return unsafe { volatile_read_u64((base + offset) as *mut u64) }; +} + +fn edu_write64(base: Int, offset: Int, value: u64) { + unsafe { volatile_write_u64((base + offset) as *mut u64, value); }; +} + +// ------------------------------------------------------------------- finding + +// The device's BDF, or -1. +fn edu_find() -> Int { + return pci_find(EDU_VENDOR, EDU_DEVICE); +} + +// Where the device's registers are, after enabling it. +// +// Both bits, and both needed: `MEMORY` is what makes the BAR answer at all, and +// `BUS_MASTER` is what lets the DMA engine issue its own cycles. Firmware often +// sets the first and never the second, so a driver that skipped this would work +// through every MMIO test and then hang on the first transfer. +fn edu_open(bdf: Int) -> Int { + pci_command_set(bdf, PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER); + return pci_bar(bdf, 0); +} + +fn edu_identify(base: Int) -> Int { + return edu_read(base, EDU_REG_IDENT) as Int; +} + +// The liveness register inverts what is written to it. A round trip through it +// is the cheapest possible proof that the BAR is mapped where the driver thinks +// and that both directions work — a read-only register would pass a driver that +// could not write. +fn edu_liveness(base: Int, value: Int) -> Int { + edu_write(base, EDU_REG_LIVENESS, value as u32); + return edu_read(base, EDU_REG_LIVENESS) as Int; +} + +// ----------------------------------------------------------------- factorial + +// n!, computed by the device. +// +// The protocol is the interesting part, and it is the shape of every real +// offload: write the operand, and the *device* sets a busy bit, which the +// driver polls until it clears. The answer replaces the operand in the same +// register. Bounded rather than a bare `while`: a device that never clears its +// busy bit is a hung machine, and a driver that cannot tell "slow" from "gone" +// is one that hangs with it. +fn edu_factorial(base: Int, n: Int) -> Int { + // Clear the raise-on-done bit: this path polls, and an interrupt with no + // handler installed would be a fault rather than an answer. + // `& ~bit` — clearing one bit, written as clearing one bit. In `Int` it had + // to be `& (0xffffffff - bit)`: a subtraction standing in for a complement, + // and correct only because the width happened to be 32. + edu_write(base, EDU_REG_STATUS, edu_read(base, EDU_REG_STATUS) & ~EDU_STATUS_IRQ_ON_DONE); + edu_write(base, EDU_REG_FACTORIAL, n as u32); + for _attempt in 0..1000000 { + if ((edu_read(base, EDU_REG_STATUS) & EDU_STATUS_COMPUTING) == 0) { + return edu_read(base, EDU_REG_FACTORIAL) as Int; + } + } + return 0 - 1; +} + +// ----------------------------------------------------------------------- DMA + +// Whether a transfer of `count` bytes at `device_offset` stays inside the +// device's buffer. +// +// Checked here rather than trusted: the device's address register takes any +// number, and a transfer that runs off the end of its 4 KiB is one QEMU +// refuses silently — the driver sees a completed transfer and wrong data. +fn edu_dma_in_range(device_offset: Int, count: Int) -> Bool { + return device_offset >= 0 && count > 0 && device_offset + count <= EDU_DMA_SIZE; +} + +// Runs one transfer and waits for it. +// +// `to_ram` picks the direction; `physical` is the RAM end, and it is physical +// because that is the address space the device's bus cycles land in. +// +// The command register write is last, and that ordering is the contract: the +// device starts on the write to 0x98 and reads the other three registers as it +// goes. Volatile is what keeps them in this order — without it a compiler is +// free to sink the address stores past the start bit, which is a transfer +// against whatever the registers held before. +fn edu_dma(base: Int, physical: Int, device_offset: Int, count: Int, to_ram: Bool) -> Bool { + if (!edu_dma_in_range(device_offset, count)) { + return false; + } + let device_address = EDU_DMA_BASE + device_offset; + if (to_ram) { + edu_write64(base, EDU_REG_DMA_SOURCE, device_address as u64); + edu_write64(base, EDU_REG_DMA_DEST, physical as u64); + } else { + edu_write64(base, EDU_REG_DMA_SOURCE, physical as u64); + edu_write64(base, EDU_REG_DMA_DEST, device_address as u64); + } + edu_write64(base, EDU_REG_DMA_COUNT, count as u64); + + let command: u64 = EDU_DMA_START; + if (to_ram) { + command = command | EDU_DMA_TO_RAM; + } + edu_write64(base, EDU_REG_DMA_COMMAND, command); + + // The start bit clears when the engine is done. Same bounded wait as the + // factorial, for the same reason. + for _attempt in 0..1000000 { + if ((edu_read64(base, EDU_REG_DMA_COMMAND) & EDU_DMA_START) == 0) { + return true; + } + } + return false; +} + +// ---------------------------------------------------------------- interrupts + +// Ask the device to raise `value`, which arrives as that value in the interrupt +// status register. A device that can be made to interrupt on demand is how a +// handler gets tested without waiting for a transfer to be slow. +fn edu_raise(base: Int, value: Int) { + edu_write(base, EDU_REG_IRQ_RAISE, value as u32); +} + +// Clearing a bit from the status register is what stops the device asserting +// its line. Not doing it from the handler is the classic way to hang: the PIC +// is told the interrupt is over, the device is still asserting, and the machine +// re-enters the handler for ever. +fn edu_acknowledge(base: Int, value: Int) { + edu_write(base, EDU_REG_IRQ_ACK, value as u32); +} + +fn edu_irq_status(base: Int) -> Int { + return edu_read(base, EDU_REG_IRQ_STATUS) as Int; +} + +// The IRQ the firmware routed this device to. +fn edu_irq_line(bdf: Int) -> Int { + return pci_interrupt_line(bdf); +} + +// The size of the register window, for a caller that wants to check the BAR is +// the shape it expects. +fn edu_bar_size(bdf: Int) -> Int { + return pci_bar_size(bdf, 0); +} diff --git a/bare-metal-x86/drivers/framebuffer.lk b/bare-metal-x86/drivers/framebuffer.lk index 98695090..50b2ca1e 100644 --- a/bare-metal-x86/drivers/framebuffer.lk +++ b/bare-metal-x86/drivers/framebuffer.lk @@ -96,7 +96,7 @@ fn copy_rows(base: Int, stride: Int, from_row: Int, to_row: Int, rows: Int) { for pair in 0..pairs { let x = pair * 2; put_pixel_pair(base, stride, x, to_row + row, - get_pixel_pair(base, stride, x, from_row + row)); + get_pixel_pair(base, stride, x, from_row + row)); } } } diff --git a/bare-metal-x86/drivers/gdt.lk b/bare-metal-x86/drivers/gdt.lk new file mode 100644 index 00000000..bc505c88 --- /dev/null +++ b/bare-metal-x86/drivers/gdt.lk @@ -0,0 +1,85 @@ +// The global descriptor table. +// +// In long mode almost everything a segment descriptor says is ignored: the base +// and the limit do not apply, and a "flat" model is the only one there is. What +// is *not* ignored is the handful of bits that decide whether a segment is +// 64-bit code, and what privilege level it runs at — which is why ring 3 needs +// its own pair of descriptors rather than a flag somewhere. Privilege is a +// property of the segment, and a segment is an entry in this table. +// +// The boot stub has a table of its own and always will: entering long mode +// requires a `lgdt` and a far jump through a 64-bit code descriptor, and both +// happen before any compiled code exists. That one is deliberately the minimum +// — null, ring-0 code, ring-0 data — and says nothing about policy. This is the +// one the machine runs on afterwards, and it is the program's, so adding a +// segment is an edit here rather than an edit to a boot path nobody wants to +// touch. + +const GDT_ENTRY_SIZE = 8; + +// One ordinary descriptor, as the single 64-bit value it is. +// +// The caller passes the whole word rather than fields, and that is the honest +// interface: with base and limit ignored, the meaningful part of a long-mode +// descriptor is a bit pattern in the middle of it that no field decomposition +// makes clearer. Naming `0x00AF9A000000FFFF` "64-bit code, ring 0" is what a +// comment is for. +fn set_entry(base: Int, index: Int, value: Int) { + unsafe { volatile_write_u64((base + index * GDT_ENTRY_SIZE) as *mut u64, value as u64); }; +} + +// The TSS descriptor, which is sixteen bytes rather than eight. +// +// A *system* descriptor in long mode is two entries wide: the familiar 32-bit +// layout, plus the base's upper word in the half after it. The fields are +// scattered across it for reasons that are purely historical — the base is in +// three pieces, the limit in two — which is why this is written out rather than +// computed. There is no rule to express. +// +// Type 9 is "available 64-bit TSS". Type 11 is the same TSS marked *busy*, and +// loading a busy one faults; that is what makes `ltr` something to do once. +fn set_tss_entry(base: Int, index: Int, tss_base: Int, tss_limit: Int) { + let low = (tss_limit & 0xffff) + | ((tss_base & 0xffffff) << 16) + | (0x89 << 40) + | (((tss_limit >> 16) & 0xf) << 48) + | (((tss_base >> 24) & 0xff) << 56); + let high = (tss_base >> 32) & 0xffffffff; + set_entry(base, index, low); + set_entry(base, index + 1, high); +} + +// Points the CPU at the table. +// +// The limit is bytes minus one, as everywhere else the hardware holds one. The +// entry count is the caller's because the table's contents are: this file knows +// how wide an entry is and nothing about how many there should be. +// +// **Not sufficient on its own.** The segment registers hold descriptors that +// were *cached* when each was last written; this changes the table they came +// from and nothing else. Until `reload` runs, the CPU is still using the boot +// stub's descriptors — which works exactly as long as the two agree, and stops +// working silently on the day they do not. +fn load(base: Int, entries: Int) { + unsafe { cpu_load_gdt(base, entries * GDT_ENTRY_SIZE - 1); }; +} + +// Reloads CS and the data segments from the table just installed. +// +// CS cannot be written by `mov` at all — the only ways to change it are a far +// jump, a far call, a far return, or an interrupt return — so this is a far +// return the intrinsic builds out of the stack. FS and GS are deliberately left +// alone: writing either zeroes its 64-bit base, which on a kernel keeping +// per-CPU state there would silently point every access at zero. +fn reload(code: Int, data: Int) { + unsafe { cpu_reload_segments(code, data); }; +} + +// Loads the task register, which is the CPU's one pointer to a TSS. +// +// Without it ring 3 is unreachable, and not because anything refuses: the first +// interrupt taken from ring 3 would have no ring-0 stack to switch to, and the +// CPU would push its frame onto the *user's* stack — which the user can rewrite. +fn load_task_register(selector: Int) { + unsafe { cpu_load_task_register(selector); }; +} diff --git a/bare-metal-x86/drivers/heap.lk b/bare-metal-x86/drivers/heap.lk index 5c69aeea..1485d0ee 100644 --- a/bare-metal-x86/drivers/heap.lk +++ b/bare-metal-x86/drivers/heap.lk @@ -36,8 +36,16 @@ const OFF_USED = 4; const OFF_NEXT = 8; // The arena's own three words, at an address the caller picks — the same -// arrangement the page allocator uses, and for the same reason: an interrupt -// handler cannot see a global. +// arrangement the page allocator uses, and for the same reason: a global is not +// *provably initialised* when a handler reads it. +// +// Not that a handler cannot see one. It can, and `program.lk`'s keyboard ISR +// reads a global table. What it cannot have is a proof that the top-level `let` +// which fills the global ran first — an interrupt arrives between any two +// instructions, including the ones before that `let` — so the value arrives +// dynamic, and "dynamic" includes nil. An allocator that answers nil is worse +// than one that answers badly. A fixed address has no initialisation order to +// be on the wrong side of. // // +0 arena base // +4 arena length diff --git a/bare-metal-x86/drivers/hpet.lk b/bare-metal-x86/drivers/hpet.lk new file mode 100644 index 00000000..107a2ea7 --- /dev/null +++ b/bare-metal-x86/drivers/hpet.lk @@ -0,0 +1,139 @@ +// The high precision event timer, which is the one clock on this machine that +// is both fast and honest about how fast it is. +// +// The PIT counts down from a divisor and raises an interrupt; the kernel counts +// those, so what it knows is "about a thousand of something per second". The RTC +// next door knows what day it is and nothing finer. The HPET is a free-running +// counter with a period the chip *states*, in femtoseconds, in its own +// capability register — so a reading converts to real time by arithmetic rather +// than by a number someone measured once. +// +// Everything here is a 64-bit memory-mapped register, and that is the reason +// this driver exists as well as what it does. A counter that fills all 64 bits +// is where an `i64` carrier stops being an accident: the period is in the *high* +// half of the capability word, the counter passes through values whose top bit +// is set, and both a shift and a divide on those have to be unsigned or the +// answer is negative time. + +// Where the block is. +// +// Not discovered: the HPET's address is published in an ACPI table, and parsing +// ACPI to find a timer is a larger machine than the timer. Every PC chipset +// since the specification puts it here, and the firmware that could move it +// would also have to tell an operating system it had — so this is the address a +// bootloader-free kernel uses, and a `hpet_present` that reads the register back +// is what keeps the assumption honest. +const HPET_BASE = 0xFED00000; + +// The three registers this uses, at their offsets from the block's base. +// +// +0x000 capabilities and period +// +0x010 configuration +// +0x0F0 the main counter +const REG_CAPABILITIES = 0x000; +const REG_CONFIGURATION = 0x010; +const REG_COUNTER = 0x0F0; + +// Configuration bit 0 starts the counter. A chip left as the firmware found it +// may have it clear, and a stopped counter reads the same value twice — which +// is indistinguishable from a counter this driver failed to find. +const CONFIG_ENABLE = 0x01; + +// The capability word's low 16 bits are a revision, and a revision of zero (or +// of all ones) is not a chip answering. +const REVISION_MASK = 0xFFFF; + +// A femtosecond is 10^-15 s, so a tick period stated in them divides into a +// second exactly this many times. +const FEMTOSECONDS_PER_SECOND: u64 = 1000000000000000; +const FEMTOSECONDS_PER_MICROSECOND: u64 = 1000000000; + +fn read_register(offset: Int) -> u64 { + return unsafe { volatile_read_u64((HPET_BASE + offset) as *mut u64) }; +} + +fn write_register(offset: Int, value: u64) { + unsafe { volatile_write_u64((HPET_BASE + offset) as *mut u64, value); }; +} + +// Whether there is a chip here at all. +// +// An address nothing decodes reads as all ones on this bus and as zero on some +// emulators; both are refused, and so is a revision of zero. Checked rather than +// assumed because the address above is a convention, and a driver that believes +// a convention reports a timer running at whatever the open bus happened to say. +fn hpet_present() -> Bool { + let capabilities = read_register(REG_CAPABILITIES); + if (capabilities == 0) { + return false; + } + let all_ones: u64 = 0xFFFFFFFFFFFFFFFF; + if (capabilities == all_ones) { + return false; + } + let revision = capabilities & (REVISION_MASK as u64); + return revision != 0; +} + +// How long one tick is, in femtoseconds. +// +// The *high* 32 bits of the capability word, which is why this is a `u64` shift +// and not an `Int` one: `capabilities >> 32` on an `i64` carrier sign-extends +// once the chip's own bit 63 is set, and the period comes back as a number with +// no relation to time. +fn hpet_period_femtoseconds() -> u64 { + return read_register(REG_CAPABILITIES) >> 32; +} + +// Ticks per second, from the period the chip stated. +// +// An unsigned divide, for the same reason. A period of zero would be a chip +// claiming an infinitely fast clock; answering 0 there is what lets the caller +// say "no timer" instead of dividing by it. +fn hpet_frequency() -> u64 { + let period = hpet_period_femtoseconds(); + let zero: u64 = 0; + if (period == zero) { + return zero; + } + return FEMTOSECONDS_PER_SECOND / period; +} + +// Starts the counter, and answers whether there was one to start. +fn hpet_start() -> Bool { + if (!hpet_present()) { + return false; + } + let configuration = read_register(REG_CONFIGURATION); + write_register(REG_CONFIGURATION, configuration | (CONFIG_ENABLE as u64)); + return true; +} + +// The main counter, free-running. +// +// Returned as a `u64` rather than an `Int` because it is one: at the frequencies +// a chipset reports — tens of megahertz — this passes `i64::MAX` after a few +// thousand years, which is not the reason. The reason is that every arithmetic +// this feeds is unsigned, and a value that says so cannot be compared or divided +// the wrong way by accident. +fn hpet_counter() -> u64 { + return read_register(REG_COUNTER); +} + +// The time between two readings, in microseconds. +// +// `later - earlier` at 64 bits, which wraps correctly across the counter's own +// wrap — the subtraction of two points on a circle is a distance whatever the +// origin. Then a divide by ticks-per-microsecond, both unsigned. +fn hpet_elapsed_microseconds(earlier: u64, later: u64) -> u64 { + let period = hpet_period_femtoseconds(); + let zero: u64 = 0; + if (period == zero) { + return zero; + } + let ticks_per_microsecond = FEMTOSECONDS_PER_MICROSECOND / period; + if (ticks_per_microsecond == zero) { + return zero; + } + return (later - earlier) / ticks_per_microsecond; +} diff --git a/bare-metal-x86/drivers/idt.lk b/bare-metal-x86/drivers/idt.lk new file mode 100644 index 00000000..43657b01 --- /dev/null +++ b/bare-metal-x86/drivers/idt.lk @@ -0,0 +1,121 @@ +// An interrupt descriptor table. +// +// This is the table the CPU indexes by vector number to find out where to go +// when something interrupts. Its contents are entirely a *decision*: which +// vector means the timer, which means the keyboard, which one a ring-3 task is +// allowed to raise itself. None of that is a property of the machine, and all +// of it used to be written in Rust for one missing reason — there was no way to +// say `lidt` in LK. There is now (`cpu_load_idt`), so the table is here, where +// the decisions it encodes already live. +// +// What stays on the board's side is the thing this cannot be: the trampolines. +// An interrupt is not a call — the code it lands in never agreed to lose its +// caller-saved registers — so entering a compiled function needs a stub that +// spills everything first and leaves with `iretq`. That is assembly, and it is +// assembly in any language. +// +// The table's storage is the caller's to choose, for the same reason the page +// allocator's state is: a driver that picks an address is a driver that +// collides with the next one. + +// Exported so a caller laying out fixed memory can say where this table ends +// without naming 4096. The size is a product of the two, and a product is an +// instruction — which a bundled module's top level may not emit — so the +// multiplication happens in `table_size()` and, for the caller, at its own top +// level where it is allowed. +const IDT_GATE_SIZE = 16; +const IDT_GATE_COUNT = 256; + +// Present, ring 0, 64-bit *interrupt* gate. +// +// "Interrupt" rather than "trap" is not a naming choice: an interrupt gate +// clears IF on entry, so the handler cannot be re-entered by the same interrupt +// before it has acknowledged the device. A trap gate leaves interrupts on, and +// the first tick that arrives mid-handler starts a second one on the same stack. +const TYPE_INTERRUPT = 0x8e; + +// How many bytes the caller has to set aside. +// +// 256 entries, always, however few are filled in. The CPU indexes this table by +// the vector it computed and reads whatever is at that index — a table sized to +// the gates actually in use is a fault that reads past the end, at the exact +// moment there is a fault to report. +// +// A function rather than a `const`, and not by preference: a bundled module's +// top level may hold literal scalars but must emit no instructions, and +// `IDT_GATE_COUNT * IDT_GATE_SIZE` is a multiply. Writing 4096 instead would be the one +// thing this file argues against everywhere else — a number that agrees with +// two others until one of them changes. +fn table_size() -> Int { + return IDT_GATE_COUNT * IDT_GATE_SIZE; +} + +// Zeroes every gate. +// +// A zeroed gate is *not present*, which is the honest state for a vector +// nothing handles: raising it is a general protection fault that says so. Left +// as whatever the memory happened to hold, it is a present gate pointing at an +// address made of old data — a jump into nothing, reported as nothing. +fn zero(base: Int) { + for offset in 0..((table_size() / 8) as Int) { + unsafe { volatile_write_u64((base + offset * 8) as *mut u64, 0 as u64); }; + } +} + +// Fills in one gate. +// +// The handler's address is split across three fields that are not adjacent, +// because this layout predates 64-bit addresses and was extended twice — 16 +// bits at offset 0, 16 more at offset 6, and the top 32 at offset 8. The +// scattering is history, not design, which is why it is written out rather than +// computed: there is no rule to express. +// +// 0..2 handler bits 0..16 +// 2..4 the code selector to enter through +// 4 IST index (0 = keep using the current stack) +// 5 type and attributes +// 6..8 handler bits 16..32 +// 8..12 handler bits 32..64 +// 12..16 reserved, and the CPU checks it is zero +// +// `dpl` is the privilege a caller must already have to raise this vector with +// an `int` instruction. Zero for everything a *device* raises: ring 3 doing +// `int 0x21` would otherwise be a way to fake a keystroke. Three for exactly +// the syscall, which is the door the ring boundary exists to make the only one. +// Answers whether the gate was written. +// +// A vector outside the table is refused rather than written, and that is not +// defensive tidiness: the table is 256 gates and whatever the kernel put after +// it is one index away. In this kernel that is the GDT. The vector a device +// interrupt lands on is `base + line`, and the line comes out of PCI config +// space — where 0xFF is the *defined* value for "not connected", which makes +// the vector 287 and the write 496 bytes past the end. +// +// The caller is told, because a device left without an interrupt is a decision +// someone should see, and silently not installing a gate looks identical to +// installing one. +fn set_gate(base: Int, vector: Int, handler: Int, selector: Int, dpl: Int) -> Bool { + if (vector < 0 || vector >= IDT_GATE_COUNT) { + return false; + } + let at = base + vector * IDT_GATE_SIZE; + let low = (handler & 0xffff) + | (selector << 16) + | ((TYPE_INTERRUPT | (dpl << 5)) << 40) + | (((handler >> 16) & 0xffff) << 48); + let high = (handler >> 32) & 0xffffffff; + unsafe { volatile_write_u64(at as *mut u64, low as u64); }; + unsafe { volatile_write_u64((at + 8) as *mut u64, high as u64); }; + return true; +} + +// Points the CPU at the table. +// +// The limit is the table's size *minus one*, which is what the hardware field +// holds: a limit of 0 means one addressable byte. Computed here rather than +// asked of the caller, because it is derived from a number this file already +// owns and the off-by-one has no way to announce itself — a limit one too small +// makes the last gate unreachable, and only the last gate. +fn install(base: Int) { + unsafe { cpu_load_idt(base, table_size() - 1); }; +} diff --git a/bare-metal-x86/drivers/keyboard.lk b/bare-metal-x86/drivers/keyboard.lk index 24c6d1e1..905c9fd7 100644 --- a/bare-metal-x86/drivers/keyboard.lk +++ b/bare-metal-x86/drivers/keyboard.lk @@ -29,3 +29,21 @@ fn is_release(scancode: Int) -> Bool { fn key_code(scancode: Int) -> Int { return scancode & 0x7f; } + +// The modifier keys, by scancode. +// +// They are keys like any other — the controller has no notion of a "modifier", +// and reports a press and a release for each. What makes them modifiers is that +// the program keeps their state instead of translating them, which is why this +// module only names them. +const SCAN_LEFT_SHIFT = 0x2a; +const SCAN_RIGHT_SHIFT = 0x36; +const SCAN_CAPS_LOCK = 0x3a; + +fn is_shift(code: Int) -> Bool { + return code == SCAN_LEFT_SHIFT || code == SCAN_RIGHT_SHIFT; +} + +fn is_caps_lock(code: Int) -> Bool { + return code == SCAN_CAPS_LOCK; +} diff --git a/bare-metal-x86/drivers/layout.lk b/bare-metal-x86/drivers/layout.lk new file mode 100644 index 00000000..f560d27a --- /dev/null +++ b/bare-metal-x86/drivers/layout.lk @@ -0,0 +1,68 @@ +// Structures laid out in memory, with the arithmetic done by the compiler. +// +// Every driver in this kernel describes at least one: a task slot, a window +// descriptor, a descriptor ring entry, an Ethernet header. They are all the same +// shape — a base address, a field at a byte offset, a width — and until now they +// were all written the same way: +// +// const STATE_MMIO = 0; +// const STATE_TX_RING = 8; +// const STATE_RX_RING = 16; +// const STATE_TX_TAIL = 24; +// const STATE_RX_HEAD = 32; +// const STATE_RX_BUFFERS = 40; +// const STATE_SIZE = 48; +// +// Six numbers a person added up, and a seventh that has to agree with all of +// them. Insert a field in the middle and every line below it changes; get one +// wrong and the driver reads the field next to the one it meant, which on a +// descriptor ring is a device pointed at the wrong address. +// +// `mmio_layout!` takes the widths and works the offsets out: +// +// mmio_layout! { +// STATE_MMIO: 8, +// STATE_TX_RING: 8, +// STATE_RX_RING: 8, +// STATE_TX_TAIL: 8, +// STATE_RX_HEAD: 8, +// STATE_RX_BUFFERS: 8, +// => STATE_SIZE +// } +// +// The size is not optional and not separate: it is the running total after the +// last field, so it cannot disagree with the layout it describes. A field whose +// width is not a machine word — a MAC address, an inline buffer — is written as +// its size in bytes, because that is what a layout is about. +// +// Nothing is generated but constants, deliberately. The accessors are two +// functions per driver (`read the word at base + offset`, and its write), not +// two per field, and generating names would need identifier concatenation the +// macro system does not have. What is removed is the part that was arithmetic. + +// The recursion carries the running offset as an expression, so what each +// constant gets is `((0) + (8)) + (8)` — folded by the compiler, and by the +// bundler too, which is what lets this be used from a module that is merged into +// its caller rather than run. +// +// `@` marks the internal rules, exactly as it does in Rust: a token that is +// legal in a token stream and illegal everywhere a person would write one, so a +// caller cannot reach them by accident. It had no meaning in this language until +// this macro needed it. +export macro_rules! mmio_layout { + // The end of the run: the accumulated offset *is* the size. + (@from $prev:expr, => $size:ident) => { + const $size = $prev; + }; + // One field: it starts where the run has got to, and the run advances. + (@from $prev:expr, $name:ident : $width:expr, $($rest:tt)*) => { + const $name = $prev; + mmio_layout!(@from ($prev) + ($width), $($rest)*); + }; + // The entry point. A layout begins at zero — an offset is from the base the + // caller supplies, so a structure that begins somewhere else is a caller + // that adds, not a layout that lies. + ($($body:tt)*) => { + mmio_layout!(@from 0, $($body)*); + }; +} diff --git a/bare-metal-x86/drivers/pages.lk b/bare-metal-x86/drivers/pages.lk index ec8cd065..9d3679a4 100644 --- a/bare-metal-x86/drivers/pages.lk +++ b/bare-metal-x86/drivers/pages.lk @@ -1,21 +1,48 @@ -// A page allocator: hand out 4 KiB pages from a range, and never take them -// back. +// A page allocator: hand out 4 KiB pages from a range, and take them back. // -// Bump allocation, deliberately. A kernel this young has nothing that frees — -// the page tables, the heap and the framebuffer mapping all live for the whole -// run — and a free list would be machinery in service of a case that does not -// exist yet. What it does have to be is *honest about the range it was given*, -// which is why the caller passes one in rather than this file guessing. +// A bump pointer for pages that have never been handed out, and an intrusive +// free list for pages that have been given back. Intrusive because a free page +// is memory nobody is using: the address of the next free one lives in the first +// word of the page itself, so the list costs nothing but the pages it holds. // -// The state is three words at an address the caller chooses, because the -// interrupt handlers and the main flow both have to see the same allocator and -// a global would not be visible across that boundary. +// It began as a bump pointer alone, on the grounds that nothing in this kernel +// freed — the page tables, the heap and the framebuffer mapping all live for the +// whole run. That stopped being true when drivers started allocating rings and +// buffers per invocation: running `net` a dozen times leaked sixty pages, and a +// task that exits should give back its stack. A bump-only allocator is not a +// simpler allocator at that point, it is a leak with a rationale. // -// +0 next page to hand out +// The state is four words at an address the caller chooses, because the +// interrupt handlers and the main flow both have to see the same allocator, and +// a global crossing that boundary is not *provably initialised* — a handler can +// run before the top-level `let` that fills it, so what it reads is dynamic and +// may be nil. (Visible, though: a handler can read a global, and `program.lk`'s +// keyboard ISR does. See `drivers/heap.lk` for the longer version.) +// +// +0 next page never yet handed out // +4 one past the last page -// +8 pages handed out so far +// +8 pages currently handed out +// +12 first page on the free list, or 0 +// +// One property is load-bearing and easy to lose. `release_run` gives a run back +// in *descending* order, and the list is last-in-first-out, so the run comes +// back off it ascending — which means a caller that needs contiguous pages can +// get the same run again. Push it ascending instead and the list hands it back +// reversed, and the next contiguous request fails while the memory to satisfy it +// is sitting right there. -const PAGE_SIZE = 4096; +// The page size is the MMU's, not the allocator's: this file hands out pages of +// whatever size the hardware walks. Importing it from `paging` is the +// dependency in the direction it actually runs. +use { PAGE_SIZE } from "paging"; + +// How much room the state needs, said by the file that decides it. +// +// Exported because a caller has to place it, and a caller that writes the number +// down instead is a caller that will not be edited when this grows. It grew +// once: a fourth word for the free list, onto a neighbour that had `+ 3 * WORD` +// written in it, and what failed was a network driver three files away. +const PAGES_STATE_SIZE = 16; fn pages_word(address: Int) -> Int { let value = unsafe { volatile_read_u32(address as *mut u32) }; @@ -34,12 +61,28 @@ fn init(state: Int, base: Int, length: Int) { pages_set_word(state, first); pages_set_word(state + 4, last); pages_set_word(state + 8, 0); + pages_set_word(state + 12, 0); } -// The next page's physical address, or 0 when the range is exhausted. Zero is -// unambiguous: page 0 is never in a range this hands out, because the first -// megabyte is not usable memory. +// A page's physical address, or 0 when there is none. Zero is unambiguous: page +// 0 is never in a range this hands out, because the first megabyte is not usable +// memory — which is also what lets it terminate the free list. +// +// The free list first, so a kernel that allocates and releases in a cycle never +// advances the bump pointer at all. Taking from the bump pointer first would +// exhaust the range while every page handed back sat unused. fn alloc(state: Int) -> Int { + let recycled = pages_word(state + 12); + if (recycled != 0) { + pages_set_word(state + 12, page_link(recycled)); + pages_set_word(state + 8, pages_word(state + 8) + 1); + // Cleared, so a page never carries the list pointer into its new life. + // Not for tidiness: the first word of a fresh page is where a descriptor + // ring's first buffer address goes, and a stale pointer there is a + // device told to write into the allocator's own bookkeeping. + set_page_link(recycled, 0); + return recycled; + } let next = pages_word(state); if (next >= pages_word(state + 4)) { return 0; @@ -49,12 +92,67 @@ fn alloc(state: Int) -> Int { return next * PAGE_SIZE; } +// Gives one page back. +// +// Page 0 is not a page this ever handed out, so it is refused rather than +// pushed: it is the value `alloc` answers when there is nothing left, and a +// caller that released its failed allocation would terminate the list with a +// page that is not one. +// Named for the page rather than for the operation: `release` is what a +// give-it-back helper wants to be called, and the lock next door got there +// first. Bundling flattens every module into one namespace, so the second one to +// want a name is the one that has to say what it is about. +fn release_page(state: Int, page: Int) { + if (page == 0) { + return; + } + set_page_link(page, pages_word(state + 12)); + pages_set_word(state + 12, page); + pages_set_word(state + 8, pages_word(state + 8) - 1); +} + +// Gives a run of pages back, highest first. +// +// The order is the whole reason this exists rather than a loop at each call +// site: the list is last-in-first-out, so pushing a run descending is what makes +// it come back ascending, and a caller that needs the run again gets it. See the +// note at the top of this file. +fn release_run(state: Int, base: Int, count: Int) { + let index = count - 1; + while (index >= 0) { + release_page(state, base + index * PAGE_SIZE); + index = index - 1; + } +} + +// The list pointer, which lives in the page itself. +fn page_link(page: Int) -> Int { + let value = unsafe { volatile_read_u32(page as *mut u32) }; + return value as Int; +} + +fn set_page_link(page: Int, next: Int) { + unsafe { volatile_write_u32(page as *mut u32, next as u32); }; +} + fn used(state: Int) -> Int { return pages_word(state + 8); } +// How many pages could still be handed out: the ones never touched, plus the +// ones given back. fn free(state: Int) -> Int { - return pages_word(state + 4) - pages_word(state); + let untouched = pages_word(state + 4) - pages_word(state); + let recycled = 0; + let page = pages_word(state + 12); + // Walked rather than counted in a word. A count would have to be kept in + // step with two operations that can each fail to; the list is the truth, and + // this is a report rather than a fast path. + while (page != 0) { + recycled = recycled + 1; + page = page_link(page); + } + return untouched + recycled; } fn total(state: Int) -> Int { diff --git a/bare-metal-x86/drivers/paging.lk b/bare-metal-x86/drivers/paging.lk new file mode 100644 index 00000000..f57fe48a --- /dev/null +++ b/bare-metal-x86/drivers/paging.lk @@ -0,0 +1,81 @@ +// Four-level page tables. +// +// Every level has the same shape — 512 eight-byte entries filling one 4 KiB +// page — and every entry has the same shape too: a physical address with its +// low twelve bits zero, because a page is 4 KiB aligned, and flags living in +// those bits precisely because they are always zero. That regularity is the +// whole reason this file is short: a PML4, a PDPT, a page directory and a page +// table differ in what they *point at*, not in how they are written. +// +// What the CPU consults is not this table but the TLB, which does not notice a +// write behind it. Changing a mapping that has already been used means saying +// so — `invalidate` for one page, or reloading CR3, which flushes everything. + +const PAGE_SIZE = 4096; +const TABLE_ENTRIES = 512; + +// The three flags this kernel uses. They are bits 0, 1 and 2 of an entry. +// +// `USER` is the one worth staring at: the CPU takes the *conjunction* down the +// whole walk, so a page is reachable from ring 3 only if every level from the +// PML4 down says so. Setting it on the leaf and not on the directory above is a +// mapping that looks user-visible in the table and faults on access — which is +// how the first ring-3 program here failed, on its own first instruction. +const PAGE_PRESENT = 0x1; +const PAGE_WRITE = 0x2; +const PAGE_USER = 0x4; + +fn page_size() -> Int { + return PAGE_SIZE; +} + +// Zeroes a table. +// +// A zeroed entry is *not present*, which is the honest state for an address +// nothing maps: touching it faults, and the fault says where. Left as whatever +// the page happened to hold, it is a present entry pointing at an address made +// of old data — and the CPU will walk it. +fn table_zero(base: Int) { + for index in 0..TABLE_ENTRIES { + unsafe { volatile_write_u64((base + index * 8) as *mut u64, 0 as u64); }; + } +} + +// Points one entry at a physical address. +// +// The address is masked rather than trusted: the low twelve bits are where the +// flags live, so an unaligned address passed here would not be a wrong mapping +// but a wrong *permission* — silently readable, or silently user-visible. +fn table_set(base: Int, index: Int, physical: Int, flags: Int) { + let entry = (physical & ~(PAGE_SIZE - 1)) | flags; + unsafe { volatile_write_u64((base + index * 8) as *mut u64, entry as u64); }; +} + +// Which entry of a level an address selects. +// +// Nine bits per level, and the shift says which level: 39 for the PML4, 30 for +// the PDPT (one entry per gigabyte), 21 for the directory (one per 2 MiB), 12 +// for the table (one per page). +fn index_of(address: Int, shift: Int) -> Int { + return (address >> shift) & (TABLE_ENTRIES - 1); +} + +const SHIFT_PML4 = 39; +const SHIFT_PDPT = 30; +const SHIFT_DIRECTORY = 21; +const SHIFT_TABLE = 12; + +// Switches address spaces, which flushes the TLB in doing so. +// +// This is the whole difference between a thread and a process, as one +// instruction: every virtual address means something else afterwards. It is +// only survivable because the kernel is mapped at the same place in every +// space — the code that runs *after* this instruction has to still be there. +fn switch_to(cr3: Int) { + unsafe { cpu_write_cr3(cr3); }; +} + +// Drops one page's cached translation, for when a mapping changed under it. +fn invalidate(address: Int) { + unsafe { cpu_invalidate_page(address); }; +} diff --git a/bare-metal-x86/drivers/pci.lk b/bare-metal-x86/drivers/pci.lk index c9457801..300406f3 100644 --- a/bare-metal-x86/drivers/pci.lk +++ b/bare-metal-x86/drivers/pci.lk @@ -1,8 +1,14 @@ -// PCI configuration space. +// PCI configuration space: finding devices, and finding where they live. // // Configuration space is reached through two ports: write a // (bus, device, function, offset) address to 0xCF8 with bit 31 set to enable // the cycle, then read or write the dword at 0xCFC. +// +// A device is named here by a single Int — the encoded (bus, device, function) +// triple, which every real stack calls a BDF. One number rather than three +// arguments everywhere: a BDF is what enumeration *produces*, and threading it +// as a triple means every function that merely passes a device along has to +// know how a device is addressed. const PCI_ADDRESS = 0xcf8; const PCI_DATA = 0xcfc; @@ -10,43 +16,266 @@ const PCI_DATA = 0xcfc; const PCI_REG_ID = 0x00; const PCI_REG_COMMAND = 0x04; const PCI_REG_CLASS = 0x08; +const PCI_REG_HEADER = 0x0c; const PCI_REG_BAR0 = 0x10; +const PCI_REG_INTERRUPT = 0x3c; +const PCI_COMMAND_IO = 0x01; const PCI_COMMAND_MEMORY = 0x02; const PCI_COMMAND_BUS_MASTER = 0x04; const PCI_CLASS_DISPLAY = 0x03; -fn pci_address(slot: Int, offset: Int) -> Int { - // Bus 0, function 0 — everything this needs is there, and walking the - // whole tree would be a lot of code for no more information. - return 0x80000000 + slot * 0x800 + (offset & 0xfc); +// An absent device reads back as all ones: nothing is there to drive the bus +// low. Every enumeration loop turns on this one value. +const PCI_NO_DEVICE = 0xffffffff; + +// A BDF, packed the way the address register wants it: bus in bits 16..24, +// device in 11..16, function in 8..11. +fn pci_bdf(bus: Int, device: Int, function: Int) -> Int { + return bus * 0x10000 + device * 0x800 + function * 0x100; +} + +fn pci_bdf_bus(bdf: Int) -> Int { + return (bdf >> 16) & 0xff; +} + +fn pci_bdf_device(bdf: Int) -> Int { + return (bdf >> 11) & 0x1f; +} + +fn pci_bdf_function(bdf: Int) -> Int { + return (bdf >> 8) & 0x07; +} + +// The enable bit, the BDF, and a dword-aligned offset. +// +// The offset is masked rather than checked: the low two bits of the address +// register are hardwired to zero on the bus anyway, so a caller that asks for +// 0x0a gets the dword at 0x08 either way. Masking here makes that visible +// instead of leaving it to the chipset. +fn pci_config_address(bdf: Int, offset: Int) -> Int { + return 0x80000000 + bdf + (offset & 0xfc); } -fn pci_read(slot: Int, offset: Int) -> Int { - unsafe { port_out_u32(PCI_ADDRESS, pci_address(slot, offset) as u32); }; +fn pci_read(bdf: Int, offset: Int) -> Int { + unsafe { port_out_u32(PCI_ADDRESS, pci_config_address(bdf, offset) as u32); }; let value = unsafe { port_in_u32(PCI_DATA) }; return value as Int; } -fn pci_write(slot: Int, offset: Int, value: Int) { - unsafe { port_out_u32(PCI_ADDRESS, pci_address(slot, offset) as u32); }; +fn pci_write(bdf: Int, offset: Int, value: Int) { + unsafe { port_out_u32(PCI_ADDRESS, pci_config_address(bdf, offset) as u32); }; unsafe { port_out_u32(PCI_DATA, value as u32); }; } -// The slot of the first display controller on bus 0, or -1. -fn pci_find_display() -> Int { - for slot in 0..32 { - let id = pci_read(slot, PCI_REG_ID); - // An absent slot reads back as all ones — there is nothing there to - // drive the bus low. - if (id != 0xffffffff) { - // The class code is the top byte of the dword at 0x08. - let class = ((pci_read(slot, PCI_REG_CLASS) / 0x1000000) as Int) & 0xff; - if (class == PCI_CLASS_DISPLAY) { - return slot; +// Configuration space is addressable only in dwords, so a byte or a word is a +// dword read and a shift. Written out because the alternative is every caller +// doing it inline and one of them getting the shift wrong. +fn pci_read_u16(bdf: Int, offset: Int) -> Int { + let dword = pci_read(bdf, offset); + return (dword >> pci_shift_of(offset, 2)) & 0xffff; +} + +fn pci_read_u8(bdf: Int, offset: Int) -> Int { + let dword = pci_read(bdf, offset); + return (dword >> pci_shift_of(offset, 3)) & 0xff; +} + +// How much to divide a dword by to bring `offset`'s field down to bit 0. +// +// Division rather than a shift operator, and a table rather than exponentiation: +// the only widths that exist here are 2 and 4 bytes, so the whole answer is six +// cases. `mask` is the count of low offset bits that matter — 2 for a word +// (offset 0 or 2), 3 for a byte (0..3). +fn pci_shift_of(offset: Int, mask: Int) -> Int { + // A shift *amount*, which is what the name says. It used to return the + // power of two to divide by (1 / 0x100 / 0x10000 / 0x1000000) and the call + // sites divided — but `/` in LK always yields a `Float` + // (docs/semantics.md), so the `&` that followed met a Float. The + // interpreter raised at runtime, the AOT refused to lower it, and + // `lk check` said nothing until the bit operations learned to require an + // Int. + let byte = offset & mask; + if (byte == 0) { return 0; } + if (byte == 1) { return 8; } + if (byte == 2) { return 16; } + return 24; +} + +fn pci_vendor(bdf: Int) -> Int { + return pci_read(bdf, PCI_REG_ID) & 0xffff; +} + +fn pci_device_id(bdf: Int) -> Int { + return (pci_read(bdf, PCI_REG_ID) >> 16) & 0xffff; +} + +// The class code is the top byte of the dword at 0x08; the subclass is the one +// under it. +fn pci_class(bdf: Int) -> Int { + return (pci_read(bdf, PCI_REG_CLASS) >> 24) & 0xff; +} + +fn pci_subclass(bdf: Int) -> Int { + return (pci_read(bdf, PCI_REG_CLASS) >> 16) & 0xff; +} + +fn pci_present(bdf: Int) -> Bool { + let id = pci_read(bdf, PCI_REG_ID); + // 0 as well as all-ones: a slot that decodes but has no vendor is not a + // device either, and QEMU's bridges leave both shapes about. + return id != PCI_NO_DEVICE && id != 0; +} + +// Whether a device has functions past 0. Only function 0 answers this, and a +// device that says no must not be probed further — on real hardware a +// single-function device aliases every function to itself, so probing finds +// eight copies of it. +fn pci_multifunction(bdf: Int) -> Bool { + return (pci_read_u8(bdf, PCI_REG_HEADER + 2) & 0x80) != 0; +} + +// The IRQ line the firmware routed this device to, as the PIC numbers them. +fn pci_interrupt_line(bdf: Int) -> Int { + return pci_read_u8(bdf, PCI_REG_INTERRUPT); +} + +fn pci_command_set(bdf: Int, bits: Int) { + pci_write(bdf, PCI_REG_COMMAND, pci_read(bdf, PCI_REG_COMMAND) | bits); +} + +// ------------------------------------------------------------- base addresses + +// A BAR's register offset. `index` is 0..6. +fn pci_bar_offset(index: Int) -> Int { + return PCI_REG_BAR0 + index * 4; +} + +fn pci_bar_is_io(bdf: Int, index: Int) -> Bool { + return (pci_read(bdf, pci_bar_offset(index)) & 0x01) != 0; +} + +// Whether a memory BAR is the 64-bit kind, in which case it eats the *next* +// BAR as its high half and an enumerator must skip that one. +fn pci_bar_is_64(bdf: Int, index: Int) -> Bool { + let low = pci_read(bdf, pci_bar_offset(index)); + return (low & 0x01) == 0 && (low & 0x06) == 0x04; +} + +// Where a BAR points. +// +// The low bits are flags, not address: four of them for a memory BAR, two for +// an I/O one. A 64-bit BAR's high half is the next register, and it is read +// unconditionally when present rather than assumed zero — QEMU puts a device +// above 4 GiB the moment the machine has enough RAM, and a driver that took +// only the low half would map the wrong page and see all ones. +fn pci_bar(bdf: Int, index: Int) -> Int { + let low = pci_read(bdf, pci_bar_offset(index)); + if ((low & 0x01) != 0) { + return low & 0xfffffffc; + } + let base = low & 0xfffffff0; + if (pci_bar_is_64(bdf, index)) { + let high = pci_read(bdf, pci_bar_offset(index + 1)); + return base + high * 0x100000000; + } + return base; +} + +// How large a BAR's region is. +// +// There is no register that says. The bus answers it by *writing* all ones and +// reading back: the bits the device leaves clear are the ones it does not +// decode, so the lowest bit still set is the size. That write destroys the +// address, which is why the original goes back before returning — a driver that +// sized a BAR and forgot to restore it has just unmapped the device. +// +// Only the low half is sized here. A 64-bit BAR whose size needs the high half +// is a region of at least 4 GiB, which this machine has nowhere to put. +// +// Computed in `u32`, which is the width the register answered in. The +// arithmetic below is two's complement — the value of the lowest set bit is +// `~bits + 1` — and in `Int` that had to be written as +// `((0xffffffff - bits) + 1) & 0xffffffff`: a subtraction standing in for a +// complement, and a mask standing in for the wrap. At the register's own width +// both of those are what the arithmetic already does. +fn pci_bar_size(bdf: Int, index: Int) -> Int { + let offset = pci_bar_offset(index); + let original = pci_read(bdf, offset); + pci_write(bdf, offset, 0xffffffff); + let probed = pci_read(bdf, offset) as u32; + pci_write(bdf, offset, original); + + let mask: u32 = 0xfffffff0; + if ((original & 0x01) != 0) { + mask = 0xfffffffc; + } + let bits = probed & mask; + if (bits == 0) { + return 0; + } + // `-bits`, at 32 bits, which is the lowest set bit's value. + let zero: u32 = 0; + return (zero - bits) as Int; +} + +// -------------------------------------------------------------- enumeration + +// Every device on bus 0, as a list of BDFs. +// +// Bus 0 only, and deliberately: walking bridges means recursion and a +// secondary-bus register, and on this machine every device is on bus 0. What +// this does do properly is *functions* — a multifunction device is eight +// devices sharing a slot, and stopping at function 0 is how a driver misses the +// second port of a two-port card. +fn pci_devices() -> List { + let found: List = []; + for device in 0..32 { + let base = pci_bdf(0, device, 0); + if (pci_present(base)) { + found.push(base); + if (pci_multifunction(base)) { + for function in 1..8 { + let bdf = pci_bdf(0, device, function); + if (pci_present(bdf)) { + found.push(bdf); + } + } } } } + return found; +} + +// The first device answering to a vendor and device id, or -1. +fn pci_find(vendor: Int, device: Int) -> Int { + let devices = pci_devices(); + for index in 0..devices.len() { + let bdf = (devices[index]) as Int; + if (pci_vendor(bdf) == vendor && pci_device_id(bdf) == device) { + return bdf; + } + } return 0 - 1; } + +// The first device of a class, or -1. +fn pci_find_class(class: Int) -> Int { + let devices = pci_devices(); + for index in 0..devices.len() { + let bdf = (devices[index]) as Int; + if (pci_class(bdf) == class) { + return bdf; + } + } + return 0 - 1; +} + +// The BDF of the first display controller, or -1. +// +// Kept as its own name because that is what the framebuffer path asks for, and +// because "the display" is a thing this kernel has exactly one of. +fn pci_find_display() -> Int { + return pci_find_class(PCI_CLASS_DISPLAY); +} diff --git a/bare-metal-x86/drivers/pic.lk b/bare-metal-x86/drivers/pic.lk new file mode 100644 index 00000000..7c868524 --- /dev/null +++ b/bare-metal-x86/drivers/pic.lk @@ -0,0 +1,221 @@ +// The 8259 interrupt controller pair. +// +// Two chips, cascaded: eight lines on the master, eight more on the slave, +// which reaches the CPU through the master's line 2. That arrangement is from +// 1981 and every x86 still boots into it, so a kernel deals with it before it +// deals with anything else that raises an interrupt. +// +// This file holds both halves of the chip's contract, and holds them together +// on purpose. Bringing it up decides which vector each line lands on; +// acknowledging an interrupt decides which chip is told the handler is done. +// Those are one decision, and split across a language boundary they become two +// files naming the same command port — which is exactly how the mouse stopped +// after one packet the first time, from an end-of-interrupt sent to the master +// and not the slave. + +// Command and data ports. The chip has two registers per chip and which one a +// write means depends on where in the initialisation sequence it arrives, +// which is why the sequence below is written as a sequence. +const PIC1_CMD = 0x20; +const PIC1_DATA = 0x21; +const PIC2_CMD = 0xa0; +const PIC2_DATA = 0xa1; + +// "The handler is finished." Until this arrives the chip believes the line is +// still in service and raises nothing further on it — a device that works once +// and then goes quiet, with nothing anywhere to say why. +const EOI = 0x20; + +// Points the chips at `base` and `base + 8`. +// +// Four writes per chip, in order, latched as ICW1-ICW4. The order is the +// protocol: the chip has no register addresses for these, only a position in +// the sequence, so a write out of place is not an error but a different +// meaning. +// +// `base` matters because the power-on default overlaps the vectors the CPU +// reserves for its own exceptions — an interrupt from the timer would arrive +// as a double fault. Remapping away from them is the first thing every x86 +// kernel does, and the number to remap to belongs to whoever builds the +// interrupt table, which is not this file. +fn remap(base: Int) { + unsafe { port_out_u8(PIC1_CMD, 0x11 as u8); }; // ICW1: begin init, expect ICW4 + unsafe { port_out_u8(PIC2_CMD, 0x11 as u8); }; + unsafe { port_out_u8(PIC1_DATA, base as u8); }; // ICW2: vector offsets + unsafe { port_out_u8(PIC2_DATA, (base + 8) as u8); }; + unsafe { port_out_u8(PIC1_DATA, 0x04 as u8); }; // ICW3: slave on line 2 + unsafe { port_out_u8(PIC2_DATA, 0x02 as u8); }; + unsafe { port_out_u8(PIC1_DATA, 0x01 as u8); }; // ICW4: 8086 mode + unsafe { port_out_u8(PIC2_DATA, 0x01 as u8); }; +} + +// Which lines are allowed to raise anything. A set bit is masked *off*. +// +// An unmasked line with no gate behind it is an interrupt into a not-present +// descriptor, which is a general protection fault raised from inside an +// interrupt — so the mask and the table have to agree, and the caller is the +// one that knows both. +fn mask(master: Int, slave: Int) { + unsafe { port_out_u8(PIC1_DATA, master as u8); }; + unsafe { port_out_u8(PIC2_DATA, slave as u8); }; +} + +// The master's line the slave arrives on. Unmasking any of lines 8-15 without +// this one open is a device that raises an interrupt the CPU never sees. +const CASCADE_LINE = 2; + +// Two chips, eight lines each, and no seventeenth. +// +// Exported because a caller with a line number out of PCI config space needs +// something to compare it against, and 16 written at the call site is a number +// that agrees with this file until one of them changes. `bit_of` returns 0x80 +// for anything past the end, so an out-of-range line does not corrupt anything +// — it quietly masks or unmasks line 7 or 15 instead, which is a device that +// never fires and another that fires when it should not. +const LINE_COUNT = 16; + +// Whether the chip has this line at all. +// +// A predicate rather than the constant, because the caller's question is not +// "how many are there" but "may I use this number as an index". The number it +// is checking came out of PCI config space, where 0xFF means "not connected". +fn line_exists(irq: Int) -> Bool { + return irq >= 0 && irq < LINE_COUNT; +} + +// The interrupt mask register reads back, which is what makes it possible to +// open one line without knowing what else is open. +// +// `mask` above takes both bytes at once, and that is right for a kernel setting +// up the machine — it knows every line it intends to allow. It is wrong for a +// *driver*: a PCI device does not learn its interrupt line until it reads +// configuration space, by which time the masks are already set, and a driver +// that wrote a whole byte would silently close every line it did not know +// about. Read, clear one bit, write back. +fn unmask(irq: Int) { + if (irq < 8) { + let current = unsafe { port_in_u8(PIC1_DATA) } as Int; + unsafe { port_out_u8(PIC1_DATA, (current & (0xff - bit_of(irq))) as u8); }; + return; + } + let current = unsafe { port_in_u8(PIC2_DATA) } as Int; + unsafe { port_out_u8(PIC2_DATA, (current & (0xff - bit_of(irq - 8))) as u8); }; + // The slave reaches the CPU through the master, so opening one of its lines + // means opening the cascade too. Done here rather than left to the caller + // because a caller that forgets sees a device that raises nothing, with the + // mask register showing its line open. + let master = unsafe { port_in_u8(PIC1_DATA) } as Int; + unsafe { port_out_u8(PIC1_DATA, (master & (0xff - bit_of(CASCADE_LINE))) as u8); }; +} + +// Closes one line, leaving the others as they are. +fn mask_line(irq: Int) { + if (irq < 8) { + let current = unsafe { port_in_u8(PIC1_DATA) } as Int; + unsafe { port_out_u8(PIC1_DATA, (current | bit_of(irq)) as u8); }; + return; + } + let current = unsafe { port_in_u8(PIC2_DATA) } as Int; + unsafe { port_out_u8(PIC2_DATA, (current | bit_of(irq - 8)) as u8); }; +} + +// 1 << n, for n in 0..8. A table rather than a shift because these are the only +// eight values that exist here and the loop that would compute them is longer +// than the answer. +fn bit_of(line: Int) -> Int { + if (line == 0) { return 0x01; } + if (line == 1) { return 0x02; } + if (line == 2) { return 0x04; } + if (line == 3) { return 0x08; } + if (line == 4) { return 0x10; } + if (line == 5) { return 0x20; } + if (line == 6) { return 0x40; } + return 0x80; +} + +// The edge/level control register, which is how a PC says that a line is held +// rather than pulsed. +// +// The 8259 is from 1981 and its lines are edge-triggered: an interrupt is a +// *transition*, and a device that keeps its line high after being serviced gets +// exactly one. PCI's INTx is the opposite — a device asserts and holds until the +// driver has dealt with it — so a PCI line left edge-triggered delivers one +// interrupt and then goes quiet for ever, while the device sits there asserting. +// +// That is not a hypothetical. A network card driven this way answered its first +// interrupt and none of the eleven after it; the driver still worked, because it +// had a timeout and the frames were already in the ring, which is a polling +// driver wearing an interrupt driver's comments. The card's own cause register +// still read 0x83 when the run finished — the line had been high the whole time. +// +// The register is the chipset's, not the chip's: two ports, one byte per PIC, +// bit n for line n. Only lines 3-7 and 9-12 and 14-15 may be level — the timer, +// the keyboard and the cascade are edge by construction and writing them is +// undefined — which is why this takes a line rather than a byte. +const ELCR1 = 0x4d0; +const ELCR2 = 0x4d1; + +fn set_level_triggered(irq: Int) { + if (irq < 8) { + let current = unsafe { port_in_u8(ELCR1) } as Int; + unsafe { port_out_u8(ELCR1, (current | bit_of(irq)) as u8); }; + return; + } + let current = unsafe { port_in_u8(ELCR2) } as Int; + unsafe { port_out_u8(ELCR2, (current | bit_of(irq - 8)) as u8); }; +} + +// Whether the chip really has this line in service. +// +// The 8259 has a documented way of lying. If a line's request disappears +// between the chip raising INTR and the CPU acknowledging — a glitch, or a line +// masked while its request was pending — the chip has already committed to +// delivering *something*, so it delivers its lowest-priority line: 7 on the +// master, 15 on the slave. That is a "spurious interrupt", and it arrives on a +// vector a kernel may otherwise have no reason to give a gate to, which turns +// it into a general protection fault raised from inside an interrupt. +// +// The in-service register is how to tell one from the other, and it is the only +// way: a spurious interrupt is indistinguishable from a real one at the vector. +// OCW3 selects which register the command port reads back. +fn in_service(irq: Int) -> Bool { + if (irq < 8) { + unsafe { port_out_u8(PIC1_CMD, 0x0b as u8); }; + let register = unsafe { port_in_u8(PIC1_CMD) } as Int; + return (register & bit_of(irq)) != 0; + } + unsafe { port_out_u8(PIC2_CMD, 0x0b as u8); }; + let register = unsafe { port_in_u8(PIC2_CMD) } as Int; + return (register & bit_of(irq - 8)) != 0; +} + +// Acknowledges by line number, which is what a driver has. +// +// The two functions below are the chip's contract; this is the one a driver +// should call. Which chip to tell is a property of the line, not a decision the +// driver should be making — and getting it wrong is the failure that looks like +// the device broke. +fn eoi(irq: Int) { + if (irq < 8) { + eoi_master(); + return; + } + eoi_slave(); +} + +// Acknowledges an interrupt from one of the master's own lines (0-7). +fn eoi_master() { + unsafe { port_out_u8(PIC1_CMD, EOI as u8); }; +} + +// Acknowledges an interrupt from the slave's lines (8-15) — to *both* chips, +// slave first. +// +// The master never saw the device; what it saw was the cascade line, and it is +// still in service until told otherwise. Acknowledging only one of the two is +// the shape of a bug that looks like the device broke: it delivers exactly one +// interrupt and then nothing, forever. +fn eoi_slave() { + unsafe { port_out_u8(PIC2_CMD, EOI as u8); }; + unsafe { port_out_u8(PIC1_CMD, EOI as u8); }; +} diff --git a/bare-metal-x86/drivers/rtc.lk b/bare-metal-x86/drivers/rtc.lk new file mode 100644 index 00000000..a5f27fc8 --- /dev/null +++ b/bare-metal-x86/drivers/rtc.lk @@ -0,0 +1,182 @@ +// The real-time clock, which is the only thing on this machine that knows what +// time it is. +// +// Everything else here counts: the PIT counts down and raises an interrupt, and +// the kernel counts those. That gives elapsed time and nothing else — a machine +// that has been up for four seconds cannot say whether it is Tuesday. The RTC +// is a battery-backed counter that kept running while the power was off, and +// reading it is the whole of "what time is it" on a PC. +// +// It is reached through two ports rather than memory: 0x70 selects a register in +// CMOS, 0x71 reads or writes it. That indirection is the reason for every hazard +// below. + +const CMOS_SELECT = 0x70; +const CMOS_DATA = 0x71; + +// The registers that hold the time, and the two that describe the format. +const RTC_SECOND = 0x00; +const RTC_MINUTE = 0x02; +const RTC_HOUR = 0x04; +const RTC_DAY = 0x07; +const RTC_MONTH = 0x08; +const RTC_YEAR = 0x09; +const RTC_STATUS_A = 0x0a; +const RTC_STATUS_B = 0x0b; + +// Status A's top bit is set while the chip is copying its counters into the +// registers. Reading during that window gives a mixture of before and after — +// 01:59:59 becoming 01:00:59, which is a clock that runs backwards for one +// second in every hour and is otherwise perfect. +const STATUS_A_UPDATING = 0x80; + +// Status B says how the values are encoded. Neither is a default: a machine may +// give binary or BCD, and 12- or 24-hour, and a driver that assumes gets the +// right answer on the machine it was written on. +const STATUS_B_BINARY = 0x04; +const STATUS_B_24_HOUR = 0x02; + +// In 12-hour mode the hour register's top bit means PM. It survives the BCD +// conversion because it is not a digit, which is why it has to be taken off +// before converting and put back after. +const HOUR_PM = 0x80; + +// The interrupt-select bit, which this driver's non-interrupt reads must leave +// alone: `select` also *masks NMI* through its top bit, and a driver that wrote +// a bare register number would be turning non-maskable interrupts back on as a +// side effect of asking the time. +const NMI_DISABLE = 0x80; + +fn cmos_read(register: Int) -> Int { + // The NMI bit is preserved rather than cleared, for the reason above. + unsafe { port_out_u8(CMOS_SELECT, (register | NMI_DISABLE) as u8); }; + let value = unsafe { port_in_u8(CMOS_DATA) }; + return value as Int; +} + +// Whether the chip is mid-update. +fn rtc_updating() -> Bool { + return (cmos_read(RTC_STATUS_A) & STATUS_A_UPDATING) != 0; +} + +// Binary-coded decimal: each nibble is a decimal digit, so 0x59 means 59. +fn from_bcd(value: Int) -> Int { + return ((value / 16) as Int) * 10 + (value & 0x0f); +} + +// The six fields, packed into one number the caller can pull apart. +// +// One number rather than six reads, because the *point* is that they were read +// together: six separate calls could straddle an update and produce a time that +// never happened. Packed as YYYYMMDDhhmmss, which is a number that compares and +// prints in the order a person reads it. +const FIELD_SCALE = 100; + +fn rtc_pack(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> Int { + return ((((year * FIELD_SCALE + month) * FIELD_SCALE + day) * FIELD_SCALE + hour) + * FIELD_SCALE + minute) * FIELD_SCALE + second; +} + +// Reads the clock, or 0 if it will not settle. +// +// Read twice and compare, which is the standard answer and the only one that +// works. Waiting for the update flag to clear is not enough on its own: the flag +// says an update is *in progress*, and one can begin between the check and the +// reads. Two identical reads mean no update happened between them, because an +// update always changes at least the seconds. +// +// Bounded, because a chip that never agrees with itself is a broken chip and a +// driver that waits for it forever is a hung machine. +fn rtc_read() -> Int { + let previous = 0 - 1; + for _attempt in 0..1000 { + // Wait out any update already running, then take a whole reading. + for _settle in 0..100000 { + if (!rtc_updating()) { + break; + } + } + let reading = rtc_read_once(); + if (reading == previous && reading != 0) { + return reading; + } + previous = reading; + } + return 0; +} + +// One pass over the registers, converted to the format status B describes. +// +// The format is read *after* the values, and that ordering does not matter for +// correctness — status B does not change — but reading it first would put two +// more port cycles between the check for an update and the values, which is the +// window this whole function is about. +fn rtc_read_once() -> Int { + let second = cmos_read(RTC_SECOND); + let minute = cmos_read(RTC_MINUTE); + let hour = cmos_read(RTC_HOUR); + let day = cmos_read(RTC_DAY); + let month = cmos_read(RTC_MONTH); + let year = cmos_read(RTC_YEAR); + let status_b = cmos_read(RTC_STATUS_B); + + // The PM flag comes off before the conversion and goes back on after: it is + // not a digit, and a BCD conversion of 0x92 (PM, 12) would answer 92. + let pm = (hour & HOUR_PM) != 0; + hour = hour & (0xff - HOUR_PM); + + if ((status_b & STATUS_B_BINARY) == 0) { + second = from_bcd(second); + minute = from_bcd(minute); + hour = from_bcd(hour); + day = from_bcd(day); + month = from_bcd(month); + year = from_bcd(year); + } + + if ((status_b & STATUS_B_24_HOUR) == 0) { + // 12 AM is hour 0 and 12 PM is hour 12; every other PM hour is plus + // twelve. Written as two cases because the wrap at noon and midnight is + // where a single formula gets it wrong. + if (pm) { + if (hour != 12) { + hour = hour + 12; + } + } else { + if (hour == 12) { + hour = 0; + } + } + } + + // The year register holds two digits. The century register exists but is + // not reliable across machines, so this does what every PC firmware does: + // assumes the current century. A kernel that outlives 2099 has other + // problems. + return rtc_pack(2000 + year, month, day, hour, minute, second); +} + +// The pieces back out of a packed reading. +fn rtc_second(packed: Int) -> Int { + return packed % FIELD_SCALE; +} + +fn rtc_minute(packed: Int) -> Int { + return ((packed / FIELD_SCALE) as Int) % FIELD_SCALE; +} + +fn rtc_hour(packed: Int) -> Int { + return ((packed / (FIELD_SCALE * FIELD_SCALE)) as Int) % FIELD_SCALE; +} + +fn rtc_day(packed: Int) -> Int { + return ((packed / (FIELD_SCALE * FIELD_SCALE * FIELD_SCALE)) as Int) % FIELD_SCALE; +} + +fn rtc_month(packed: Int) -> Int { + return ((packed / (FIELD_SCALE * FIELD_SCALE * FIELD_SCALE * FIELD_SCALE)) as Int) % FIELD_SCALE; +} + +fn rtc_year(packed: Int) -> Int { + return ((packed / (FIELD_SCALE * FIELD_SCALE * FIELD_SCALE * FIELD_SCALE * FIELD_SCALE)) as Int) % 10000; +} diff --git a/bare-metal-x86/drivers/serial.lk b/bare-metal-x86/drivers/serial.lk index f7ec6f2d..0011bbd0 100644 --- a/bare-metal-x86/drivers/serial.lk +++ b/bare-metal-x86/drivers/serial.lk @@ -45,6 +45,21 @@ fn uart_putc(byte: Int) { unsafe { port_out_u8(COM1 + REG_DATA, byte as u8); }; } +// The same, for a message written as a message. +// +// `byte_at` is the string read that allocates nothing, which is what makes this +// usable where `uart_write` was: on the path that reports a failure, before +// there is a heap, and from inside an interrupt. Every line this kernel printed +// used to be an array of ASCII numbers because there was no such read. +fn uart_text(text: String) { + for index in 0..text.len() { + // `!`, because the index is `0..text.len()`: in range by construction. + // `byte_at` answers `Int?` since a position past the end is nil, and + // this is where the program says it knows better. + uart_putc(text.byte_at(index)!); + } +} + fn uart_write(bytes: List) { for byte in bytes { uart_putc(byte); diff --git a/bare-metal-x86/drivers/tarfs.lk b/bare-metal-x86/drivers/tarfs.lk index a19808ef..dd11943d 100644 --- a/bare-metal-x86/drivers/tarfs.lk +++ b/bare-metal-x86/drivers/tarfs.lk @@ -112,3 +112,41 @@ fn find(name: List, buffer: Int, out: Int) -> Int { } return TAR_NOT_FOUND; } + +// The sector the entry after `lba` starts at, or 0 at the end of the archive. +// +// Walking is the only way to enumerate a tar: an entry's length is in its own +// header, so the next one's position is not known until this one is read. That +// is the same walk `find` does, exposed so a caller can do something other than +// compare names — listing, for one. +// +// Zero for "no more", which is unambiguous: sector 0 is the first header, so no +// *following* entry can be there. +fn next_entry(lba: Int, buffer: Int) -> Int { + if (read_sector(lba, buffer) != ATA_OK) { + return 0; + } + if (byte_at(buffer, TAR_NAME) == 0) { + return 0; + } + return lba + 1 + sectors_for(header_size(buffer)); +} + +// The length of the name in the header now in `buffer`. +fn name_length(buffer: Int) -> Int { + let length = 0; + while (length < TAR_NAME_MAX && byte_at(buffer, TAR_NAME + length) != 0) { + length = length + 1; + } + return length; +} + +// The `index`th byte of that name. +fn name_byte(buffer: Int, index: Int) -> Int { + return byte_at(buffer, TAR_NAME + index); +} + +// The size the header records, for a caller that wants to print it. +fn entry_size(buffer: Int) -> Int { + return header_size(buffer); +} diff --git a/bare-metal-x86/drivers/tasks.lk b/bare-metal-x86/drivers/tasks.lk new file mode 100644 index 00000000..f1a76325 --- /dev/null +++ b/bare-metal-x86/drivers/tasks.lk @@ -0,0 +1,263 @@ +use { mmio_layout } from "layout"; + +// The task table, and the frame a task starts life on. +// +// A task, on this machine, is three words: where its stack pointer is while it +// is *not* running, which address space it runs in, and which kernel stack an +// interrupt from it should land on. Everything else about switching is one +// instruction — point RSP at another task's stack and let the same restore +// sequence run — and that instruction is in the board's trampoline, because a +// language cannot express "return on a different stack". +// +// The table lives at an address the caller chooses, for the reason every table +// here does: a driver that picks an address is a driver that collides with the +// next one. It is read from inside an interrupt, so every access goes through +// `volatile_*` — a value the compiler kept in a register across a poll would be +// a task table that never changes. + +// Two header words, then one slot per task. +// +// header +0 how many slots are in use +// header +8 which slot is on the CPU right now +// +// slot +0 the saved stack pointer, valid while the task is not running +// slot +8 its address space, as the value CR3 takes (0 = the kernel's) +// slot +16 the ring-0 stack an interrupt from it lands on (0 = none) +// slot +24 free, ready, blocked, idle, or dead-and-waiting-to-be-reclaimed +// slot +32 the tick a blocked task becomes ready at, or 0 +// slot +40 what a blocked task is waiting to be told, or 0 +mmio_layout! { + TASK_USED_OFFSET: 8, + TASK_CURRENT_OFFSET: 8, + => TASK_HEADER_SIZE +} + +mmio_layout! { + TASK_RSP_OFFSET: 8, + TASK_CR3_OFFSET: 8, + TASK_STACK_OFFSET: 8, + TASK_STATE_OFFSET: 8, + TASK_WAKE_OFFSET: 8, + TASK_WAIT_OFFSET: 8, + => TASK_SLOT_SIZE +} + +// What a slot is. +// +// A slot used to be either "below the count" or "above it", which is another way +// of saying a task could be born and never die. A kernel where nothing exits is +// a kernel where the table is a watermark; giving a task an end means a slot has +// to be reusable, and reusable means a slot has to say what it is. +// +// `DEAD` is a state and not a step, and that is the whole design. A task cannot +// release the stack it is standing on — the release would hand back the pages +// holding the very frame that is about to return. So an exiting task marks +// itself and stops, and something that is *not it* gives the pages back later. +const TASK_FREE = 0; +const TASK_READY = 1; +const TASK_DEAD = 2; +// Waiting for a moment that has not arrived. `TASK_WAKE_OFFSET` says which one, +// as a tick count; whoever counts ticks is the one that puts the task back. +// +// A blocked task is the difference between a scheduler and a round-robin. Until +// now every wait in this kernel was a spin with interrupts on — which works, and +// which means the machine is at full tilt doing nothing, and which means a task +// waiting a second is a second of every other task's time. +const TASK_BLOCKED = 3; +// The task that runs when there is nothing to run. +// +// Its own state rather than a slot number kept somewhere, because the two +// questions the scheduler asks are "may I rotate onto this?" and "is this where +// I go when nothing else will have me?" — and both are answered by the field it +// is already reading. A kernel with no idle task and every task blocked has +// nowhere to go: the last one out would be resumed anyway, which is a scheduler +// running a task it has just been told is not runnable. +const TASK_IDLE = 4; + +fn task_table_size(capacity: Int) -> Int { + return TASK_HEADER_SIZE + capacity * TASK_SLOT_SIZE; +} + +// Every access goes through these two, and through `volatile_*` inside them. +// The table is read from an interrupt handler, so a value the compiler decided +// to keep in a register across a poll would be a table that never changes. +fn task_word(address: Int) -> Int { + let value = unsafe { volatile_read_u64(address as *mut u64) }; + return value as Int; +} + +fn task_set_word(address: Int, value: Int) { + unsafe { volatile_write_u64(address as *mut u64, value as u64); }; +} + +// A slot's field, by the named offset it lives at. +// +// One pair rather than a wrapper per field: the offset already says which field +// it is, and a wrapper per field is six functions saying the same thing again. +// +// This was written when a bundled program could hold only 256 functions and six +// of them were worth saving. That ceiling has since moved — the merge numbers +// directly-called functions first — so what is left is the reason that survives +// it, which is the only one worth writing down. +fn task_field(base: Int, slot: Int, offset: Int) -> Int { + return task_word(base + TASK_HEADER_SIZE + slot * TASK_SLOT_SIZE + offset); +} + +fn task_set_field(base: Int, slot: Int, offset: Int, value: Int) { + task_set_word(base + TASK_HEADER_SIZE + slot * TASK_SLOT_SIZE + offset, value); +} + +// One task at boot: the one already running, on the stack the boot path gave +// it. It is slot 0, and it never gets an address space of its own. +fn task_table_init(base: Int, capacity: Int) { + for offset in 0..((task_table_size(capacity) / 8) as Int) { + task_set_word(base + offset * 8, 0); + } + // Slot 0 is the task already running, on the stack the boot path gave it. + // `TASK_FREE` is zero, so every other slot is already free from the zeroing + // above — which is why this only has to say what slot 0 is. + task_set_field(base, 0, TASK_STATE_OFFSET, TASK_READY); + task_set_word(base + TASK_USED_OFFSET, 1); +} + +// The lowest slot nothing is using, or -1. +// +// Lowest rather than next: the point of an exit is that the slot comes back, and +// a table that only ever grew would not need to look. The watermark is raised +// when a slot past it is taken, so the scheduler still has a bound to sweep. +fn task_find_free(base: Int, capacity: Int) -> Int { + for slot in 0..capacity { + if (task_field(base, slot, TASK_STATE_OFFSET) == TASK_FREE) { + return slot; + } + } + return 0 - 1; +} + +fn task_state(base: Int, slot: Int) -> Int { + return task_field(base, slot, TASK_STATE_OFFSET); +} + +fn task_set_state(base: Int, slot: Int, state: Int) { + task_set_field(base, slot, TASK_STATE_OFFSET, state); +} + +// Marks a slot as waiting until `tick`, or for `channel` to be signalled, or +// whichever comes first. +// +// Two ways to be woken and not one, because a driver needs both and needs them +// together. A wait with only a channel is a wait that never ends if the device +// is broken; a wait with only a deadline is a poll with extra steps. What a +// driver actually wants to say is "until the card answers, but not for ever", +// and that is one blocked task with two ways out. +// +// Zero means "not this one" for both. It is not an address a device lives at +// and it is not a tick anything waits for, since a deadline is always in the +// future when it is written. +// +// The state last, and that ordering is the same contract as everywhere else in +// this table: the state is the word a waker reads to decide this slot is its +// business, so a slot announced blocked before its deadline and channel are +// written is one that wakes on whatever the fields last held. +fn task_block_on(base: Int, slot: Int, tick: Int, channel: Int) { + task_set_field(base, slot, TASK_WAKE_OFFSET, tick); + task_set_field(base, slot, TASK_WAIT_OFFSET, channel); + task_set_field(base, slot, TASK_STATE_OFFSET, TASK_BLOCKED); +} + +// Puts a blocked slot back, clearing both of its reasons for waiting. +// +// One function rather than two lines at each waker, because a slot woken with a +// stale channel still in it is one the *next* signal on that channel wakes +// again — while it is running, in the middle of something else. +fn task_unblock(base: Int, slot: Int) { + task_set_field(base, slot, TASK_WAKE_OFFSET, 0); + task_set_field(base, slot, TASK_WAIT_OFFSET, 0); + task_set_field(base, slot, TASK_STATE_OFFSET, TASK_READY); +} + +// Wakes every task waiting to be told about `channel`. Answers how many. +// +// Called from interrupt handlers: a device that has something to say signals +// what it is, and whoever asked wakes. That is the whole of "blocking I/O" — +// what makes it worth having over a poll is not the code but the machine, which +// is asleep for the wait rather than turning over. +fn task_wake_channel(base: Int, watermark: Int, channel: Int) -> Int { + if (channel == 0) { + return 0; + } + let woken = 0; + for slot in 0..watermark { + if (task_field(base, slot, TASK_STATE_OFFSET) != TASK_BLOCKED) { + continue; + } + if (task_field(base, slot, TASK_WAIT_OFFSET) != channel) { + continue; + } + task_unblock(base, slot); + woken = woken + 1; + } + return woken; +} + +// Puts back every task whose moment has come. Answers how many. +// +// Swept rather than kept in a sorted queue: the table is sixteen slots and this +// runs on a timer, so the scan is cheaper than the bookkeeping a queue would +// need to stay correct across a task that is reaped while queued. +fn task_wake_due(base: Int, watermark: Int, now: Int) -> Int { + let woken = 0; + for slot in 0..watermark { + if (task_field(base, slot, TASK_STATE_OFFSET) != TASK_BLOCKED) { + continue; + } + let deadline = task_field(base, slot, TASK_WAKE_OFFSET); + // Zero is "no deadline": this one is waiting to be told something, and + // the clock is not the one to tell it. + if (deadline == 0 || deadline > now) { + continue; + } + task_unblock(base, slot); + woken = woken + 1; + } + return woken; +} + +// Builds the stack a task starts life on, and answers the stack pointer that +// resumes it. +// +// It is the exact picture the interrupt path leaves behind, because that is +// what the restore sequence will read: fifteen saved registers, and under them +// the frame the CPU itself pushes — SS, RSP, RFLAGS, CS, RIP, high to low. +// Getting the order wrong is not an error anything reports; it is a jump to +// whatever the wrong slot held. +// +// `code` and `data` are what make a task a *user* task. Not what it runs, not +// where its stack is — four numbers in this frame. +// `saved_words` is how much the board's own save sequence leaves on the stack, +// and it is asked of the board rather than counted here: that sequence is what +// decides it, and a register added there has to appear in the frame a task +// *starts* on too. Two numbers in two languages would be two places to add it, +// and a frame short by one word is not an error anything reports — it is a +// resume that reads its RIP out of whatever the next slot held. +fn task_prepare_frame(top: Int, entry: Int, code: Int, data: Int, resume_sp: Int, saved_words: Int) -> Int { + let sp = top; + sp = sp - 8; + task_set_word(sp, data); // SS + sp = sp - 8; + task_set_word(sp, resume_sp); // the RSP the task resumes with + sp = sp - 8; + task_set_word(sp, 0x202); // RFLAGS: interrupts enabled, bit 1 always set + sp = sp - 8; + task_set_word(sp, code); // CS + sp = sp - 8; + task_set_word(sp, entry); // RIP + // Zeroed rather than merely reserved: the first restore loads whatever is + // here into the saved registers, and a task should not begin life holding + // another task's numbers. + for _word in 0..saved_words { + sp = sp - 8; + task_set_word(sp, 0); + } + return sp; +} diff --git a/bare-metal-x86/drivers/tss.lk b/bare-metal-x86/drivers/tss.lk new file mode 100644 index 00000000..459acdea --- /dev/null +++ b/bare-metal-x86/drivers/tss.lk @@ -0,0 +1,63 @@ +// The task state segment. +// +// Almost all of it is dead weight in long mode. The register-save fields a +// 32-bit TSS had are gone, and hardware task switching went with them; what is +// left that the CPU still reads is one field, and the whole structure exists to +// hold it: `rsp0`, the stack to switch to when an interrupt or a syscall takes +// the machine from ring 3 back to ring 0. +// +// The layout, at the offsets the hardware fixes: +// +// 0..4 reserved +// 4..12 rsp0 the ring-0 stack +// 12..28 rsp1, rsp2 (unused: nothing here runs at rings 1 or 2) +// 28..36 reserved +// 36..92 ist[7] interrupt stack table (unused) +// 92..102 reserved +// 102..104 io_map_base +// +// `rsp0` at offset 4 is not eight-byte aligned, and that is the hardware's +// doing, not a mistake: a 64-bit field starting at a 32-bit offset. x86 does +// not care, but it is the reason this file writes it as a word at an address +// rather than through anything that would want alignment. + +const TSS_SIZE = 104; + +// Past the end of the segment, deliberately. +// +// An I/O permission bitmap whose *start* is beyond the segment's limit means +// "no ports are permitted", which is what a ring-3 task should be able to do: +// nothing. Leaving this zero instead would point the CPU at the beginning of +// the TSS and let a user task read a permission map made of the TSS's own +// fields — whatever bytes happened to be there, interpreted as permission. +const IO_MAP_NONE = 104; + +const RSP0_OFFSET = 4; +const IO_MAP_OFFSET = 102; + +fn byte_size() -> Int { + return TSS_SIZE; +} + +// Zeroes the segment and points its I/O bitmap past the end. +// +// Zeroing matters more than it looks: every reserved field is one the CPU +// checks or will check, and `ist[7]` left as old memory is seven stack pointers +// a future interrupt gate could be told to use. +fn prepare(base: Int) { + for offset in 0..((TSS_SIZE / 8) as Int) { + unsafe { volatile_write_u64((base + offset * 8) as *mut u64, 0 as u64); }; + } + unsafe { volatile_write_u16((base + IO_MAP_OFFSET) as *mut u16, IO_MAP_NONE as u16); }; +} + +// Points the CPU at the ring-0 stack for the next ring change. +// +// Per task, not once. Two user tasks sharing one kernel stack would have the +// second one's interrupt frame land on top of the first one's, and the first +// would resume into whatever was left of it. The scheduler calls this on every +// switch, because the scheduler is the only thing that knows whose stack is +// next. +fn set_kernel_stack(base: Int, top: Int) { + unsafe { volatile_write_u64((base + RSP0_OFFSET) as *mut u64, top as u64); }; +} diff --git a/bare-metal-x86/drivers/window.lk b/bare-metal-x86/drivers/window.lk index d9f037f6..4a683289 100644 --- a/bare-metal-x86/drivers/window.lk +++ b/bare-metal-x86/drivers/window.lk @@ -105,7 +105,7 @@ fn put(base: Int, stride: Int, table: Int, slot: Int, x: Int, y: Int, colour: In // on the shared page in the inner loop, which is both the cost this is removing // and a race with the handlers. fn put_within(base: Int, stride: Int, left: Int, top: Int, width: Int, height: Int, - x: Int, y: Int, colour: Int) -> Int { + x: Int, y: Int, colour: Int) -> Int { if (x < 0 || y < 0 || x >= width || y >= height) { return 1; } @@ -123,7 +123,7 @@ fn fill(base: Int, stride: Int, table: Int, slot: Int, x: Int, y: Int, width: In for row in 0..height { for column in 0..width { drops = drops + put_within(base, stride, left, top, limit_x, limit_y, - x + column, y + row, colour); + x + column, y + row, colour); } } if (drops > 0) { diff --git a/bare-metal-x86/kernel.py b/bare-metal-x86/kernel.py new file mode 100644 index 00000000..3fd98372 --- /dev/null +++ b/bare-metal-x86/kernel.py @@ -0,0 +1,66 @@ +"""The kernel image the `check_*.py` scripts boot — built from the tree they run in. + +Every one of them booted `target/x86_64-unknown-none/release/*.multiboot`, and +not one of them put it there. The path was a bare default, so a script tested +whatever happened to be on disk. Both ways that lies have happened here: + +* **The file is absent.** QEMU exits before it opens its monitor socket, and the + script dies in `connection.connect(monitor)` with `ConnectionRefusedError` — + a message about a socket, nowhere near the cause. +* **The file is a `fault-probe` build.** `CARGO_FLAGS=--features=fault-probe + ./run.sh` builds a kernel that touches 0x900000000 on purpose, to prove the + exception reporter names a fault; `run.sh` used to objcopy it over the very + path every check boots. The next check then reported `#PF page fault` as a + regression, and bisecting it is hopeless: every revision "fails", because no + revision is what is running. + +So the image is an *output* of these scripts, not an input. `cargo build` with +default features is a fast no-op when nothing changed and rebuilds when a +feature set differs, which is what makes the second case above unreachable +rather than merely unlikely. + +Passing a path explicitly (`python3 check_pci.py IMAGE`) still boots that file +as given — that is for testing an image from somewhere else, and it is the +caller's business whether it matches the tree. +""" + +import os +import subprocess +import sys + +ROOT = os.path.dirname(os.path.abspath(__file__)) +BIN = os.path.join(ROOT, "target/x86_64-unknown-none/release/lk-bare-metal-x86") +# The compiler that turns this crate's `.lk` files into native code. `build.rs` +# falls back to whatever `lk` is on PATH, which is how a two-day-old installed +# binary once got credit for a fix that was never compiled — so this names the +# repo's own build and refuses if it is missing. +LK = os.path.join(ROOT, "../target/debug/lk") + + +def kernel_image(argv=None): + """Build the image and return its path, or return `argv[1]` untouched.""" + argv = sys.argv if argv is None else argv + if len(argv) > 1: + return argv[1] + + # An explicit `LK_BIN` names a specific compiler and is used as given; only + # the default is checked, because that is the one nobody chose. + compiler = os.environ.get("LK_BIN") or LK + if not os.environ.get("LK_BIN") and not os.path.exists(LK): + sys.exit( + f"{os.path.relpath(LK, ROOT)} is missing: the kernel's LK sources are compiled by\n" + "the repo's own `lk`, not by whatever is installed on PATH. Build it first:\n" + " cargo build -p lk-cli --features aot" + ) + + environment = dict(os.environ, LK_BIN=os.path.abspath(compiler)) + # This crate's `.cargo/config.toml` carries `relocation-model=static` and + # the SSE settings in its own rustflags table, and an inherited `RUSTFLAGS` + # would replace that table rather than add to it — including an empty one. + environment.pop("RUSTFLAGS", None) + subprocess.run(["cargo", "build", "--release"], cwd=ROOT, env=environment, check=True) + # QEMU's multiboot loader only accepts ELF32, while the code is 64-bit; + # `run.sh` explains why converting the class loses nothing. + objcopy = os.environ.get("OBJCOPY", "llvm-objcopy") + subprocess.run([objcopy, "-O", "elf32-i386", BIN, f"{BIN}.multiboot"], check=True) + return f"{BIN}.multiboot" diff --git a/bare-metal-x86/link.ld b/bare-metal-x86/link.ld index 16d18be1..333db044 100644 --- a/bare-metal-x86/link.ld +++ b/bare-metal-x86/link.ld @@ -9,6 +9,38 @@ * section, placed first. */ ENTRY(_start) +/* The machine's memory, decided here and nowhere else. + * + * Here because this file already decides where the image goes, and because it + * is the one place *both* languages can read: Rust takes the address of an + * `extern static`, and LK asks `symbol_address`. It used to be a table in a doc + * comment plus a literal in each of them — `0x00380000` was written down twice + * and cross-checked at run time by `kernel_run` refusing any other address, + * which is a way of noticing the two had drifted, not a way of stopping it. + * + * The relations are written as relations, so "the arena starts where the run + * heap ends" is a statement rather than an arithmetic coincidence between two + * constants nobody recomputed. + * + * 0x00100000 the image: code, data, page tables and stacks (bounded by ASSERT) + * 0x00300000 the shared page, which the program and its handlers agree on + * 0x00380000 64 KiB staging for a source file read off the disk + * 0x00400000 the interpreter's heap + * 0x00800000 the arena a hosted run allocates from, reset per run + * 0x02000000 the LK page allocator's arena + * + * The sizes are symbols too. A linker symbol is an address, and an address is a + * number: `__heap_size` is simply a symbol whose number happens to be a length. + */ +__shared_base = 0x00300000; +__source_base = 0x00380000; +__source_max = 0x00010000; +__heap_base = 0x00400000; +__heap_size = 0x00400000; +__run_heap_base = __heap_base + __heap_size; +__run_heap_size = 0x01800000; +__page_arena_base = __run_heap_base + __run_heap_size; + SECTIONS { . = 0x100000; @@ -22,6 +54,58 @@ SECTIONS *(.text .text.*) } + /* Everything ring 3 may reach, on its own pages. + * + * Page-aligned at both ends because permission is granted per page: a user + * region sharing a page with kernel code would hand ring 3 that code too, + * which is exactly what the first version of this did with a 2 MiB page. */ + . = ALIGN(4096); + __user_start = .; + .user : { *(.user .user.*) } + + /* The stack the one-shot `user` command's ring-3 program runs on. + * + * Inside the user region, and it has to be: that program runs in the + * *kernel's* address space, so its stack is only reachable because these + * pages carry the U bit. The per-task ones below are a different case. */ + . = ALIGN(4096); + __user_shell_stack = .; + . = . + 4096; + __user_end = .; + + /* The user region has to fit under 4 MiB, and that is not a preference. + * + * `boot.rs` grants the U bit by indexing `__pt0` with `__user_start >> 12` + * — an *absolute* page number — and `__pt0`/`__pt1` together are 1024 + * entries, so they describe the first 4 MiB and nothing above it. A user + * region past that indexes off the end: nothing is granted, the ring-3 + * page stays kernel-only, and the first instruction of the program takes a + * page fault with `rip == cr2`. + * + * That is exactly what happened, and what it took to get there was `.text` + * growing by one page. `__user_start` had been sitting at 0x1ff000 — one + * page from the edge — so any change that made the compiler bigger pushed + * the kernel's ring 3 off a cliff, with a page fault as the only symptom. + * A link error says which line to read. */ + ASSERT(__user_end <= 0x400000, + "the ring-3 region no longer fits under 4 MiB, where boot.rs's 4 KiB page tables end") + + /* The ring-3 tasks' stacks, deliberately *outside* the user region. + * + * Each task maps its own at `USER_STACK_VIRTUAL` in its own address space, + * so it needs no U bit here — and being outside is what stops one task + * handing the kernel a pointer into the other one's stack through + * `write(ptr, len)`, which checks against exactly this range. + * + * Named here rather than declared as Rust statics so `program.lk` can ask + * for them itself. A `static mut [u8; N]` and a linker reservation are the + * same thing; only one of them has a name every language can say. */ + . = ALIGN(4096); + __user_task_stack_a = .; + . = . + 4096; + __user_task_stack_b = .; + . = . + 4096; + .rodata : { *(.rodata .rodata.*) } .data : { *(.data .data.*) } @@ -46,6 +130,39 @@ SECTIONS near the top of the 32-bit physical range, far outside the first. */ __pd = .; . = . + 4 * 4096; + /* Two 4 KiB-granular tables, covering the first 4 MiB, so the user pages + can be marked separately from the kernel ones sharing that range. + + Adjacent, and that is load-bearing: `boot.rs` grants the U bit by + indexing from `__pt0` with an absolute page number, so the two tables + have to read as one 1024-entry array. There was one table, covering + 2 MiB, and the ring-3 region had drifted to 0x1ff000 — one page below + the end of what it describes. */ + __pt0 = .; + . = . + 4096; + __pt1 = .; + . = . + 4096; + + /* No user page tables reserved here any more. `program.lk` builds an + address space out of pages from its own allocator, so how many there can + be is bounded by memory rather than by a number written down here — and + the number written down here was four, which was never justified by + anything except that two had been. */ + + /* The ring-0 stack the CPU switches to when an interrupt arrives from ring + 3 while *task 0* is running — the one task that pre-exists the table and + so never published a stack of its own. + * + * Its own, not the interrupted task's: a user task's stack pointer is a + * value the user chose, and an interrupt that pushed onto it would hand the + * frame it is about to `iretq` from to the program it interrupted. + * + * Both ends named, so `program.lk` can ask for the top without also being + * told how big it is. */ + . = ALIGN(16); + __ring0_stack = .; + . = . + 16384; + __ring0_stack_top = .; /* The stack grows down from here. */ . = ALIGN(16); @@ -60,9 +177,25 @@ SECTIONS * not fail to boot: it would quietly put the key handler's line buffer on * top of the kernel's own data. Since the interpreter went in, the image is * ten times the size it was, so this is a real bound rather than a - * theoretical one. */ - ASSERT(__bss_end < 0x300000, - "the image has grown into the shared page at 0x300000 (see SHARED_BASE in program.lk)") + * theoretical one. + * + * `__stack_top`, not `__bss_end`. Everything reserved above — the page + * tables, the ring-0 stack, and the stack itself — comes *after* `.bss`, + * and at the time of writing that is 111 KiB the old test could not see. + * It would have let the stack sit 111 KiB inside the shared page and still + * called the layout fine, which is the same mistake the ring-3 grant made + * with `__pt0`: an invariant checked against the wrong quantity does not + * announce that it is the wrong one. */ + ASSERT(__stack_top <= __shared_base, + "the image has grown into the shared page (see the memory map at the top of this file)") + + /* And the staging area must stop below the interpreter's heap. + * + * `__source_max` is a length, not an address, so the two are only related + * by an addition nobody was performing. A file one byte too long would be + * read straight into the heap the interpreter is about to allocate from. */ + ASSERT(__source_base + __source_max <= __heap_base, + "the source staging area overlaps the interpreter heap") /DISCARD/ : { *(.eh_frame*) *(.comment) } } \ No newline at end of file diff --git a/bare-metal-x86/program.lk b/bare-metal-x86/program.lk index be63688e..b24394d2 100644 --- a/bare-metal-x86/program.lk +++ b/bare-metal-x86/program.lk @@ -11,37 +11,75 @@ // them. They live here rather than in the driver modules for the same reason: // a driver decodes a scancode, but only the program knows what to do with it. -use { uart_init, uart_putc, uart_write, uart_put_int, uart_put_hex } from "drivers/serial"; -use { pci_read, pci_write, pci_find_display, PCI_REG_BAR0, PCI_REG_COMMAND, - PCI_COMMAND_MEMORY, PCI_COMMAND_BUS_MASTER } from "drivers/pci"; +use { uart_init, uart_putc, uart_write, uart_text, uart_put_int, uart_put_hex } from "drivers/serial"; +use { pci_find_display, pci_devices, pci_vendor, pci_device_id, pci_class, + pci_subclass, pci_bar, pci_bar_size, pci_bar_is_io, pci_bdf_device, + pci_bdf_function, pci_command_set, + PCI_COMMAND_MEMORY, PCI_COMMAND_BUS_MASTER } from "drivers/pci"; +use { edu_find, edu_open, edu_identify, edu_liveness, edu_factorial, edu_dma, + edu_irq_line, edu_bar_size, edu_raise, edu_acknowledge, edu_irq_status, + EDU_IDENT, EDU_IRQ_TEST } from "drivers/edu"; +use { e1000_find, e1000_open, e1000_init, e1000_mac, e1000_set_mac, + e1000_link_up, e1000_transmit, e1000_receive_within, e1000_receive, + e1000_enable_receive_interrupt, e1000_disable_interrupts, e1000_interrupt_cause, + TX_COUNT, RX_COUNT, RX_BUFFER_SIZE, DESC_SIZE, STATE_SIZE } from "drivers/e1000"; +use { arp_build_request, arp_match_reply, frame_set_byte as net_set_byte, + frame_byte as net_byte_at, MAC_SIZE, IP_SIZE } from "drivers/arp"; use { vbe_set_mode_virtual, vbe_set_pan } from "drivers/vbe"; use { get_pixel, put_pixel, fill_rect, copy_rows } from "drivers/framebuffer"; use { draw_glyph, draw_cells, clear, scroll_up, draw_cursor, CELL_WIDTH, CELL_HEIGHT } from "drivers/text"; -use { read_scancode, is_release, key_code } from "drivers/keyboard"; +use { read_scancode, is_release, key_code, is_shift, is_caps_lock } from "drivers/keyboard"; use { mouse_init, has_packet_byte, read_packet_byte as mouse_byte, is_first_byte, - dx_of, dy_of, left_down } from "drivers/mouse"; + dx_of, dy_of, left_down } from "drivers/mouse"; use { start as pit_start } from "drivers/pit"; +use { remap as pic_remap, mask as pic_mask, unmask as pic_unmask, + mask_line as pic_mask_line, eoi as pic_eoi, in_service as pic_in_service, + set_level_triggered as pic_set_level, + eoi_master as pic_eoi_master, eoi_slave as pic_eoi_slave, + line_exists as pic_line_exists } from "drivers/pic"; +use { hpet_present, hpet_start, hpet_frequency, hpet_counter, + hpet_elapsed_microseconds } from "drivers/hpet"; +use { zero as idt_zero, set_gate as idt_set_gate, install as idt_install, + IDT_GATE_COUNT, IDT_GATE_SIZE } from "drivers/idt"; +use { set_entry as gdt_set_entry, set_tss_entry as gdt_set_tss_entry, + load as gdt_load, reload as gdt_reload, + load_task_register as gdt_load_task_register, GDT_ENTRY_SIZE } from "drivers/gdt"; +use { prepare as tss_prepare, set_kernel_stack as tss_set_kernel_stack, + TSS_SIZE } from "drivers/tss"; use { acquire as lock, release as unlock } from "drivers/lock"; use { define as window_define, put as window_put, fill as window_fill, - dropped as window_dropped, outline as window_outline, - put_within as window_put_within, drop_count as window_drop_count, - left as window_left, top as window_top, width as window_width, - height as window_height } from "drivers/window"; + dropped as window_dropped, outline as window_outline, + put_within as window_put_within, drop_count as window_drop_count, + left as window_left, top as window_top, width as window_width, + height as window_height } from "drivers/window"; use { glyph_row, glyph_width, glyph_rows } from "drivers/text"; use { reset as queue_reset, push as queue_push, pop as queue_pop, - is_empty as queue_empty } from "drivers/queue"; + is_empty as queue_empty } from "drivers/queue"; use { has_mmap, mem_upper_kb, entry_count, entry_at, entry_base, entry_length, - entry_is_available } from "drivers/multiboot"; + entry_is_available } from "drivers/multiboot"; use { heap_init, heap_alloc, heap_release, - used_bytes as heap_used, block_count as heap_blocks, largest_free as heap_largest } from "drivers/heap"; + used_bytes as heap_used, block_count as heap_blocks, largest_free as heap_largest } from "drivers/heap"; +use { task_table_init, task_table_size, task_word, task_set_word, task_field, task_set_field, + task_prepare_frame, task_find_free, task_state, task_set_state, + TASK_USED_OFFSET, TASK_CURRENT_OFFSET, TASK_RSP_OFFSET, + TASK_CR3_OFFSET, TASK_STACK_OFFSET, + task_block_on, task_unblock, task_wake_due, task_wake_channel, + TASK_FREE, TASK_READY, TASK_DEAD, TASK_BLOCKED, TASK_IDLE } from "drivers/tasks"; +use { table_zero, table_set, index_of, switch_to as space_switch, + PAGE_PRESENT, PAGE_WRITE, PAGE_USER, SHIFT_PML4, SHIFT_PDPT, + SHIFT_DIRECTORY, SHIFT_TABLE } from "drivers/paging"; +use { rtc_read, rtc_year, rtc_month, rtc_day, rtc_hour, rtc_minute, rtc_second } from "drivers/rtc"; use { init as pages_init, alloc as page_alloc, free as pages_free, - total as pages_total, PAGE_SIZE } from "drivers/pages"; + release_page as page_release, release_run as pages_release_run, + total as pages_total, PAGE_SIZE, PAGES_STATE_SIZE } from "drivers/pages"; use { read_sector as ata_read_sector, write_sector as ata_write_sector, - identify as ata_identify, identify_sectors as ata_identify_sectors, - ATA_OK, ATA_TIMEOUT, ATA_ERROR_BIT, ATA_NO_DRIVE, ATA_BAD_LBA } from "drivers/ata"; -use { find as tar_find, TAR_OK, TAR_NOT_FOUND, TAR_READ_FAILED } from "drivers/tarfs"; + identify as ata_identify, identify_sectors as ata_identify_sectors, + ATA_OK, ATA_TIMEOUT, ATA_ERROR_BIT, ATA_NO_DRIVE, ATA_BAD_LBA } from "drivers/ata"; +use { find as tar_find, next_entry as tar_next, name_length as tar_name_length, + name_byte as tar_name_byte, entry_size as tar_entry_size, + TAR_OK, TAR_NOT_FOUND, TAR_READ_FAILED } from "drivers/tarfs"; use { read as shared_read, write as shared_write, bump as shared_bump, - read_byte as shared_read_byte, write_byte as shared_write_byte } from "drivers/shared"; + read_byte as shared_read_byte, write_byte as shared_write_byte } from "drivers/shared"; const WIDTH = 320; const HEIGHT = 200; @@ -60,7 +98,7 @@ const POINTER_COLOUR = 0xff4060; // module's top level cannot cross a bundled import without changing what the // VM would do — see that module's comment. const FONT = [ - 0, 0, 0, 0, 0, 0, 0, 0, // space + 0, 0, 0, 0, 0, 0, 0, 0, // space 14, 17, 17, 31, 17, 17, 17, 0, // A 30, 17, 30, 17, 17, 17, 30, 0, // B 15, 16, 16, 16, 16, 16, 15, 0, // C @@ -70,7 +108,7 @@ const FONT = [ 15, 16, 16, 19, 17, 17, 15, 0, // G 17, 17, 31, 17, 17, 17, 17, 0, // H 14, 4, 4, 4, 4, 4, 14, 0, // I - 7, 2, 2, 2, 2, 18, 12, 0, // J + 7, 2, 2, 2, 2, 18, 12, 0, // J 17, 18, 20, 24, 20, 18, 17, 0, // K 16, 16, 16, 16, 16, 16, 31, 0, // L 17, 27, 21, 17, 17, 17, 17, 0, // M @@ -88,23 +126,23 @@ const FONT = [ 17, 17, 10, 4, 4, 4, 4, 0, // Y 31, 1, 2, 4, 8, 16, 31, 0, // Z 14, 17, 19, 21, 25, 17, 14, 0, // 0 - 4, 12, 4, 4, 4, 4, 14, 0, // 1 + 4, 12, 4, 4, 4, 4, 14, 0, // 1 14, 17, 1, 2, 4, 8, 31, 0, // 2 31, 2, 6, 1, 1, 17, 14, 0, // 3 - 2, 6, 10, 18, 31, 2, 2, 0, // 4 + 2, 6, 10, 18, 31, 2, 2, 0, // 4 31, 16, 30, 1, 1, 17, 14, 0, // 5 - 6, 8, 16, 30, 17, 17, 14, 0, // 6 + 6, 8, 16, 30, 17, 17, 14, 0, // 6 31, 1, 2, 4, 8, 8, 8, 0, // 7 14, 17, 17, 14, 17, 17, 14, 0, // 8 14, 17, 17, 15, 1, 2, 12, 0, // 9 - 0, 0, 0, 0, 0, 12, 12, 0, // . - 0, 0, 0, 0, 12, 12, 8, 0, // , - 0, 12, 12, 0, 12, 12, 0, 0, // : - 0, 0, 0, 31, 0, 0, 0, 0, // - - 4, 4, 4, 4, 4, 0, 4, 0, // ! + 0, 0, 0, 0, 0, 12, 12, 0, // . + 0, 0, 0, 0, 12, 12, 8, 0, // , + 0, 12, 12, 0, 12, 12, 0, 0, // : + 0, 0, 0, 31, 0, 0, 0, 0, // - + 4, 4, 4, 4, 4, 0, 4, 0, // ! 14, 17, 1, 2, 4, 0, 4, 0, // ? - 0, 0, 31, 0, 31, 0, 0, 0, // = - 0, 16, 8, 4, 8, 16, 0, 0, // > + 0, 0, 31, 0, 31, 0, 0, 0, // = + 0, 16, 8, 4, 8, 16, 0, 0, // > ]; // Free RAM: the image, its heap, its stack and the page tables all live below @@ -133,7 +171,13 @@ const SCANCODE_ASCII = [ // // This is what a systems language owes a shared page when it has no allocator: // not safety, but an arrangement in which the mistake is not expressible. -const SHARED_BASE = 0x00300000; +// Asked of the linker rather than written down, because `link.ld` is where the +// machine's memory map lives and it is the one place the board reads it from +// too. The old note here said an interrupt handler "cannot look anything up" — +// which was true of a run-time lookup and never true of this one: a symbol's +// address is resolved when the image is linked, so what the handler executes is +// an immediate either way. +const SHARED_BASE: Int = unsafe { symbol_address("__shared_base") }; const WORD = 4; const SHARED_KEY_COUNT = SHARED_BASE; @@ -147,10 +191,16 @@ const SHARED_LINE_READY = SHARED_LINE_LEN + WORD; // it, this program reads it; the address appears in both files, and changing // one without the other is the hazard. const SHARED_MULTIBOOT = SHARED_LINE_READY + WORD; -// The page allocator's three words. +// The page allocator's state, whose size is the allocator's to say. +// +// It said three words until it grew a free list, and the fourth landed on +// `SHARED_SPIN` — which is the spinner task's step, so what broke was the +// *network card*, three files away, by way of an allocator handing out a page +// whose bookkeeping the scheduler was incrementing. A neighbour's size written +// down here as a literal is a dependency that only exists in a comment. const SHARED_PAGES = SHARED_MULTIBOOT + WORD; // The spinner task's step, and the scheduler's own counter. -const SHARED_SPIN = SHARED_PAGES + 3 * WORD; +const SHARED_SPIN = SHARED_PAGES + PAGES_STATE_SIZE; const SHARED_SLICE = SHARED_SPIN + WORD; // Two words that must always agree. Both the timer handler and the spinner // task bump the pair; a lost update — an increment that read a stale value — @@ -215,8 +265,8 @@ const SCRATCH_SECTOR = 32; // commands here and small enough that the failure it is meant to demonstrate — // running out, and recovering by freeing — happens within a session. const HEAP_PAGES = 16; -const SOURCE_BASE = 0x00380000; -const SOURCE_MAX = 0x10000; +const SOURCE_BASE: Int = unsafe { symbol_address("__source_base") }; +const SOURCE_MAX: Int = unsafe { symbol_address("__source_max") }; // Two words the filesystem answers through: the first data sector of a file it // found, and the file's size. const SHARED_FILE = SHARED_SECTOR + SECTOR_SIZE; @@ -252,7 +302,13 @@ const SHARED_CURSOR_DRAWN = SHARED_CURSOR_Y + WORD; // one thing about who is above whom. // What second the clock window is currently showing, so it is only redrawn // when the answer changed. -const SHARED_CLOCK_SHOWN = SHARED_CURSOR_DRAWN + WORD; +// The modifier keys' state. In the shared page because the handler is the only +// thing that sees a key event, and the same handler has to read it back on the +// next one. +const SHARED_SHIFT = SHARED_CURSOR_DRAWN + WORD; +const SHARED_CAPS = SHARED_SHIFT + WORD; + +const SHARED_CLOCK_SHOWN = SHARED_CAPS + WORD; // How many tasks exist. Written by the program when it spawns them; read by // the scheduler, which runs in an interrupt and cannot ask the board. const SHARED_TASK_COUNT = SHARED_CLOCK_SHOWN + WORD; @@ -283,6 +339,16 @@ const SHARED_DRAG_SLOT = SHARED_WINDOW_PAINT + WINDOW_COUNT * WORD; const SHARED_DRAG_DX = SHARED_DRAG_SLOT + WORD; const SHARED_DRAG_DY = SHARED_DRAG_DX + WORD; +// Set when a ring-3 task asks to exit, which is how "the excursion finished" +// is told apart from "the excursion faulted". The user program makes that call +// *before* it tries the forbidden thing, so the two are distinguishable at all. +// +// TODO: nothing reads this yet — the ring-3 checks assert on what reached the +// serial line instead. It is recorded rather than dropped because it is the +// only evidence of the difference; a `user` command that reported it would make +// the evidence reachable. +const SHARED_USER_EXITED = SHARED_DRAG_DY + WORD; + // The terminal's characters, one byte per cell. // // The screen was the only record of what had been typed, which is fine until @@ -293,13 +359,127 @@ const SHARED_DRAG_DY = SHARED_DRAG_DX + WORD; // // ASCII, not glyph indices: the same bytes the shell already handles, so the // grid and the line buffer say the same thing about a character. -const SHARED_GRID = SHARED_DRAG_DY + WORD; +// Where the PCI device's registers are, and what its handler last saw. +// +// The handler needs the first because an interrupt entry takes no arguments but +// its vector, and the device's BAR is not knowable until configuration space +// has been read — which happens long after the program's globals are laid out. +// A shared word is how every other handler here reaches state it did not +// receive. +// +// `SHARED_EDU_SEEN` is written by the handler and read by the shell, so it is +// the ordinary shape of a completion flag: 0 until an interrupt arrives, then +// the bits the device said it raised. Read through `shared_read`, which is +// volatile — a poll the compiler hoisted is a wait that never ends. +// How much work the short-lived tasks got through, so the shell can tell "the +// task ran and ended" from "the task was never scheduled". +const SHARED_BRIEF_TICKS = SHARED_USER_EXITED + WORD; + +// How many times the sleeping task has woken. +const SHARED_SLEEP_WAKES = SHARED_BRIEF_TICKS + WORD; + +// How many times the idle task has been given the CPU. The difference between +// waiting and spinning, as a number. +const SHARED_IDLE_TURNS = SHARED_SLEEP_WAKES + WORD; + +// The slots of the tasks that never block, so the shell can take them off the +// CPU when it wants to observe a machine with nothing to do. +// +// Three of them, and two are in ring 3. That is not incidental: suspending a +// task is writing a word in a table the scheduler reads, and it works the same +// whichever ring the task runs in — a ring-3 task has no say in it, which is the +// difference between a kernel and a cooperative loop. +const SHARED_BUSY_SLOTS = SHARED_IDLE_TURNS + WORD; +const BUSY_SLOT_COUNT = 3; + +// Where the network card's registers are, and what its handler last saw. The +// handler needs the first for the same reason the PCI device's does: an +// interrupt entry takes no arguments but its vector. +const SHARED_NET_MMIO = SHARED_BUSY_SLOTS + BUSY_SLOT_COUNT * WORD; +const SHARED_NET_CAUSE = SHARED_NET_MMIO + WORD; +// How many interrupts the card actually raised during the exchange. +// +// Reported because without it this driver's comments are unfalsifiable. A wait +// with a deadline works whether or not the interrupt ever arrives — it waits out +// the timeout and finds the frame already in the ring — so "interrupt-driven" +// and "polling with extra steps" produce the same output and the same success. +// +// The number here is not twelve. On this device model some exchanges are +// answered by the card and the rest by the deadline, which is why the deadline +// is load-bearing rather than a safety net. Counting is what turned that from an +// assumption into a fact; the first version of this driver scored one. +const SHARED_NET_IRQS = SHARED_NET_CAUSE + WORD; +const SHARED_NET_WAITS = SHARED_NET_IRQS + WORD; + +const SHARED_EDU_MMIO = SHARED_NET_WAITS + WORD; +const SHARED_EDU_SEEN = SHARED_EDU_MMIO + WORD; + +const SHARED_GRID = SHARED_EDU_SEEN + WORD; const GRID_COLUMNS = 53; const GRID_ROWS = 25; // The end of what is spoken for. Anything added goes here, not into a gap. const SHARED_END = SHARED_GRID + GRID_COLUMNS * GRID_ROWS; +// The interrupt descriptor table. +// +// Not a shared word like everything above — nothing in this program reads it. +// The *CPU* reads it, on every interrupt, at the address `lidt` was given. It +// is here because this is the region the memory map set aside for things with +// no allocator behind them, and because the rule this file states about that +// region ("anything added goes here, not into a gap") is what keeps two of them +// from landing on each other. +// +// Rounded up to sixteen: a gate is sixteen bytes, and a table that starts +// mid-gate is one where every entry straddles two of them. The CPU does not +// require the alignment, which is exactly why nothing would report its absence. +const IDT_BASE = (((SHARED_END + 15) / 16) as Int) * 16; +const IDT_SIZE = IDT_GATE_COUNT * IDT_GATE_SIZE; + +// The descriptor table the machine runs on, and the task state segment it +// names. Both after the interrupt table, sized from the same numbers the +// drivers use rather than from a literal: the next thing added starts at +// `TSS_BASE + TSS_SIZE`, and every one of these bases is a sum rather than a +// typed address, which is the arrangement this file argues for at +// `SHARED_BASE`. +// +// Seven entries: null, ring-0 code and data, ring-3 code and data, and the two +// that make up the TSS descriptor — a system descriptor in long mode is sixteen +// bytes, so it occupies two. +const GDT_ENTRIES = 7; +const GDT_BASE = IDT_BASE + IDT_SIZE; +const TSS_BASE = GDT_BASE + GDT_ENTRIES * GDT_ENTRY_SIZE; + +// Where a user task's stack lives *in its own address space*. +// +// A virtual address the kernel's space does not map to the same thing: in the +// kernel's tables 0x4000_0000 is identity-mapped RAM that nothing uses, and in +// the task's it is the stack. That difference is what makes them two address +// spaces rather than one with extra permissions. +// +// The `- 8` is the ABI's phase: a function assumes a `call` has just pushed a +// return address, and `iretq` pushes nothing. Without it the first aligned SSE +// spill inside the task faults, in whatever it happened to call. +const USER_STACK_VIRTUAL = 0x40000000; +const USER_STACK_RESUME = USER_STACK_VIRTUAL + PAGE_SIZE - 8; + +// The task table. +// +// How many slots is a decided number rather than a discovered one: the table is +// a fixed region, so it needs a size, and sixteen slots cost four hundred +// bytes. It used to be six, which was "the tasks that exist, plus one" — a +// number that says nothing except that nobody had needed a seventh. What the +// capacity is *not* any more is a bound on memory: the stacks come from the +// page allocator now, so a task past this is refused because the table is full, +// not because a linker script reserved four of something. +const TASK_CAPACITY = 16; +const TASK_TABLE_BASE = TSS_BASE + TSS_SIZE; + +// A task's stack, in pages. 32 KiB, which is what the board's static array was: +// compiled LK spills SSE registers, and an interrupt lands on top of whatever +// depth the task had already reached. +const TASK_STACK_PAGES = 8; + // The framebuffer is twice the screen's height. Scrolling moves the *view* // down through it; only when the view reaches the bottom does anything have to // be copied, and then once for a whole screenful of scrolling. @@ -308,6 +488,52 @@ const VIRTUAL_HEIGHT = 400; const FRAME_IDLE = 0x203040; const FRAME_FOCUSED = 0xffc040; +// What a key produces once the modifiers are applied. +// +// Two different rules, which is why this is not one table: caps lock affects +// *letters only*, and shift affects everything — a keyboard where CapsLock +// turned `1` into `!` would be one nobody could type on. Letters therefore ask +// "is exactly one of shift and caps in effect", and the rest ask only about +// shift. +fn shifted(ascii: Int, shift: Bool, caps: Bool) -> Int { + if (ascii >= 97 && ascii <= 122) { + // Upper case when exactly one of them is on: both on is lower again, + // which is what a shifted letter does under caps lock. + if (shift != caps) { + return ascii - 32; + } + return ascii; + } + if (!shift) { + return ascii; + } + // The US layout's shifted punctuation, spelled out rather than computed: + // there is no arithmetic relation between `1` and `!`, only a convention, + // and writing the convention down is the honest way to say so. + if (ascii == 49) { return 33; } // 1 -> ! + if (ascii == 50) { return 64; } // 2 -> @ + if (ascii == 51) { return 35; } // 3 -> # + if (ascii == 52) { return 36; } // 4 -> $ + if (ascii == 53) { return 37; } // 5 -> % + if (ascii == 54) { return 94; } // 6 -> ^ + if (ascii == 55) { return 38; } // 7 -> & + if (ascii == 56) { return 42; } // 8 -> * + if (ascii == 57) { return 40; } // 9 -> ( + if (ascii == 48) { return 41; } // 0 -> ) + if (ascii == 45) { return 95; } // - -> _ + if (ascii == 61) { return 43; } // = -> + + if (ascii == 91) { return 123; } // [ -> { + if (ascii == 93) { return 125; } // ] -> } + if (ascii == 92) { return 124; } // \ -> | + if (ascii == 59) { return 58; } // ; -> : + if (ascii == 39) { return 34; } // ' -> " + if (ascii == 44) { return 60; } // , -> < + if (ascii == 46) { return 62; } // . -> > + if (ascii == 47) { return 63; } // / -> ? + if (ascii == 96) { return 126; } // ` -> ~ + return ascii; +} + // Where a character sits in FONT. The font has one case, so lower-case maps to // the upper-case glyph; anything it does not cover renders as a space, which // is what a terminal with a small font should do rather than drawing noise. @@ -349,11 +575,11 @@ fn framebuffer_address() -> Int { // different, invalid request — the kind of value that produces a plausible // number instead of an error. fn probe_framebuffer() -> Int { - let slot = pci_find_display(); - if (slot < 0) { + let display = pci_find_display(); + if (display < 0) { return 0; } - return pci_read(slot, PCI_REG_BAR0) & 0xfffffff0; + return pci_bar(display, 0); } // The address of the pixel the screen shows at its top-left. @@ -426,7 +652,7 @@ fn repaint_cells(base: Int, left: Int, top: Int, width: Int, height: Int) { for column in first_column..last_column + 1 { if (column >= 0 && row >= 0 && column < text_columns() && row < text_rows()) { draw_glyph(base, WIDTH, FONT, glyph_index(grid_read(column, row)), - column * CELL_WIDTH, row * CELL_HEIGHT, FOREGROUND, BACKGROUND); + column * CELL_WIDTH, row * CELL_HEIGHT, FOREGROUND, BACKGROUND); } } } @@ -440,6 +666,17 @@ fn repaint_cells(base: Int, left: Int, top: Int, width: Int, height: Int) { // are not the handler mask interrupts around it: the two would otherwise // interleave on the cursor. fn put_char(base: Int, ascii: Int) { + // `as Int` on a parameter already declared `Int` is not redundant here, and + // the reason is a property of the lowering rather than of this function: a + // parameter's type is *observed* from its call sites, and one call site + // passing a boxed value widens it for every other one. This has several — + // `console_byte` is `#[export]`ed (called from C, so nothing proves the + // argument came from LK) and the queue read comes out of shared memory — + // so the cast is put once, here, instead of at each of them. + // + // Without it the arithmetic on `ascii` stops lowering, which on this target + // is a hard failure rather than a fallback to the VM. + let ascii = ascii as Int; let column = shared_read(SHARED_CURSOR_COL); let row = shared_read(SHARED_CURSOR_ROW); // Erase the cursor before anything moves it, so the block never gets left @@ -464,7 +701,7 @@ fn put_char(base: Int, ascii: Int) { return; } draw_glyph(base, WIDTH, FONT, glyph_index(ascii), - column * CELL_WIDTH, row * CELL_HEIGHT, FOREGROUND, BACKGROUND); + column * CELL_WIDTH, row * CELL_HEIGHT, FOREGROUND, BACKGROUND); grid_write(column, row, ascii); let next = column + 1; if (next >= text_columns()) { @@ -534,7 +771,59 @@ fn scroll_view() { // // Interrupts are masked by the caller: this shares the cursor with the key // handler, and the two interleaving would splice the output. +// A literal's bytes, for a line built out of more than one piece. +// +// The lines that interleave text with numbers — `"7 devices"`, `"edu: ident "` +// then a hex value — are still assembled as lists, because that is what +// `decimal` and `hex` answer. What changes is that the literal parts of them are +// written as literals. +fn text_bytes(text: String) -> List { + let out: List = []; + for index in 0..text.len() { + // In range by construction (`0..text.len()`), so the `Int?` that + // `byte_at` answers for a past-the-end read is unwrapped here rather + // than carried into a `List`. + out.push(text.byte_at(index)!); + } + return out; +} + +// The same, for a message written as a message. +// +// Every line this kernel printed used to be an array of ASCII numbers: the +// literal `110, 101, 116, 58, 32, 111, 107` is what "net: ok" was written as, +// eighty-six times over. Not for effect — a string had no way to give up its +// bytes without allocating one per character, and a kernel cannot allocate on +// the path where it reports that allocation failed. +// +// `byte_at` is that way: one byte, as a number, no allocation, and pure enough +// that the optimizer may hoist it. It is the whole difference between a +// language that can write this layer and one that can only be compiled for it. +fn emit_text(base: Int, text: String) { + // The lock, for the line and not for the command. + // + // The console has three writers — this task, the spinner and the clock — + // and what they contend over is the cursor. Holding the lock for a line is + // what that needs. The shell used to get it by masking interrupts around + // the *whole* command instead, which protected the same thing and also + // stopped the timer, which stopped the scheduler, which meant every command + // that waited for anything had to remember to turn them back on. Four + // separate waits in this file got that wrong, each in a way that still + // produced the right answer: a device that never interrupted, twenty tasks + // that never ran, a sleep that took a tenth of the time it asked for. + let irq = lock(); + for index in 0..text.len() { + let code = text.byte_at(index)!; + uart_putc(code); + put_char(base, code); + } + uart_putc(10); + put_char(base, 10); + unlock(irq); +} + fn emit(base: Int, text: List) { + let irq = lock(); for code in text { let ascii = (code) as Int; uart_putc(ascii); @@ -542,6 +831,7 @@ fn emit(base: Int, text: List) { } uart_putc(10); put_char(base, 10); + unlock(irq); } // Whether the collected line starts with `word`, and what follows it. @@ -565,18 +855,18 @@ fn line_starts_with(word: List) -> Bool { // The line's bytes from `start` onwards. (`from` is a keyword.) fn line_tail(start: Int) -> List { - let out = [0]; + let out: List = []; let length = shared_read(SHARED_LINE_LEN); let i = start; while (i < length) { out.push(shared_read_byte(SHARED_LINE + i)); i = i + 1; } - return out.slice(1, out.len()); + return out; } fn hex(value: Int) -> List { - let out = [0]; + let out: List = []; let shift = 28; while (shift >= 0) { let nibble = (value >> shift) & 0xf; @@ -587,26 +877,135 @@ fn hex(value: Int) -> List { } shift = shift - 4; } - return out.slice(1, out.len()); + return out; } fn decimal(value: Int) -> List { if (value == 0) { return [48]; } - let digits = [0]; + let digits: List = []; let rest = value; while (rest > 0) { digits.push(48 + rest % 10); rest = (rest / 10) as Int; } - let out = [0]; + let out: List = []; let i = digits.len() - 1; - while (i >= 1) { + while (i >= 0) { out.push((digits[i]) as Int); i = i - 1; } - return out.slice(1, out.len()); + return out; +} + +// The high precision timer: its rate, and that it runs. +// +// Two readings with the shell's own busy work between them, because a counter +// that reads the same twice is the failure this is most likely to have — an +// address nothing decodes, or a chip whose enable bit was never set. Printing +// the *difference* rather than the raw value is what makes that visible in one +// line. +fn report_hpet(base: Int) { + if (!hpet_present()) { + emit_text(base, "hpet: no timer at the standard address"); + return; + } + if (!hpet_start()) { + emit_text(base, "hpet: would not start"); + return; + } + let first = hpet_counter(); + // Something for the counter to count. A `hlt` would wait for the timer + // interrupt, which is a different clock and would make this measure that + // one instead. + let spin = 0; + for i in 0..200000 { + spin = spin + i; + } + let second = hpet_counter(); + let elapsed = hpet_elapsed_microseconds(first, second); + emit(base, text_bytes("hpet ") + .chain(decimal(u64_as_int(hpet_frequency()))) + .chain(text_bytes("Hz ticks ")) + .chain(decimal(u64_as_int(second - first))) + .chain(text_bytes(" us ")) + .chain(decimal(u64_as_int(elapsed)))); +} + +// A `u64` the kernel prints as a number. +// +// Every value this applies to is a rate or a duration that fits an `Int` many +// times over; what the cast states is that the *width* stops here, so `decimal` +// is not handed a value whose top bit means something. +fn u64_as_int(value: u64) -> Int { + return value as Int; +} + +// What `boot.rs` identity-maps: four gigabytes, with 2 MiB pages. Four rather +// than one because a PCI framebuffer sits near the top of the 32-bit range. +const MAP_CEILING = 4 * 1024 * 1024 * 1024; + +// The largest available range this kernel can actually *reach*, clipped to +// `[floor, MAP_CEILING)`. +// +// Clipped before the comparison rather than after, which is the whole point. The +// loader reports several ranges, and on a machine with more than four gigabytes +// the largest is the one above them — an address the page tables have never +// heard of. Picking it and trimming afterwards leaves nothing; picking it +// untrimmed faulted on the first page the heap took. `qemu -m 8G` did exactly +// that, and `-m 3G` did not, which is why nothing noticed. +// +// Two functions rather than one returning a pair, matching the two next door: +// this file has no tuples and the pair would be a list allocated before there is +// an allocator. +fn reachable_length(floor: Int) -> Int { + let best = 0; + for i in 0..entry_count() { + let span = clipped_span(entry_at(i), floor); + if (span > best) { + best = span; + } + } + return best; +} + +fn reachable_base(floor: Int) -> Int { + let best = 0; + let best_span = 0; + for i in 0..entry_count() { + let entry = entry_at(i); + let span = clipped_span(entry, floor); + if (span > best_span) { + best_span = span; + let start = entry_base(entry); + if (start < floor) { + start = floor; + } + best = start; + } + } + return best; +} + +// How much of one entry survives both bounds. Zero for an entry that is not +// available, or that lies entirely outside them. +fn clipped_span(entry: Int, floor: Int) -> Int { + if (entry < 0 || !entry_is_available(entry)) { + return 0; + } + let start = entry_base(entry); + let end = start + entry_length(entry); + if (start < floor) { + start = floor; + } + if (end > MAP_CEILING) { + end = MAP_CEILING; + } + if (end <= start) { + return 0; + } + return end - start; } // The largest available range the loader reported, as (base, length). @@ -645,18 +1044,17 @@ fn largest_available_length() -> Int { fn report_memory(base: Int) { if (!has_mmap()) { - // "no map" - emit(base, [110, 111, 32, 109, 97, 112]); + emit_text(base, "no map"); return; } // "upper " KiB - let upper = [117, 112, 112, 101, 114, 32]; - emit(base, upper.chain(decimal(mem_upper_kb())).chain([32, 107, 98])); + let upper = text_bytes("upper "); + emit(base, upper.chain(decimal(mem_upper_kb())).chain(text_bytes(" kb"))); // "pages " free "/" total - let pages = [112, 97, 103, 101, 115, 32]; + let pages = text_bytes("pages "); emit(base, pages.chain(decimal(pages_free(SHARED_PAGES))) - .chain([47]) - .chain(decimal(pages_total(SHARED_PAGES)))); + .chain([47]) + .chain(decimal(pages_total(SHARED_PAGES)))); } // Runs the collected line. Returns 1 to keep going, 0 to stop. @@ -667,6 +1065,699 @@ fn report_memory(base: Int) { // medium — a checksum is equally consistent with a bug that reads the same // wrong thing every time. Unprintable bytes become '.', the same convention a // hex dump uses. +// The network card, made to ask a question and hear the answer. +// +// This is the same four things `edu` does, on hardware people shipped, and the +// difference is not size. A NIC is not commanded but *fed*: two rings of +// descriptors shared with the card, each side owning a moving index, and nothing +// ever started by writing a start bit. A driver that thinks in transfers instead +// of rings produces something that works once. +// +// What it proves that a transmit alone could not: an ARP reply comes back only +// if the frame that left was well-formed enough for something on the segment to +// answer it, and only if the receive ring, the address filter and the card's own +// MAC are all right. One frame out, one frame back, and every field of the +// answer checked — see `arp_match_reply` for what each check rules out. +fn report_net(base: Int) { + let bdf = e1000_find(); + if (bdf < 0) { + emit_text(base, "net: absent"); + return; + } + let mmio = e1000_open(bdf); + if (mmio == 0) { + emit_text(base, "net: no bar"); + return; + } + + // One page for the two rings and the driver's state, four for the receive + // buffers. Pages rather than the heap because the card reads them by + // physical address: a heap block is fine on this machine, where the identity + // map makes the two the same, and would stop being fine the moment it did + // not — and a driver that quietly depended on that would be one nobody could + // move. + let scratch = page_alloc(SHARED_PAGES); + if (scratch == 0) { + emit_text(base, "net: no memory"); + return; + } + let buffers = alloc_contiguous(NET_BUFFER_PAGES * PAGE_SIZE); + if (buffers == 0) { + page_release(SHARED_PAGES, scratch); + emit_text(base, "net: no memory"); + return; + } + + // Released on the way out, on every path. The rings and buffers exist for + // one exchange run: without this, a dozen `net` commands leak sixty pages, + // which is where the allocator's free list came from. + let outcome = net_exchange(base, mmio, bdf, scratch, buffers); + // Reverse order of allocation, and that is a rule rather than a style. + // + // The free list is last-in-first-out, so what is released last is handed out + // first. Releasing in *allocation* order means the next run takes them in a + // different order than it took them the first time — and this caller wants + // its four buffer pages contiguous, so it got the scratch page in the middle + // of the run and reported "no memory" with five pages free. + pages_release_run(SHARED_PAGES, buffers, NET_BUFFER_PAGES); + page_release(SHARED_PAGES, scratch); + // `outcome` is not consulted: `net_exchange` has already said what happened, + // on the same console. What it is for is making the release unconditional. + let _done = outcome; +} + +// The exchange itself, with the memory already in hand. +// +// Split out so the release above happens once rather than at each of the seven +// places this gives up. A failure path that forgets to free is the ordinary way +// an allocator leaks, and it is invisible: the command reports the failure it +// was looking for and the pages are simply gone. +fn net_exchange(base: Int, mmio: Int, bdf: Int, scratch: Int, buffers: Int) -> Bool { + let tx_ring = scratch; + let rx_ring = scratch + TX_COUNT * DESC_SIZE; + let state = rx_ring + RX_COUNT * DESC_SIZE; + let mac = state + STATE_SIZE; + let frame = mac + 64; + let reply = frame + 256; + let answer = reply + 256; + + if (!e1000_mac(mmio, mac)) { + emit_text(base, "net: no eeprom"); + return false; + } + e1000_init(state, mmio, tx_ring, rx_ring, buffers); + e1000_set_mac(mmio, mac); + + if (!e1000_link_up(mmio)) { + emit_text(base, "net: no link"); + return false; + } + + // The card's own interrupt, installed by the driver at the line + // configuration space named — the same shape the PCI device uses, on + // hardware people shipped. + // + // The order is the same and for the same reason: the registers are + // published before the gate exists, because an interrupt arriving between + // the two would find a handler that cannot reach the card to read its cause; + // and the line is unmasked last, because a line opened before its gate is + // filled is an interrupt into a not-present descriptor. + let irq = edu_irq_line(bdf); + // Same check as `edu_interrupt`, and for the same reason: this line came out + // of the card's config space, and 0xFF there means "not connected". + if (!pic_line_exists(irq)) { + uart_text("net: the card reports no usable IRQ line ("); + uart_put_hex(irq); + uart_text("), leaving it without an interrupt\n"); + return false; + } + shared_write(SHARED_NET_CAUSE, 0); + shared_write(SHARED_NET_MMIO, mmio); + if (!install_device_handler(VECTOR_IRQ_BASE + irq, unsafe { symbol_address("lk_net_isr") })) { + uart_text("net: no gate for its vector\n"); + return false; + } + e1000_enable_receive_interrupt(mmio); + // Level, not edge. A PCI device holds its line until the driver has dealt + // with it, and the 8259's default is to look for a transition — so without + // this the card answers once and then asserts in silence for ever. + pic_set_level(irq); + pic_unmask(irq); + + // The addresses an emulated user-mode network hands out: the guest is .15 + // and the gateway is .2. Written as bytes because that is what a packet + // holds — see `arp.lk` on why an IP is an address here and not a number. + let sender_ip = answer + MAC_SIZE; + let target_ip = sender_ip + IP_SIZE; + write_ipv4(sender_ip, 10, 0, 2, 15); + write_ipv4(target_ip, 10, 0, 2, 2); + + // The exchange, more times than either ring has entries. + // + // Once proves the rings work. Twelve proves they *wrap*: both are eight + // entries, the card wraps its own index, and the driver has to wrap its + // idea of where the card has got to in step with it. An off-by-one there + // survives the first pass and every pass until the eighth — which is to say + // it survives any test that runs the command a few times, because each + // command re-initialises the rings and starts the count again. + // What the machine did while it waited. + // + // A poll scores zero here: a polling task is runnable, and the rotation + // never falls through to idle while anything is runnable. So do the three + // tasks this demonstration deliberately keeps runnable — a spinner that + // exists to be interrupted mid-update and two ring-3 tasks that never yield + // — which is why they are suspended for the exchange and put back after. + // The number is the difference between a driver that waits and one that + // watches, and it can only be read on a machine where nothing else is + // watching on purpose. + suspend_busy_tasks(); + let idle_before = shared_read(SHARED_IDLE_TURNS); + let irqs_before = shared_read(SHARED_NET_IRQS); + let waits_before = shared_read(SHARED_NET_WAITS); + + let exchanges = 0; + for _round in 0..NET_EXCHANGES { + let length = arp_build_request(frame, mac, sender_ip, target_ip); + if (!e1000_transmit(state, frame, length)) { + emit_text(base, "net: tx"); + net_quiesce(mmio, irq); + resume_busy_tasks(); + return false; + } + if (!net_await_reply(state, reply, mac, target_ip, answer)) { + emit_text(base, "net: no reply"); + net_quiesce(mmio, irq); + resume_busy_tasks(); + return false; + } + exchanges = exchanges + 1; + } + + // "net: ok N/N " + our MAC + " -> " + the gateway's + emit(base, text_bytes("net: ok ") + .chain(decimal(exchanges)) + .chain(text_bytes("/")) + .chain(decimal(NET_EXCHANGES)) + .chain(text_bytes(" ")) + .chain(mac_text(mac)) + .chain(text_bytes(" -> ")) + .chain(mac_text(answer)) + .chain(text_bytes(" idle ")) + .chain(decimal(shared_read(SHARED_IDLE_TURNS) - idle_before)) + .chain(text_bytes(" irqs ")) + .chain(decimal(shared_read(SHARED_NET_IRQS) - irqs_before)) + .chain(text_bytes("/")) + .chain(decimal(shared_read(SHARED_NET_WAITS) - waits_before))); + net_quiesce(mmio, irq); + resume_busy_tasks(); + return true; +} + +// Puts the card's interrupt away. +// +// Masked at the card *and* at the chip, and the cause read one last time. Any +// one of the three left undone is a line still asserted after this driver has +// stopped listening — and the next thing to unmask that IRQ inherits it, which +// is a fault in a handler that was expecting something else entirely. +fn net_quiesce(mmio: Int, irq: Int) { + e1000_disable_interrupts(mmio); + pic_mask_line(irq); + shared_write(SHARED_NET_MMIO, 0); +} + +// How many times `net` runs the exchange. Larger than either ring so both wrap. +const NET_EXCHANGES = 12; + +// The receive buffers, as pages. Named because the release has to give back +// exactly what the allocation took, and two expressions that must agree are two +// places to get it wrong. +const NET_BUFFER_PAGES = 4; + +// Waits for the reply to the request just sent. +// +// The budget is the *whole* wait, not a wait per frame. An empty polling window +// means "not yet"; only the exhausted budget means "not coming". Several frames +// may arrive before the one being waited for — the segment carries whatever else +// the host is doing — so a frame that does not match is dropped and the wait +// goes on. +fn net_await_reply(state: Int, reply: Int, mac: Int, target_ip: Int, answer: Int) -> Bool { + for _attempt in 0..NET_WAIT_ROUNDS { + // Look, and decide to wait, without letting the interrupt in between. + // + // This is the lost-wakeup race, and it is the whole reason a blocking + // driver is harder than a polling one. Check the ring, find it empty, + // and in the instant before saying "I am waiting" the card delivers the + // frame and wakes nobody — after which the wait is for the *next* frame, + // which is not coming. Checking first narrows the window; holding the + // mask across both closes it, because the handler cannot run until this + // task has already announced itself. + // + // Measured, not assumed: with the check outside the mask, six of twelve + // exchanges were answered by an interrupt and six by the deadline. + let irq = unsafe { cpu_irq_save() }; + let received = e1000_receive(state, reply, 256); + if (received > 0) { + unsafe { cpu_irq_restore(irq); }; + if (arp_match_reply(reply, received, mac, target_ip, answer)) { + return true; + } + // Some other traffic on the segment. Take the next one without + // sleeping — there may already be more in the ring. + continue; + } + // Nothing there. Off the CPU until the card says otherwise, or until the + // deadline says the card is not going to. `task_wait_on` masks around + // its own announcement and restores what it found, so the mask taken + // here is still held when it yields — which is what makes the pair + // atomic against the handler. + shared_bump(SHARED_NET_WAITS); + task_wait_on(NET_WAIT_CHANNEL, NET_WAIT_TICKS); + unsafe { cpu_irq_restore(irq); }; + } + return false; +} + +// What the driver listens on, how long it waits for one frame, and how many +// frames it is prepared to sift through before giving up. The channel is the +// card's own BAR address, which is unique by construction — two cards in one +// machine are two channels without anything having to hand out numbers. +const NET_WAIT_CHANNEL = 0xe1000; +const NET_WAIT_TICKS = 100; +const NET_WAIT_ROUNDS = 200; + +fn write_ipv4(address: Int, a: Int, b: Int, c: Int, d: Int) { + net_set_byte(address, a); + net_set_byte(address + 1, b); + net_set_byte(address + 2, c); + net_set_byte(address + 3, d); +} + +// A MAC as `aabbccddeeff`. Hex without separators: the six bytes are one +// identifier, and the colons a reader expects are a convention of `ip link` +// rather than anything the wire has. +fn mac_text(mac: Int) -> List { + let out: List = []; + for index in 0..MAC_SIZE { + out = out.chain(hex(net_byte_at(mac + index)).slice(6, 8).to_list()); + } + return out; +} + +// A run of pages, contiguous. +// +// The allocator hands out one page at a time and happens to hand them out in +// order, so this asks for as many as it needs and *checks* — the card is given +// one address and a length, and a run that turned out not to be contiguous would +// be the card writing frames into whatever was between. +fn alloc_contiguous(bytes: Int) -> Int { + let pages = ((bytes + PAGE_SIZE - 1) / PAGE_SIZE) as Int; + let base = page_alloc(SHARED_PAGES); + if (base == 0) { + return 0; + } + for index in 1..pages { + let page = page_alloc(SHARED_PAGES); + if (page != base + index * PAGE_SIZE) { + // Give back what was taken, including the page that broke the run. + // + // Failing without this is how an allocator with a free list leaks + // worse than one without: the caller sees "no memory", tries again, + // and each attempt keeps a few more pages. The odd page goes back + // first so the run stays together at the head of the list. + page_release(SHARED_PAGES, page); + pages_release_run(SHARED_PAGES, base, index); + return 0; + } + } + return base; +} + +// What time it is, as the machine's battery-backed clock has it. +// +// Every other clock here counts: the PIT counts down and the kernel counts its +// interrupts, which gives elapsed time and nothing else. This is the one thing +// on the machine that kept running while the power was off. +// +// Printed with the fields spelled out rather than as the packed number the +// driver answers, because a reader checking this against a wall clock should not +// have to divide by a hundred five times. +fn report_clock(base: Int) { + let now = rtc_read(); + if (now == 0) { + // The chip never agreed with itself twice running. That is a real + // outcome and it is not the same as "the time is zero". + emit_text(base, "clock: unsettled"); + return; + } + emit(base, text_bytes("clock ") + .chain(decimal(rtc_year(now))) + .chain(text_bytes("-")) + .chain(two_digits(rtc_month(now))) + .chain(text_bytes("-")) + .chain(two_digits(rtc_day(now))) + .chain(text_bytes(" ")) + .chain(two_digits(rtc_hour(now))) + .chain(text_bytes(":")) + .chain(two_digits(rtc_minute(now))) + .chain(text_bytes(":")) + .chain(two_digits(rtc_second(now)))); +} + +// Zero-padded, because a clock that prints 9:5:3 is one a reader has to think +// about and one a check has to parse loosely. +fn two_digits(value: Int) -> List { + if (value < 10) { + return text_bytes("0").chain(decimal(value)); + } + return decimal(value); +} + +// Every device on the bus, one line each. +// +// A kernel that can say what is plugged into the machine is a kernel that found +// them rather than knew them. The line is deliberately the same shape a real +// `lspci` prints — slot.function, vendor:device, class, and where its first BAR +// lives — because what is being checked is that each of those came out of +// configuration space and not out of a constant. +// Starts a task that sleeps, and says how long it took. +// +// The elapsed tick count is the claim, and it is the only one that separates +// waiting from spinning. Ten sleeps of fifty ticks have to take about five +// hundred ticks: a `task_sleep` that returned immediately would finish in +// almost none, and one that spun would finish in about the same time while +// taking every other task's slice to do it. +// The tasks that exist to keep the CPU busy, off and back on. +// +// They are the demonstration's, not the kernel's: a spinner that must be +// interrupted mid-update, and two ring-3 tasks that never yield. While any of +// them runs there is always something runnable, so "the machine had nothing to +// do" is not a state this kernel can reach — which is the only reason these +// exist. +fn suspend_busy_tasks() { + for index in 0..BUSY_SLOT_COUNT { + let slot = shared_read(SHARED_BUSY_SLOTS + index * WORD); + if (slot > 0) { + task_suspend(slot); + } + } +} + +fn resume_busy_tasks() { + for index in 0..BUSY_SLOT_COUNT { + let slot = shared_read(SHARED_BUSY_SLOTS + index * WORD); + if (slot > 0) { + task_resume(slot); + } + } +} + +fn report_sleep(base: Int) { + let before_wakes = shared_read(SHARED_SLEEP_WAKES); + let before_ticks = shared_read(SHARED_TICKS); + let before_idle = shared_read(SHARED_IDLE_TURNS); + + // The spinner is deliberately CPU-bound — it exists to be interrupted in + // the middle of an update — so while it runs there is always something + // runnable and the idle task never gets a turn. Suspended for the + // measurement and put back after: the number below is the difference + // between waiting and spinning, and it can only be read when nothing else + // is spinning on purpose. + suspend_busy_tasks(); + + // The spawn under the lock, because `spawn_task` writes a table the timer + // handler reads. The *wait* below is not under it — that is the whole point + // of the change that moved masking off the command. + let irq = lock(); + let slot = spawn_task(unsafe { symbol_address("lk_task_sleeper") }); + unlock(irq); + if (slot < 0) { + resume_busy_tasks(); + emit_text(base, "sleep: no slot"); + return; + } + // The shell waits by *waiting*, not by spinning. + // + // A busy poll here would work and would also make the measurement below + // meaningless: the idle task only gets a turn when nothing else is + // runnable, and a spinning shell is runnable. It would also be the thing + // this whole change is against — a shell at full tilt while the machine has + // nothing to do. + for _wait in 0..WAIT_POLLS { + if (task_state(TASK_TABLE_BASE, slot) == TASK_FREE) { + break; + } + task_sleep(WAIT_POLL_TICKS); + } + let idle_turns = shared_read(SHARED_IDLE_TURNS) - before_idle; + resume_busy_tasks(); + + emit(base, text_bytes("sleep wakes ") + .chain(decimal(shared_read(SHARED_SLEEP_WAKES) - before_wakes)) + .chain(text_bytes("/")) + .chain(decimal(TASK_SLEEP_ROUNDS)) + .chain(text_bytes(" ticks ")) + .chain(decimal(shared_read(SHARED_TICKS) - before_ticks)) + .chain(text_bytes(" want ")) + .chain(decimal(TASK_SLEEP_ROUNDS * TASK_SLEEP_TICKS)) + .chain(text_bytes(" idle ")) + .chain(decimal(idle_turns))); +} + +// Starts several tasks that end, and says what came back. +// +// The numbers are the claim. A slot count that does not return to what it was +// means a task ended without its slot being reusable; a page count that does not +// return means the stack was not given back. Both are invisible from the outside +// — the machine goes on running perfectly well while it runs out. +fn report_tasks(base: Int) { + let before_slots = task_word(TASK_TABLE_BASE + TASK_USED_OFFSET); + let before_pages = pages_free(SHARED_PAGES); + let before_ticks = shared_read(SHARED_BRIEF_TICKS); + + let started = 0; + for _round in 0..TASK_BRIEF_ROUNDS { + // The spawn under the lock — it writes a table the timer handler reads — + // and nothing else. A command runs with interrupts on now, so the wait + // below simply works; the first version of this masked for the whole + // cycle and spawned twenty tasks that never ran, never returned and + // never gave anything back, and reported it as the table filling up. + let irq = lock(); + let slot = spawn_task(unsafe { symbol_address("lk_task_brief") }); + unlock(irq); + if (slot < 0) { + break; + } + started = started + 1; + // Wait for it to finish and be reclaimed before starting the next. + // + // One at a time, deliberately: the point is that the *same* slot and the + // *same* pages come back and get used again. Starting all of them at + // once would only show that the table is big enough. + for _wait in 0..WAIT_POLLS { + if (task_state(TASK_TABLE_BASE, slot) == TASK_FREE) { + break; + } + task_sleep(WAIT_POLL_TICKS); + } + } + + let ran = shared_read(SHARED_BRIEF_TICKS) - before_ticks; + emit(base, text_bytes("task ") + .chain(decimal(started)) + .chain(text_bytes("/")) + .chain(decimal(TASK_BRIEF_ROUNDS)) + .chain(text_bytes(" slots ")) + .chain(decimal(before_slots)) + .chain(text_bytes("->")) + .chain(decimal(task_word(TASK_TABLE_BASE + TASK_USED_OFFSET))) + .chain(text_bytes(" pages ")) + .chain(decimal(before_pages)) + .chain(text_bytes("->")) + .chain(decimal(pages_free(SHARED_PAGES))) + .chain(text_bytes(" ran ")) + .chain(decimal(ran))); +} + +// More rounds than the table has spare slots, so a slot that never came back +// would run the table out rather than merely look tidy. +const TASK_BRIEF_ROUNDS = 20; + +fn report_pci(base: Int) { + let devices = pci_devices(); + for index in 0..devices.len() { + let bdf = (devices[index]) as Int; + // "SS.F VVVV:DDDD cCC/SS" + let line = hex(pci_bdf_device(bdf)).slice(6, 8).to_list() + .chain([46]) + .chain(decimal(pci_bdf_function(bdf))) + .chain([32]) + .chain(hex(pci_vendor(bdf)).slice(4, 8).to_list()) + .chain([58]) + .chain(hex(pci_device_id(bdf)).slice(4, 8).to_list()) + .chain(text_bytes(" c")) + .chain(hex(pci_class(bdf)).slice(6, 8).to_list()) + .chain([47]) + .chain(hex(pci_subclass(bdf)).slice(6, 8).to_list()); + // A device with no memory BAR 0 — the bridges — says so by omission + // rather than by printing a zero that looks like an address. + if (pci_bar(bdf, 0) != 0 && !pci_bar_is_io(bdf, 0)) { + line = line.chain(text_bytes(" @")) // " @" + .chain(hex(pci_bar(bdf, 0))) + .chain([43]) + .chain(hex(pci_bar_size(bdf, 0))); + } + emit(base, line); + } + emit(base, decimal(devices.len()).chain(text_bytes(" devices"))); // " devices" +} + +// The PCI device, driven through all three of the things a PCI device does. +// +// Four checks, and each one fails differently on purpose. Identification proves +// the BAR is mapped where configuration space said; liveness proves writes +// arrive; the factorial proves the device *computes* on a value the driver gave +// it and hands it back through the busy protocol; and the DMA proves the device +// wrote RAM by itself, which is the only one of the four that no amount of port +// I/O could have done. +// +// It reports which step failed rather than a single yes/no: "edu: dma" and +// "edu: ident" are different machines, and a check that says only "no" makes +// them the same. +fn report_edu(base: Int) { + let bdf = edu_find(); + if (bdf < 0) { + emit_text(base, "edu: absent"); + return; + } + let mmio = edu_open(bdf); + if (mmio == 0) { + emit_text(base, "edu: no bar"); + return; + } + + let ident = edu_identify(mmio); + if (ident != EDU_IDENT) { + // "edu: ident " + what came back + emit(base, text_bytes("edu: ident ").chain(hex(ident))); + return; + } + + // The register inverts what it is given, in 32 bits. + let live = edu_liveness(mmio, 0x0f0f0f0f); + if (live != 0xf0f0f0f0) { + // "edu: live " + what came back + emit(base, text_bytes("edu: live ").chain(hex(live))); + return; + } + + let factorial = edu_factorial(mmio, 5); + if (factorial != 120) { + // "edu: fact " + what came back + emit(base, text_bytes("edu: fact ").chain(hex(factorial))); + return; + } + + if (!edu_round_trip(mmio)) { + emit_text(base, "edu: dma"); + return; + } + + let irq = edu_irq_line(bdf); + if (!edu_interrupt(mmio, irq)) { + emit_text(base, "edu: irq"); + return; + } + + // "edu: ok irq " + the line the firmware routed it to + emit(base, text_bytes("edu: ok irq ") + .chain(decimal(irq)) + .chain(text_bytes(" bar ")) // " bar " + .chain(hex(edu_bar_size(bdf)))); +} + +// Makes the device interrupt, and waits for the handler to say it did. +// +// The driver installs its own gate here rather than at boot, and that is the +// point: a PCI device does not know its interrupt line until configuration +// space has been read, so a kernel that installed every handler in one place at +// startup could not have a driver for a device it had not been told about. +// Installing a handler is two stores — a gate, and an address in the runtime's +// table — so a driver can do it for itself when it knows what it needs. +// +// The order matters at both ends. The MMIO base is published *before* the gate +// exists, because an interrupt arriving between the two would find a handler +// that cannot reach the device to quieten it; and the line is unmasked last, +// because a line opened before its gate is filled is an interrupt into a +// not-present descriptor, which is a fault raised from inside an interrupt. +fn edu_interrupt(mmio: Int, irq: Int) -> Bool { + // What the device said its line is, checked before it is used as an index. + // + // PCI config space answers this in one byte, and 0xFF is the defined value + // for "not connected" — a number the 8259 has no line for and the IDT has + // no gate for. Believed, it would install a gate 496 bytes past the end of + // a 256-entry table, which in this kernel is the GDT. + if (!pic_line_exists(irq)) { + uart_text("edu: the device reports no usable IRQ line ("); + uart_put_hex(irq); + uart_text(")\n"); + return false; + } + shared_write(SHARED_EDU_SEEN, 0); + shared_write(SHARED_EDU_MMIO, mmio); + if (!install_device_handler(VECTOR_IRQ_BASE + irq, unsafe { symbol_address("lk_edu_isr") })) { + uart_text("edu: no gate for its vector\n"); + return false; + } + pic_unmask(irq); + + // A value with a bit the device has no other reason to set, so what the + // handler reports back is traceable to this request and not to some other + // condition that happened to be pending. + edu_raise(mmio, EDU_IRQ_TEST); + + // Waits to be told, rather than polling until it is. + // + // The first version of this spun with interrupts on for a million + // iterations. It worked — an interrupt does arrive between any two + // instructions — and it meant the CPU was at full tilt for a wait measured + // in microseconds. The handler now wakes whoever asked, and this task is off + // the rotation entirely until it does. + // + // The deadline is what makes it usable rather than a hang: a device that + // never raises is a real outcome, and a driver that waited on a channel + // alone would take the machine with it. + task_wait_on(EDU_WAIT_CHANNEL, EDU_WAIT_TICKS); + let seen = shared_read(SHARED_EDU_SEEN); + // The line goes back to masked, and the device's own bit is already cleared + // by the handler: this is a probe, and leaving a line open behind it means + // the next thing that happens to raise it lands in a handler that is no + // longer expecting anything. + pic_mask_line(irq); + return (seen & EDU_IRQ_TEST) != 0; +} + +// What the driver listens on, and how long it is prepared to wait. +// +// A channel is any number both sides agree on; this one is a constant because +// there is one such device. A driver for a card that can be plugged in twice +// would use the card's own address, which is unique by construction and is why +// a channel is an Int rather than an index into something. +const EDU_WAIT_CHANNEL = 0xed0; +const EDU_WAIT_TICKS = 200; + +// A pattern out to the device and back to a different address. +// +// Back to a *different* address, and that is the whole test: reading into the +// buffer it was sent from would pass even if the device never touched RAM, +// because the bytes were already there. Two buffers means the second one can +// only hold the pattern if the device's own DMA engine put it there. +// +// The addresses are physical, which on this machine equals virtual — see +// `edu.lk`'s note on why that is a coincidence and not a rule. +fn edu_round_trip(mmio: Int) -> Bool { + let out = SHARED_SECTOR; + let back = SHARED_SECTOR + 256; + let count = 64; + + for i in 0..count { + shared_write_byte(out + i, (i * 7 + 3) & 0xff); + shared_write_byte(back + i, 0); + } + + if (!edu_dma(mmio, out, 0, count, false)) { + return false; + } + if (!edu_dma(mmio, back, 0, count, true)) { + return false; + } + for i in 0..count { + if (shared_read_byte(back + i) != ((i * 7 + 3) & 0xff)) { + return false; + } + } + return true; +} + fn report_disk(base: Int) { let outcome = ata_identify(SHARED_SECTOR); if (outcome != ATA_OK) { @@ -701,7 +1792,7 @@ fn report_disk(base: Int) { // from outside by reading the image file after the machine has stopped. fn write_disk(base: Int) { // "LK-WROTE-SECTOR1", then zeros to the end of the sector. - let text = [76, 75, 45, 87, 82, 79, 84, 69, 45, 83, 69, 67, 84, 79, 82, 49]; + let text = text_bytes("LK-WROTE-SECTOR1"); for i in 0..SECTOR_SIZE { shared_write_byte(SHARED_SECTOR + i, 0); } @@ -759,8 +1850,7 @@ fn print_file(base: Int, name: List) { emit(base, disk_error(ATA_ERROR_BIT)); return; } - let line = [0]; - line = []; + let line = []; let i = 0; while (i < SECTOR_SIZE && printed + i < size) { let byte = shared_read_byte(SHARED_SECTOR + i); @@ -812,14 +1902,40 @@ fn report_heap(base: Int) { heap_release(SHARED_HEAP, d); let after = heap_blocks(SHARED_HEAP); // "blocks / reused <0|1> used " - emit(base, [98, 108, 111, 99, 107, 115, 32] - .chain(decimal(before)) - .chain([47]) - .chain(decimal(after)) - .chain([32, 114, 101, 117, 115, 101, 100, 32]) - .chain(decimal(reused)) - .chain([32, 117, 115, 101, 100, 32]) - .chain(decimal(heap_used(SHARED_HEAP)))); + emit(base, text_bytes("blocks ") + .chain(decimal(before)) + .chain([47]) + .chain(decimal(after)) + .chain(text_bytes(" reused ")) + .chain(decimal(reused)) + .chain(text_bytes(" used ")) + .chain(decimal(heap_used(SHARED_HEAP)))); +} + +// Lists what is on the disk: one line per entry, name and size. +// +// The walk is the filesystem's, not this function's — an entry's length lives +// in its own header, so where the next one starts is not known until this one +// has been read. What is here is only what to do with each: read the name a +// byte at a time, because a name in a header is bytes and this program has no +// strings. +fn list_files(base: Int) { + let lba = 0; + for entry in 0..64 { + let next = tar_next(lba, SHARED_SECTOR); + if (next == 0) { + return; + } + // `tar_next` left this entry's header in the buffer. + let line = [0]; + line = []; + let length = tar_name_length(SHARED_SECTOR); + for i in 0..length { + line = line.chain([tar_name_byte(SHARED_SECTOR, i)]); + } + emit(base, line.chain([32]).chain(decimal(tar_entry_size(SHARED_SECTOR)))); + lba = next; + } } // Reads a file off the disk and asks the board to run it. @@ -837,8 +1953,7 @@ fn run_file(base: Int, name: List) { let lba = shared_read(SHARED_FILE); let size = shared_read(SHARED_FILE + WORD); if (size > SOURCE_MAX) { - // "too big" - emit(base, [116, 111, 111, 32, 98, 105, 103]); + emit_text(base, "too big"); return; } let copied = 0; @@ -855,32 +1970,27 @@ fn run_file(base: Int, name: List) { copied = copied + i; lba = lba + 1; } - // Interrupts back on for the duration of the run, and masked again after. + // Nothing to say about interrupts any more. // - // `run_command` is called with them masked, which is right for a command - // that touches a few shared words and returns. A whole parse and execution - // of an arbitrary program is not that: it takes seconds, and holding the - // mask across it stops the timer, starves the other task, and drops every - // keystroke that arrives meanwhile. What the mask protects — the cursor and - // the line buffer — is not touched by anything that can preempt this: the - // key handler only queues bytes, and the spinner draws in its own window. - unsafe { cpu_irq_restore(1); }; + // This used to turn them back on for the duration and mask again after, + // because `run_command` held the mask across the whole command and a whole + // parse and execution takes seconds. The mask is gone from the command; what + // is left here is a call. let outcome = kernel_run(SOURCE_BASE, size); - unsafe { cpu_irq_save() }; if (outcome != 0) { // "failed " then the stage the board reported. - emit(base, [102, 97, 105, 108, 101, 100, 32].chain(decimal(0 - outcome))); + emit(base, text_bytes("failed ").chain(decimal(0 - outcome))); } } fn file_error(outcome: Int) -> List { if (outcome == TAR_NOT_FOUND) { // "no file" - return [110, 111, 32, 102, 105, 108, 101]; + return text_bytes("no file"); } if (outcome == TAR_READ_FAILED) { // "read failed" - return [114, 101, 97, 100, 32, 102, 97, 105, 108, 101, 100]; + return text_bytes("read failed"); } // "?" return [63]; @@ -892,19 +2002,19 @@ fn file_error(outcome: Int) -> List { fn disk_error(outcome: Int) -> List { if (outcome == ATA_NO_DRIVE) { // "no disk" - return [110, 111, 32, 100, 105, 115, 107]; + return text_bytes("no disk"); } if (outcome == ATA_TIMEOUT) { // "timeout" - return [116, 105, 109, 101, 111, 117, 116]; + return text_bytes("timeout"); } if (outcome == ATA_ERROR_BIT) { // "drive error" - return [100, 114, 105, 118, 101, 32, 101, 114, 114, 111, 114]; + return text_bytes("drive error"); } if (outcome == ATA_BAD_LBA) { // "bad sector" - return [98, 97, 100, 32, 115, 101, 99, 116, 111, 114]; + return text_bytes("bad sector"); } // "?" return [63]; @@ -915,45 +2025,38 @@ fn run_command(base: Int) -> Int { if (length == 0) { return 1; } - if (line_starts_with([104, 101, 108, 112])) { // "help" + if (line_starts_with(text_bytes("help"))) { // "help" // "help clear echo keys exit" - // "... time disk cat run heap exit" - emit(base, [104, 101, 108, 112, 32, 99, 108, 101, 97, 114, 32, 101, 99, - 104, 111, 32, 107, 101, 121, 115, 32, 109, 101, 109, 32, - 112, 97, 103, 101, 32, 115, 121, 110, 99, 32, - 121, 105, 101, 108, 100, 32, 119, 105, 110, 32, - 116, 105, 109, 101, 32, 100, 105, 115, 107, 32, 99, 97, 116, 32, 114, 117, 110, 32, 104, 101, 97, 112, 32, - 101, 120, 105, 116]); + emit_text(base, "help clear echo keys mem page sync yield win time disk cat run heap user ls exit"); return 1; } - if (line_starts_with([99, 108, 101, 97, 114])) { // "clear" + if (line_starts_with(text_bytes("clear"))) { // "clear" clear(base, WIDTH, HEIGHT, BACKGROUND); place_cursor(base, 0, 0); return 1; } - if (line_starts_with([101, 99, 104, 111])) { // "echo" + if (line_starts_with(text_bytes("echo"))) { // "echo" emit(base, line_tail(5)); return 1; } - if (line_starts_with([107, 101, 121, 115])) { // "keys" + if (line_starts_with(text_bytes("keys"))) { // "keys" emit(base, decimal(shared_read(SHARED_KEY_COUNT))); return 1; } - if (line_starts_with([109, 101, 109])) { // "mem" + if (line_starts_with(text_bytes("mem"))) { // "mem" report_memory(base); return 1; } - if (line_starts_with([112, 97, 103, 101])) { // "page" + if (line_starts_with(text_bytes("page"))) { // "page" let address = page_alloc(SHARED_PAGES); if (address == 0) { - // "none" - emit(base, [110, 111, 110, 101]); + emit_text(base, "none"); } else { emit(base, hex(address)); } return 1; } - if (line_starts_with([115, 121, 110, 99])) { // "sync" + if (line_starts_with(text_bytes("sync"))) { // "sync" let state = lock(); let a = shared_read(SHARED_PAIR_A); let b = shared_read(SHARED_PAIR_B); @@ -961,17 +2064,16 @@ fn run_command(base: Int) -> Int { emit(base, decimal(a).chain([47]).chain(decimal(b))); return 1; } - if (line_starts_with([121, 105, 101, 108, 100])) { // "yield" + if (line_starts_with(text_bytes("yield"))) { // "yield" task_yield(); - // "back" - emit(base, [98, 97, 99, 107]); + emit_text(base, "back"); return 1; } - if (line_starts_with([119, 105, 110])) { // "win" + if (line_starts_with(text_bytes("win"))) { // "win" emit(base, decimal(window_dropped(SHARED_WINDOWS, WINDOW_SPINNER))); return 1; } - if (line_starts_with([116, 105, 109, 101])) { // "time" + if (line_starts_with(text_bytes("time"))) { // "time" // Measure the two things this program does that are not cheap: a // scroll moves the whole picture a pixel row at a time, and a focus // change repaints both frames. Both run with interrupts masked, so @@ -985,50 +2087,91 @@ fn run_command(base: Int) -> Int { let t2 = unsafe { cpu_timestamp() }; unlock(irq_time); // "scroll " cycles " frames " cycles - emit(base, [115, 99, 114, 111, 108, 108, 32] - .chain(decimal((t1 as Int) - (t0 as Int))) - .chain([32, 102, 114, 97, 109, 101, 115, 32]) - .chain(decimal((t2 as Int) - (t1 as Int)))); + emit(base, text_bytes("scroll ") + .chain(decimal((t1 as Int) - (t0 as Int))) + .chain(text_bytes(" frames ")) + .chain(decimal((t2 as Int) - (t1 as Int)))); return 1; } - if (line_starts_with([109, 115])) { // "ms" + if (line_starts_with(text_bytes("ms"))) { // "ms" // ", b p" emit(base, decimal(shared_read(SHARED_MOUSE_X)) - .chain([44]) - .chain(decimal(shared_read(SHARED_MOUSE_Y))) - .chain([32, 98]) - .chain(decimal(shared_read(SHARED_MOUSE_BUTTONS))) - .chain([32, 112]) - .chain(decimal(shared_read(SHARED_MOUSE_PHASE))) - .chain([32, 102]) - .chain(decimal(shared_read(SHARED_MOUSE_FLAGS))) - .chain([32, 100]) - .chain(decimal(shared_read(SHARED_MOUSE_DX)))); + .chain([44]) + .chain(decimal(shared_read(SHARED_MOUSE_Y))) + .chain(text_bytes(" b")) + .chain(decimal(shared_read(SHARED_MOUSE_BUTTONS))) + .chain(text_bytes(" p")) + .chain(decimal(shared_read(SHARED_MOUSE_PHASE))) + .chain(text_bytes(" f")) + .chain(decimal(shared_read(SHARED_MOUSE_FLAGS))) + .chain(text_bytes(" d")) + .chain(decimal(shared_read(SHARED_MOUSE_DX)))); return 1; } - if (line_starts_with([104, 101, 97, 112])) { // "heap" + if (line_starts_with(text_bytes("user"))) { // "user" + // "ring3" then the machine leaves for ring 3 and does not return: the + // program there prints through a syscall and then deliberately writes + // an address it is not allowed to, which the exception reporter names. + emit_text(base, "ring3"); + enter_user(unsafe { symbol_address("__user_program") }, + (unsafe { symbol_address("__user_shell_stack") } as Int) + PAGE_SIZE - 8, + USER_CODE_SELECTOR, USER_DATA_SELECTOR); + return 1; + } + if (line_starts_with(text_bytes("heap"))) { // "heap" report_heap(base); return 1; } - if (line_starts_with([114, 117, 110])) { // "run" + if (line_starts_with(text_bytes("run"))) { // "run" run_file(base, line_tail(4)); return 1; } - if (line_starts_with([99, 97, 116])) { // "cat" + if (line_starts_with(text_bytes("ls"))) { // "ls" + list_files(base); + return 1; + } + if (line_starts_with(text_bytes("cat"))) { // "cat" print_file(base, line_tail(4)); return 1; } - if (line_starts_with([100, 105, 115, 107, 32, 119])) { // "disk w" + if (line_starts_with(text_bytes("disk w"))) { // "disk w" write_disk(base); return 1; } - if (line_starts_with([100, 105, 115, 107])) { // "disk" + if (line_starts_with(text_bytes("disk"))) { // "disk" report_disk(base); return 1; } - if (line_starts_with([101, 120, 105, 116])) { // "exit" - // "bye" - emit(base, [98, 121, 101]); + if (line_starts_with(text_bytes("pci"))) { // "pci" + report_pci(base); + return 1; + } + if (line_starts_with(text_bytes("edu"))) { // "edu" + report_edu(base); + return 1; + } + if (line_starts_with(text_bytes("clock"))) { // "clock" + report_clock(base); + return 1; + } + if (line_starts_with(text_bytes("hpet"))) { // "hpet" + report_hpet(base); + return 1; + } + if (line_starts_with(text_bytes("sleep"))) { // "sleep" + report_sleep(base); + return 1; + } + if (line_starts_with(text_bytes("task"))) { // "task" + report_tasks(base); + return 1; + } + if (line_starts_with(text_bytes("net"))) { // "net" + report_net(base); + return 1; + } + if (line_starts_with(text_bytes("exit"))) { // "exit" + emit_text(base, "bye"); return 0; } // "?" @@ -1076,7 +2219,19 @@ fn halt() { // three bytes that are meaningless apart, so the handler assembles them and // publishes the result. What it never does is draw — the cursor is chrome, and // chrome belongs to whoever owns the screen. +// Acknowledging the chip is the driver's job, and it is a *wrapper* rather than +// a last line, in all three cases, for one reason: an end-of-interrupt that a +// `return` can skip is an end-of-interrupt that will be skipped. `on_key` +// already returns early from four places. Written this way the property is +// structural — there is nowhere for the acknowledgement to not happen — instead +// of something the next edit has to remember. #[export("lk_mouse")] +fn isr_mouse() { + on_mouse(); + // IRQ12 is the slave's, so both chips are told. + pic_eoi_slave(); +} + fn on_mouse() { while (has_packet_byte() == 1) { let byte = mouse_byte(); @@ -1131,10 +2286,20 @@ fn task_clock() { repaint_above(surface, WINDOW_CLOCK); unlock(irq); } - task_yield(); + // Sleeps rather than yields, and the difference is the whole point of a + // blocked state. A `yield` hands over the rest of the slice and comes + // straight back: this task is runnable again immediately, so the machine + // never has nothing to do, so it never sleeps. A clock that changes once + // a second has no business being runnable a thousand times a second. + task_sleep(CLOCK_POLL_TICKS); } } +// A tenth of a second, which is a tenth of the resolution the clock shows. Close +// enough that a second never visibly arrives late, coarse enough that this task +// is off the CPU for almost all of it. +const CLOCK_POLL_TICKS = 100; + // Give up the rest of this task's slice. // // `#[extern]` is the mirror of `#[export]`: the board implements this, and a @@ -1151,13 +2316,754 @@ fn task_clock() { // Named `spawn_task`, not `spawn`: the language already has a `spawn` for its // own concurrency, which returns a `Task`. Two things called spawn in one // program is a question every reader would have to answer twice. -#[extern("lk_spawn")] +// Leaves for ring 3 and does not come back — see `src/user.rs`. +// Enters ring 3, and does not come back. +// +// Every number is this file's: what runs, on which stack, and through which two +// descriptors. What the board contributes is the one thing that cannot be said +// here — `iretq`, the only way *into* ring 3, because no instruction lowers +// privilege directly. There is no return value because there is no return: the +// only ways out are a syscall (which returns *into* ring 3) and a fault. +#[extern("lk_enter_user")] +fn enter_user(entry: Int, stack: Int, code: Int, data: Int) -> Int { + return 0; +} + +// A stack for a task, out of the page allocator, and its high end. +// +// Contiguous because the allocator is a bump: consecutive pages are +// consecutive addresses, which the heap's own startup already depends on and +// checks. Answers 0 when it cannot get all of them — a stack that is short by a +// page is not a stack, it is a task that overruns into whatever is next. +fn alloc_task_stack() -> Int { + let base = alloc_contiguous(TASK_STACK_PAGES * PAGE_SIZE); + if (base == 0) { + return 0; + } + return base + TASK_STACK_PAGES * PAGE_SIZE; +} + +// Gives a stack back, by the top the spawner was handed. +// +// The top rather than the base, because the top is what the table holds — a task +// slot records where the stack *ends*, since that is what the frame was built +// down from. Converting here rather than at the two call sites keeps the one +// piece of arithmetic that has to match `alloc_task_stack` next to it. +fn release_task_stack(top: Int) { + pages_release_run(SHARED_PAGES, top - TASK_STACK_PAGES * PAGE_SIZE, TASK_STACK_PAGES); +} + +// Publishes a prepared slot. The count goes last, and that ordering is the +// whole of the locking here: the scheduler reads it from a timer interrupt, so +// a slot announced before its stack is complete is a resume into zero. +fn publish_task(slot: Int, rsp: Int, cr3: Int, kernel_stack: Int) -> Int { + task_set_field(TASK_TABLE_BASE, slot, TASK_RSP_OFFSET, rsp); + task_set_field(TASK_TABLE_BASE, slot, TASK_CR3_OFFSET, cr3); + task_set_field(TASK_TABLE_BASE, slot, TASK_STACK_OFFSET, kernel_stack); + // The state last, and that ordering is the contract: this is the word the + // scheduler reads to decide the slot is worth resuming, so a slot announced + // ready before its stack pointer is written is a resume into zero. It is the + // same reason the count used to go last, made per-slot. + task_set_state(TASK_TABLE_BASE, slot, TASK_READY); + // The watermark: one past the highest slot ever taken, which is how far the + // scheduler has to sweep. It only rises — a slot that comes free stays + // inside the swept range, and shrinking it would mean proving nothing above + // is live, which is a scan to save a scan. + if (slot + 1 > task_word(TASK_TABLE_BASE + TASK_USED_OFFSET)) { + task_set_word(TASK_TABLE_BASE + TASK_USED_OFFSET, slot + 1); + shared_write(SHARED_TASK_COUNT, slot + 1); + } + return slot; +} + +// Starts a task at `entry`, on a stack of its own. +// +// The caller must have interrupts masked, for the reason `publish_task` states. fn spawn_task(entry: Int) -> Int { - return 0 - 1; + if (entry == 0) { + return 0 - 1; + } + let slot = task_find_free(TASK_TABLE_BASE, TASK_CAPACITY); + if (slot < 0) { + return 0 - 1; + } + let top = alloc_task_stack(); + if (top == 0) { + return 0 - 1; + } + // Where the task goes when its body returns. + // + // The very top word of the stack, and the frame is built *below* it. That + // ordering is the whole of it: `task_prepare_frame` writes downward from + // whatever it is given, and its first write is the frame's SS — so writing + // the exit address at `top - 8` and then building the frame from `top` puts + // the selector on top of it. The first version did exactly that, and the + // task returned into `0x10` and took a #UD at address 0x13. + // + // The word has to exist because the ABI assumes a `call` has just pushed a + // return address, and `iretq` pushes nothing. Left zero, a task that simply + // finished would `ret` into address zero. A task with no way to return is + // not a task, it is a loop that was started. + let exit_slot = top - 8; + task_set_word(exit_slot, unsafe { symbol_address("lk_task_exit") } as Int); + // One word below the top, so the task begins with the stack in the phase a + // function expects: the ABI assumes a `call` has just pushed a return + // address, and `iretq` pushes nothing. Without the offset the first aligned + // SSE spill inside the task faults, in whatever it happened to call. + let rsp = task_prepare_frame(exit_slot, entry, KERNEL_CODE_SELECTOR, KERNEL_DATA_SELECTOR, + exit_slot, task_saved_words()); + return publish_task(slot, rsp, 0, top); +} + +// A ring-3 task: same table, same switch and same scheduler as any other, +// entered on a frame whose selectors say ring 3, and running in the address +// space it is handed. +// +// The stack it resumes on is the *virtual* one — the task runs in its own +// space, where that address is its stack and the kernel's space has something +// else entirely there. The kernel stack published beside it is a different +// stack again: its own, because two user tasks sharing one would have the +// second one's interrupt frame land on top of the first one's. +fn spawn_user_task(entry: Int, stack_virtual: Int, cr3: Int) -> Int { + if (entry == 0 || cr3 == 0) { + return 0 - 1; + } + let slot = task_find_free(TASK_TABLE_BASE, TASK_CAPACITY); + if (slot < 0) { + return 0 - 1; + } + let top = alloc_task_stack(); + if (top == 0) { + return 0 - 1; + } + // No exit address for a ring-3 task: the word below the top of *this* stack + // is in the kernel's space, and the task returns onto its own virtual stack + // in its own space. A user task that runs off the end faults, which is the + // boundary doing its job — ending it is what the syscall is for. + let rsp = task_prepare_frame(top, entry, USER_CODE_SELECTOR, USER_DATA_SELECTOR, stack_virtual, + task_saved_words()); + return publish_task(slot, rsp, cr3, top); +} + +// How much the board's save sequence leaves on a task's stack. Its number, not +// this file's: the assembly that pushes those registers is what decides it. +#[extern("lk_task_saved_words")] +fn task_saved_words() -> Int { + return 0; +} + +// Which task runs next, and everything that has to be true before it does. +// +// Called from the board's trampoline with the interrupted task's stack pointer, +// and answering the one to resume on. Everything between is bookkeeping this +// program owns: which slot was running, which is next, whose address space, and +// whose kernel stack. +// A task that does a little work and then ends. +// +// It exists to prove the end. Every other task in this kernel is a `while (true)` +// — which is honest for a spinner and a clock, and useless for showing that a +// slot and a stack come back, because they never do. This one counts to a number +// and returns, and returning is the whole point: no exit call, no cooperation +// with the scheduler, just a function that finishes the way any function does. +// The task that runs when nothing else will. +// +// `hlt` rather than a spin, because that is the only difference that matters: a +// machine with nothing to do should be stopped until the next interrupt, not +// burning through a loop. On real hardware it is the difference between a fan +// that is on and one that is not; here it is what makes "everything is blocked" +// a thing the scheduler can express at all. +// +// It never ends and never blocks, so it is always somewhere to go. +#[export("lk_task_idle")] +fn task_idle() { + while (true) { + // Counted before the halt, so the number is "times this task was given + // the CPU and had nothing to do with it". That is the measurement that + // turns "the task waited" into "the task waited without spending + // anything": a spin never reaches here, because a spinning task is + // always runnable and the rotation never falls through to idle. + shared_bump(SHARED_IDLE_TURNS); + unsafe { cpu_wait_for_interrupt(); }; + } +} + +// A task that sleeps rather than spins. +// +// It wakes `TASK_SLEEP_ROUNDS` times, waiting `TASK_SLEEP_TICKS` between, and +// counts each wake. What that proves is not that it woke — a spin would also +// "wake" — but *when*: the elapsed ticks have to be the product, which a spin +// could not produce because a spinning task would be preempted and resumed long +// before its deadline. +#[export("lk_task_sleeper")] +fn task_sleeper() { + for _round in 0..TASK_SLEEP_ROUNDS { + task_sleep(TASK_SLEEP_TICKS); + shared_bump(SHARED_SLEEP_WAKES); + } +} + +// How the shell waits for something it has no channel for: a short sleep, over +// and over, with a bound. Sleeping rather than spinning because a spinning +// waiter is a runnable task, and a runnable task is one the machine stays awake +// for. +const WAIT_POLLS = 2000; +const WAIT_POLL_TICKS = 2; + +const TASK_SLEEP_ROUNDS = 10; +const TASK_SLEEP_TICKS = 50; + +#[export("lk_task_brief")] +fn task_brief() { + for _step in 0..200000 { + shared_bump(SHARED_BRIEF_TICKS); + } +} + +// Waits `ticks` timer interrupts without using the CPU. +// +// The difference between this and the spin loops elsewhere in this file is the +// whole point of a blocked state: a spin waits at full tilt and takes its slice +// of every other task's time, while this leaves the rotation entirely and comes +// back when the timer says so. +// +// The state goes on with interrupts masked, because between reading the clock +// and announcing the deadline a tick would be one the waker had already passed. +// The yield is *after* they are back on: yielding with them masked would switch +// to a task that then runs with interrupts off. +fn task_sleep(ticks: Int) { + if (ticks <= 0) { + return; + } + let irq = unsafe { cpu_irq_save() }; + let current = task_word(TASK_TABLE_BASE + TASK_CURRENT_OFFSET); + task_block_on(TASK_TABLE_BASE, current, shared_read(SHARED_TICKS) + ticks, 0); + unsafe { cpu_irq_restore(irq); }; + // Hands the CPU over now rather than at the end of the slice. Not required — + // the next tick would switch away from a slot that is no longer ready — but + // a task that has said it is waiting should not go on running. + task_yield(); +} + +// Waits to be told about `channel`, for at most `ticks`. +// +// The pair is what makes this usable by a driver. A wait with only a channel +// never ends if the device is broken, and a driver that hangs the machine on a +// bad card is worse than one that polls; a wait with only a deadline is a poll +// with extra steps. Answers whether it was *told* rather than timed out, which +// is the one thing the caller cannot work out for itself. +// +// The deadline is read and the block announced with interrupts masked, because +// the thing being waited for is an interrupt: between deciding to wait and +// saying so, the very signal being waited for could arrive and find nobody +// waiting — and then the wait is for the next one, which is not coming. +fn task_wait_on(channel: Int, ticks: Int) -> Bool { + let irq = unsafe { cpu_irq_save() }; + let current = task_word(TASK_TABLE_BASE + TASK_CURRENT_OFFSET); + task_block_on(TASK_TABLE_BASE, current, shared_read(SHARED_TICKS) + ticks, channel); + unsafe { cpu_irq_restore(irq); }; + task_yield(); + // Back on the CPU, so something woke it. Which one is the question, and the + // channel field is the answer: a signal clears it, a deadline clears it too + // — so the caller is told by whoever set the flag it was waiting for, not by + // this. What is returned is the honest weaker claim: the wait is over. + return true; +} + +// Takes a task off the CPU until somebody puts it back. +// +// Blocked with no deadline and no channel, which is the one combination nothing +// wakes — it falls out of the two-reason block rather than being a third state, +// because "waiting for a moment that never comes and a message nobody sends" is +// exactly what suspended means. +// +// Safe to do to a task holding the drawing lock, because that lock is interrupt +// masking: a task cannot be scheduled away while it holds one, so this takes +// effect at the next moment it is safe to take effect at. +fn task_suspend(slot: Int) { + let irq = unsafe { cpu_irq_save() }; + task_block_on(TASK_TABLE_BASE, slot, 0, 0); + unsafe { cpu_irq_restore(irq); }; +} + +fn task_resume(slot: Int) { + let irq = unsafe { cpu_irq_save() }; + task_unblock(TASK_TABLE_BASE, slot); + unsafe { cpu_irq_restore(irq); }; +} + +// Where a task goes when its body returns. +// +// It marks its own slot dead and then stops. It cannot do the rest: releasing +// the stack would hand back the pages holding the frame this is standing on, and +// the very next interrupt would land on memory the allocator had given away. So +// this is half an exit, and `reap_dead_tasks` below is the other half — run by +// something that is not this task. +// +// The wait is `hlt` rather than a spin: there is nothing to do, and the only +// thing that can end it is the timer, which is exactly what `hlt` waits for. +#[export("lk_task_exit")] +fn task_exit() { + let irq = unsafe { cpu_irq_save() }; + let current = task_word(TASK_TABLE_BASE + TASK_CURRENT_OFFSET); + task_set_state(TASK_TABLE_BASE, current, TASK_DEAD); + unsafe { cpu_irq_restore(irq); }; + while (true) { + unsafe { cpu_wait_for_interrupt(); }; + } +} + +// Gives back the stacks of tasks that have ended. +// +// Run from the scheduler, and it skips two slots on purpose: the one the CPU is +// on and the one it is about to be on. A dead task is still standing on its +// stack until something else is running, so the first switch away from it can +// only mark it — the pages come back a tick later, when it is neither. +// +// That is why `DEAD` is a state rather than a step. There is no moment inside +// the exiting task where the release is safe. +fn reap_dead_tasks(current: Int, next: Int) { + let watermark = task_word(TASK_TABLE_BASE + TASK_USED_OFFSET); + for slot in 0..watermark { + if (slot == current || slot == next) { + continue; + } + if (task_state(TASK_TABLE_BASE, slot) != TASK_DEAD) { + continue; + } + let top = task_field(TASK_TABLE_BASE, slot, TASK_STACK_OFFSET); + if (top != 0) { + release_task_stack(top); + } + task_set_field(TASK_TABLE_BASE, slot, TASK_STACK_OFFSET, 0); + task_set_field(TASK_TABLE_BASE, slot, TASK_RSP_OFFSET, 0); + task_set_field(TASK_TABLE_BASE, slot, TASK_CR3_OFFSET, 0); + // Free last: it is what makes the slot takeable, and a slot handed to a + // spawner before its stack is back is a spawner that allocates while + // this one is still releasing. + task_set_state(TASK_TABLE_BASE, slot, TASK_FREE); + } +} + +#[export("lk_schedule_from_interrupt")] +fn schedule_from_interrupt(rsp: Int) -> Int { + let current = task_word(TASK_TABLE_BASE + TASK_CURRENT_OFFSET); + task_set_field(TASK_TABLE_BASE, current, TASK_RSP_OFFSET, rsp); + + // Every tick, before anything is chosen: a task whose moment has come has to + // be runnable by the time the choice is made, or it waits an extra slice for + // no reason. Cheaper than it looks — the watermark is at most the table's + // sixteen slots, and this is the only place that reads the clock against + // them. + let watermark = task_word(TASK_TABLE_BASE + TASK_USED_OFFSET); + task_wake_due(TASK_TABLE_BASE, watermark, shared_read(SHARED_TICKS)); + + let next = schedule(current); + // Clamped against the watermark, not the capacity: a scheduler naming a slot + // above everything ever spawned would resume a stack that was never + // prepared. And clamped against the slot's *state*, because since tasks can + // end and can wait, a slot inside the watermark is not necessarily one to + // resume. + if (next < 0 || next >= watermark) { + next = current; + } + let chosen = task_state(TASK_TABLE_BASE, next); + if (chosen != TASK_READY && chosen != TASK_IDLE) { + next = current; + } + reap_dead_tasks(current, next); + + // The address space before the stack pointer, and that order is the point: + // the value returned below is a pointer the CPU reads *after* this returns, + // so it has to still mean the same thing in whatever space is current then. + // It does, because the kernel is mapped identically in every space — which + // is what makes this order safe rather than lucky. + let want = task_field(TASK_TABLE_BASE, next, TASK_CR3_OFFSET); + if (want != 0 && want != unsafe { cpu_read_cr3() }) { + space_switch(want); + } + task_set_word(TASK_TABLE_BASE + TASK_CURRENT_OFFSET, next); + + // The ring-0 stack the *next* task will be interrupted onto. Task 0 keeps + // the boot stack and never runs at ring 3, so it has none. + let kernel_stack = task_field(TASK_TABLE_BASE, next, TASK_STACK_OFFSET); + if (kernel_stack != 0) { + tss_set_kernel_stack(TSS_BASE, kernel_stack); + } + return task_field(TASK_TABLE_BASE, next, TASK_RSP_OFFSET); +} + +// What a ring-3 task may ask the kernel for. +// +// One number per call, in `rax`. Deliberately few: every entry here is a hole +// in the wall the ring boundary just built, and the way to keep the wall +// meaningful is to have few holes and to know what each one lets through. +const SYS_WRITE = 1; +const SYS_EXIT = 2; +const SYS_WRITE_STR = 3; + +// The answer to a call the kernel refuses. The ring-3 programs compare against +// `-1`, which is the same bits either side reads it as. +// +// Read back with `as Int` at every use, and that is not decoration: a constant +// this far down the file is past the point where the compiler keeps top-level +// bindings in registers, so a function reads it through the global table and +// gets it *dynamically typed*. Returning it beside a plain `0` is then two +// return types in one function, which the native lowering refuses. The cast is +// where the program states what it already knows. +const SYS_REFUSED = 0 - 1; + +// How long a string the kernel will accept in one call. +// +// A bound, not a guess: without one a user task can hand over a length that +// keeps the kernel inside the syscall for as long as it likes — with interrupts +// on, so the machine survives, but the caller's own slice is spent in kernel +// code where nothing can preempt the loop's *effects*. +const MAX_WRITE = 4096; + +// The bounds of everything ring 3 may reach, from the linker script — so from +// the board, which is the only side that can ask the linker anything. +// Is `[address, address + length)` memory the caller is allowed to hand over? +// +// The only region ring 3 can reach is its own section, so that is the whole +// test — and it is a test the *kernel* performs, not a promise the caller +// makes. Without it, `write(0x100010, 64)` has the kernel read out its own code +// on request, which is the shape of every "the kernel followed a pointer it was +// given" bug there has ever been. +// +// The arithmetic is checked too. An `Int` is signed, which does half the work +// for free: an address with the top bit set arrives negative and fails the +// lower bound. What it does not cover is the other end — an address just under +// the maximum wraps `address + length` to a negative number, which would then +// pass `end <= limit`. LK's addition wraps rather than trapping, so the wrap is +// what the comparison below looks for. +fn user_range_is_valid(address: Int, length: Int) -> Bool { + if (length <= 0 || length > MAX_WRITE) { + return false; + } + let end = address + length; + if (end < address) { + return false; + } + return address >= unsafe { symbol_address("__user_start") } + && end <= unsafe { symbol_address("__user_end") }; } -#[extern("kernel_yield")] +// The syscall handler, entered from the board's trampoline with the number in +// `rax` and the arguments in `rdi` and `rsi`. +// +// Returning a value rather than writing registers: the trampoline puts the +// answer back in `rax`, which keeps the register discipline in one place. +#[export("lk_syscall_dispatch")] +fn syscall_dispatch(number: Int, arg: Int, arg2: Int) -> Int { + // Deliberately a *byte*, not a pointer: one byte per call is slow and + // honest, and it was the first call precisely because it needs no decision + // about whether to believe an address. + if (number == SYS_WRITE) { + console_byte(arg & 0xff); + return 0 as Int; + } + // The kernel checks, copies, and only then uses. Printing straight out of + // user memory would be one instruction shorter and would leave the window + // between the check and the use open — which on a machine with more than + // one CPU is not a window but a race. Reading a byte and printing it before + // reading the next is that copy, at the smallest granularity there is. + if (number == SYS_WRITE_STR) { + if (!user_range_is_valid(arg, arg2)) { + return SYS_REFUSED as Int; + } + for offset in 0..arg2 { + let byte = unsafe { volatile_read_u8((arg + offset) as *mut u8) }; + console_byte(byte as Int); + } + return arg2 as Int; + } + if (number == SYS_EXIT) { + shared_write(SHARED_USER_EXITED, 1); + return 0 as Int; + } + // An unknown number is not a crash: a kernel that dies on a bad syscall is + // one that any program can take down. + return SYS_REFUSED as Int; +} + +// Builds an address space for a user task, and answers its CR3. +// +// Almost all of it is shared with the kernel, and shared by *pointing at* the +// kernel's own tables rather than by copying them. Three of the four gigabytes +// are the kernel's directories named directly. The kernel has to be mapped in +// every address space — an interrupt arriving during ring 3 lands in kernel +// code, and it must be there to land in — and a copy would work today and drift +// on the day a mapping is added to one and not the other. +// +// Only the second gigabyte is this task's own, and it holds one page: its +// stack. That single difference is what makes two spaces two spaces. Two tasks +// can then put their stacks at the same virtual address and not see each +// other's, which one address space cannot be told apart from "a bit more +// permission". +// +// Returns 0 when there are no pages left. Zero is unambiguous as a CR3: page 0 +// is not in any range this allocator hands out. +fn build_user_space(stack_physical: Int) -> Int { + let pml4 = page_alloc(SHARED_PAGES); + let pdpt = page_alloc(SHARED_PAGES); + let directory = page_alloc(SHARED_PAGES); + let table = page_alloc(SHARED_PAGES); + if (pml4 == 0 || pdpt == 0 || directory == 0 || table == 0) { + return 0; + } + table_zero(pml4); + table_zero(pdpt); + table_zero(directory); + table_zero(table); + + let shared = PAGE_PRESENT | PAGE_WRITE | PAGE_USER; + // The kernel's four page directories, one per gigabyte, asked of the + // linker directly. The 32-bit boot code filled them in before long mode and + // therefore before any of this existed — but "where did the linker put it" + // is a question this language can ask. A user space points *at* them rather + // than copying them, which is what keeps the two from drifting. + let kernel_directories = unsafe { symbol_address("__pd") }; + // The first, third and fourth gigabytes: the kernel's, named not copied. + // The framebuffer is in the fourth. + table_set(pdpt, 0, kernel_directories, shared); + table_set(pdpt, 2, kernel_directories + 2 * PAGE_SIZE, shared); + table_set(pdpt, 3, kernel_directories + 3 * PAGE_SIZE, shared); + + // The second: this task's own, holding one page of stack at the bottom of + // it. Every index is *computed from the virtual address* rather than + // written as a number — a hand-written 0 here is right only because the + // stack happens to sit at the start of its gigabyte, and would go on + // looking right after it stopped being true. + let stack_virtual = USER_STACK_RESUME; + table_set(pdpt, index_of(stack_virtual, SHIFT_PDPT), directory, shared); + table_set(directory, index_of(stack_virtual, SHIFT_DIRECTORY), table, shared); + table_set(table, index_of(stack_virtual, SHIFT_TABLE), stack_physical, shared); + + table_set(pml4, index_of(stack_virtual, SHIFT_PML4), pdpt, shared); + return pml4; +} + +// Where that stack is in the task's own address space — a different number for +// the same memory. +// Give up the rest of this task's slice. +// +// `int VECTOR_YIELD` — and the vector is the *same constant* the gate is +// installed with, twenty lines up, because it can finally be one. It used to be +// two spellings of one number in two languages: `int` takes its vector as an +// immediate, so the board owned a Rust function containing `int 0x30` and +// nothing checked that it matched the gate this file installs. +// +// The runtime answers it with a table of 256 stubs, which is what `isr.rs` +// already did for the entry side. A kernel that can handle an interrupt but not +// raise one can answer a syscall and not define one. fn task_yield() { + unsafe { cpu_raise_interrupt(VECTOR_YIELD); }; +} + + + +// A deliberate fault, when the image was built with the `fault-probe` feature; +// nothing otherwise. +// +// Called immediately after the table is installed, because that is the earliest +// moment there is a gate to land in — and because the table it lands in should +// be the one this file just built. A reporter that is never made to report is +// indistinguishable from one that cannot. +#[extern("lk_fault_probe")] +fn fault_probe() { +} + +// The vector map. Which number means what is a decision, not a property of the +// machine — the only constraint the hardware imposes is that 0..32 belong to +// the CPU's own exceptions, which is why the PIC is remapped away from them at +// all. +// Where the PIC's lines land. Line n arrives as vector `VECTOR_IRQ_BASE + n`, +// which is the same number `remap` was given — named here because a driver that +// discovers its line at runtime has to do that arithmetic in both directions: +// forwards to find its gate, backwards to know which chip to acknowledge. +const VECTOR_IRQ_BASE = 0x20; + +// The two lines the chips deliver when they have nothing real to deliver. See +// `in_service` in the PIC driver for why they exist at all. +const IRQ_SPURIOUS_MASTER = 7; +const IRQ_SPURIOUS_SLAVE = 15; + +const VECTOR_TIMER = 0x20; +const VECTOR_KEYBOARD = 0x21; +// IRQ12, on the *slave* chip: 0x20 + 8 + 4. Its interrupts reach the CPU +// through the master's IRQ2 cascade line. +const VECTOR_MOUSE = 0x2c; +// Past the PIC's remapped range, so no device can raise either: there is +// nothing behind them but an `int` instruction. The reschedule one is the +// board's own (`kernel_yield` executes `int 0x30`); the syscall is the ring-3 +// program's, and its gate is the only one a ring-3 caller may raise. +const VECTOR_YIELD = 0x30; +const VECTOR_SYSCALL = 0x80; +const EXCEPTION_COUNT = 32; +// Every vector the CPU can raise; the runtime has a stub for each. +const VECTOR_COUNT = 256; + +// The selectors, which are byte offsets into the descriptor table plus the +// privilege the *caller* is requesting in the low two bits. Ring 3's carry a 3 +// there, which is what makes a ring-3 `iretq` frame a ring-3 one. +const KERNEL_CODE_SELECTOR = 0x08; +const KERNEL_DATA_SELECTOR = 0x10; +const USER_CODE_SELECTOR = 0x18 | 3; +const USER_DATA_SELECTOR = 0x20 | 3; +const TSS_SELECTOR = 0x28; + +// The descriptors themselves, as the 64-bit words they are. +// +// In long mode the base and the limit in each of these are ignored — the FFFF +// and the zeros are there because the format has the fields, not because +// anything reads them. What is read is the middle byte: 0x9A is executable and +// readable, 0x92 is writable data, and 0xFA and 0xF2 are the same two with DPL +// 3. Privilege is a property of the segment, which is why ring 3 needs its own +// pair rather than a flag somewhere else. +const DESCRIPTOR_KERNEL_CODE = 0x00AF9A000000FFFF; +const DESCRIPTOR_KERNEL_DATA = 0x00AF92000000FFFF; +const DESCRIPTOR_USER_CODE = 0x00AFFA000000FFFF; +const DESCRIPTOR_USER_DATA = 0x00AFF2000000FFFF; + +// Builds the descriptor table the machine runs on, and the segment it names. +// +// The boot stub has a table of its own and always will — entering long mode +// takes a `lgdt` and a far jump through a 64-bit code descriptor, both before +// any compiled code exists. That one is the minimum needed to get here and says +// nothing about policy. This one says what segments the machine has, which is a +// decision, so it is the program's. +fn install_descriptor_table() { + gdt_set_entry(GDT_BASE, 0, 0); + gdt_set_entry(GDT_BASE, 1, DESCRIPTOR_KERNEL_CODE); + gdt_set_entry(GDT_BASE, 2, DESCRIPTOR_KERNEL_DATA); + gdt_set_entry(GDT_BASE, 3, DESCRIPTOR_USER_CODE); + gdt_set_entry(GDT_BASE, 4, DESCRIPTOR_USER_DATA); + + tss_prepare(TSS_BASE); + // Task 0's ring-0 stack, and it belongs in the table as well as in the + // segment. Task 0 pre-exists the table — it is the one already running when + // the first interrupt lands — so nothing ever published a stack for it, and + // a switch *back* to it used to leave `rsp0` pointing at whichever task ran + // last. The `user` command enters ring 3 from task 0, and an interrupt + // during that excursion would have landed its frame on another task's + // kernel stack. + let boot_stack = unsafe { symbol_address("__ring0_stack_top") }; + task_set_field(TASK_TABLE_BASE, 0, TASK_STACK_OFFSET, boot_stack); + tss_set_kernel_stack(TSS_BASE, boot_stack); + // The limit is the segment's last valid byte, so one less than its size. + gdt_set_tss_entry(GDT_BASE, 5, TSS_BASE, TSS_SIZE - 1); + + gdt_load(GDT_BASE, GDT_ENTRIES); + // The reload changes nothing today, and is here anyway: the two tables + // agree on 0x08 and 0x10, so the cached descriptors are already right. What + // it buys is that they stay right when this table stops matching the boot + // stub's — which is the entire reason for having a second one. + gdt_reload(KERNEL_CODE_SELECTOR, KERNEL_DATA_SELECTOR); + gdt_load_task_register(TSS_SELECTOR); +} + +// The scheduler's hook into the segment above. Called from the board on every +// task switch, because the board is where the scheduler still lives. +#[export("lk_set_kernel_stack")] +fn set_kernel_stack(top: Int) { + tss_set_kernel_stack(TSS_BASE, top); +} + + + +// Builds the interrupt table and loads it. +// +// The first thing the program does, and it has to be: until this runs there is +// no gate for any vector, and a fault with no gate is a triple fault — which on +// this machine is a reset with nothing printed. Every line below this one is +// covered; nothing above it is. +// Points a vector at the runtime's generic entry, and says what it should call. +// +// The stride is measured rather than named, the same way the exception stubs' +// is: the assembler's `.align` decides it, and a copy of that number on this +// side would be a copy nothing checks. +// Answers whether the vector was installed. +// +// Two tables are indexed by it — the IDT and `lkrt`'s handler table — and both +// end at `VECTOR_COUNT`. The gate write refuses on its own (see +// `drivers/idt.lk`), and the handler write is refused here for the same reason: +// the vector for a device interrupt is `VECTOR_IRQ_BASE + line`, and the line is +// a byte read out of PCI config space. +fn install_device_handler(vector: Int, handler: Int) -> Bool { + let stubs = unsafe { symbol_address("lkrt_isr_stubs") }; + let span = unsafe { symbol_address("lkrt_isr_stubs_end") } - stubs; + if ((span % VECTOR_COUNT) != 0) { + uart_text("bad isr stride\n"); + halt(); + } + if (vector < 0 || vector >= VECTOR_COUNT) { + return false; + } + let stride = ((span / VECTOR_COUNT) as Int); + if (!idt_set_gate(IDT_BASE, vector, stubs + vector * stride, KERNEL_CODE_SELECTOR, 0)) { + return false; + } + let table = unsafe { symbol_address("lkrt_isr_handlers") }; + unsafe { volatile_write_u64((table + vector * 8) as *mut u64, handler as u64); }; + return true; +} + +fn install_interrupt_table() { + idt_zero(IDT_BASE); + + // The CPU's own exceptions, one stub each so the report can name the + // vector. The stubs are a `.rept` in the board's assembly, padded to a + // fixed stride — and the stride is *measured* here rather than named, + // because the assembler's `.align` is what decides it and a copy of that + // number on this side would be a copy nothing checks. + let stubs = unsafe { symbol_address("ISR_STUBS") }; + let span = unsafe { symbol_address("ISR_STUBS_END") } - stubs; + // The stride is only derivable if the array really is 32 equal slots. It + // was not, once: the assembler padded the *front* of each stub and not the + // back, so the span was 505 and this division gave 15 — after which every + // gate but the first pointed into the middle of the stub before it. The + // check costs one modulo at boot and is the thing that would have said so. + if ((span % EXCEPTION_COUNT) != 0) { + uart_text("bad isr stride\n"); + halt(); + } + let stride = ((span / EXCEPTION_COUNT) as Int); + for vector in 0..EXCEPTION_COUNT { + idt_set_gate(IDT_BASE, vector, stubs + vector * stride, KERNEL_CODE_SELECTOR, 0); + } + + // The two vectors whose entry path is their own. The timer's has to return + // on a *different* stack, which is what a task switch is; the reschedule + // vector is the same path without the device work. Neither fits a shared + // tail, so both stay the board's. + idt_set_gate(IDT_BASE, VECTOR_TIMER, + unsafe { symbol_address("__task_trampoline") }, KERNEL_CODE_SELECTOR, 0); + idt_set_gate(IDT_BASE, VECTOR_YIELD, + unsafe { symbol_address("__yield_trampoline") }, KERNEL_CODE_SELECTOR, 0); + + // The ordinary device interrupts, through the runtime's generic entry. + // + // One stub per vector because the CPU does not tell a handler which one + // arrived; a shared tail because after that they are identical. Installing + // a driver's interrupt is now two stores from here — a gate, and a handler + // address — rather than a hand-written trampoline in a file the driver has + // nothing to do with. + install_device_handler(VECTOR_KEYBOARD, unsafe { symbol_address("lk_key_isr") }); + install_device_handler(VECTOR_MOUSE, unsafe { symbol_address("lk_mouse") }); + // The chips' own two vectors, installed whether or not anything uses those + // lines — the point of a spurious interrupt is that it arrives on a line + // nothing is using. + install_device_handler(VECTOR_IRQ_BASE + IRQ_SPURIOUS_MASTER, + unsafe { symbol_address("lk_spurious_isr") }); + install_device_handler(VECTOR_IRQ_BASE + IRQ_SPURIOUS_SLAVE, + unsafe { symbol_address("lk_spurious_isr") }); + // The one gate with DPL 3, and the whole reason the others have DPL 0: a + // ring-3 task can raise this vector and no other. `int 0x21` from ring 3 + // would otherwise be a way to fake a keystroke. + idt_set_gate(IDT_BASE, VECTOR_SYSCALL, + unsafe { symbol_address("__syscall_trampoline") }, KERNEL_CODE_SELECTOR, 3); + + idt_install(IDT_BASE); } // Repaints a window's frame if the focus has moved since it last did. @@ -1198,7 +3104,7 @@ fn draw_glyph_in_window(base: Int, slot: Int, index: Int, x: Int, y: Int) { colour = TITLE_COLOUR; } drops = drops + window_put_within(base, WIDTH, left, top, limit_x, limit_y, - x + column, y + row, colour); + x + column, y + row, colour); } } if (drops > 0) { @@ -1336,7 +3242,7 @@ fn paint_shell(base: Int, slot: Int) -> Int { #[export("lk_paint_spinner")] fn paint_spinner(base: Int, slot: Int) -> Int { window_fill(base, WIDTH, SHARED_WINDOWS, slot, 0, 0, - window_width(SHARED_WINDOWS, slot), window_height(SHARED_WINDOWS, slot), BACKGROUND); + window_width(SHARED_WINDOWS, slot), window_height(SHARED_WINDOWS, slot), BACKGROUND); draw_glyph_in_window(base, WINDOW_SPINNER, spinner_glyph(), 1, 1); repaint_frame(base, slot); return 0; @@ -1345,7 +3251,7 @@ fn paint_spinner(base: Int, slot: Int) -> Int { #[export("lk_paint_clock")] fn paint_clock(base: Int, slot: Int) -> Int { window_fill(base, WIDTH, SHARED_WINDOWS, slot, 0, 0, - window_width(SHARED_WINDOWS, slot), window_height(SHARED_WINDOWS, slot), BACKGROUND); + window_width(SHARED_WINDOWS, slot), window_height(SHARED_WINDOWS, slot), BACKGROUND); draw_clock(base); repaint_frame(base, slot); return 0; @@ -1375,8 +3281,8 @@ fn repaint_above(base: Int, slot: Int) { for index in 0..WINDOW_COUNT { let other = z_slot(index); if (above && other != WINDOW_SHELL - && overlaps(other, window_left(SHARED_WINDOWS, slot), window_top(SHARED_WINDOWS, slot), - window_width(SHARED_WINDOWS, slot), window_height(SHARED_WINDOWS, slot))) { + && overlaps(other, window_left(SHARED_WINDOWS, slot), window_top(SHARED_WINDOWS, slot), + window_width(SHARED_WINDOWS, slot), window_height(SHARED_WINDOWS, slot))) { repaint_window(base, other); } if (other == slot) { @@ -1524,7 +3430,17 @@ fn schedule(current: Int) -> Int { // switch costs a stack's worth of pushes, and a slice this short would // spend more time switching than running. let slice = shared_bump(SHARED_SLICE); - if (slice % 8 != 0) { + // Stay on the current task for the rest of its slice — but only if it is + // still one that may run. + // + // The state check is not a nicety. This shortcut used to fire on seven ticks + // out of eight and hand back `current` without looking at it, which meant a + // task that had just marked itself blocked was resumed anyway: `task_sleep` + // set its deadline, yielded, and the yield came straight back. Every sleep + // returned immediately, the wake count was still right, and the only thing + // that gave it away was the elapsed time — ten sleeps of fifty ticks taking + // eighty ticks in total. + if (slice % 8 != 0 && task_state(TASK_TABLE_BASE, current) == TASK_READY) { return current; } // Round robin over the tasks that exist. The count comes from the shared @@ -1535,7 +3451,40 @@ fn schedule(current: Int) -> Int { if (count <= 1) { return current; } - return (current + 1) % count; + // The next slot that is *ready*, not simply the next slot. Since a task can + // end, the range below the watermark has holes in it, and handing one to the + // switch would resume a stack that has been given back. + // + // Bounded by the range rather than by "until we find one": every slot may be + // dead but the one running, and a loop with no bound would be a scheduler + // that never returns from a timer interrupt. + // The next slot that is *ready*, not simply the next slot. Since a task can + // end and can wait, the range below the watermark has holes in it, and + // handing one to the switch would resume a stack that has been given back or + // a task that said it is waiting. + // + // Bounded by the range rather than by "until we find one": a loop with no + // bound would be a scheduler that never returns from a timer interrupt. + let idle = 0 - 1; + for step in 1..(count + 1) { + let candidate = (current + step) % count; + let state = task_state(TASK_TABLE_BASE, candidate); + if (state == TASK_READY) { + return candidate; + } + if (state == TASK_IDLE) { + idle = candidate; + } + } + // Nothing is runnable. The idle task is where that goes — and it is found + // here rather than remembered in a word because the rotation has just walked + // past it. Staying on `current` instead would be resuming a task the loop + // above has just decided is not runnable, which for a blocked task means it + // wakes early and for a dead one means it runs after it ended. + if (idle >= 0) { + return idle; + } + return current; } // The second task: a spinner in the top-right corner. @@ -1557,7 +3506,7 @@ fn task_b() { column = 0; } else { draw_glyph_in_window(framebuffer_origin(), WINDOW_SPINNER, - glyph_index(typed), 1 + column * CELL_WIDTH, CELL_HEIGHT + 1); + glyph_index(typed), 1 + column * CELL_WIDTH, CELL_HEIGHT + 1); column = column + 1; } shared_write(SHARED_PANE_COL, column % 10); @@ -1599,6 +3548,11 @@ fn task_b() { // The counter lives in the shared page because a handler's state has to // survive between invocations, and it is the only state this one keeps. #[export("lk_timer_isr")] +fn isr_tick() { + on_tick(); + pic_eoi_master(); +} + fn on_tick() { bump_pair(); let ticks = shared_bump(SHARED_TICKS); @@ -1607,6 +3561,109 @@ fn on_tick() { } } +// The two vectors a PIC delivers when it has nothing to deliver. +// +// Not a defensive nicety: a spurious interrupt is a documented behaviour of the +// chip, it arrives on a vector nothing here claims, and a vector with no gate +// is a general protection fault raised from inside an interrupt — which is how +// this kernel reported the first PCI interrupt probe that timed out. Masking a +// line while its request is pending is enough to cause one, and a driver that +// gives up waiting does exactly that. +// +// Two rules, and the second is the one that is easy to get wrong. A spurious +// interrupt was never in service, so it must *not* be acknowledged — an EOI for +// something not in service clears whatever is. But a spurious interrupt from +// the slave still reached the CPU through the master's cascade line, which the +// master does consider in service; so the master, and only the master, is told. +#[export("lk_spurious_isr")] +fn isr_spurious(vector: Int) { + let irq = vector - VECTOR_IRQ_BASE; + if (pic_in_service(irq)) { + // A real interrupt, on a line this kernel has no driver for. Nothing to + // do but acknowledge it, or the chip holds the line in service and + // everything below it in priority goes quiet. + pic_eoi(irq); + return; + } + if (irq >= 8) { + pic_eoi_master(); + } +} + +// The network card's interrupt handler. +// +// Three obligations, and this card makes the first one a trap: `ICR` clears on +// read, so the read *is* the acknowledgement and asking twice gets the answer +// once. A handler that reads it into a variable and then reads it again to check +// another bit has already thrown that bit away. +// +// It does not touch the rings. A handler's job is to say that something happened +// — taking the frame out is work, and work belongs to whoever was waiting, on +// its own stack, with interrupts on. What this does is wake them. +#[export("lk_net_isr")] +fn isr_net(vector: Int) { + let mmio = shared_read(SHARED_NET_MMIO); + if (mmio != 0) { + let cause = e1000_interrupt_cause(mmio); + if (cause != 0) { + // Out of the register's width and into a shared word, which is + // where the driver's types stop and the kernel's `Int` begins. + shared_write(SHARED_NET_CAUSE, cause as Int); + shared_bump(SHARED_NET_IRQS); + task_wake_channel(TASK_TABLE_BASE, + task_word(TASK_TABLE_BASE + TASK_USED_OFFSET), + NET_WAIT_CHANNEL); + } + } + pic_eoi(vector - VECTOR_IRQ_BASE); +} + +// The PCI device's interrupt handler. +// +// The last of the four things a PCI device does, and the only one that runs +// with the program suspended somewhere it did not choose. Three obligations, +// and dropping any of them looks like different broken hardware: +// +// * ask the device *what* it raised, before clearing anything +// * clear it at the device, which is what makes it stop asserting its line — +// an interrupt acknowledged only at the PIC re-arrives immediately, for ever +// * acknowledge the PIC, by line number, which for a line above 7 means both +// chips +// +// It takes its vector, which the runtime's shared entry passes: one handler can +// serve several gates, and the line to acknowledge is derivable from the vector +// rather than being a second thing to keep in step. It allocates nothing, for +// the same reason the keyboard's does not — an interrupt lands between any two +// instructions, including instructions inside the allocator. +#[export("lk_edu_isr")] +fn isr_edu(vector: Int) { + let mmio = shared_read(SHARED_EDU_MMIO); + if (mmio != 0) { + let raised = edu_irq_status(mmio); + edu_acknowledge(mmio, raised); + // Recorded after the acknowledgement, so a shell that sees this word + // set knows the device has already been quietened. `| 1` because the + // device may legitimately raise a value of zero bits and the shell + // needs to tell "arrived" from "not yet". + // Out of the register's width and into a shared word: the driver's + // types stop where the kernel's `Int` begins. + shared_write(SHARED_EDU_SEEN, raised | 1); + // And whoever asked. This is what a blocked wait is for: the driver said + // which channel it was listening on, and the device's own handler is the + // thing that knows the answer has arrived. Waking from here rather than + // letting the waiter poll is the difference between a machine that is + // halted for the wait and one that is turning over. + // + // Safe from an interrupt because it is writes to a table of volatile + // words and interrupts are already masked; it allocates nothing, which + // is the rule every handler here keeps. + task_wake_channel(TASK_TABLE_BASE, + task_word(TASK_TABLE_BASE + TASK_USED_OFFSET), + EDU_WAIT_CHANNEL); + } + pic_eoi(vector - VECTOR_IRQ_BASE); +} + // The keyboard interrupt handler. // // It reads the controller itself: what the board owes the device is the @@ -1615,12 +3672,39 @@ fn on_tick() { // instructions of the interrupted program, including instructions inside the // runtime's allocator. #[export("lk_key_isr")] +fn isr_key() { + on_key(); + pic_eoi_master(); +} + fn on_key() { let scancode = read_scancode(); + let code = key_code(scancode); + // Shift is a *state*, so both edges matter: the release is what ends it. + // Every other key ignores releases, which is why they are dropped below + // rather than here. + if (is_shift(code)) { + if (is_release(scancode)) { + shared_write(SHARED_SHIFT, 0); + } else { + shared_write(SHARED_SHIFT, 1); + } + return; + } if (is_release(scancode)) { return; } - let code = key_code(scancode); + // Caps lock toggles on press and does nothing on release — the lock is the + // key's whole purpose, and a keyboard that unlocked when you let go would + // be a shift key. + if (is_caps_lock(code)) { + if (shared_read(SHARED_CAPS) == 0) { + shared_write(SHARED_CAPS, 1); + } else { + shared_write(SHARED_CAPS, 0); + } + return; + } if (code >= SCANCODE_ASCII.len()) { return; } @@ -1632,6 +3716,7 @@ fn on_key() { if (ascii == 0) { return; } + let ascii = shifted(ascii, shared_read(SHARED_SHIFT) == 1, shared_read(SHARED_CAPS) == 1); shared_bump(SHARED_KEY_COUNT); shared_write(SHARED_LAST_KEY, ascii); uart_putc(ascii); @@ -1655,25 +3740,76 @@ fn on_key() { } uart_init(); -// ~1 kHz. The board has already unmasked IRQ0 and enabled interrupts; what -// this decides is how often it fires. +// Before anything that can fault, and before anything that can be interrupted. +install_interrupt_table(); +// And immediately, on a probe build, something that faults. +fault_probe(); +// Then the segments. After the table rather than before it because a fault in +// here should be reportable, and the boot stub's descriptors are enough to run +// on until this replaces them. +// The task table first: the descriptor table's setup publishes task 0's own +// ring-0 stack into it, and before interrupts are enabled either way — the +// timer handler reads the table, and a table of zeros names task 0 forever, +// which looks like a working machine with one task rather than an +// uninitialised table. +// The fixed-address chain has to stop below the staging area. +// +// Everything from `SHARED_BASE` up is a chain of sums — shared words, then the +// IDT, the GDT, the TSS, the task table — and the next thing at a fixed address +// is `SOURCE_BASE`, half a megabyte higher, where a file read off the disk +// lands. Nothing related the two. Adding a shared word or raising +// `TASK_CAPACITY` would walk the far end of the chain into that buffer, and the +// symptom would be a program that loads wrong rather than a machine that stops. +// +// The linker script makes the same kind of statement about the image, and can: +// its numbers are link-time. These are computed here, so this is where the +// check has to be. It is the first line before anything writes at the far end. +// +// Halting rather than degrading, unlike the heap above. A heap that reports +// "nothing fits" is a machine with no heap; a task table on top of the staging +// area is a machine that corrupts what it reads and says nothing. +let fixed_end = TASK_TABLE_BASE + task_table_size(TASK_CAPACITY); +if (fixed_end > SOURCE_BASE) { + uart_text("layout: fixed-address chain ends at "); + uart_put_hex(fixed_end); + uart_text(", past the staging area at "); + uart_put_hex(SOURCE_BASE); + uart_text("\n"); + halt(); +} +task_table_init(TASK_TABLE_BASE, TASK_CAPACITY); +install_descriptor_table(); +// Then the chip that raises them, and the flag that lets them through. In that +// order: an interrupt taken before the table was loaded goes wherever the +// previous table said, and there is no previous table. +pic_remap(VECTOR_TIMER); +// The timer, the keyboard, and the cascade — a zero bit is a line allowed to +// raise something. The cascade (line 2) is not a device: it is how the slave +// chip reaches the CPU at all, so the mouse's IRQ12 is invisible without it. +// Every other line stays masked, because an unmasked line with no gate is a +// fault raised from inside an interrupt. +pic_mask(0xf8, 0xef); +// `cpu_irq_restore(1)` is `sti`, and the shape is right rather than merely +// convenient: this says "put the machine in the state where interrupts are +// enabled". There is no nesting to get wrong here — they have never been on. +unsafe { cpu_irq_restore(1); }; +// ~1 kHz. IRQ0 is unmasked and interrupts are on; what this decides is how +// often it fires. pit_start(1193); -let slot = pci_find_display(); -if (slot < 0) { +let display = pci_find_display(); +if (display < 0) { // No display on bus 0. Say so on the line that does exist and stop, rather // than programming a configuration address made of the `-1` sentinel and // drawing into whatever it names. - // "no display" - uart_write([110, 111, 32, 100, 105, 115, 112, 108, 97, 121, 10]); + uart_text("no display\n"); halt(); } // Let the device answer to memory cycles at all. The firmware usually has // already, but a driver that depends on that is a driver that works on one // machine. -let command = pci_read(slot, PCI_REG_COMMAND); -pci_write(slot, PCI_REG_COMMAND, command | PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER); +pci_command_set(display, PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER); // Find the framebuffer once and remember it: everything that draws reads this // word, and an interrupt handler cannot walk a bus. @@ -1689,24 +3825,36 @@ clear(framebuffer, WIDTH, HEIGHT, BACKGROUND); // run time would be work the program already knows the answer to. // "LK ON BARE METAL" draw_cells(framebuffer, WIDTH, FONT, - [12, 11, 0, 15, 14, 0, 2, 1, 18, 5, 0, 13, 5, 20, 1, 12], - 1, 1, TITLE_COLOUR, BACKGROUND); + [12, 11, 0, 15, 14, 0, 2, 1, 18, 5, 0, 13, 5, 20, 1, 12], + 1, 1, TITLE_COLOUR, BACKGROUND); // "TYPE HELP" then the prompt on the next line draw_cells(framebuffer, WIDTH, FONT, [20, 25, 16, 5, 0, 8, 5, 12, 16], 1, 3, FOREGROUND, BACKGROUND); draw_cells(framebuffer, WIDTH, FONT, [44], 1, 5, FOREGROUND, BACKGROUND); -// Hand the page allocator the biggest usable range the loader reported. The -// image, its stack and its page tables all sit below 2 MiB, so the range is -// trimmed to start there rather than on top of them. -let ram_base = largest_available_base(); -let ram_length = largest_available_length(); -let usable_base = ram_base; -// Above the interpreter's heap, which `src/main.rs` places at 4 MiB and sizes -// at 28 MiB. Two allocators on one machine agree by arrangement or not at all. -if (usable_base < 0x02000000) { - usable_base = 0x02000000; +// Hand the page allocator the biggest usable range it can actually reach. +// +// Two bounds, and the second is the one that was missing. The floor is +// `__page_arena_base`, where `link.ld` puts this arena so it starts exactly +// where the run heap ends — two allocators on one machine agree by arrangement +// or not at all. The *ceiling* is what `boot.rs` identity-maps: four gigabytes, +// with 2 MiB pages, four rather than one because a PCI framebuffer sits near the +// top of the 32-bit range. An address above that is not mapped, and a page +// allocator handing one out is a fault the moment anything writes to it. +// +// So the range is chosen *after* clipping rather than before. The loader reports +// several available ranges, and on a machine with more than four gigabytes the +// largest of them is the one above them — picking that and then trimming leaves +// nothing, and picking it untrimmed faulted on the first page the heap took. +// `qemu -m 8G` did exactly that; `-m 3G` was fine, which is why nothing noticed. +let arena_floor: Int = unsafe { symbol_address("__page_arena_base") }; +let usable_base = reachable_base(arena_floor); +let usable_length = reachable_length(arena_floor); +if (usable_length == 0) { + // Every range the loader reported lies outside what this kernel can reach. + // Saying so beats handing out addresses that fault. + uart_text("pages: no usable memory below the identity map\n"); } -pages_init(SHARED_PAGES, usable_base, ram_length - (usable_base - ram_base)); +pages_init(SHARED_PAGES, usable_base, usable_length); // The kernel heap: sixteen pages taken from the page allocator, once, at // startup. Taken rather than declared at a fixed address, because an address @@ -1731,8 +3879,7 @@ if (heap_pages_ok) { // An empty arena: every heap call then reports "nothing fits" instead of // writing somewhere it was never given. heap_init(SHARED_HEAP, 0, 0); - // "no heap" - uart_write([110, 111, 32, 104, 101, 97, 112, 10]); + uart_text("no heap\n"); } // Two windows: the shell owns everything but the corner cell the spinner has. @@ -1745,11 +3892,11 @@ window_define(SHARED_WINDOWS, WINDOW_SHELL, 0, 0, WIDTH, HEIGHT); // Two pixels wider and taller than its content, so the one-pixel frame it // draws for itself does not eat into a glyph. window_define(SHARED_WINDOWS, WINDOW_SPINNER, - (text_columns() - 11) * CELL_WIDTH, 0, 10 * CELL_WIDTH + 2, 2 * CELL_HEIGHT + 2); + (text_columns() - 11) * CELL_WIDTH, 0, 10 * CELL_WIDTH + 2, 2 * CELL_HEIGHT + 2); // A third window, further down the right-hand side: 8 cells for a number and // its unit. window_define(SHARED_WINDOWS, WINDOW_CLOCK, - (text_columns() - 11) * CELL_WIDTH, 3 * CELL_HEIGHT, 8 * CELL_WIDTH + 2, CELL_HEIGHT + 2); + (text_columns() - 11) * CELL_WIDTH, 3 * CELL_HEIGHT, 8 * CELL_WIDTH + 2, CELL_HEIGHT + 2); // Bottom of the stack to the top: the shell is the desktop, and the two panes // sit on it in the order they were defined until a click says otherwise. // Each window says what draws it, once, here. @@ -1770,17 +3917,44 @@ shared_write(SHARED_DRAWN_FOCUS, 0 - 1); // the table from a timer interrupt, and a slot published before its stack is // ready is a jump to zero. let irq_spawn = lock(); +// The idle task first, so there is somewhere to go before anything can block. +// +// It is an ordinary task in an ordinary slot; what makes it the idle one is its +// state, which is set after `spawn_task` has published it ready. The rotation +// skips that state and falls back to it, so it costs nothing while anything else +// is runnable. +let idle_task = spawn_task(unsafe { symbol_address("lk_task_idle") }); +if (idle_task >= 0) { + task_set_state(TASK_TABLE_BASE, idle_task, TASK_IDLE); +} let spinner_task = spawn_task(unsafe { symbol_address("lk_task_b") }); +shared_write(SHARED_BUSY_SLOTS, spinner_task); let clock_task = spawn_task(unsafe { symbol_address("lk_task_clock") }); -// Derived from the last slot handed out, not written as a number: a literal -// here is a second place that has to be edited whenever a task is added, and -// the failure when it is not — a scheduler naming a slot that was never -// spawned — is a jump to zero. -shared_write(SHARED_TASK_COUNT, clock_task + 1); +// And one that runs in ring 3. It never yields; the timer is what takes the +// CPU back, which is the whole difference from the one-shot `user` command. +// The space is built here and handed over, rather than asked of the board: +// what an address space *contains* is a decision, and the board's share of it +// is the four page directories it filled in before long mode. How many spaces +// there can be is now "how many pages are left", which is a bound with a +// reason, unlike the four the linker script used to reserve. +let user_space = build_user_space(unsafe { symbol_address("__user_task_stack_a") }); +let user_task = spawn_user_task(unsafe { symbol_address("__user_task_a") }, + USER_STACK_RESUME, user_space); +shared_write(SHARED_BUSY_SLOTS + WORD, user_task); +// A second one, with a stack at the *same* virtual address in a space of its +// own: if they shared a space, one would overwrite the other's memory. +let user_space_b = build_user_space(unsafe { symbol_address("__user_task_stack_b") }); +let user_task_b = spawn_user_task(unsafe { symbol_address("__user_task_b") }, + USER_STACK_RESUME, user_space_b); +shared_write(SHARED_BUSY_SLOTS + 2 * WORD, user_task_b); +// The count is no longer written here: `publish_task` writes it as the last +// step of every spawn, which is the only moment at which it is true. Writing it +// afterwards from the last slot handed out was correct and was also a second +// place to edit whenever a task was added — and the failure when it was not is +// a scheduler naming a slot that was never spawned. unlock(irq_spawn); -if (spinner_task < 0 || clock_task < 0) { - // "no tasks" - uart_write([110, 111, 32, 116, 97, 115, 107, 115, 10]); +if (spinner_task < 0 || clock_task < 0 || user_task < 0 || user_task_b < 0) { + uart_text("no tasks\n"); } // The mouse, if there is one. A machine without it is a report rather than a @@ -1796,8 +3970,7 @@ grid_clear(); // They have to agree; if they ever stop, every line wraps one cell early and // the repaint puts characters in the wrong place. if (GRID_COLUMNS != text_columns() || GRID_ROWS != text_rows()) { - // "grid mismatch" - uart_write([103, 114, 105, 100, 32, 109, 105, 115, 109, 97, 116, 99, 104, 10]); + uart_text("grid mismatch\n"); halt(); } // Masked for the whole sequence. Every mouse command answers with an ACK, and @@ -1809,8 +3982,7 @@ let irq_mouse = lock(); let mouse_present = mouse_init(); unlock(irq_mouse); if (mouse_present == 0) { - // "no mouse" - uart_write([110, 111, 32, 109, 111, 117, 115, 101, 10]); + uart_text("no mouse\n"); } // The cursor starts after the prompt. @@ -1826,7 +3998,7 @@ place_cursor(framebuffer, 3, 5); // is the same claim checked at run time, on the machine, after the boot path // has enabled SSE in CR0/CR4. // "half " then the answer -uart_write([104, 97, 108, 102, 32]); +uart_text("half "); uart_put_int(((88 / 2.0) as Int)); uart_putc(10); @@ -1844,17 +4016,22 @@ let title = get_pixel(framebuffer, WIDTH, 6, 8); // `cpu_irq_save` returns the previous state rather than a flag, so nesting one // of these inside another does not enable interrupts early on the way out. let irq = unsafe { cpu_irq_save() }; -// "display at pci slot " -uart_write([100, 105, 115, 112, 108, 97, 121, 32, 97, 116, 32, 112, 99, 105, 32, - 115, 108, 111, 116, 32]); -uart_put_int(slot); +// "display at pci " then the device's own address on the bus. +// +// The address, not a slot number: enumeration now walks functions as well as +// devices, so what identifies a device is the (bus, device, function) triple +// the whole stack calls a BDF. Printing it in the form `pci` lists — SS.F — +// rather than as the packed integer, because the two lines are about the same +// device and a reader should not have to divide by 0x800 to see that. +uart_text("display at pci "); +uart_put_int(pci_bdf_device(display)); +uart_putc(46); // '.' +uart_put_int(pci_bdf_function(display)); uart_putc(10); -// "framebuffer 0x" -uart_write([102, 114, 97, 109, 101, 98, 117, 102, 102, 101, 114, 32, 48, 120]); +uart_text("framebuffer 0x"); uart_put_hex(framebuffer); uart_putc(10); -// "pixels " -uart_write([112, 105, 120, 101, 108, 115, 32]); +uart_text("pixels "); uart_put_hex(empty); uart_putc(32); uart_put_hex(title); @@ -1894,8 +4071,8 @@ while (running == 1 && ((shared_read(SHARED_TICKS) - started) & TICK_MASK) < 600 // deciding that a click *means* "focus this window" is a policy — the same // reason the key handler routes bytes but does not run commands. if (shared_read(SHARED_MOUSE_X) != shared_read(SHARED_CURSOR_X) - || shared_read(SHARED_MOUSE_Y) != shared_read(SHARED_CURSOR_Y) - || shared_read(SHARED_CURSOR_DRAWN) == 0) { + || shared_read(SHARED_MOUSE_Y) != shared_read(SHARED_CURSOR_Y) + || shared_read(SHARED_CURSOR_DRAWN) == 0) { let irq_cursor = lock(); move_cursor(framebuffer_origin()); unlock(irq_cursor); @@ -1923,7 +4100,7 @@ while (running == 1 && ((shared_read(SHARED_TICKS) - started) & TICK_MASK) < 600 // excluded: it is the whole screen, so "moving" it would mean // moving everything, and there is nowhere for it to go. if (hit > WINDOW_SHELL - && pointer_y < window_top(SHARED_WINDOWS, hit) + TITLE_HEIGHT) { + && pointer_y < window_top(SHARED_WINDOWS, hit) + TITLE_HEIGHT) { shared_write(SHARED_DRAG_SLOT, hit); shared_write(SHARED_DRAG_DX, pointer_x - window_left(SHARED_WINDOWS, hit)); shared_write(SHARED_DRAG_DY, pointer_y - window_top(SHARED_WINDOWS, hit)); @@ -1988,11 +4165,26 @@ while (running == 1 && ((shared_read(SHARED_TICKS) - started) & TICK_MASK) < 600 unlock(irq_in); if (shared_read(SHARED_LINE_READY) == 1) { - // Mask around the whole exchange: the handler shares the cursor and - // the line buffer, and a keystroke arriving mid-command would splice - // its echo into the output and its byte into the next line. - let irq = unsafe { cpu_irq_save() }; + // With interrupts *on*, which is the only way a command that waits for + // anything can work. + // + // This used to mask around the whole exchange, on the grounds that the + // key handler shares the cursor and the line buffer. It shares neither: + // the handler only queues bytes, and both the cursor and the buffer are + // written by this task. What the mask really protected was the console + // against the two drawing *tasks* — which is a lock's job, and `emit` + // now takes one for the line it prints. + // + // The cost of the old shape was not a race. It was that masking + // interrupts stops the timer, and stopping the timer stops the + // scheduler, so every command that waited for a device, a task or a + // deadline had to know to turn them back on. Four of them did not, and + // every one of those still printed the right answer. + // + // A keystroke arriving mid-command now stays in the queue and is read + // as the next line, which is what type-ahead is. running = run_command(framebuffer_origin()); + let irq = lock(); shared_write(SHARED_LINE_LEN, 0); shared_write(SHARED_LINE_READY, 0); if (running == 1) { @@ -2000,23 +4192,25 @@ while (running == 1 && ((shared_read(SHARED_TICKS) - started) & TICK_MASK) < 600 uart_putc(62); put_char(framebuffer_origin(), 62); } - unsafe { cpu_irq_restore(irq); }; + unlock(irq); } } let keys = shared_read(SHARED_KEY_COUNT); let last = shared_read(SHARED_LAST_KEY); -// Masked and deliberately not restored: what runs after this is the runtime's -// epilogue echoing the script's value, which no LK code is in a position to -// protect. +// Both halves, and in this order. Masking the flag stops the CPU taking +// anything; masking the chip stops it raising anything, which is what keeps a +// pending tick from being delivered the moment something else enables the flag. +// Deliberately not restored: what runs after this is the runtime's epilogue +// echoing the script's value, which no LK code is in a position to protect — +// and a tick landing mid-line would splice a '.' into it. unsafe { cpu_irq_save(); }; +pic_mask(0xff, 0xff); uart_putc(10); -// "keys " -uart_write([107, 101, 121, 115, 32]); +uart_text("keys "); uart_put_int(keys); -// " last " -uart_write([32, 108, 97, 115, 116, 32]); +uart_text(" last "); uart_put_int(last); uart_putc(10); diff --git a/bare-metal-x86/run.sh b/bare-metal-x86/run.sh index 9b1b127b..7aaf758b 100755 --- a/bare-metal-x86/run.sh +++ b/bare-metal-x86/run.sh @@ -11,11 +11,23 @@ cd "$(dirname "$0")" BIN=target/x86_64-unknown-none/release/lk-bare-metal-x86 OBJCOPY=${OBJCOPY:-llvm-objcopy} +# A feature build gets its own image name. `CARGO_FLAGS=--features=fault-probe` +# builds a kernel that page-faults on purpose, and this wrote it to the one path +# every `check_*.py` boots — so the next check reported `#PF page fault` as a +# regression, and bisecting that is hopeless because every revision "fails". +# `kernel.py` builds its own image now, which closes the same hole from the +# other side; this keeps the file name from lying about what is in it. +IMAGE="$BIN.multiboot" +if [ -n "${CARGO_FLAGS:-}" ]; then + IMAGE="$BIN.$(printf '%s' "${CARGO_FLAGS}" | tr -cs 'a-zA-Z0-9' '-').multiboot" +fi + LK_BIN=${LK_BIN:-../target/debug/lk} cargo build --release ${CARGO_FLAGS:-} -"$OBJCOPY" -O elf32-i386 "$BIN" "$BIN.multiboot" +"$OBJCOPY" -O elf32-i386 "$BIN" "$IMAGE" # `isa-debug-exit` ends the machine when the kernel writes to port 0xf4, so # this returns instead of needing a timeout to decide it is finished. Its exit # code is `(value << 1) | 1`, so a clean run is 1. -exec qemu-system-x86_64 -kernel "$BIN.multiboot" -display none -serial stdio \ - -device isa-debug-exit,iobase=0xf4,iosize=0x04 "$@" +exec qemu-system-x86_64 -kernel "$IMAGE" -display none -serial stdio \ + -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ + -device edu "$@" diff --git a/bare-metal-x86/src/boot.rs b/bare-metal-x86/src/boot.rs index 288333ca..00acc651 100644 --- a/bare-metal-x86/src/boot.rs +++ b/bare-metal-x86/src/boot.rs @@ -17,12 +17,12 @@ global_asm!( // finds the header and falls back to the Linux/PVH path. ".section .multiboot, \"a\"", ".align 4", - ".long 0x1BADB002", // magic + ".long 0x1BADB002", // magic // Flags bit 0 asks the loader for memory information: how much there is, // and the map of which ranges are usable. Without it a kernel knows only // what it can guess. - ".long 0x00000001", // flags: MEMORY_INFO - ".long -(0x1BADB002 + 1)", // checksum + ".long 0x00000001", // flags: MEMORY_INFO + ".long -(0x1BADB002 + 1)", // checksum ); global_asm!( @@ -64,15 +64,68 @@ global_asm!( " loop 1b", " mov edi, offset __pdpt", " mov eax, offset __pd", - " or eax, 3", + // User-accessible at this level too: the CPU takes the *conjunction* of the + // U bits along the walk, so a user page under a kernel-only directory is + // still kernel-only. + " or eax, 7", " mov ecx, 4", "2: mov [edi], eax", " mov dword ptr [edi + 4], 0", " add eax, 0x1000", " add edi, 8", " loop 2b", + // The first 4 MiB gets 4 KiB granularity, so ring 3 can be given the pages + // it needs and *only* those. + // + // Every page kernel-only to start with; then the ones between + // `__user_start` and `__user_end` — the linker script's own section — get + // the U bit. The directory entry above them needs it too, because the CPU + // takes the conjunction of the U bits along the walk: a user page under a + // kernel-only directory is still kernel-only. + // + // Two tables rather than one, filled as a single 1024-entry run because the + // linker script places them adjacently. One covered 2 MiB, and `.user` had + // drifted to 0x1ff000 — one page below the end of it. Growing `.text` by a + // page moved `.user` to 0x200000, the grant loop indexed past `__pt0`, and + // the ring-3 program's first instruction took a page fault. Nothing said + // so; `rip == cr2` at 0x2000a5 was the whole diagnosis. The linker script + // now refuses the layout that does it, and there is 2 MiB of room before + // that refusal can happen again. + " mov edi, offset __pt0", + " mov eax, 0x03", // present | writable, no user + " mov ecx, 1024", + "5: mov [edi], eax", + " mov dword ptr [edi + 4], 0", + " add eax, 0x1000", + " add edi, 8", + " loop 5b", + " mov esi, offset __user_start", + " shr esi, 12", // first user page + " mov edx, offset __user_end", + " add edx, 0xfff", + " shr edx, 12", + " sub edx, esi", // how many + " jz 7f", // nothing to grant + " mov edi, offset __pt0", + " lea edi, [edi + esi*8]", + "6: or dword ptr [edi], 4", // user-accessible + " add edi, 8", + " dec edx", + " jnz 6b", + // Both tables replace their 2 MiB pages in the directory. The U bit here is + // the walk's conjunction again: without it the pages granted above stay + // kernel-only. + "7: mov eax, offset __pt0", + " or eax, 7", // present | writable | user + " mov edi, offset __pd", + " mov [edi], eax", + " mov dword ptr [edi + 4], 0", + " mov eax, offset __pt1", + " or eax, 7", + " mov [edi + 8], eax", + " mov dword ptr [edi + 12], 0", " mov eax, offset __pdpt", - " or eax, 3", + " or eax, 7", " mov edi, offset __pml4", " mov [edi], eax", " mov dword ptr [edi + 4], 0", @@ -133,14 +186,33 @@ global_asm!( " jmp 3b", ); -// A minimal GDT. Long mode ignores the base and limit of a code segment, but a -// descriptor still has to exist and say "64-bit code" (the L bit) — that is -// what the far jump above selects. +// The GDT that gets this code as far as long mode, and no further. +// +// Long mode ignores a code segment's base and limit, but a descriptor still has +// to exist and say "64-bit code" (the L bit) — that is what the far jump above +// selects. So this table cannot be avoided: entering long mode takes a `lgdt` +// and a far jump, both before any compiled code exists to do them. +// +// Three entries, and deliberately three. It once had six — the ring-3 pair and +// a TSS descriptor as well — and that was a table describing the machine's +// *policy*, written in the one file least able to say why. `program.lk` builds +// the table the machine actually runs on (`install_descriptor_table`), with +// whatever segments it has decided to have; this one only has to be enough to +// reach the code that does that. +// +// Which makes the ring-3 test a proof rather than a demonstration: there is no +// ring-3 descriptor anywhere in this image except the one the program writes at +// run time. A user task that runs at all is a user task running on the +// program's table. +// +// `.rodata` would do now that nothing is filled in at boot, and `.data` is kept +// only because a descriptor table is a thing the machine may yet want to write. global_asm!( - ".section .rodata, \"a\"", + ".section .data, \"aw\"", ".align 16", + ".global __gdt", "__gdt:", - " .quad 0", // null descriptor + " .quad 0", // 0x00: null descriptor " .quad 0x00AF9A000000FFFF", // 0x08: 64-bit code, ring 0 " .quad 0x00AF92000000FFFF", // 0x10: data, ring 0 "__gdt_descriptor:", diff --git a/bare-metal-x86/src/interrupts.rs b/bare-metal-x86/src/interrupts.rs index aa4a3066..9e5d34f9 100644 --- a/bare-metal-x86/src/interrupts.rs +++ b/bare-metal-x86/src/interrupts.rs @@ -1,314 +1,40 @@ -//! Interrupts: an IDT, the legacy 8259 PIC, and the trampoline that reaches -//! the LK handler. +//! What is left of interrupts on the board's side: the trampolines, and the +//! exception reporter. //! -//! The division of labour matches the aarch64 demo. The board decides *which -//! vector* an interrupt lands on and does the acknowledging; what a tick -//! *means* is the program's, and that part is LK. +//! Everything that is a *decision* has moved to LK. The table lives in +//! `drivers/idt.lk`, the vector map and the install order in `program.lk`, the +//! 8259 and its end-of-interrupt in `drivers/pic.lk`. What could not move is +//! here, and the line is sharp: an interrupt is not a call. The code it lands +//! in never agreed to lose its caller-saved registers, so a compiled handler +//! has to be entered through a stub that spills every one of them and leaves +//! with `iretq`. There is no language in which that is not assembly. +//! +//! The exception reporter stays for a different reason, and the one first given +//! for it was wrong. It was "formatting a report in LK would allocate", which +//! the clock task disproves — that one draws its digits by dividing, and +//! allocates nothing. +//! +//! The real reason is that **every dependency a fault reporter has is a way for +//! the report not to happen**. This one has two: a sixteen-byte buffer on its +//! own stack, and `out` to a port. It does not read the shared page, call +//! through a table, or touch an allocator — so a fault that damaged any of +//! those still gets reported. Moving it would trade that for making the list of +//! named vectors editable in LK, which is a small thing to want and a large +//! thing to pay for. +//! +//! The same argument says where the line is: if this ever needs to do something +//! a fault does not already guarantee is possible, it is doing too much. use core::arch::global_asm; -/// One IDT entry. The handler address is split across three fields because the -/// layout predates 64-bit addresses and was extended twice. -#[repr(C)] -#[derive(Clone, Copy, Default)] -struct Gate { - offset_low: u16, - selector: u16, - ist: u8, - type_attr: u8, - offset_mid: u16, - offset_high: u32, - reserved: u32, -} - -#[repr(C, packed)] -struct Descriptor { - limit: u16, - base: u64, -} - -/// 256 entries because the CPU indexes this table by vector number and will -/// read whatever is at the index it computes — a short table is a fault that -/// reads past the end. -static mut IDT: [Gate; 256] = [Gate { - offset_low: 0, - selector: 0, - ist: 0, - type_attr: 0, - offset_mid: 0, - offset_high: 0, - reserved: 0, -}; 256]; - -/// Where the PIC's IRQ0 is remapped to. 0-31 are reserved for CPU exceptions, -/// and the PIC's power-on default overlaps them — which is why every kernel -/// remaps it before enabling interrupts. -const PIT_VECTOR: usize = 0x20; - -/// The PS/2 keyboard is IRQ1, one past the timer. -const KEYBOARD_VECTOR: usize = 0x21; - -/// The PS/2 mouse is IRQ12, which is on the *slave* PIC — vector 0x20 + 8 + 4. -/// Its interrupts reach the CPU through the master's IRQ2 cascade line, so -/// unmasking IRQ12 alone does nothing: IRQ2 has to be unmasked as well, and -/// its end-of-interrupt has to be sent to both chips. -const MOUSE_VECTOR: usize = 0x2c; - -/// The 8259 pair's command and data ports. -const PIC1_CMD: u16 = 0x20; -const PIC1_DATA: u16 = 0x21; -const PIC2_CMD: u16 = 0xa0; -const PIC2_DATA: u16 = 0xa1; - -unsafe extern "C" { - /// The assembly trampolines. The timer's lives in `tasks`, because - /// returning on a *different* stack is what a task switch is. - fn __task_trampoline(); - fn __keyboard_trampoline(); - fn __mouse_trampoline(); - fn __yield_trampoline(); -} - -/// Fills in one gate. -/// -/// # Safety -/// -/// `handler` must be a function the CPU can enter with an interrupt frame on -/// the stack — one of the stubs in this file, not an ordinary Rust function. -unsafe fn set_gate(idt: *mut [Gate; 256], vector: usize, handler: u64) { - unsafe { - (*idt)[vector] = Gate { - offset_low: handler as u16, - // The 64-bit code selector the boot GDT defines. - selector: 0x08, - ist: 0, - // Present, ring 0, 64-bit interrupt gate. "Interrupt" rather than - // "trap" matters: it clears IF on entry, so the handler cannot be - // re-entered by the same interrupt before it acknowledges. - type_attr: 0x8e, - offset_mid: (handler >> 16) as u16, - offset_high: (handler >> 32) as u32, - reserved: 0, - }; - } -} - -/// Builds the IDT, remaps the PIC, unmasks the timer and enables interrupts. -pub fn init() { - let handler = __task_trampoline as *const () as usize as u64; - // SAFETY: single-threaded boot path; nothing else touches the IDT, and - // interrupts are still masked until the `sti` at the end. - unsafe { - let idt = &raw mut IDT; - // Vectors 0-31 are the CPU's own exceptions. Without gates for them a - // fault becomes a double fault becomes a triple fault, which on this - // machine is a silent reset loop — the failure mode that tells you - // nothing at all. One stub each, so the report can name the vector. - let stubs = &raw const ISR_STUBS as usize; - for vector in 0..32 { - set_gate(idt, vector, (stubs + vector * STUB_STRIDE) as u64); - } - set_gate(idt, PIT_VECTOR, handler); - set_gate( - idt, - KEYBOARD_VECTOR, - __keyboard_trampoline as *const () as usize as u64, - ); - set_gate(idt, MOUSE_VECTOR, __mouse_trampoline as *const () as usize as u64); - // The vector a task uses to ask for a reschedule. No device is behind - // it, so it can only arrive from an `int` instruction. - set_gate( - idt, - crate::tasks::YIELD_VECTOR, - __yield_trampoline as *const () as usize as u64, - ); - let descriptor = Descriptor { - limit: (core::mem::size_of_val(&*idt) - 1) as u16, - base: idt as u64, - }; - core::arch::asm!("lidt [{}]", in(reg) &descriptor, options(readonly, nostack, preserves_flags)); - - // Remap the PIC. The initialisation sequence is four writes per chip, - // in order, and the chip latches them as ICW1-ICW4. - crate::port_out_u8(PIC1_CMD, 0x11); // ICW1: begin init, expect ICW4 - crate::port_out_u8(PIC2_CMD, 0x11); - crate::port_out_u8(PIC1_DATA, PIT_VECTOR as u8); // ICW2: vector offsets - crate::port_out_u8(PIC2_DATA, PIT_VECTOR as u8 + 8); - crate::port_out_u8(PIC1_DATA, 0x04); // ICW3: slave on IRQ2 - crate::port_out_u8(PIC2_DATA, 0x02); - crate::port_out_u8(PIC1_DATA, 0x01); // ICW4: 8086 mode - crate::port_out_u8(PIC2_DATA, 0x01); - // Unmask the timer, the keyboard and the cascade; mask the rest. An - // unmasked line with no handler is a vector into a zeroed IDT entry, - // which is a triple fault. The cascade (IRQ2) is not a device — it is - // how the slave chip reaches the CPU at all, so the mouse's IRQ12 is - // invisible without it. - crate::port_out_u8(PIC1_DATA, 0xf8); - crate::port_out_u8(PIC2_DATA, 0xef); - - core::arch::asm!("sti", options(nomem, nostack)); - } -} - -/// Masks the timer and disables interrupts, in that order — disabling first -/// would leave a pending interrupt to be taken the moment anything unmasks. -pub fn stop() { - // SAFETY: fixed ISA ports and a flag instruction. - unsafe { - crate::port_out_u8(PIC1_DATA, 0xff); - core::arch::asm!("cli", options(nomem, nostack)); - } -} - -unsafe extern "C" { - /// The interrupt handlers, written in LK. `#[export("…")]` in `program.lk` - /// is what makes these names exist. - fn lk_timer_isr(); - fn lk_key_isr(); -} - -/// Called from the trampoline with every caller-saved register already spilled. -#[unsafe(no_mangle)] -pub extern "C" fn pit_dispatch() { - // SAFETY: the LK function `#[export]`ed under that name, compiled to a - // `void(void)` by the same build. - unsafe { lk_timer_isr() }; - // End-of-interrupt. Without it the PIC never delivers IRQ0 again. - // SAFETY: a fixed ISA port. - unsafe { crate::port_out_u8(PIC1_CMD, 0x20) }; -} - -/// As [`pit_dispatch`], for the mouse — with the slave chip's end-of-interrupt -/// as well as the master's. Acknowledging only the master leaves the slave -/// believing the interrupt is still in service, and it never raises another: -/// the mouse moves once and then stops, with nothing to say why. -#[unsafe(no_mangle)] -pub extern "C" fn mouse_dispatch() { - unsafe extern "C" { - fn lk_mouse(); - } - // SAFETY: the LK handler is an `#[export]`ed function with no arguments. - unsafe { lk_mouse() }; - // SAFETY: fixed ISA ports. - unsafe { - crate::port_out_u8(PIC2_CMD, 0x20); - crate::port_out_u8(PIC1_CMD, 0x20); - } -} - -/// As [`pit_dispatch`], for the keyboard. -/// -/// The scancode is deliberately *not* read here: the controller's data port is -/// the driver's business, and the driver is LK. What the board owes the device -/// is the acknowledgement. -#[unsafe(no_mangle)] -pub extern "C" fn keyboard_dispatch() { - // SAFETY: as `pit_dispatch`. - unsafe { lk_key_isr() }; - // SAFETY: a fixed ISA port. - unsafe { crate::port_out_u8(PIC1_CMD, 0x20) }; -} - -// An interrupt lands between any two instructions of the interrupted program, -// so every register the called code may clobber has to be saved: the System V -// caller-saved integer registers, and all sixteen XMM registers because LK -// numbers are `f64` and the interrupted computation may hold one. Missing a -// register corrupts a value rather than crashing, which is the hardest kind of -// bug to find. +// The keyboard's and the mouse's trampolines used to be here, one hand-written +// copy each of a spill/restore that is identical for every device interrupt. +// They are `lkrt`'s now — 256 stubs and a handler table, so a kernel points a +// gate at one and writes an address into the other. Adding a device stopped +// being an edit to this file. // -// The CPU aligns RSP to 16 bytes when it takes an interrupt in 64-bit mode. -// Nine 8-byte pushes leave it misaligned, so the `sub` below both reserves the -// XMM area and restores the alignment `call` expects. -global_asm!( - ".section .text, \"ax\"", - // The spill/restore is identical for every IRQ, so it lives in a macro - // rather than being copied per vector — a register missing from one copy - // corrupts a value only when that particular interrupt lands. - ".macro IRQ_SAVE", - " push rax", - " push rcx", - " push rdx", - " push rsi", - " push rdi", - " push r8", - " push r9", - " push r10", - " push r11", - " sub rsp, 264", - " movups [rsp + 0], xmm0", - " movups [rsp + 16], xmm1", - " movups [rsp + 32], xmm2", - " movups [rsp + 48], xmm3", - " movups [rsp + 64], xmm4", - " movups [rsp + 80], xmm5", - " movups [rsp + 96], xmm6", - " movups [rsp + 112], xmm7", - " movups [rsp + 128], xmm8", - " movups [rsp + 144], xmm9", - " movups [rsp + 160], xmm10", - " movups [rsp + 176], xmm11", - " movups [rsp + 192], xmm12", - " movups [rsp + 208], xmm13", - " movups [rsp + 224], xmm14", - " movups [rsp + 240], xmm15", - ".endm", - ".macro IRQ_RESTORE", - " movups xmm0, [rsp + 0]", - " movups xmm1, [rsp + 16]", - " movups xmm2, [rsp + 32]", - " movups xmm3, [rsp + 48]", - " movups xmm4, [rsp + 64]", - " movups xmm5, [rsp + 80]", - " movups xmm6, [rsp + 96]", - " movups xmm7, [rsp + 112]", - " movups xmm8, [rsp + 128]", - " movups xmm9, [rsp + 144]", - " movups xmm10, [rsp + 160]", - " movups xmm11, [rsp + 176]", - " movups xmm12, [rsp + 192]", - " movups xmm13, [rsp + 208]", - " movups xmm14, [rsp + 224]", - " movups xmm15, [rsp + 240]", - " add rsp, 264", - " pop r11", - " pop r10", - " pop r9", - " pop r8", - " pop rdi", - " pop rsi", - " pop rdx", - " pop rcx", - " pop rax", - ".endm", - ".global __mouse_trampoline", - "__mouse_trampoline:", - // The same full save as the keyboard's, and for the same reason: an - // interrupt is not a call. The code it lands in never agreed to lose its - // caller-saved registers, and the handler is compiled LK — it uses them, - // and the SSE ones. The first version of this did `call` and `iretq` with - // nothing in between, which corrupts whatever it interrupted at a moment - // nothing can predict. - " IRQ_SAVE", - " call mouse_dispatch", - " IRQ_RESTORE", - " iretq", - ".global __keyboard_trampoline", - "__keyboard_trampoline:", - " IRQ_SAVE", - " call keyboard_dispatch", - " IRQ_RESTORE", - " iretq", -); - -/// Each stub is padded to this, so their addresses are computable rather than -/// needing 32 labels. A stub is at most nine bytes: two `push imm8` and a -/// `jmp rel32`. -const STUB_STRIDE: usize = 16; - -unsafe extern "C" { - /// The first of the 32 exception stubs. - static ISR_STUBS: u8; -} +// What could not go is the timer's, next door in `tasks`: returning on a +// *different* stack is what a task switch is, and no shared tail can do that. /// Where every CPU exception ends up. /// @@ -389,6 +115,22 @@ global_asm!( " jmp __exception_common", " .set vec, vec + 1", ".endr", + // Past the *padding* of the last stub, not past its last instruction. + // + // The `.align 16` above is at the top of each iteration, so without this + // one the array ends nine bytes into its final slot and `end - start` is + // 505 rather than 512. The caller divides by 32 to get the stride, gets 15, + // and every stub after the first is entered at an address inside the one + // before it. That is what happened: a deliberate page fault reported itself + // as vector 2, with the faulting address in the error-code field. + ".align 16", + // One past the last stub. `program.lk` needs the stride to compute a + // stub's address, and this is what lets it *derive* one — `(end - start) + // / 32` — instead of naming 16 a second time. The `.align 16` above is + // what decides the stride, and a copy of it on the other side of the + // language boundary would be a number nothing checks. + ".global ISR_STUBS_END", + "ISR_STUBS_END:", "__exception_common:", // [rsp] = vector, +8 = error code, +16 = faulting RIP. " mov rdi, [rsp]", diff --git a/bare-metal-x86/src/main.rs b/bare-metal-x86/src/main.rs index d9422b5a..e78bd1e5 100644 --- a/bare-metal-x86/src/main.rs +++ b/bare-metal-x86/src/main.rs @@ -30,47 +30,49 @@ extern crate alloc; mod boot; mod interrupts; mod tasks; +mod user; +mod user_programs; use core::alloc::{GlobalAlloc, Layout}; use core::panic::PanicInfo; use core::ptr::addr_of_mut; use core::sync::atomic::{AtomicUsize, Ordering}; -/// The machine's memory, decided here and nowhere else. -/// -/// Written down because three things now want RAM and none of them can ask: the -/// kernel image, the Rust heap the interpreter allocates from, and the page -/// allocator the LK program hands out. A heap in `.bss` would have been simpler -/// until it grew — `.bss` follows the image, and at a few megabytes it reaches -/// up over the shared page at 0x300000, which is a fixed address the program -/// and the interrupt handlers agree on. Fixed regions cannot creep. -/// -/// | region | what | -/// | --- | --- | -/// | `0x00100000`.. | this image, and its `.bss` | -/// | `0x00300000`.. | the shared page (`SHARED_BASE` in `program.lk`) | -/// | `0x00380000`.. | 64 KiB staging for a source file read off the disk | -/// | `0x00400000`.. | the interpreter's heap, 28 MiB | -/// | `0x02000000`.. | the LK page allocator's arena | -/// The kernel's own arena, and the interpreter's. +// The machine's memory, decided in `link.ld` and read from here. +// +// Not a table in this comment any more. Three things want RAM and none of them +// can ask — the kernel image, the Rust heap the interpreter allocates from, +// and the page allocator the LK program hands out — so the map has to be +// written down somewhere, and the somewhere has to be a place *both* languages +// can read. A linker script is that place: Rust takes the address of an +// `extern static`, LK asks `symbol_address`, and there is one answer. +// +// It used to be a doc table here plus a literal in each language. `0x00380000` +// in particular was written twice and cross-checked at run time by +// `kernel_run` refusing any other address — which notices the drift rather +// than preventing it. +unsafe extern "C" { + static __heap_base: u8; + static __heap_size: u8; + static __run_heap_base: u8; + static __run_heap_size: u8; + static __source_base: u8; + static __source_max: u8; +} + +/// A linker symbol's value. It is an address, and an address is a number — the +/// size symbols are ones whose number happens to be a length. /// -/// Two regions rather than one, because they have different lifetimes and a -/// bump allocator cannot tell them apart otherwise. The kernel's LK code -/// allocates a little per command (a list of bytes to print) and keeps some of -/// it; a hosted program allocates an AST, a module registry and a whole VM -/// heap, and keeps *none* of it — the only thing that crosses back is an -/// `i64`. With one region, `run` would be a leak with a bound: about a dozen -/// invocations before 28 MiB was gone, and nothing to say why. +/// # Safety /// -/// So a run allocates from the second region, which is reset at the start of -/// each run. That is sound only because nothing allocated during a run -/// outlives it: output leaves through `lk_console_byte` as it is produced, and -/// the tasks that can preempt a run — the timer's scheduler and the spinner — -/// are the ones already forbidden to allocate. -const HEAP_BASE: usize = 0x0040_0000; -const HEAP_SIZE: usize = 4 * 1024 * 1024; -const RUN_HEAP_BASE: usize = HEAP_BASE + HEAP_SIZE; -const RUN_HEAP_SIZE: usize = 24 * 1024 * 1024; +/// Taking a symbol's address reads nothing, so this is safe for any of them. +macro_rules! linker_value { + ($name:ident) => { + // SAFETY: taking the address of a linker-placed symbol reads no memory. + unsafe { (&raw const $name) as usize } + }; +} + static OFFSET: AtomicUsize = AtomicUsize::new(0); static RUN_OFFSET: AtomicUsize = AtomicUsize::new(0); /// Set for the duration of a hosted run, so allocation goes to the run's arena. @@ -82,9 +84,9 @@ unsafe impl GlobalAlloc for Bump { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { let running = RUNNING.load(Ordering::Relaxed) != 0; let (base, size, offset) = if running { - (RUN_HEAP_BASE, RUN_HEAP_SIZE, &RUN_OFFSET) + (linker_value!(__run_heap_base), linker_value!(__run_heap_size), &RUN_OFFSET) } else { - (HEAP_BASE, HEAP_SIZE, &OFFSET) + (linker_value!(__heap_base), linker_value!(__heap_size), &OFFSET) }; let mut cur = offset.load(Ordering::Relaxed); loop { @@ -131,6 +133,14 @@ const COM1: u16 = 0x3f8; /// `program.lk` has its own copy of this — that one is the demo. This exists /// because `lkrt` needs a sink for the value a script evaluates to, and it /// cannot call back into LK. +/// +/// It does *not* configure the device, and no longer needs to. The board used +/// to bring COM1 up before `main()` because it enabled interrupts itself, and a +/// tick landing before the program's `uart_init()` would have transmitted +/// through an unconfigured UART. The program owns interrupts now and turns them +/// on long after its own first statement, which is `uart_init()`. A fault +/// earlier than that has no gate to land in either, so there is nothing left +/// for a second initialisation to protect. pub(crate) fn serial_write(text: &str) { for byte in text.bytes() { // SAFETY: COM1 is a fixed ISA port; `in`/`out` on it cannot touch @@ -143,24 +153,6 @@ pub(crate) fn serial_write(text: &str) { } } -/// Bring COM1 up before interrupts are enabled. -/// -/// `program.lk`'s driver configures it too, with the same values — this is not -/// a substitute for it. It is here because the timer handler transmits, and a -/// tick landing before `uart_init()` would write to an unconfigured device. -fn serial_init() { - // SAFETY: fixed ISA ports. The sequence matches `program.lk`'s. - unsafe { - port_out_u8(COM1 + 1, 0x00); // interrupts off; this driver polls - port_out_u8(COM1 + 3, 0x80); // DLAB: the divisor latch - port_out_u8(COM1, 0x03); // divisor 3 = 38400 baud - port_out_u8(COM1 + 1, 0x00); - port_out_u8(COM1 + 3, 0x03); // 8N1 - port_out_u8(COM1 + 2, 0xc7); // FIFOs on and cleared - port_out_u8(COM1 + 4, 0x0b); // DTR + RTS + OUT2 - } -} - /// # Safety /// /// The caller must know what device answers at `port`. @@ -193,6 +185,32 @@ pub(crate) fn write_hex(value: u64) { serial_write(unsafe { core::str::from_utf8_unchecked(&buf) }); } +/// A deliberate fault, so the exception path is exercised rather than merely +/// present. Without a build that takes it, a broken reporter looks exactly like +/// a working one — right up until the day something faults. +/// +/// Called from `program.lk`, immediately after it installs its interrupt table, +/// and that is not a detail: the table is the program's now, so before `main()` +/// there is no gate for anything. Faulting here used to be a report; faulting +/// there is a triple fault, which on this machine is a silent reset — the exact +/// failure this probe exists to make impossible. Moving the probe to the other +/// side of the install also makes it a stronger claim, because the table it +/// lands in is the one the program actually built. +/// +/// Empty without the feature, rather than absent: `program.lk` calls it either +/// way, and a call that does nothing costs less than two versions of the +/// program's boot sequence. +#[unsafe(no_mangle)] +pub extern "C" fn lk_fault_probe() { + #[cfg(feature = "fault-probe")] + // SAFETY: nothing about this is safe — that is the point. The address is + // 36 bits wide, far past the identity map, so the access cannot land on + // anything real. + unsafe { + core::ptr::write_volatile(0x9_0000_0000u64 as *mut u64, 1) + }; +} + /// Where the compiled code's result is left, so it cannot be optimised away and /// a debugger or test harness can read it. #[unsafe(no_mangle)] @@ -206,26 +224,34 @@ pub extern "C" fn kernel_main() -> ! { // calls into is simply absent. let _ = lkrt::link_anchor(); lkrt::set_output(serial_write); - serial_init(); // No task table to prepare any more: the program spawns what it wants by // address (`lk_spawn`), and until it does there is one task — this one. - // The handler transmits, and it can fire from here on — which is why the - // UART is already up. - interrupts::init(); + // The TSS before the IDT: a gate that can be raised from ring 3 needs a + // ring-0 stack to switch to, and the CPU reads that from the TSS. + // No `user::init()` either: the descriptor table and the task state + // segment are the program's now, built in `install_descriptor_table()` + // right after the interrupt table. The board's share of them is one static + // (`lk_boot_kernel_stack`), because a ring-0 stack has to exist before + // there is an allocator to ask for one. + // No `interrupts::init()` here any more. The interrupt table is the + // program's — `program.lk` builds its own gates and loads them — so the + // board cannot enable interrupts before it, and does not try. What the + // board still owns is `interrupts::stop()` below, because it runs after + // the program has returned and there is no program left to ask. + // + // The window this opens is real and was already there: between here and + // the program's `idt_install()` a fault has no gate, and a fault with no + // gate is a triple fault, which on this machine is a silent reset. It is + // the first thing `program.lk` does for exactly that reason. - // A deliberate fault, so the exception path is exercised rather than - // merely present. Without a build that takes it, a broken reporter looks - // exactly like a working one — right up to the day something faults. - #[cfg(feature = "fault-probe")] - unsafe { - core::ptr::write_volatile(0x9_0000_0000u64 as *mut u64, 1) - }; // SAFETY: `main` is the object emitted by `lk compile object:`, linked by // build.rs, and takes no arguments. let result = unsafe { main() }; - // Stop the clock before reporting: a tick landing mid-line would splice a - // '.' into it. - interrupts::stop(); + // The clock is already stopped, and by the program: masking the flag and + // then the chip is the last thing `program.lk` does. That is where it + // belongs now that the chip is the program's — and the board could not do + // it here without naming the PIC's ports a second time, for the sake of a + // line the program has already handled. unsafe { core::ptr::write_volatile(addr_of_mut!(LK_RESULT), result); } @@ -241,11 +267,6 @@ pub extern "C" fn kernel_main() -> ! { // ------------------------------------------------------------ the interpreter -/// Where a source file read off the disk is staged, and how much of one this -/// kernel will take. See the memory map above. -const SOURCE_BASE: usize = 0x0038_0000; -const SOURCE_MAX: usize = 64 * 1024; - unsafe extern "C" { /// The console, which belongs to the LK program: it owns the cursor, the /// window rectangles and the serial line. Rust holds the interpreter and @@ -274,7 +295,10 @@ fn console_write(text: &str) { /// failure and a runtime failure want different next steps. #[unsafe(no_mangle)] pub extern "C" fn kernel_run(address: i64, length: i64) -> i64 { - if address as usize != SOURCE_BASE || length < 0 || length as usize > SOURCE_MAX { + // The address is checked rather than trusted, and it is checked against the + // *same symbol* the program staged into — one answer, not two that agree. + if address as usize != linker_value!(__source_base) || length < 0 || length as usize > linker_value!(__source_max) + { return -1; } let bytes = unsafe { core::slice::from_raw_parts(address as *const u8, length as usize) }; @@ -317,6 +341,25 @@ fn run_program(source: &str) -> i64 { let mut ctx = VmContext::new().with_resolver(Arc::new(ModuleResolver::with_registry(registry))); match execute_program_with_ctx(&program, &mut ctx) { Ok(_) => 0, - Err(_) => -5, + Err(error) => { + // What it said, not just that it said no. + // + // The stage code alone is `-5`, which means "it ran and raised" and + // nothing more. That is enough to know the parser and the type + // checker were happy and useless for anything after: a program that + // used `try`/`catch` failed here for a whole round before anyone + // found out the bare host had no `error` global, because "it + // raised" reads the same whether the cause is the program or the + // host. + // + // Printed through the same console the program prints through, so + // the report lands where the output the reader was watching for + // would have. `{:#}` rather than `{}`: `anyhow` puts the cause + // chain behind the alternate flag, and the cause is the useful end. + console_write("run: "); + console_write(&alloc::format!("{error:#}")); + console_write("\n"); + -5 + } } } diff --git a/bare-metal-x86/src/tasks.rs b/bare-metal-x86/src/tasks.rs index 29a8ad84..e2604a93 100644 --- a/bare-metal-x86/src/tasks.rs +++ b/bare-metal-x86/src/tasks.rs @@ -7,9 +7,12 @@ //! on the interrupted task's stack, a switch is one instruction — point RSP at //! another task's stack and let the same restore sequence run. //! -//! What lives here is the mechanics: stacks, the frame a task starts life -//! with, and the register bookkeeping. *Which* task runs next is -//! `lk_schedule`, an `#[export]`ed LK function — policy is the program's. +//! What is left here is the one thing a language cannot say: *return on a +//! different stack*. Everything else has moved to `program.lk` and +//! `drivers/tasks.lk` — the table, the frame a task starts life on, which slot +//! runs next, whose address space, whose kernel stack. This file supplies the +//! register spill either side of that decision, and the software interrupt a +//! task uses to ask for it. //! //! A task may not allocate. `lkrt` has one arena and no locks around it, so two //! tasks in it at once would corrupt it; the same rule the interrupt handlers @@ -17,167 +20,26 @@ use core::arch::global_asm; -/// How many tasks the board can hold. A capacity, not a count: the stacks are -/// static because nothing here can grow a table while interrupts are reading -/// it, but which of them are in use is decided at run time by `lk_spawn`. -pub const TASK_CAPACITY: usize = 4; - -const STACK_SIZE: usize = 32 * 1024; - -/// A task stack. -/// -/// The alignment is not decoration: compiled LK code spills SSE registers with -/// `movaps`, which faults on a stack that is not 16-byte aligned. A plain -/// `[u8; N]` has alignment 1, and the fault it produces is a #GP inside the -/// task, nowhere near the array. -#[repr(align(16))] -struct Stack([u8; STACK_SIZE]); - -/// One stack per task past the first. Task 0 keeps the stack the boot path -/// gave it — it is the one already running when the first interrupt lands. -static mut TASK_STACKS: [Stack; TASK_CAPACITY - 1] = [const { Stack([0; STACK_SIZE]) }; TASK_CAPACITY - 1]; - -/// Each task's saved stack pointer, valid while it is *not* running. -static mut TASK_RSP: [u64; TASK_CAPACITY] = [0; TASK_CAPACITY]; - -/// How many slots are in use. One at boot: the task already running, whose -/// stack the boot path gave it. -static mut TASK_USED: usize = 1; - -/// Which entry of `TASK_RSP` belongs to the task currently on the CPU. -static mut CURRENT: usize = 0; - -unsafe extern "C" { - /// The scheduler, written in LK. The task *bodies* are no longer named - /// here: a program spawns them by address, so the board does not have to - /// know what they are called. - fn lk_schedule(current: i64) -> i64; -} - -/// Builds the stack a task starts life on. -/// -/// It is the exact picture the interrupt path leaves behind, because that is -/// what the restore sequence will read: fifteen saved registers, then the -/// frame the CPU itself pushes. Getting the order wrong here is not a compile -/// error — it is a jump to whatever the wrong slot held. -/// -/// # Safety -/// -/// `top` must be the high end of a writable, 16-byte-aligned stack that -/// nothing else uses. -unsafe fn prepare_stack(top: *mut u8, entry: u64) -> u64 { - // The CPU's frame, pushed high to low: SS, RSP, RFLAGS, CS, RIP. - let mut sp = top as u64; - let mut push = |value: u64| { - sp -= 8; - // SAFETY: within the caller's stack, which is ours to write. - unsafe { core::ptr::write_volatile(sp as *mut u64, value) }; - }; - push(0x10); // SS — the boot GDT's data selector - // One word below the top, so the task begins with the stack in the phase a - // function expects: the ABI assumes a `call` has just pushed a return - // address, and `iretq` pushes nothing. Without the offset the first - // aligned SSE spill in the task faults, inside whatever it called. - push(top as u64 - 8); // RSP the task resumes with - push(0x202); // RFLAGS: interrupts enabled, bit 1 always set - push(0x08); // CS — the boot GDT's 64-bit code selector - push(entry); // RIP - // The saved registers, in the order `IRQ_RESTORE` pops them — that is, - // the reverse of the order `IRQ_SAVE` pushes. - for _ in 0..15 { - push(0); - } - sp -} - -/// The vector a task uses to ask for a reschedule. -/// -/// Past the PIC's remapped range, so it can only arrive from an `int` -/// instruction — there is no device behind it, and nothing to acknowledge. -pub const YIELD_VECTOR: usize = 0x30; - -/// Gives up the rest of this task's slice. -/// -/// A software interrupt rather than a direct call: the switch has to happen -/// with a complete interrupt frame on the stack, because that is what the -/// resume path expects to find. `int` builds one; a call does not. -/// -/// This is what an `#[extern]` declaration in `program.lk` names — the LK -/// program asks the board for something the board alone can do. -#[unsafe(no_mangle)] -pub extern "C" fn kernel_yield() { - // SAFETY: the vector has a gate, installed before interrupts were enabled. - unsafe { core::arch::asm!("int 0x30", options(nomem, nostack)) }; -} - -/// Starts a task at `entry`, on a stack of its own. Returns its slot, or -1. -/// -/// The address comes from `symbol_address` on the LK side, which is what makes -/// this a *table* rather than a list the board has to know the names in: the -/// program decides what runs, the board only supplies stacks and the switch. -/// -/// The caller must have interrupts masked. The scheduler reads `TASK_USED` from -/// an interrupt, so publishing a slot before its stack is prepared would let a -/// timer tick resume a task that does not exist yet — which is a jump to zero. -/// -/// # Safety -/// -/// Called from LK with a code address; a value that is not one is a jump to -/// wherever it points. That is what `unsafe` in the LK source is claiming. -#[unsafe(no_mangle)] -pub extern "C" fn lk_spawn(entry: i64) -> i64 { - if entry == 0 { - return -1; - } - // SAFETY: the caller holds interrupts masked, so nothing else is reading - // or writing these while this runs. - unsafe { - let used = *(&raw const TASK_USED); - if used >= TASK_CAPACITY { - return -1; - } - let stacks = &raw mut TASK_STACKS; - let top = (*stacks)[used - 1].0.as_mut_ptr().add(STACK_SIZE); - let sp = prepare_stack(top, entry as u64); - let rsp = &raw mut TASK_RSP; - (*rsp)[used] = sp; - // Published last: the stack has to be complete before the scheduler - // can pick the slot. - *(&raw mut TASK_USED) = used + 1; - used as i64 - } -} - -/// Called from the trampoline with every register already on the interrupted -/// task's stack. Returns the stack to resume on. -/// -/// # Safety -/// -/// `rsp` must be the interrupted task's stack pointer, with a complete saved -/// frame at it. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn schedule_from_interrupt(rsp: u64) -> u64 { - // SAFETY: interrupts are masked inside an interrupt gate, so nothing else - // is touching these while this runs. - unsafe { - let current = *(&raw const CURRENT); - let table = &raw mut TASK_RSP; - (*table)[current] = rsp; - let next = lk_schedule(current as i64) as usize; - // Clamped against what is *spawned*, not against the capacity: a - // scheduler that names an empty slot would resume a stack that was - // never prepared. - let next = if next < *(&raw const TASK_USED) { next } else { current }; - *(&raw mut CURRENT) = next; - (*table)[next] - } -} // The timer's trampoline, extended to switch tasks. // // Every register is saved, not just the caller-saved ones: what is on this // stack has to be a *whole* task, because the stack this returns on may not be // the one it arrived on. +/// How many eight-byte words `SAVE_TASK` leaves on the stack. +/// +/// Fifteen integer registers and sixteen XMM registers of sixteen bytes each. +/// Asked of the board rather than counted again in LK, because the macro below +/// is the thing that decides it: a register added there has to appear in the +/// frame a task *starts* on too, and two numbers in two languages are two +/// places to add it. A frame short by one word is not an error anything +/// reports — it is a resume that reads its RIP out of whatever the next slot +/// held. +#[unsafe(no_mangle)] +pub extern "C" fn lk_task_saved_words() -> i64 { + 15 + 256 / 8 +} + global_asm!( ".section .text, \"ax\"", // A whole task's registers, not just the caller-saved ones: what is on @@ -198,8 +60,57 @@ global_asm!( " push r13", " push r14", " push r15", + // And the SSE registers, which this used to leave to whoever was + // interrupted. Every other interrupt path here saves them, for the reason + // written beside those: a compiled LK handler may clobber any XMM register + // under the System V ABI, LK numbers are `f64`, and the interrupted + // computation may hold one. This path calls two of them a thousand times a + // second — and it is also the one that switches tasks, so without this a + // task's SSE state is not part of what travels with it. + // + // 256 rather than the device path's 264, and the difference is the whole + // alignment question. A multiple of sixteen *preserves* whatever alignment + // the pushes above produced, and that alignment already works: this path + // calls compiled LK today. The device path adds the extra eight because its + // nine pushes leave it needing them; deriving either number from first + // principles is not required, and trying to was what made this look harder + // than it is. + " sub rsp, 256", + " movups [rsp + 0], xmm0", + " movups [rsp + 16], xmm1", + " movups [rsp + 32], xmm2", + " movups [rsp + 48], xmm3", + " movups [rsp + 64], xmm4", + " movups [rsp + 80], xmm5", + " movups [rsp + 96], xmm6", + " movups [rsp + 112], xmm7", + " movups [rsp + 128], xmm8", + " movups [rsp + 144], xmm9", + " movups [rsp + 160], xmm10", + " movups [rsp + 176], xmm11", + " movups [rsp + 192], xmm12", + " movups [rsp + 208], xmm13", + " movups [rsp + 224], xmm14", + " movups [rsp + 240], xmm15", ".endm", ".macro RESTORE_TASK", + " movups xmm0, [rsp + 0]", + " movups xmm1, [rsp + 16]", + " movups xmm2, [rsp + 32]", + " movups xmm3, [rsp + 48]", + " movups xmm4, [rsp + 64]", + " movups xmm5, [rsp + 80]", + " movups xmm6, [rsp + 96]", + " movups xmm7, [rsp + 112]", + " movups xmm8, [rsp + 128]", + " movups xmm9, [rsp + 144]", + " movups xmm10, [rsp + 160]", + " movups xmm11, [rsp + 176]", + " movups xmm12, [rsp + 192]", + " movups xmm13, [rsp + 208]", + " movups xmm14, [rsp + 224]", + " movups xmm15, [rsp + 240]", + " add rsp, 256", " pop r15", " pop r14", " pop r13", @@ -222,18 +133,21 @@ global_asm!( "__yield_trampoline:", " SAVE_TASK", " mov rdi, rsp", - " call schedule_from_interrupt", + " call lk_schedule_from_interrupt", " mov rsp, rax", " RESTORE_TASK", " iretq", ".global __task_trampoline", "__task_trampoline:", " SAVE_TASK", - // The handler's own work (the LK tick, the end-of-interrupt) happens - // before the switch, on the interrupted task's stack. - " call pit_dispatch", + // The handler's own work — the LK tick, and the end-of-interrupt it sends + // itself — happens before the switch, on the interrupted task's stack. + // Called directly rather than through a forwarding function on this side: + // there is nothing left for one to add now that acknowledging the chip is + // the driver's. + " call lk_timer_isr", " mov rdi, rsp", - " call schedule_from_interrupt", + " call lk_schedule_from_interrupt", " mov rsp, rax", " RESTORE_TASK", " iretq", diff --git a/bare-metal-x86/src/user.rs b/bare-metal-x86/src/user.rs new file mode 100644 index 00000000..bbb7900d --- /dev/null +++ b/bare-metal-x86/src/user.rs @@ -0,0 +1,182 @@ +//! Ring 3, and the one door back in. +//! +//! Everything else in this kernel runs at ring 0, where a wrong address is a +//! fault and a right one is whatever the hardware does. That is fine while all +//! the code is the kernel's own. It stops being fine the moment the kernel runs +//! a *program* — which it now does — because "the program cannot touch the +//! framebuffer" has so far been a matter of the program not trying. +//! +//! This makes it a property of the machine. A task entered through `iretq` with +//! ring-3 selectors cannot execute `in`/`out`, cannot write the kernel's pages, +//! and cannot reach any memory the page tables do not mark user-accessible. What +//! it *can* do is `int 0x80`, which is the whole interface: one vector, one +//! handler, and a number in a register saying what is wanted. +//! +//! Two things have to exist before that is possible, and both are the kind of +//! structure whose absence shows up as a triple fault rather than an error: +//! +//! - a **TSS**, because the CPU needs somewhere to put the stack pointer when an +//! interrupt arrives while ring 3 is running. Without `rsp0` it pushes the +//! interrupt frame onto the *user* stack, which the user can then rewrite. +//! - a **ring-3 code and data descriptor**, because privilege is a property of +//! the segment the CPU is executing from. + +use core::arch::global_asm; + +// The syscall vector is 0x80, spelled as an immediate in the ring-3 programs +// below and named `VECTOR_SYSCALL` in `program.lk`, which installs its gate — +// the only gate with DPL 3, which is what makes it the one vector ring 3 can +// raise and every other one a general protection fault. + +unsafe extern "C" { + /// The one-shot ring-3 program's stack, placed by the linker script. + static __user_shell_stack: u8; +} + +global_asm!( + ".global __syscall_trampoline", + "__syscall_trampoline:", + // The interrupt already switched to the ring-0 stack from the TSS. What is + // saved here is what the *caller* expects to keep: everything except the + // return value. + " push rcx", + " push rdx", + " push rsi", + " push r8", + " push r9", + " push r10", + " push r11", + // And the SSE registers, which this did not use to save. + // + // The handler is a compiled LK function now, and LK numbers are `f64`: the + // System V ABI lets it clobber every XMM register, and the ring-3 caller + // never agreed to that. Today's user programs are assembly that touches no + // XMM at all, so nothing would have gone wrong yet — which is the whole + // problem with leaving it out. The interrupt trampolines next door save + // these for exactly this reason; the syscall path had a Rust handler that + // did integer work, and now it does not. + // + // The CPU aligns RSP to 16 on the way in and seven pushes leave it eight + // off, so the 264 both reserves the area and restores the alignment `call` + // expects. + " sub rsp, 264", + " movups [rsp + 0], xmm0", + " movups [rsp + 16], xmm1", + " movups [rsp + 32], xmm2", + " movups [rsp + 48], xmm3", + " movups [rsp + 64], xmm4", + " movups [rsp + 80], xmm5", + " movups [rsp + 96], xmm6", + " movups [rsp + 112], xmm7", + " movups [rsp + 128], xmm8", + " movups [rsp + 144], xmm9", + " movups [rsp + 160], xmm10", + " movups [rsp + 176], xmm11", + " movups [rsp + 192], xmm12", + " movups [rsp + 208], xmm13", + " movups [rsp + 224], xmm14", + " movups [rsp + 240], xmm15", + // The ABI: number in `rax`, arguments in `rdi` and `rsi`, which the call + // wants in `rdi`, `rsi` and `rdx`. One shuffle, in one place. + " mov rdx, rsi", + " mov rsi, rdi", + " mov rdi, rax", + // The dispatcher is `program.lk`'s: what a user task may ask for is a list + // of holes in the wall the ring boundary just built, and deciding what is + // on that list is not the board's business. + " call lk_syscall_dispatch", + " movups xmm0, [rsp + 0]", + " movups xmm1, [rsp + 16]", + " movups xmm2, [rsp + 32]", + " movups xmm3, [rsp + 48]", + " movups xmm4, [rsp + 64]", + " movups xmm5, [rsp + 80]", + " movups xmm6, [rsp + 96]", + " movups xmm7, [rsp + 112]", + " movups xmm8, [rsp + 128]", + " movups xmm9, [rsp + 144]", + " movups xmm10, [rsp + 160]", + " movups xmm11, [rsp + 176]", + " movups xmm12, [rsp + 192]", + " movups xmm13, [rsp + 208]", + " movups xmm14, [rsp + 224]", + " movups xmm15, [rsp + 240]", + " add rsp, 264", + " pop r11", + " pop r10", + " pop r9", + " pop r8", + " pop rsi", + " pop rdx", + " pop rcx", + " iretq", +); + +/// Enters ring 3 at `entry`, on `stack`, and does not come back. +/// +/// `iretq` is the only way in: there is no instruction that lowers privilege +/// directly, so the kernel builds the frame an interrupt *would* have left +/// behind — as if ring 3 had been interrupted and is now being resumed — and +/// returns from an interrupt that never happened. +/// +/// # Safety +/// +/// `entry` must be code the user segment can reach and `stack` a mapped, +/// writable, 16-byte-aligned stack. Both are user-visible from here on. +pub unsafe fn enter(entry: u64, stack: u64, code: u64, data: u64) -> ! { + // SAFETY: the caller's claim, plus a frame this function builds itself. + unsafe { + core::arch::asm!( + // The data selectors first: they are loaded by `iretq` for SS, but + // the others are the kernel's until something sets them. + "mov ds, {data:x}", + "mov es, {data:x}", + "mov fs, {data:x}", + "mov gs, {data:x}", + // The frame `iretq` pops, pushed high to low: SS, RSP, RFLAGS, CS, RIP. + "push {data}", + "push {stack}", + // Interrupts enabled (bit 9) and bit 1, which is always set. A ring-3 + // task with interrupts masked would own the machine. + "push 0x202", + "push {code}", + "push {entry}", + "iretq", + data = in(reg) data, + stack = in(reg) stack, + code = in(reg) code, + entry = in(reg) entry, + options(noreturn), + ) + } +} + + +unsafe extern "C" { + /// The ring-3 program's first instruction. + pub fn __user_program(); + /// The two preemptible ring-3 tasks'. + pub fn __user_task_a(); + pub fn __user_task_b(); +} + +/// Runs the ring-3 program, and does not come back. +/// +/// Called from LK through `#[extern]`. There is no return: the only ways out of +/// ring 3 here are a syscall (which returns *into* ring 3) and a fault, and the +/// fault reporter halts. Making that explicit in the signature is what stops a +/// caller from writing code after it that would never run. +/// +/// A second task would be the way to make this survivable — enter ring 3 on its +/// own stack, and let the timer take the CPU back. That needs the scheduler to +/// know about privilege, which it does not yet. +#[unsafe(no_mangle)] +pub extern "C" fn lk_enter_user(entry: i64, stack: i64, code: i64, data: i64) -> ! { + // Every number comes from the program, and that is the point: what runs, + // on which stack, and through which two descriptors. This side contributes + // the one thing the program cannot say — `iretq`, which is the only way + // into ring 3, because no instruction lowers privilege directly. + // + // SAFETY: the caller's claim, made by writing `unsafe` in the LK source. + unsafe { enter(entry as u64, stack as u64, code as u64, data as u64) } +} diff --git a/bare-metal-x86/src/user_programs.rs b/bare-metal-x86/src/user_programs.rs new file mode 100644 index 00000000..208d3798 --- /dev/null +++ b/bare-metal-x86/src/user_programs.rs @@ -0,0 +1,121 @@ +//! The ring-3 programs, which are what the ring boundary is *for*. +//! +//! Not part of the mechanism. `user.rs` has the TSS descriptor's loading, the +//! syscall trampoline, and `iretq` — the things that make ring 3 reachable at +//! all. These are the programs that get run there, and they exist to be +//! *checked*: each one does something the boundary has to allow and then +//! something it has to refuse, so that a boundary which quietly permitted +//! everything would fail rather than look identical to one that works. +//! +//! They are assembly, and they have to be. A ring-3 program must not touch +//! anything a compiler might reasonably reach for — no spills into kernel +//! pages, no call into `lkrt`, no globals — and the only thing it may ask the +//! machine for is `int 0x80`. LK cannot express that: `int` takes its vector as +//! an immediate, so there is no operand to pass one through, and compiled LK +//! reaches for the runtime constantly. This is the one place in this kernel +//! where "written in assembly" is a statement about the *program*, not about +//! the machine. + +use core::arch::global_asm; + +// A program that runs in ring 3. +// +// Written in assembly rather than Rust for one reason: it must not touch +// anything the compiler might reasonably reach for — no stack spills into +// kernel pages, no calls into `lkrt`, no globals. What it does is the whole +// point of the exercise: print through the syscall, then *try* to write the +// framebuffer directly, which is the instruction the ring boundary has to +// stop. +global_asm!( + ".section .user, \"ax\"", + ".global __user_program", + "__user_program:", + // "USER" through the syscall, one byte per call. + " mov rax, 1", + " mov rdi, 85", // 'U' + " int 0x80", + " mov rax, 1", + " mov rdi, 83", // 'S' + " int 0x80", + " mov rax, 1", + " mov rdi, 69", // 'E' + " int 0x80", + " mov rax, 1", + " mov rdi, 82", // 'R' + " int 0x80", + // A string, through the call that takes a pointer. The kernel checks the + // range before following it — this one is inside the user section, so it + // prints. + " mov rax, 3", + " lea rdi, [rip + __user_message]", + " mov rsi, 3", + " int 0x80", + // And the same call with a *kernel* pointer. The kernel must answer with an + // error rather than printing its own memory: a pointer from ring 3 is a + // number, and believing it is how a kernel reads out its own secrets on + // request. The reply lands in `rax`, which the program then hands back + // through the byte-at-a-time call so the check can see it: 'N' for + // refused, 'Y' for followed. + " mov rax, 3", + " mov rdi, 0x100010", + " mov rsi, 8", + " int 0x80", + " cmp rax, -1", + " je 5f", + " mov rdi, 89", // 'Y' — the kernel followed it + " jmp 6f", + "5: mov rdi, 78", // 'N' — refused + "6: mov rax, 1", + " int 0x80", + // Say the excursion finished *before* trying anything forbidden, so the + // check can tell "ring 3 ran" from "ring 3 was stopped". + " mov rax, 2", + " int 0x80", + // And now the forbidden thing — a *read* of the kernel's own code, at an + // address inside the same 2 MiB as this program. + // + // Deliberately a read, and deliberately near: while the first 2 MiB was one + // user-accessible page, this succeeded and told nobody. With 4 KiB pages it + // faults, which is the difference between "ring 3 cannot reach the shared + // page two megabytes away" and "ring 3 cannot reach the kernel". + " mov rax, 0x100010", + " mov rax, qword ptr [rax]", + // Unreachable: the fault above does not return. + "2: jmp 2b", + "__user_message:", + " .ascii \"str\"", +); + +// A ring-3 task that never yields. +// +// It prints a `3` through the syscall, spins for a while, and does it again, +// for ever. Nothing in it cooperates: if the shell keeps answering while this +// runs, the timer took the CPU away from ring 3 and gave it back — which is +// the claim. The earlier `user` command could not show that, because it had +// no way back at all. +global_asm!( + ".section .user, \"ax\"", + // Two tasks, one body, one difference: the letter each writes into its own + // stack. Both stacks are at the same *virtual* address, so if they shared + // an address space the second write would land on the first's and both + // would print the same letter for ever after. They print A and B. + ".global __user_task_a", + "__user_task_a:", + " mov rbx, 65", // 'A' + " jmp __user_task_body", + ".global __user_task_b", + "__user_task_b:", + " mov rbx, 66", // 'B' + "__user_task_body:", + // Write it into this task's own stack page, then read it back from there + // every time round: a task that prints its letter is one whose memory + // still says what it wrote. + " mov [rsp - 16], bl", + "3: mov rax, 1", + " movzx rdi, byte ptr [rsp - 16]", + " int 0x80", + " mov rcx, 40000000", + "4: dec rcx", + " jnz 4b", + " jmp 3b", +); diff --git a/bare-metal/Cargo.lock b/bare-metal/Cargo.lock index 856203c7..adec2e77 100644 --- a/bare-metal/Cargo.lock +++ b/bare-metal/Cargo.lock @@ -246,7 +246,6 @@ dependencies = [ "lk-stdlib-hash", "lk-stdlib-iter", "lk-stdlib-math", - "lk-stdlib-slice", "lk-stdlib-string", ] @@ -322,15 +321,6 @@ dependencies = [ "lk-stdlib-common", ] -[[package]] -name = "lk-stdlib-slice" -version = "0.1.3" -dependencies = [ - "anyhow", - "lk-core", - "lk-stdlib-common", -] - [[package]] name = "lk-stdlib-string" version = "0.1.3" diff --git a/bare-metal/Cargo.toml b/bare-metal/Cargo.toml index 7f955931..55e6d315 100644 --- a/bare-metal/Cargo.toml +++ b/bare-metal/Cargo.toml @@ -17,7 +17,7 @@ bench = false [dependencies] lk-core = { path = "../core", default-features = false } -lk-stdlib-bare = { path = "../stdlib/bare", default-features = false, features = ["math","string","bytes","iter","slice","hash","encoding"] } +lk-stdlib-bare = { path = "../stdlib/bare", default-features = false, features = ["math","string","bytes","iter","hash","encoding"] } cortex-m = "0.7" cortex-m-rt = "0.7" cortex-m-semihosting = "0.5" diff --git a/bare-metal/uart.lk b/bare-metal/uart.lk index 8c51f401..ce54152b 100644 --- a/bare-metal/uart.lk +++ b/bare-metal/uart.lk @@ -38,7 +38,11 @@ fn uart_putc(byte: Int) { let full = 1; while (full != 0) { let state = unsafe { volatile_read_u32((UART0_BASE + REG_STATE) as *mut u32) }; - full = state & STATE_TX_FULL; + // `as Int`: the mask is a `u32` because the register is, and `full` is + // the loop's own Int flag. Stating the narrowing is what `lk check` + // asks for — the embedded path never ran that check, so this file + // disagreed with the checker for as long as it existed. + full = (state & STATE_TX_FULL) as Int; } unsafe { volatile_write_u32((UART0_BASE + REG_DATA) as *mut u32, byte as u32); }; } diff --git a/bench/README.md b/bench/README.md index 32eb0f7d..cd8c919e 100644 --- a/bench/README.md +++ b/bench/README.md @@ -714,3 +714,456 @@ lookups, `histogram_group_count` about `3.5K`, `log_parse_filter` about `2.2K`, and `inventory_reorder` about `2.8K`. The next optimization target should move to general loop materialization, arithmetic temporaries, `Move` elimination, and branch lowering. + +## AOT 的数字不能说谎(2026-07-30) + +`RUN_AOT=1` 那条路以前用一句普通的 `lk compile` 编译语料。普通的 `compile` 对 +Cranelift 降不下来的形状**会回落**到 VM bundle,于是那一趟仍然产出一个二进制、仍然 +被报成 "AOT",而实际跑的是解释器 —— 一个错的数字,而且没有任何东西说一句话。 + +现在它带 `LK_AOT_HYBRID=0 LK_AOT_NO_FALLBACK=1`:降不下来就编译失败,报告说 +"a shape stopped lowering natively",AOT 那一列直接缺席。 + +同一份语料也进了 `scripts/aot_coverage.sh` 的扫描 —— 两半各管一头:门禁保证它**一直** +能全原生降低,严格编译保证即使有人跳过门禁,**测量本身**也不会说谎。 + +顺带:报告里的 "AOT: … (unknown)" 是残留。它在从编译日志里刮一行 "backend X,",而那 +行随着字符串 IR 的 `llvm` 后端一起退休了。现在只有一个原生后端,直接写出来。 + +## GC 的触发阈值按存活集缩放(2026-08-20) + +`perf` 打在 `workloads_business_algorithms.lk` 上,`HeapStore::collect` 占 5.9%, +加上 `Arc::drop_slow` / `cfree` / `drop_glue` 共约 12% 在回收与释放上。 + +第一个假设是"收得太勤",于是把阈值调到 100 万 —— **反而更慢**(0.32s → 0.34s)。 +不收的话堆一直长,空闲槽用不上,分配的局部性变差。也就是说 GC 在这个负载上 +**是赚的**,5.9% 换回来的比花掉的多。这条负面结果值得记着。 + +真正的问题是**渐近**的,不是频率:标记清扫每次都是 O(存活),而阈值是固定的 1024 次分配, +所以总开销是 O(分配数 x 存活集)。存活集越大,回收占的比例越高。 + +改成 `should_collect() = alloc_since_gc >= max(gc_threshold, live_len / 2)`: +堆比上次回收后长大一半就再收一次,于是每次分配摊到的回收开销是常数。 +`gc_threshold` 变成**下限**,所以小堆上的行为不变(测试里设成 1 仍然是"每次都收")。 + +除数是量出来的,不是猜的。1 / 2 / 4 / 8 各跑一遍: + +| 除数 | 大存活集负载 | 门禁 geomean | +| --- | --- | --- | +| live/1 | 快 | 1.059x | +| live/2 | 快 | 1.013x | +| live/4 | 快 | 1.013x | +| live/8 | 快 | 1.015x | + +`live/1` 等太久,堆长得太开,门禁上掉 4%;`live/2` 两头都拿到。 + +效果:30 万存活对象 + 80 万次分配的程序 **2.1s → 0.09s(24x)**; +门禁 geomean 1.015x → 1.019x(同一轮里基线也在 1.013–1.019 之间波动,没有回归)。 + +护栏是 `collections_do_not_multiply_with_the_live_set`:**数回收次数**而不是计时, +所以在任何机器上说的都是同一句话 —— 存活集大十倍,每次分配摊到的回收不该也大十倍。 +把那一行改回固定阈值,它就红(20 对 20)。 + +### 尺度是**槽表**,不是存活集(同日续) + +上面那版按存活集缩放,还剩一个形状是二次的:分配一大批、放掉、再做很多小分配。 +`slots` 只增不减,所以每次回收还是走峰值那么多格,而存活集近似为零 —— +按存活集定间隔就等于"堆是空的,尽管收",每次却仍然走四十万格。 + +两处一起改: + +- `sweep` 结束时把**空的尾部**还回去(`release_dead_tail`)。只能切尾部:`HeapRef` 就是下标, + 搬动一个存活的格子需要重写每一处引用,而那份清单不存在;切掉空的末尾不搬任何东西。 + `generations` **不跟着切** —— 那个向量正是用来分辨"重用的格子"和"它替掉的那个"的, + 切了就会从零重新数,让一对陈旧的 `(下标, 代)` 匹配上另一个对象。 + 代价是堆曾经到过的每一格留 8 字节。 +- 间隔改成按 `slots.len() / 2` —— 清扫的开销就是槽表,所以按槽表定间隔才是常数摊还。 + 光切尾部不够:在那批之后分配的**一个**存活对象就能把整张表钉住。 + +| | churn 40 万 | +| --- | --- | +| 固定阈值 | 0.61s | +| 按存活集 | 0.45s | +| 按槽表 + 切尾部 | **0.07s** | + +门禁 geomean 也从 1.015–1.019 降到 **1.009x**。 + +第二条护栏 `a_released_burst_gives_its_slots_back_without_reusing_a_generation` +把两半写在一个测试里,因为第二半是第一半的代价:尾部被切了,表就会长回用过的下标, +而 `generations` 是唯一分得清的东西。 + +`set_gc_threshold` 明确设过的阈值**不参与缩放**(`pinned_threshold`): +按名字要一个数,要的是一个策略,不是别人策略下面的一条下限。 +嵌入方设成 1 就是每次分配都收 —— 缩放只作用于默认值。 + +顺带删掉 `collect` 开头那趟"把所有标记刷白"的循环:它扫的是堆**曾经**持有过的每一格, +写的是每一格**已经**是的值。不变量由三处共同维持 —— `sweep` 把存活的 `BLACK` 翻回来, +被清掉的那格从来没被标记过,`alloc` 两条路径都写 `WHITE`。改成一条 `debug_assert`, +说出这个不变量而不是每次重新建立它。 + +## 循环里拼字符串:二次是共有的,常数不是(2026-08-20) + +`acc = acc + "x"` 跑 n 次,LK 与 Lua 都是 O(n²)(不可变字符串的必然结果, +两边的答案都是"用 `join`")。但**常数**差很多: + +| n | Lua | LK(改前) | LK(改后) | +| --- | --- | --- | --- | +| 2 万 | 0.00s | 0.08s | 0.07s | +| 4 万 | 0.02s | 0.28s | 0.28s | +| 8 万 | 0.12s | 1.18s | 1.10s | + +`dynamic_add` 的字符串分支每一步把累加器**复制三遍**:两侧各一次 +`display_string`,再一次进 `format!`,再一次进 `Arc`;外加守卫那句 +"这是字符串吗"为短字符串建了一个随即丢掉的 `Arc`。现在是一个按精确大小开的缓冲, +填一次。 + +**但量出来只有 1.03–1.12x**(交错 min-of-9)。原因是这个循环约 70% 的时间在 libc: +主导的是每步一次的**分配**,不是复制,所以三份复制减到一份并不像数量暗示的那么值钱。 +这条负面结果值得留着 —— 推理说的比测量多。 + +要把它变成线性,得换一个可增长的表示。`HeapValue::String(Arc)` 在 core 里被 +匹配 125 处,lkrt 那边还有一份镜像要跟着改,而且两边必须逐字节一致 —— +那是单独一轮的活,不是这里一行。 + +### 再量一次(2026-08-20):两边都是二次的,差的是常数 + +上面那条写着"约 70% 在 libc,主导的是分配"。用 `perf` 按符号重新量了一次, +分布不是这样: + +| 项 | 占比 | +| --- | --- | +| libc(单个地址,workload 只做拷贝,是 `memcpy`) | 78.8% | +| libc(另外三个地址,malloc/free 段) | 6.7% | +| `concat_string_operands` | 1.6% | +| `dispatch_within_frame` | 1.3% | +| 其余 VM | < 5% | + +主导的是**复制**,不是分配。 + +同时和 Lua 对了一遍(`acc = acc .. "x"` 循环,同一台机器): + +| n | LK | Lua | 比 | +| --- | --- | --- | --- | +| 8 千 | 18ms | 4ms | 4.5x | +| 1.6 万 | 58ms | 7ms | 8.3x | +| 3.2 万 | 200ms | 20ms | 10x | +| 6.4 万 | 846ms | 81ms | 10.4x | + +**Lua 的 `..` 在循环里同样是二次的**(20→81ms,2 倍输入 4 倍时间)。所以这不是 +"LK 有个渐进复杂度问题而 Lua 没有",而是同一个算法上约 10 倍的常数差。 + +### 那个常数在哪:不是复制,是缺页 + +先把上面那句"复制主导"也订正掉 —— 那是按 `perf` 的符号占比推的,而符号没解析出来。 +逐条量下来,复制、分配次数、GC 时机**三个都不是**: + +| 实验 | 结果 | 说明 | +| --- | --- | --- | +| 空循环 6.4 万次 | 5ms | 循环本身不要钱 | +| 同样次数、**定长**字符串拼接 | 12ms | 每次分配 + 拼接也不要钱 | +| 累加器增长 | 845ms | 差的全在这里,且与长度成正比 | +| 同一算法的 Rust 版(两次复制) | 58ms | 字节量完全一样 | + +关键那条: + +| | LK | 等价 Rust | +| --- | --- | --- | +| 缺页 | **533,982** | **148** | + +533,982 × 4KB ≈ 2.1GB,正好是这个循环复制的总字节数 —— 每一次 `memcpy` 都写在 +刚缺页的新页上。Rust 版同样的算法、同样的字节量,只有 148 次缺页:它的分配器 +把刚释放的块拿回来复用了。 + +试过并**都没用**(实测,不是推理): + +- 把 `ConcatString` 的一般分支改走 `dynamic_add` 那条单缓冲路径(每步三次复制减到两次)。 + 交错 min-of-3 三轮:改前 {671, 663, 662}ms,改后 {668, 665, 665}ms —— **没有差别**。 + 改动已撤回:量出来是中性的东西不该留在树上。 +- 给 GC 加按**字节**计的阈值(现在只按分配**次数**,1024 次可以攒下几十 MB 垃圾)。 + 阈值确实频繁触发,缺页数**一个没少**(533,994 vs 533,982)。撤回。 +- `Arc<[u8]>: FromIterator` 走 `TrustedLen` 一次分配、原地写(把两次复制减到一次)。 + 实测**慢 9–30 倍**:逐字节写打不过 `memcpy`。 + +**原生后端一样**(2026-08-20 补测):6.4 万步 895ms,500,966 次缺页 —— 和 VM 同一个 +形状。lkrt 的字符串是 arena 里的 C 串,和 `Arc` 是两份独立的表示,所以 +"换成可增长表示"这件事要**两边都做**,而且两边的输出必须逐字节一致。原来记的 +"125 处 + lkrt 镜像"是低估:那是 VM 一侧的数,原生一侧另算。 + +所以问题收窄成一句:**每步都向分配器要一块比上一块大一点的内存,拿不回已释放的那块**。 +这不是复制次数、不是分配次数、不是 GC 时机能解决的,只能靠"就地增长"—— +也就是那个可增长表示。这条负面结果把范围钉死了,值得留着。 + +### 原来那段(每步两次分配、两次复制) + +```rust +let mut joined = String::with_capacity(left.len() + right.len()); // 分配 1 +joined.push_str(left); joined.push_str(right); // 复制 1 +Some(Arc::from(joined)) // 分配 2 + 复制 2 +``` + +第二次复制是 `Arc` 的**结构性**开销:`Arc` 是"引用计数头 + 数据"的单块 +分配,std 没有安全的办法从两个来源就地构造它。`Arc<[u8]>: FromIterator` 走 +`TrustedLen` 可以只分配一次并原地写,但那是逐字节写,对大缓冲多半比 `memcpy` 慢 —— +这条**没有量过,别按推理下结论**(上一条负面结果就是这么来的)。 + +所以判断不变,理由更硬了:便宜的赢面不在这里。要动就是换表示,那仍然是 +`HeapValue::String(Arc)` 的 125 处 + lkrt 镜像,单独一轮。 + +顺带一条给写 LK 的人:`join` 是线性的,`acc = acc + x` 的循环不是 —— 两个后端都不是。 + +## 最慢的那个 workload profile 过了,没有可修的东西(2026-08-20) + +`fraud_rule_scoring` 是列表里最落后的一个(约 1.9x Lua)。把它的热循环单独抽出来 +跑 170 万次(327ms),`perf -F 999`: + +| 项 | 占比 | +| --- | --- | +| `dispatch_within_frame` | 47.3% | +| `dispatch_call_method_k` | 8.2% | +| `load_const_instr` | 7.3% | +| `write_returns` + `finish_return` + `push_call_frame` | 11.4% | +| `core_call_method_windowed` | 3.2% | +| `from_utf8`(ShortStr 读) | 2.8% | +| map 查找(`IndexMap, bool, Fx>`) | 4.4% | + +**没有分配问题**:170 万次迭代只有 1005 次缺页,libc 合计约 4.5%。map 用的是 +`FxBuildHasher`(profile 里那点 SipHash 是编译期的噪声)。 + +剩下的全是结构性的:解释器分发加调用开销。两条看着可疑的线索都已经被仓库 +自己量过并否掉了 —— `from_utf8` 换 `from_utf8_unchecked` 见 +`values/src/types.rs` 的注释(min-of-9:0.87s vs 0.89s,买不到东西)。 + +结论:这个 workload 上没有具体缺陷,1.9x 是"LK 的解释器比 Lua 的慢 1.9x"。 +geomean 已经在 1.01x,尾巴上的这几个不值得再挖。**别重复这次 profile。** + + +## 常量容器每次加载都被深拷贝两遍(2026-08-21) + +`config_defaults_merge` 是列表里第二落后的(1.91x Lua)。它的循环体是 +`let config = {};` 加四次查找,900 万次跑 2.69s。`perf -F 499`: + +| 项 | 占比 | +| --- | --- | +| `dispatch_within_frame` | 39.2% | +| `ConstHeapValue::clone` + `materialize_heap_const` + `load_heap_const` | 7.5% | +| `TypedMap` 的 drop_glue + `Arc::drop_slow` | 8.9% | +| `IndexMap::from_iter`(1 个元素) | 4.0% | +| `HeapStore::alloc` | 3.1% | +| `IndexMap::get_index_of` + `insert_full` | 5.7% | +| malloc/free | 3.3% | + +`LoadHeapConst` 把常量**按值**取:先 `clone()` 整个 `ConstHeapValue`(每个键、 +每个嵌套常量),再走一遍克隆体去建真正的值——一个常量容器每次加载付两趟深拷贝。 +改成按引用物化(常量留在函数常量池里,只读不消耗),再给**空** `{}` / `[]` 加一条 +直达:空的没什么可走,通用路径还要建一个空 `ValueMap`、下钻一层、跑一次立刻放弃的 +形状扫描。 + +min-of-9:2.69s → 2.53s(按引用)→ 2.47s(加空容器直达),**8%**。 +`config_defaults_merge` 的比值 1.91x → 1.73x,geomean 1.008x → 1.000x。 + +**第二趟(同日)**:上面记为"量过但没做"的那条做了。常量池从 `Vec` 改成 +`Vec>`,`known_string_key` 从 `Option<&str>` 改成 `Option<&Arc>`, +往有类型字符串 map 里插入走一个 `KeyText` —— 借来的文本(寄存器里的键)还是分配, +池里的常量直接 `Arc::clone`。一个参数而不是"`&str` 加一个可选 `Arc`",因为那两个 +必须一致而没人会检查。 + +min-of-9:2.47s → **2.24s**。两趟合起来 2.69s → 2.24s,**17%**。 +`config_defaults_merge` 的比值 1.91x → 1.62x。 + +**第三趟(同日),结构体构造**:上面两趟都在 map 上,结构体构造是另一条路 +(`NewObject`),而它的字段名每次构造都要新分配。300 万次构造一个三字段结构体, +`perf -F 499`: + +| 项 | 占比 | +| --- | --- | +| `dispatch_new_object` | 17.9% | +| `get_heap_index_slow_path`(读 `p.x`) | 11.5% | +| `HeapValue` drop_glue + `Arc::drop_slow` | 18.9% | +| `runtime_value_to_plain_string_maybe` | 7.6% | +| `insert_full` | 5.4% | + +两处: + +1. 字段名走的是通用转换 —— 把值渲染成一个新 `String`,再拷进 `Arc`,**每个字段 + 每次构造两次分配**,而字段名本来就是字符串(堆上的那种本身就是 `Arc`)。 +2. 分配掉的那一次也不必有:**声明**里就存着字段名的 `Arc` + (`DeclaredType::fields`),让实例直接用它,整个程序一个字段名一次分配。 + +min-of-9:0.96s → 0.87s(去掉双重分配)→ **0.68s**(共用声明里的 `Arc`), +合计 **29%**。 + +再看一眼 `lk coverage --runtime` 的索引计数,发现第三处: +`index_keys=known_string_key:600000, slow_path:600000` —— 结构体的**每一次** +字段读都走冷路径。`get_heap_index` 的快路径有 Map / List / String 三支,唯独没有 +Object,所以 `p.x` 一律掉进 `#[cold]` 的 `get_heap_index_slow_path`。而那条冷路径 +上的字段槽缓存在这里也没用上:有静态事实时根本不查内联缓存,它做的就是同一次 +哈希查找,只是隔着一次冷调用。补一支 Object 快路径:0.68s → **0.61s**。 + +三处合计:0.96s → 0.61s,**36%**。 + +## 参数后面的容器没有编译期事实,于是每次索引都走冷路径(2026-08-21) + +`cart_pricing_rules`(1.89x Lua)的计数器: + + index_keys=dynamic_register_key:7000000, dynamic_short_string_key:7000000, + slow_path:7000000, typed_map_direct:7000000 + +**7 000 000 次里 7 000 000 次走 `#[cold] get_heap_index_slow_path`**,然后从 +同一个有类型的载体上答出来。列表那边一样:`fn at(xs, i) { return xs[i]; }` +是 `slow_path:6000000`。 + +原因是快路径的三支(Map / List / String)全都写在 `if let Some(fact) = index_fact` +里面 —— 而**参数后面的容器没有事实**。`prices.get(sku)` 写在 +`fn line_total(prices, …)` 里是最普通的形状。 + +改法:把"这是什么容器"这个问题提前一次问清楚 —— 有事实用事实,没有就看一眼堆 +(一次 match)。四支(Map / List / Object / String)收进同一个 `match`。冷路径 +本身留着,它还管着内联缓存、观察到的类型和事实不符、以及 Unknown。 + +min-of-9: + +| workload | 前 | 后 | +| --- | --- | --- | +| 参数后面的列表索引 | 0.22s | **0.17s** | +| `cart_pricing_rules` 抽出来 | 0.51s | 0.49s | + +门禁上 `cart_pricing_rules` 1.89x → 1.77x,`binary_search` 24.8ms → 23.7ms, +`two_sum_map` 27.1ms → 24.4ms,geomean 0.987x。 + +`IndexMap::from_iter::<…, 1>` 那 4.0% 仍然在:`Mixed` 空 map 第一次插入时提升成 +`StringInt`,每轮循环各提升一次。这是表示切换本身,不是浪费。 + +## 原生比解释器**慢**——两个 workload 上,而且没有门禁在看(2026-08-21) + +perf 门禁跑的是 `RUN_AOT=0`,量的是纯解释器。原生这一侧从来没被量过。量了一下 +(dist 构建,`LK_AOT_NO_FALLBACK=1` 确认全原生,min-of-5): + +| workload | VM | 原生 | | +| --- | --- | --- | --- | +| 参数后面的列表索引 | 0.18s | **0.02s** | 快 9x | +| `cart_pricing_rules` | 0.51s | **0.10s** | 快 5x | +| `config_defaults_merge` | 2.40s | 5.48s | **慢 2.3x** | +| 结构体构造 300 万次 | 0.62s | 2.03s | **慢 3.3x** | + +算术和索引密集的快 5–9 倍,**分配密集的反而慢**。`perf` 看结构体那条: + +| 项 | 占比 | +| --- | --- | +| `RuntimeState::register_container` | 25.8% | +| libc(malloc/free) | 26.2% | +| `IndexMap::insert_full` | 7.6% | +| `check_declared_value` | 7.3% | + +**改掉的一项**:`obj_mark` 除了写下类型 id,还会把 map 里**每个键拷成 `String`、 +逐个重新做声明类型检查**。而降低那边本来就在每个字段上发过检查(值的类型能定下来 +的还会省掉)。拆成两个入口:`obj_mark` 只写 id,`obj_mark_checked` 才重扫——后者 +只给 `P { ..base }` 这种"先建 map 再打标记"的形状用,它的字段确实没有更早的检查 +时机。结构体构造 2.03s → **1.67s**,`config_defaults_merge` 5.48s → **4.98s**, +两端答案不变(`spread check` 那条仍然报同一句声明类型错误)。 + +**把这 1.43s 拆开量了**(三字段结构体 300 万次,把两处分别改成空操作再测): + +| 拆出来的部分 | 耗时 | 占比 | +| --- | --- | --- | +| 释放(每个容器一次 `free`,加上它的 `String` 键) | 0.60s | 42% | +| arena 记账(两次哈希操作 + 两次 TLS/RefCell) | 0.27s | 19% | +| 其余(分配、插入、循环本身) | 0.56s | 39% | + +对照:同一个程序 VM 跑 0.57s。**差距的主要来源不是 arena,是"每个对象都立刻 +`free`"**——VM 那边有 GC 阈值(`gc_threshold: 1024`),300 万个对象是分批回收的, +不是 300 万次 malloc/free。这条纠正上面那段的暗示:arena 只占 19%,不是全部。 + +**改掉的一项**:`str_dyn_new_sized(n)`。结构体字面量在建 map 之前就知道字段数, +而扩容要把已插入的全部重新哈希一遍——每字段成本从 3 字段的 135ns 涨到 6 字段的 +277ns,涨的就是这个。min-of-7:6 字段 2.37s → **1.92s**(19%),3 字段带一个 +`String` 的 1.63s → **1.51s**,1 字段 1.15s → 1.10s,纯 3 字段持平。 + +**又改掉一项:常量键借用,不再每个实例拷一份。** map 的键类型从 `String` 换成 + +```rust +enum StrKey { Static(&'static str), Owned(String) } +``` + +结构体的字段名和 map 字面量的键都是程序镜像里的数据符号(降低那边的 +`materialize_key` 内联一个 global),活得比任何用它的 map 都长——以前每个实例每个 +字段都把它拷成一个 `String`,构造时一次分配、析构时一次释放。`str_dyn_set_const` +借用它,`str_dyn_set` 保持拷贝(运行期算出来的键必须自己拥有,它来源的字符串可能 +先被释放)。哈希和比较都走 `as_str`,所以同一段文本的两种形式是同一个键,而且哈希 +和 `String` 逐字节相同——迭代顺序不变,`vm_mirror` 会替这条把关。 + +`transmute` 到 `'static` 是这里唯一的 unsafe,契约写在函数上,**只有 `NewObject` +的字段存储用它**,其余存储走拷贝版。 + +min-of-7:6 字段 1.92s → **1.36s**(29%),3 字段 1.47s → **1.10s**(25%), +1 字段 1.10s → **0.89s**。 + +三项合起来(重扫拆分、按大小建表、常量键借用),结构体构造 300 万次 +**2.03s → 1.05s**,`config_defaults_merge` **5.48s → 4.19s**。对 VM 的比值从 +3.3x 降到 1.7x。 + +**map 字面量同样处理**(同一条规则往外推一格):字面量的条目数在建表前就知道, +键是常量的那些也是程序镜像里的符号。写常量键要先判断——`{name: 1}` 这种**算出来的** +键没有常量文本,它来源的字符串可能先被释放,那些仍然拷贝。 + +`{"alpha": i, "beta": "s", "gamma": i, "delta": "t"}` 建 300 万次: +原生 **0.41s → 0.17s**(2.4x)。 + +量这条时踩了一次坑,记下来:第一个探针写的是 `{"alpha": i, "beta": i+1, ...}`, +全是 Int 值,于是走的是 `MapStrI64` 载体而不是 `MapStrDyn`,改动根本没生效,读数 +是 1.94 → 1.96(噪声),差点当成"没用"删掉。**同一种字面量按值的类型走不同载体, +探针必须挑对那一支。** + +那个"挑错的探针"反过来指出了下一处:`Map` 是最常见的 map 字面量, +而它的载体 `StrI64Map` 的键还是 `String`。`StrKey` 推到 `StrI64Map` / `StrF64Map` +(加上各自的 `_sized` 构造和 `_set_const`)之后: + +`{"alpha": i, "beta": i+1, "gamma": i+2, "delta": i+3}` 建 300 万次: + +| | 前 | 后 | +| --- | --- | --- | +| 原生 | 1.94s | **1.45s** | +| VM | 1.75s | 1.75s | + +**符号翻过来了**——这个形状上原生从慢于解释器变成快于它。 + +**没改的一项,量在这里**:`register_container` 是一张 +`HashMap`,每个容器一次插入、一次删除。它买的是两件事—— +退出时全部释放,以及**释放是幂等的**(未知句柄返回 `None`,重复释放是空操作)。 +所以不能简单地"作用域局部的容器不登记":那会把重复释放从空操作变成 double free, +是拿正确性换速度。 + +**这张网被触发过几次?量了:零。** 给"释放未知句柄"加计数器,跑完 81 个示例、 +bench 那个 workload、120 例模糊测试——**一次都没有**。原因大概是 MIR 里句柄是 SSA +值(`Move` 保持同一个 ValueId),所以"两个寄存器持同一句柄"在那一层根本不是两个东西, +作用域回收对每个值只发一次释放。 + +零次不等于不可能,而万一发生就是内存破坏,不是错答——所以网还留着。但这个数字是 +设计下一步时该带上的:它值 19%,拦下的是一个在实测里从不出现的情况。另一个诱人 +但**不该做**的改法也记在这里:释放时不 free 而是回收进空闲链表复用。那会把 +use-after-free 从"ASan 能抓的崩溃"变成"读到另一个活对象的静默错答",对一个靠差分 +门禁的项目来说是往坏处换。真正的改法是把 drop 信息放进分配本身的头部、用侵入式链表串起来 +(登记变成两次指针写,释放变成 O(1) 摘链,退出时走链表),幂等性靠节点上的标记。 +那是 lkrt 分配路径的一次 unsafe 重构,没有在这一轮做。 + +## 长字符串常量每次加载都分配堆对象——但没有任何 workload 会走到(2026-08-21) + +`LoadString` 对 ≤7 字节的常量给 `ShortStr`(内联,不分配),更长的每次执行都 +`alloc_heap_value(HeapValue::String(...))` —— 同一个不可变常量,每次加载分配一 +个新堆对象。微基准里差 4 倍:5M 次循环加载,短常量 0.07s,35 字节常量 0.27s +(约 40ns/次)。 + +看起来该修,**实测下来不该**。给 `LoadString` 的长常量分支加计数器,跑完整 +workload 套件和 `examples/` 全部 70 个程序: + +| 语料 | 触发次数 | +| --- | --- | +| `bench/workloads_business_algorithms.lk` | 0 | +| `examples/**` | 0 | + +热路径上的字符串常量都 ≤7 字节(键名、标签、短前缀),一个都没走到分配那支。 +要修得加一份 (function, const) → HeapRef 的缓存,而缓存里的句柄必须进 GC 根 +(见 `RuntimeModuleState::export_root` 那次),复杂度不小。为一个测不到的路径 +加这些不划算——记在这里,免得下次再从微基准出发推一遍。 + +真要修的话:键必须带**函数索引**,不能只用 pc。只用 pc 的两份缓存 +(call shape、global slot)已经因为跨函数会串而删掉了。 diff --git a/bench/run_workload_bench.sh b/bench/run_workload_bench.sh index 8c6b8f44..764b83b7 100755 --- a/bench/run_workload_bench.sh +++ b/bench/run_workload_bench.sh @@ -142,13 +142,11 @@ run_with_timeout() { } collect_profile_once() { - local exec_widths=(28 12 10 10 8 10 10 10 10) - local copy_widths=(28 10 10 10 10 10 10 10 10 10 10) + local exec_widths=(28 12 12 10 10 8 10 10 10 10) local opcode_widths=(28 70) local write_source_widths=(28 70) local index_key_widths=(28 70) local exec_rows=() - local copy_rows=() local opcode_rows=() local write_source_rows=() local index_key_rows=() @@ -156,7 +154,7 @@ collect_profile_once() { echo "VM Profile by Workload" for name in "${WORKLOADS[@]}"; do - local err_file opcodes top_opcodes write_sources index_keys calls branches typed containers list_ops map_ops string_ops clones heap_clones copy_heap reg_heap local_heap load_heap store_heap const_heap arg_heap cont_heap + local err_file opcodes top_opcodes write_sources index_keys calls branches typed containers list_ops map_ops string_ops reg_writes err_file="$TMPDIR/profile_${name}.err" local out_file out_file="$TMPDIR/profile_${name}.out" @@ -165,6 +163,13 @@ collect_profile_once() { sed 's/^/ /' "$err_file" >&2 return 1 fi + # A binary without `--features vm-profile` used to answer LK_VM_PROFILE=1 + # with a full profile of zeros, which this happily tabulated as measurements. + if grep -q "VM profile: unavailable" "$err_file"; then + echo "VM profile requested but '$LK_BIN' has no profiling counters compiled in." >&2 + echo " Rebuild with: cargo build --profile dist -p lk-cli --features vm-profile" >&2 + return 1 + fi opcodes=$(profile_value "$err_file" opcode_steps) top_opcodes=$(profile_value "$err_file" top_opcodes) write_sources=$(profile_value "$err_file" write_sources) @@ -176,41 +181,20 @@ collect_profile_once() { list_ops=$(profile_value "$err_file" list_ops) map_ops=$(profile_value "$err_file" map_ops) string_ops=$(profile_value "$err_file" string_ops) - clones=$(profile_value "$err_file" val_clones) - heap_clones=$(profile_value "$err_file" heap_clones) - copy_heap=$(profile_value "$err_file" copy_policy_heap_clones) - reg_heap=$(profile_value "$err_file" register_copy_heap_clones) - local_heap=$(profile_value "$err_file" local_copy_heap_clones) - load_heap=$(profile_value "$err_file" local_load_heap_clones) - store_heap=$(profile_value "$err_file" local_store_heap_clones) - const_heap=$(profile_value "$err_file" const_load_heap_clones) - arg_heap=$(profile_value "$err_file" call_arg_heap_clones) - cont_heap=$(profile_value "$err_file" container_copy_heap_clones) - exec_rows+=("$name|$opcodes|$calls|$branches|$typed|$containers|$list_ops|$map_ops|$string_ops") - copy_rows+=("$name|$clones|$heap_clones|$copy_heap|$reg_heap|$local_heap|$load_heap|$store_heap|$const_heap|$arg_heap|$cont_heap") + reg_writes=$(profile_value "$err_file" register_writes) + exec_rows+=("$name|$opcodes|$reg_writes|$calls|$branches|$typed|$containers|$list_ops|$map_ops|$string_ops") opcode_rows+=("$name|$top_opcodes") write_source_rows+=("$name|$write_sources") index_key_rows+=("$name|$index_keys") done - printf "%-28s %12s %10s %10s %8s %10s %10s %10s %10s\n" \ - "Workload" "Opcodes" "Calls" "Branches" "Typed" "Containers" "List" "Map" "String" + printf "%-28s %12s %12s %10s %10s %8s %10s %10s %10s %10s\n" \ + "Workload" "Opcodes" "RegWrites" "Calls" "Branches" "Typed" "Containers" "List" "Map" "String" print_separator "${exec_widths[@]}" for row in "${exec_rows[@]}"; do - IFS='|' read -r name opcodes calls branches typed containers list_ops map_ops string_ops <<< "$row" - printf "%-28s %12s %10s %10s %8s %10s %10s %10s %10s\n" \ - "$name" "$opcodes" "$calls" "$branches" "$typed" "$containers" "$list_ops" "$map_ops" "$string_ops" - done - - echo "" - echo "VM Copy Profile by Workload" - printf "%-28s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n" \ - "Workload" "Clones" "HeapClone" "CopyHeap" "RegHeap" "LocalHeap" "LoadHeap" "StoreHeap" "ConstHeap" "ArgHeap" "ContHeap" - print_separator "${copy_widths[@]}" - for row in "${copy_rows[@]}"; do - IFS='|' read -r name clones heap_clones copy_heap reg_heap local_heap load_heap store_heap const_heap arg_heap cont_heap <<< "$row" - printf "%-28s %10s %10s %10s %10s %10s %10s %10s %10s %10s %10s\n" \ - "$name" "$clones" "$heap_clones" "$copy_heap" "$reg_heap" "$local_heap" "$load_heap" "$store_heap" "$const_heap" "$arg_heap" "$cont_heap" + IFS='|' read -r name opcodes reg_writes calls branches typed containers list_ops map_ops string_ops <<< "$row" + printf "%-28s %12s %12s %10s %10s %8s %10s %10s %10s %10s\n" \ + "$name" "$opcodes" "$reg_writes" "$calls" "$branches" "$typed" "$containers" "$list_ops" "$map_ops" "$string_ops" done echo "" @@ -390,16 +374,23 @@ echo "LK: $LK_BIN" echo "Lua: $($LUA_BIN -v 2>&1 | head -1)" if [ "$RUN_AOT" != "0" ]; then AOT_COMPILE_LOG="$TMPDIR/aot_compile.log" - if "$LK_BIN" compile "$BENCH_DIR/workloads_business_algorithms.lk" --output "$AOT_BIN" > "$AOT_COMPILE_LOG" 2>&1; then + # `LK_AOT_NO_FALLBACK=1`: a plain `compile` would happily bundle the VM for a + # shape Cranelift cannot lower, and the run would then be reported as "AOT" + # while executing the interpreter — a wrong number with nothing saying so. + # `scripts/aot_coverage.sh` scans this same file for the same reason; this is + # the second half, so the measurement cannot lie even if the gate is skipped. + if LK_AOT_HYBRID=0 LK_AOT_NO_FALLBACK=1 "$LK_BIN" compile \ + "$BENCH_DIR/workloads_business_algorithms.lk" --output "$AOT_BIN" > "$AOT_COMPILE_LOG" 2>&1; then AOT_ENABLED=1 - AOT_BACKEND=$(sed -nE 's/.*backend ([^,]+),.*/\1/p' "$AOT_COMPILE_LOG" | tail -1) - if [ -z "$AOT_BACKEND" ]; then - AOT_BACKEND="unknown" - fi + # There is one native backend (Cranelift); the string-IR `llvm` one retired. + # This used to scrape a "backend X," line out of the compile log — a line the + # compiler stopped emitting when that backend went away, so the report had + # been saying "(unknown)" ever since. + AOT_BACKEND="cranelift" echo "AOT: $AOT_BIN ($AOT_BACKEND)" else AOT_BACKEND="skipped" - echo "AOT: skipped (compile failed)" + echo "AOT: skipped (compile failed — a shape stopped lowering natively)" echo "AOT compile failed; continuing with LK VM and Lua only:" >&2 sed 's/^/ /' "$AOT_COMPILE_LOG" >&2 fi diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0f982273..8bafc64d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -40,7 +40,18 @@ nu-ansi-term = "0.50" tracing-subscriber = { workspace = true } sha2 = { workspace = true } +# `signal(SIGPIPE, SIG_DFL)` — see `restore_default_sigpipe` in `main.rs`. +[target.'cfg(unix)'.dependencies] +libc = "0.2" + [dev-dependencies] +# `RuntimeVal`'s `PartialEq` exists for test code only — see the `testing` +# feature in lk-core. +lk-core = { path = "../core", features = ["testing"] } +# The named-parameter conformance test needs both the lowering's table and the +# stdlib's declaration in one process; the CLI is the crate that has both. +lk-aot-lower = { path = "../aot/lower" } +lk-stdlib = { path = "../stdlib" } assert_cmd = "2" predicates = "3" tempfile = { workspace = true } diff --git a/cli/src/coverage.rs b/cli/src/coverage.rs index fe03c272..31f7c56b 100644 --- a/cli/src/coverage.rs +++ b/cli/src/coverage.rs @@ -72,9 +72,19 @@ fn print_static_coverage(path: &Path, module: &lk_core::vm::Module) { } } + // No `natives:` line. It printed `module.natives.len()`, and the only path + // that builds a module here — `compile_program_module_with_ctx`, which the + // executor uses too — passes `Vec::new()` for that table. The number was 0 + // for every program ever compiled, including ones whose whole body is a + // `println` call: a stdlib native arrives as a context global and is called + // through `GetGlobal` + `Call`, both of which the opcode table below counts + // honestly. A statistic that cannot be anything but zero reads as "this + // program makes no native calls", which is the opposite of true. + // + // The table it reported on is reachable from no binary at all; see the task + // tracking whether `LoadNative` should exist. println!("Instr coverage: {}", path.display()); println!(" functions: {}", module.functions.len()); - println!(" natives: {}", module.natives.len()); println!(" globals: {}", module.globals.len()); println!(" instructions: {instructions}"); println!(" registers: {registers}"); @@ -88,16 +98,7 @@ fn print_static_coverage(path: &Path, module: &lk_core::vm::Module) { fn print_runtime_metrics(metrics: VmRuntimeMetrics) { println!("Runtime metrics:"); println!(" opcode_steps: {}", metrics.opcode_steps); - println!(" copy_policy_heap_clones: {}", metrics.copy_policy_heap_clones); - println!(" register_copy_heap_clones: {}", metrics.register_copy_heap_clones); - println!(" local_copy_heap_clones: {}", metrics.local_copy_heap_clones); - println!(" local_load_heap_clones: {}", metrics.local_load_heap_clones); - println!(" local_store_heap_clones: {}", metrics.local_store_heap_clones); - println!(" const_load_heap_clones: {}", metrics.const_load_heap_clones); - println!(" call_arg_heap_clones: {}", metrics.call_arg_heap_clones); - println!(" container_copy_heap_clones: {}", metrics.container_copy_heap_clones); println!(" register_writes: {}", metrics.register_writes); - println!(" return_value_moves: {}", metrics.return_value_moves); println!(" branch_ops: {}", metrics.branch_ops); println!(" typed_branch_ops: {}", metrics.typed_branch_ops); println!(" call_ops: {}", metrics.call_ops); diff --git a/cli/src/main.rs b/cli/src/main.rs index 6a6c6143..79adb938 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -21,7 +21,8 @@ use lk_core::{ vm::{ ModuleArtifact, Opcode, VM_INDEX_KEY_METRIC_NAMES, VM_REGISTER_WRITE_SOURCE_NAMES, VmContext, VmRuntimeMetrics, compile_program_module_with_ctx, execute_compiled_module_with_ctx, execute_module_artifact_with_ctx, - execute_program_with_ctx_and_limits, vm_runtime_metrics_reset, vm_runtime_metrics_snapshot, + execute_program_with_ctx_and_limits, vm_runtime_metrics_enabled, vm_runtime_metrics_reset, + vm_runtime_metrics_snapshot, }, }; @@ -51,7 +52,7 @@ use coverage::run_coverage_report; use fmt::run_fmt; #[cfg(test)] use paths::split_compile_args_with_cwd; -use paths::{expand_program_file, parse_options_for_file, parse_sanitized_path, sanitize_path, split_compile_args}; +use paths::{expand_program_file, parse_options_for_file, parse_path_arg, split_compile_args}; use pkg::run_pkg_command; #[derive(Debug, Parser)] @@ -69,7 +70,7 @@ struct CliArgs { command: Option, /// If no subcommand, treat as a source file to execute (statements only) - #[arg(value_name = "FILE", value_parser = parse_sanitized_path)] + #[arg(value_name = "FILE", value_parser = parse_path_arg)] file: Option, } @@ -98,26 +99,38 @@ pub(crate) enum CompileMode { enum Commands { /// Compile sources into supported migration targets. Compile { - /// 支持 `lk compile [TARGET] [FILE]`(默认编译 exe;省略 FILE 时自动查找当前目录入口) + /// `lk compile [TARGET] [FILE]` — the default target is a native exe, + /// and an omitted FILE looks for this directory's entry point. #[arg(value_name = "ARGS", num_args = 0..=2)] positional: Vec, #[cfg(feature = "aot")] - /// 输出文件路径(针对默认 exe 目标指定最终可执行文件路径) + /// Where to write the answer; for the default exe target, the final + /// executable's path. #[arg(long)] output: Option, }, /// Type-check a source file without executing it. Check { /// Source file to type-check - #[arg(value_name = "FILE", value_parser = parse_sanitized_path)] + #[arg(value_name = "FILE", value_parser = parse_path_arg)] file: PathBuf, + /// Also require every function's parameters and return type to resolve + /// to something other than `Any`. + /// + /// Off by default because `lk check` answers "will this run", and an + /// unannotated parameter runs: `fn process(xs) { … }` is a program both + /// backends accept. Demanding the annotation is a rigour policy, and a + /// policy that rejects working programs cannot be the default answer of + /// the command you run *before* running. + #[arg(long)] + strict: bool, }, /// Format LK sources in place (4-space indent). Without a path, formats the /// whole project (nearest `Lk.toml` directory, else the current directory). /// `--check` reports without writing. Fmt { /// Files or directories to format. Defaults to the whole project. - #[arg(value_name = "PATH", value_parser = parse_sanitized_path)] + #[arg(value_name = "PATH", value_parser = parse_path_arg)] paths: Vec, /// Do not write; exit non-zero if any file is not already formatted. #[arg(long)] @@ -127,16 +140,17 @@ enum Commands { /// that embeds the program and the VM (100% coverage; runs the VM at launch). Bundle { /// Source file to bundle - #[arg(value_name = "FILE", value_parser = parse_sanitized_path)] + #[arg(value_name = "FILE", value_parser = parse_path_arg)] file: PathBuf, - /// Output executable path - #[arg(short, long, value_name = "OUT", value_parser = parse_sanitized_path)] - output: PathBuf, + /// Output executable path (default: the source path without its + /// extension, as `lk compile` does) + #[arg(short, long, value_name = "OUT", value_parser = parse_path_arg)] + output: Option, }, /// Report VM coverage for a source file. Coverage { /// Source file to inspect - #[arg(value_name = "FILE", value_parser = parse_sanitized_path)] + #[arg(value_name = "FILE", value_parser = parse_path_arg)] file: PathBuf, /// Print disassembled VM functions after static coverage #[arg(long)] @@ -162,7 +176,7 @@ enum MacroCommand { /// Expand macros in a source file and print the resulting LK token stream. Expand { /// Source file to expand - #[arg(value_name = "FILE", value_parser = parse_sanitized_path)] + #[arg(value_name = "FILE", value_parser = parse_path_arg)] file: PathBuf, /// Print expansion trace entries before expanded source #[arg(long)] @@ -186,7 +200,7 @@ enum PkgCommand { /// Package name. Defaults to the current directory name. name: Option, }, - /// Add a GitHub dependency to Lk.toml. + /// Add a dependency to Lk.toml: `owner/repo` (GitHub), a git URL, or a local path. Add { name: String, source: String, @@ -283,39 +297,87 @@ fn maybe_print_vm_profile(enabled: bool) { if !enabled { return; } - let metrics = vm_runtime_metrics_snapshot(); - eprintln!("{}", vm_profile_line(metrics)); + eprintln!("{}", vm_profile_report()); +} + +/// What `LK_VM_PROFILE=1` prints — including when it can't profile. +/// +/// The recording sites are `#[cfg]`-gated: without `--features vm-profile` they +/// compile to nothing, so every counter reads 0. This asked the environment +/// variable and nothing else, so a default build answered `LK_VM_PROFILE=1` with +/// a full, well-formed profile in which every single number was fiction — +/// `opcode_steps=0` for a program that had just run four thousand of them. +/// `lk coverage --runtime` was already checking `vm_runtime_metrics_enabled()`; +/// one rule, two carriers, one of them following it. +fn vm_profile_report() -> String { + if !vm_runtime_metrics_enabled() { + return "VM profile: unavailable — this binary has no profiling counters compiled in. \ + Rebuild with `cargo build -p lk-cli --features vm-profile`." + .to_string(); + } + vm_profile_line(vm_runtime_metrics_snapshot()) } fn vm_profile_line(metrics: VmRuntimeMetrics) -> String { - let heap_clones = metrics.copy_policy_heap_clones; - let val_clones = heap_clones; format!( - "VM profile: opcode_steps={} top_opcodes={} write_sources={} index_keys={} calls={} branches={} typed_branches={} containers={} list_ops={} map_ops={} string_ops={} val_clones={} heap_clones={} copy_policy_heap_clones={} register_copy_heap_clones={} local_copy_heap_clones={} local_load_heap_clones={} local_store_heap_clones={} const_load_heap_clones={} call_arg_heap_clones={} container_copy_heap_clones={}", + "VM profile: opcode_steps={} top_opcodes={} write_sources={} index_keys={} calls={} call_kinds={} branches={} typed_branches={} containers={} list_ops={} map_ops={} string_ops={} register_writes={}", metrics.opcode_steps, top_opcode_profile(&metrics), top_register_write_source_profile(&metrics), top_index_key_profile(&metrics), metrics.call_ops, + call_kind_profile(&metrics), metrics.branch_ops, metrics.typed_branch_ops, metrics.container_ops, metrics.list_ops, metrics.map_ops, metrics.string_ops, - val_clones, - heap_clones, - metrics.copy_policy_heap_clones, - metrics.register_copy_heap_clones, - metrics.local_copy_heap_clones, - metrics.local_load_heap_clones, - metrics.local_store_heap_clones, - metrics.const_load_heap_clones, - metrics.call_arg_heap_clones, - metrics.container_copy_heap_clones + metrics.register_writes, ) } +/// The call total split by what the call site dispatched to. +/// +/// `call_ops` alone cannot answer the question the counter exists for — where +/// call cost goes — because the four kinds have nothing in common: an `exact` +/// call is a direct index into the function table, a `method` call is a +/// devirtualized table lookup, and a `native` call is the only one that reads a +/// heap `CallableValue`. On the arithmetic-heavy benchmark the split is 62 +/// native against 228k total; on a stdlib-call-heavy program it is 34 of 34. +/// One number cannot say both. +/// +/// The breakdown was already being collected, and `lk coverage --runtime` +/// already printed it — this line dropped it. Same measurement, two surfaces, +/// one of them lossy. +fn call_kind_profile(metrics: &VmRuntimeMetrics) -> String { + let kinds = [ + ("native", metrics.native_call_ops), + ("closure", metrics.closure_call_ops), + ("exact", metrics.exact_call_ops), + ("named", metrics.named_call_ops), + ("method", metrics.method_call_ops), + ]; + let named: u64 = kinds.iter().map(|(_, count)| count).sum(); + let mut parts: Vec = kinds + .iter() + .filter(|(_, count)| *count != 0) + .map(|(name, count)| format!("{name}:{count}")) + .collect(); + // A call the executor could not classify is still a call. Naming the + // remainder keeps the parts summing to `calls=`, so a reader can tell + // "none of these" from "not measured". + if let Some(rest) = metrics.call_ops.checked_sub(named) + && rest != 0 + { + parts.push(format!("other:{rest}")); + } + if parts.is_empty() { + return "none".to_string(); + } + parts.join(",") +} + fn top_index_key_profile(metrics: &VmRuntimeMetrics) -> String { let mut pairs = Vec::new(); for (name, count) in VM_INDEX_KEY_METRIC_NAMES.iter().zip(metrics.index_key_metrics.iter()) { @@ -391,7 +453,32 @@ fn top_opcode_profile(metrics: &VmRuntimeMetrics) -> String { .join(",") } +/// Die on `SIGPIPE` like every other Unix filter, instead of panicking. +/// +/// Rust sets `SIGPIPE` to `SIG_IGN` before `main`, so a write to a closed pipe +/// comes back as `EPIPE` and `println!` unwraps it into a panic: `lk gen.lk | +/// head` printed `thread 'main' panicked at library/std/src/io/stdio.rs … note: +/// run with RUST_BACKTRACE=1` and exited 101. That is the implementation +/// talking, not the language, and piping into `head` is the most ordinary thing +/// a shell does with a program that prints. +/// +/// The AOT-compiled binary was already right — its `main` is a C `main`, so +/// Rust's startup never ran and it died with signal 13 (exit 141), silently. +/// So this is also the two backends disagreeing, with the native one correct. +#[cfg(unix)] +fn restore_default_sigpipe() { + // SAFETY: sets a signal disposition before anything has been printed and + // before any thread exists. + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_DFL); + } +} + +#[cfg(not(unix))] +fn restore_default_sigpipe() {} + fn main() -> anyhow::Result<()> { + restore_default_sigpipe(); let mut startup = startup_trace::StartupTrace::new("main"); mem::configure(); maybe_init_perf_tracing(); @@ -413,27 +500,32 @@ fn main() -> anyhow::Result<()> { #[cfg(feature = "aot")] output: output_arg, } => { - let (pos_target, safe) = split_compile_args(&positional)?; + let (pos_target, safe, implicit_output) = split_compile_args(&positional)?; + #[cfg(feature = "aot")] + let output_arg_given = output_arg.is_some(); #[cfg(feature = "aot")] let output = output_arg - .map(|p| { - sanitize_path(p.to_string_lossy().as_ref()).inspect_err(|e| { - diagnostic::error(e); - }) - }) - .transpose()?; + // A package build the user did not name a file for: the + // output belongs at the package root, not inside `src/`. + .or(implicit_output.clone()); let compile_mode = pos_target; + // The guard is about the *flag*, not about the implicit default + // a package build derives — `--output` still means nothing for + // `bytecode`, and the default still has to reach it. #[cfg(feature = "aot")] - if matches!(compile_mode, CompileMode::Bytecode) && output.is_some() { + if matches!(compile_mode, CompileMode::Bytecode) && output_arg_given { anyhow::bail!("--output is only supported for `lk compile ` and `object:`"); } match compile_mode { CompileMode::Bytecode => { - compile_instr_module(&safe)?; + #[cfg(feature = "aot")] + compile_instr_module(&safe, output.as_deref())?; + #[cfg(not(feature = "aot"))] + compile_instr_module(&safe, implicit_output.as_deref())?; return Ok(()); } CompileMode::Object { triple } => { @@ -463,8 +555,8 @@ fn main() -> anyhow::Result<()> { } } } - Commands::Check { file } => { - run_type_check(&file)?; + Commands::Check { file, strict } => { + run_type_check(&file, strict)?; return Ok(()); } Commands::Fmt { paths, check } => { @@ -472,8 +564,24 @@ fn main() -> anyhow::Result<()> { return Ok(()); } Commands::Bundle { file, output } => { - run_bundle(&file, &output)?; - return Ok(()); + #[cfg(not(feature = "aot"))] + { + let _ = (&file, &output); + anyhow::bail!( + "bundling links the VM in through lk-api's staticlib, which is part of the native backend; rebuild with `--features aot`" + ); + } + #[cfg(feature = "aot")] + { + // The same default `lk compile` uses. Both commands produce + // an executable from a source file, and one of them used to + // demand a name for it while the other worked one out — + // `lk bundle app.lk` was a usage error, which is also the + // spelling `CLAUDE.md` and `README` documented. + let output = output.unwrap_or_else(|| file.with_extension("")); + run_bundle(&file, &output)?; + return Ok(()); + } } Commands::Coverage { file, @@ -495,9 +603,7 @@ fn main() -> anyhow::Result<()> { } // Otherwise: execute FILE as statements let file = file.expect("internal: file should be present when no subcommand"); - let safe = sanitize_path(file.to_string_lossy().as_ref()).inspect_err(|e| { - diagnostic::error(e); - })?; + let safe = file; let src_path_str = safe.to_string_lossy().to_string(); let raw = std::fs::read(&safe).map_err(|e| anyhow::anyhow!("Failed to read file '{}': {}", src_path_str, e))?; @@ -570,6 +676,12 @@ fn main() -> anyhow::Result<()> { let macro_free = expansion.proc_macro_dependencies.is_empty(); let program = expansion.program; + // Built before the type check, not after: registering the standard library + // is what publishes its declared signatures to the checker, and a check that + // runs first sees only the small fallback table in core — `string.split("a")` + // would go unchecked here while `lk check` caught it. + let mut base_env = build_vm_context(&safe)?; + // Cross-file signatures, checked here rather than inside the VM: the type // check `execute_with_ctx` runs has no path to resolve imports against, so // this is the only place a running program gets the same checking that @@ -580,8 +692,6 @@ fn main() -> anyhow::Result<()> { program.type_check(&mut checker)?; } - let mut base_env = build_vm_context(&safe)?; - let profile_enabled = vm_profile_enabled(); maybe_start_vm_profile(profile_enabled); let fuel = fuel_budget_from_env(); @@ -600,15 +710,37 @@ fn main() -> anyhow::Result<()> { } Err(err) => Err(err), }, - None => program.execute_with_ctx(&mut base_env), + // The directory, not `None`: `execute_with_ctx` type-checks the + // program *again* with a fresh checker, and one without a path to + // resolve imports against rejects every name that crosses a module + // boundary — so `lk check FILE` passed and `lk FILE` answered + // `Unknown type 'P' in parameter 'p'` for the same file. A program + // that clears the pre-flight command has to be runnable. + // + // The check above stays because it is the only one the sandboxed and + // cached branches get; that this path now checks twice is a startup + // cost, not a correctness one. + None => program.execute_with_ctx_from(&mut base_env, safe.parent()), } - } - .with_context(|| "VM execution failed"); + }; // Shutdown runtime after execution base_env.shutdown_async_runtime(); - let result = unwrap_with_traceback(exec_result, &base_env)?; + // Reported here rather than propagated, so a failing program reads the same + // whichever backend ran it. This used to carry `.with_context("VM execution + // failed")`, which anyhow rendered as four lines around the real message — + // and the claim was false for half of what reaches here: the compiler's own + // errors come out of this `Result` too (`compile_program_module_with_ctx`), + // and nothing had executed. A native binary prints one `Error: …` line; + // `an_uncaught_error_exits_and_reads_the_same_on_both_backends` pins the two together. + let result = match unwrap_with_traceback(exec_result, &base_env) { + Ok(result) => result, + Err(err) => { + diagnostic::error(format!("{err:#}")); + std::process::exit(1); + } + }; maybe_print_vm_profile(profile_enabled); if !result.first_return_is_nil() { @@ -637,10 +769,15 @@ fn expand_macro_file(path: &Path, trace: bool, deps: bool, origins: bool, featur // Deduplicate features preserving first-occurrence order. let mut seen = std::collections::HashSet::new(); options.macro_features = features.into_iter().filter(|f| seen.insert(f.clone())).collect(); - let expanded = expand_program_source(&input, options).map_err(|parse_err| { - diagnostic::parse_error(&parse_err, &input); - anyhow::anyhow!(parse_err.to_string()) - })?; + // Printed and exited — see `run_type_check` for why returning it prints + // the same line twice. + let expanded = match expand_program_source(&input, options) { + Ok(expanded) => expanded, + Err(parse_err) => { + diagnostic::parse_error(&parse_err, &input); + std::process::exit(1); + } + }; if trace { for step in &expanded.source.trace { println!( @@ -785,14 +922,39 @@ fn json_span(span: &lk_core::token::Span) -> JsonSpan { } } -fn run_type_check(path: &Path) -> anyhow::Result<()> { +/// `lk check FILE` — the same type check the executors run, without running. +/// +/// "The same" is the whole point, and it was not: this used +/// `TypeChecker::new_strict()` while `Program::execute_with_ctx_from` builds a +/// plain `TypeChecker::new()`, so four of the language's own examples were +/// rejected here and ran fine — `fn process_list(xs) { … }` is +/// `infers implicit Any for return type` to `check` and a working program to +/// everything else. `lk compile` produced a native executable from the same +/// file. +/// +/// The strict pass is still reachable with `--strict`; it is a lint about +/// under-specified signatures, not a statement about whether the program runs. +fn run_type_check(path: &Path, strict: bool) -> anyhow::Result<()> { let input = std::fs::read_to_string(path).with_context(|| format!("read LK source {}", path.display()))?; let options = parse_options_for_file(path)?; - let expanded = expand_program_source(&input, options).map_err(|parse_err| { - diagnostic::parse_error(&parse_err, &input); - anyhow::anyhow!(parse_err.to_string()) - })?; - let mut checker = TypeChecker::new_strict(); + // Printed *and* exited, not printed and returned: returning it makes the + // caller print the same line a second time, so `lk check` answered every + // syntax error twice — once with its caret snippet and once bare. The two + // sites below in this function already do it this way, and so does + // `lk FILE`. + let expanded = match expand_program_source(&input, options) { + Ok(expanded) => expanded, + Err(parse_err) => { + diagnostic::parse_error(&parse_err, &input); + std::process::exit(1); + } + }; + ensure_stdlib_signatures(); + let mut checker = if strict { + TypeChecker::new_strict() + } else { + TypeChecker::new() + }; seed_imports(&expanded.program, path, &mut checker); if let Err(err) = expanded.program.type_check(&mut checker) { let mut message = err.to_string(); @@ -847,13 +1009,76 @@ fn heap_object_limit_from_env() -> Option { .filter(|&limit| limit > 0) } +/// Tier 0 embeds **one file**, so a program that imports another one cannot +/// work — and used to say so only at run time, as `lk: Module 'dep' not found` +/// from a binary `lk compile` had just reported as built. +/// +/// A stdlib import is fine: the VM linked into the bundle has the whole +/// standard library. What cannot travel is a *file* import (`use "…"`) or a +/// package dependency, because the bundle carries no filesystem context and the +/// dependency's source was never embedded. +#[cfg(feature = "aot")] +fn refuse_bundle_with_source_imports(source_path: &Path, source: &str) -> anyhow::Result<()> { + use lk_core::stmt::{ImportSource, ImportStmt, Stmt}; + + let Ok(program) = lk_core::syntax::parse_program_source(source, Default::default()) else { + // Not parseable: the compile below will say so in the language's words. + return Ok(()); + }; + let stdlib_module = |name: &str| lk_stdlib::stdlib_catalog().modules.iter().any(|spec| spec.name == name); + let mut offenders: Vec = Vec::new(); + for statement in &program.statements { + let Stmt::Import(import) = statement.as_ref() else { + continue; + }; + match import { + ImportStmt::File { path } => offenders.push(format!("`use \"{path}\"`")), + ImportStmt::Module { module } | ImportStmt::ModuleAlias { module, .. } if !stdlib_module(module) => { + offenders.push(format!("`use {module}`")); + } + ImportStmt::Items { + source: ImportSource::File(path), + .. + } + | ImportStmt::Namespace { + source: ImportSource::File(path), + .. + } => offenders.push(format!("`use … from \"{path}\"`")), + ImportStmt::Items { + source: ImportSource::Module(module), + .. + } + | ImportStmt::Namespace { + source: ImportSource::Module(module), + .. + } if !stdlib_module(module) => offenders.push(format!("`use … from {module}`")), + _ => {} + } + } + if offenders.is_empty() { + return Ok(()); + } + offenders.sort(); + offenders.dedup(); + anyhow::bail!( + "{} imports {} — the Tier 0 bundle embeds a single file plus the VM, so an imported \ + module's source never travels with it and the binary would fail at launch with \ + \"Module not found\". Run it with `lk {}`, or keep the program in one file", + source_path.display(), + offenders.join(", "), + source_path.display() + ) +} + /// AOT Tier 0: bundle `source_path` into a self-contained native executable that /// embeds the program source and the VM (via lk-api's C-ABI staticlib). 100% /// coverage — the produced binary just runs the VM at launch, so any program that /// runs under the VM bundles (unlike the MIR native path). Linux/`cc` for now. +#[cfg(feature = "aot")] fn run_bundle(source_path: &Path, output: &Path) -> anyhow::Result<()> { let source = std::fs::read_to_string(source_path).map_err(|e| anyhow::anyhow!("read {}: {}", source_path.display(), e))?; + refuse_bundle_with_source_imports(source_path, &source)?; let staticlib = ensure_lk_api_staticlib()?; // Dev workspace layout: the C-ABI header lives in the workspace. let header_dir = workspace_root()?.join("api/include"); @@ -862,7 +1087,9 @@ fn run_bundle(source_path: &Path, output: &Path) -> anyhow::Result<()> { "#include \n#include \"lk.h\"\nstatic const char *LK_SRC = \"{escaped}\";\n\ int main(void) {{\n LkVm *vm = lk_vm_new();\n char *out = lk_vm_eval(vm, LK_SRC);\n\ if (out) {{ if (out[0]) printf(\"%s\\n\", out); lk_string_free(out); lk_vm_free(vm); return 0; }}\n\ - lk_vm_free(vm); fprintf(stderr, \"lk: execution failed\\n\"); return 1;\n}}\n" + const char *err = lk_vm_last_error(vm);\n\ + fprintf(stderr, \"lk: %s\\n\", err ? err : \"execution failed\");\n\ + lk_vm_free(vm); return 1;\n}}\n" ); let scratch = std::env::temp_dir().join(format!("lk_bundle_{}", std::process::id())); std::fs::create_dir_all(&scratch)?; @@ -890,6 +1117,11 @@ fn run_bundle(source_path: &Path, output: &Path) -> anyhow::Result<()> { Ok(()) } +/// Both callers (`run_bundle` and `native_compile`'s staticlib builder) are +/// `#[cfg(feature = "aot")]`, so this is too — a helper that outlives the only +/// configuration that calls it is dead code, and CI builds the CLI *without* +/// `aot` (the bare-metal step needs a `lk` that only compiles bytecode). +#[cfg(feature = "aot")] fn workspace_root() -> anyhow::Result { Ok(Path::new(env!("CARGO_MANIFEST_DIR")) .parent() @@ -913,9 +1145,21 @@ pub(crate) fn build_vm_context(path: &Path) -> anyhow::Result { resolver.set_base_dir(base); configure_package_resolver(&mut resolver, path)?; let resolver = Arc::new(resolver); - Ok(VmContext::new() - .with_resolver(Arc::clone(&resolver)) - .with_type_checker(Some(TypeChecker::new_strict()))) + Ok(VmContext::new().with_resolver(Arc::clone(&resolver))) +} + +/// Publish the standard library's declared signatures to the type checker. +/// +/// Registering the modules is what does it — `register_stdlib_module_metadata` +/// forwards each module's signatures to `lk_core::typ`, process-wide. The +/// commands that type-check without running (`lk check`, `lk compile`) never +/// build a `VmContext`, so without this they fall back to the small table core +/// keeps for its own tests and miss everything outside `os`/`env`/`math`. +/// +/// The registry is built and dropped; what survives is global. Idempotent. +pub(crate) fn ensure_stdlib_signatures() { + let mut registry = ModuleRegistry::new(); + let _ = register_enabled_stdlib(&mut registry); } pub(crate) fn register_enabled_stdlib(registry: &mut ModuleRegistry) -> anyhow::Result<()> { @@ -971,6 +1215,19 @@ pub(crate) enum BundleOutcome { Bundled(ModuleArtifact, Vec), } +#[cfg(feature = "aot")] +/// One validated dependency, held until the merge knows how to number it. +#[cfg(feature = "aot")] +struct PendingBundle { + import_path: String, + canonical: PathBuf, + dep: ModuleArtifact, + dep_entry: usize, + /// Exported name → the dep's own function index. + pairs: Vec<(String, u32)>, + dep_consts: Vec<(String, BundledConst)>, +} + #[cfg(feature = "aot")] fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Result { use lk_core::vm::{Instr, Opcode}; @@ -980,10 +1237,30 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu // driver. Keyed by resolved path rather than by the text of the import, // because two files can name the same module differently — and because // that is also what makes a cycle terminate. + // Every renamed item a file import binds, from any module in the bundle. + // + // A bundled module's constants fold into their reads by *slot*, and the slot + // is the constant's own name. `use { SIZE as TSS_SIZE }` reads a different + // name, so the fold missed it and the read survived as a `GetGlobal` of a + // slot nothing initialises — "does not resolve to anything natively + // lowerable" under `compile object:`, a fall back to the VM otherwise. The + // VM binds it, so the two backends differed in coverage. + // + // Collected from every module because a driver may rename another driver's + // constant, which is where this would have been found rather than reasoned + // about. + let mut renamed_items: Vec<(String, String)> = Vec::new(); + collect_renamed_file_items(&artifact.imports, &mut renamed_items); let mut queue: Vec<(String, PathBuf)> = file_import_paths(&artifact.imports) .into_iter() .map(|path| resolve_bundled_import(&base_dir, &path).map(|resolved| (path, resolved))) .collect::>>()?; + // A package dependency is a `.lk` file like any other, and the binding it + // produces is the same shape a file import produces — so it bundles the + // same way. It did not, and the workspace example was the whole sweep's + // one fallback: every program that reaches for a dependency ran on the + // Tier 0 VM bundle, about 3x slower, with nothing said. + queue.extend(package_import_modules(source, &artifact.imports)?); if queue.is_empty() { return Ok(BundleOutcome::Nothing); } @@ -993,8 +1270,7 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu // by a driver next to it are the same file under two names; recording only // the first left the second one's `use { .. }` resolving to nothing, which // the lowering reports as an unresolved global far from the cause. - let mut bundled_fns: std::collections::HashMap> = - std::collections::HashMap::new(); + let mut bundled_fns: std::collections::HashSet = std::collections::HashSet::new(); // Every constant any bundled module defined, and which module defined it. // Two deps exporting the same name would both fold into one merged slot, // and the first one popped off the queue would win for the importer's @@ -1005,15 +1281,21 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu let mut merged = artifact.clone(); let mut bundles: Vec = Vec::new(); + // Validated deps, waiting to be numbered. Nothing is appended inside the + // loop: which merged index each function gets depends on every dep, so the + // numbering is decided once, afterwards. + let mut pending: Vec = Vec::new(); + // Import paths naming a file some other path already brought in. They add + // no functions, only a binding table — which does not exist until the + // numbering does. + let mut aliases: Vec<(String, PathBuf)> = Vec::new(); while let Some((import_path, dep_path)) = queue.pop() { let canonical = std::fs::canonicalize(&dep_path).unwrap_or_else(|_| dep_path.clone()); - if let Some(fns) = bundled_fns.get(&canonical) { - bundles.push(lk_aot::BundledImport { - path: import_path, - fns: fns.clone(), - }); + if bundled_fns.contains(&canonical) { + aliases.push((import_path, canonical)); continue; } + bundled_fns.insert(canonical.clone()); let dep = compile_instr_artifact_with_dependencies(&dep_path)?.artifact; // Bundling this module would give its functions a *reference* to the @@ -1031,6 +1313,7 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu // The dep's own file imports resolve relative to *its* directory, not // the importing file's. let dep_dir = dep_path.parent().unwrap_or_else(|| Path::new(".")).to_path_buf(); + collect_renamed_file_items(&dep.imports, &mut renamed_items); for nested in file_import_paths(&dep.imports) { let resolved = resolve_bundled_import(&dep_dir, &nested) .with_context(|| format!("nested import of '{import_path}'"))?; @@ -1046,7 +1329,6 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu // why this restriction is what keeps the two backends agreeing. let mut reg_fn: std::collections::HashMap = std::collections::HashMap::new(); let mut reg_const: std::collections::HashMap = std::collections::HashMap::new(); - let mut fns: std::collections::HashMap = std::collections::HashMap::new(); let mut pairs: Vec<(String, u32)> = Vec::new(); let mut dep_consts: Vec<(String, BundledConst)> = Vec::new(); let dep_entry_fn = &dep.module.functions[dep_entry]; @@ -1103,6 +1385,75 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu ); } } + // A constant derived from constants. + // + // A module's top level is already required to be effect-free — + // that is what everything else in this scan enforces. What was + // missing was the ability to *evaluate* a pure one, so + // `const FRAME = HEADER + BODY;` was rejected as an effect + // while `const FRAME = 42;` was not. Deriving one constant from + // two others is the ordinary shape of a protocol header, and + // the alternative is the same number written twice. + // + // Folded rather than deferred: the value has to be known here, + // because what crosses the bundle boundary is a value and not + // an expression — `rewrite_bundled_globals` replaces each + // `GetGlobal` in the importer with a load. + Opcode::GetGlobal => { + let name = dep.module.globals.get(instr.bx() as usize).cloned().unwrap_or_default(); + match dep_consts.iter().rev().find(|(known, _)| *known == name) { + Some((_, value)) => { + reg_const.insert(instr.a(), value.clone()); + } + None => anyhow::bail!( + "bundled import '{import_path}' reads `{name}` at its top level, which is not a constant defined above it" + ), + } + } + Opcode::Move => { + if let Some(value) = reg_const.get(&instr.b()).cloned() { + reg_const.insert(instr.a(), value); + } else if let Some(&fidx) = reg_fn.get(&instr.b()) { + reg_fn.insert(instr.a(), fidx); + } else { + anyhow::bail!( + "bundled import '{import_path}' moves a top-level register that holds neither a function nor a constant" + ) + } + } + // Integer arithmetic on values already known. + // + // These three and their immediate forms, and deliberately not + // division: `/` is float division in LK, so an integer divide + // here is a cast the front end proved, and matching its exact + // truncation and its behaviour at zero is a second + // implementation of a thing worth having only one of. A header + // constant that needs one gets the diagnostic below, naming the + // opcode. + // + // Wrapping, because that is what the executor does — a fold + // that panicked where the VM wrapped would be a compiler that + // rejects a program the VM runs. + Opcode::AddInt | Opcode::SubInt | Opcode::MulInt => { + let lhs = int_operand(®_const, instr.b(), &import_path)?; + let rhs = int_operand(®_const, instr.c(), &import_path)?; + let value = match instr.opcode() { + Opcode::AddInt => lhs.wrapping_add(rhs), + Opcode::SubInt => lhs.wrapping_sub(rhs), + _ => lhs.wrapping_mul(rhs), + }; + reg_const.insert(instr.a(), BundledConst::Int(value)); + } + Opcode::AddIntI | Opcode::MulIntI => { + let lhs = int_operand(®_const, instr.b(), &import_path)?; + let rhs = instr.sc() as i64; + let value = if instr.opcode() == Opcode::AddIntI { + lhs.wrapping_add(rhs) + } else { + lhs.wrapping_mul(rhs) + }; + reg_const.insert(instr.a(), BundledConst::Int(value)); + } Opcode::Return0 => {} // A container at a module's top level cannot cross this // boundary and keep the VM's meaning. @@ -1153,18 +1504,94 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu } } - // Merge: append every dep function except its entry; function indices - // and global slots (by name) rewrite in place — pcs are unchanged, so - // pc-keyed facts stay valid. - let base = merged.module.functions.len() as u32; - let mut remap: Vec> = vec![None; dep.module.functions.len()]; - let mut next = base; - for (i, slot) in remap.iter_mut().enumerate() { - if i != dep_entry { - *slot = Some(next); + // Nothing is merged yet: the numbering the merge hands out depends on + // every dep, so it is decided once, after all of them are known. + pending.push(PendingBundle { + import_path, + canonical, + dep, + dep_entry, + pairs, + dep_consts, + }); + } + + // The numbering, and the reason it is not simply "append in the order they + // arrived". + // + // A `CallDirect` or `MakeClosure` names its target in the instruction's `b` + // field, which is a byte. A dep's instructions are already emitted by the + // time they reach here — rewriting one into two would move every jump + // offset after it — so any dep function that one of those names has to land + // below 256. Appending in arrival order made that a bound on the *whole* + // program, and `bare-metal-x86/program.lk` with its drivers hit it at 260. + // + // But most functions are not named that way. Of 159 driver functions there, + // 52 are: the rest are reached by name from the importing program, which + // the lowering resolves through `BundledImport::fns` — a `u32`. So the + // targets go first and the bound becomes "the importing file's functions, + // plus the ones a dep calls directly", which for that program is 144. + // + // What still has no answer is a program that crosses *that*. The honest fix + // is a wider field, and that is an instruction-encoding change. + let mut targets: Vec> = Vec::with_capacity(pending.len()); + for entry in &pending { + let mut set = std::collections::HashSet::new(); + for (index, function) in entry.dep.module.functions.iter().enumerate() { + if index == entry.dep_entry { + continue; + } + for raw in &function.code { + let instr = Instr::try_from_raw(*raw) + .map_err(|_| anyhow::anyhow!("bundled import '{}': bad instruction", entry.import_path))?; + if matches!(instr.opcode(), Opcode::CallDirect | Opcode::MakeClosure) { + set.insert(instr.b() as usize); + } + } + } + targets.push(set); + } + + let base = merged.module.functions.len() as u32; + let mut remaps: Vec>> = pending + .iter() + .map(|entry| vec![None; entry.dep.module.functions.len()]) + .collect(); + let mut next = base; + // Directly-called functions first, across every dep, then everything else. + #[allow( + clippy::needless_range_loop, + reason = "the bound is the dep module's function count, not `remaps`' length" + )] + for directly_called in [true, false] { + for (which, entry) in pending.iter().enumerate() { + for index in 0..entry.dep.module.functions.len() { + if index == entry.dep_entry || targets[which].contains(&index) != directly_called { + continue; + } + remaps[which][index] = Some(next); next += 1; } } + } + + // Laid out by merged index rather than pushed as they are rewritten: the + // two passes above interleave the deps, so arrival order is no longer + // append order. + let mut placed: Vec> = (base..next).map(|_| None).collect(); + let mut canonical_fns: std::collections::HashMap> = + std::collections::HashMap::new(); + let mut all_consts: Vec<(String, Vec<(String, BundledConst)>)> = Vec::new(); + for (which, entry) in pending.into_iter().enumerate() { + let PendingBundle { + import_path, + canonical, + dep, + dep_entry, + pairs, + dep_consts, + } = entry; + let remap = &remaps[which]; let slot_of = |name: &str, globals: &mut Vec| -> u16 { match globals.iter().position(|g| g == name) { Some(slot) => slot as u16, @@ -1174,8 +1601,8 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu } } }; - for (i, function) in dep.module.functions.iter().enumerate() { - if i == dep_entry { + for (index, function) in dep.module.functions.iter().enumerate() { + if index == dep_entry { continue; } let mut function = function.clone(); @@ -1190,8 +1617,17 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu .copied() .flatten() .ok_or_else(|| anyhow::anyhow!("bundled import '{import_path}' calls its entry"))?; - let new = u8::try_from(new) - .map_err(|_| anyhow::anyhow!("bundled import '{import_path}': function index overflow"))?; + // The numbering above put every one of these below 256. + // Reaching here means the *importing* file plus every + // directly-called dep function came to more than that, + // which is the ceiling this layout postponed rather + // than removed. + let new = u8::try_from(new).map_err(|_| { + anyhow::anyhow!( + "bundled import '{import_path}': more than 256 directly-called functions — \ + the call instruction names its target in a byte" + ) + })?; Some(Instr::abc(instr.opcode(), instr.a(), new, instr.c())) } Opcode::LoadFunction => { @@ -1216,8 +1652,107 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu *raw_instr = instr.raw(); } } - merged.module.functions.push(function); + let at = remap[index].expect("every non-entry function was numbered") - base; + placed[at as usize] = Some(function); + } + // The dep's `impl` blocks come across too, with their method indices + // rewritten by the same remap. + // + // Without this the merged artifact had the *functions* of an imported + // `impl` but no record that they implement anything, so the AOT's trait + // environment (`trait_env_prescan`, which reads `type_info.impls`) could + // not see them: `types.make(3, 4).norm()` fell out of the native subset + // — every cross-module method call did — while the same code inside the + // defining module lowered fine. + // + // A type declared in two bundled modules under one name would now share + // a dispatch key. The VM keeps them apart by `TypeScope`, so this + // refuses rather than resolving, by the rule the rest of this bundler + // follows. + // A `trait` the dep declares comes across too. Nothing in the AOT + // pipeline reads `type_info.traits` — dispatch is devirtualized from + // `impls` — but leaving it out produced a *module* whose impls name + // traits it does not declare, and `register_module_types` refuses that + // outright ("Trait 'Area' not found") the moment any consumer runs the + // merged artifact with a type checker attached. An artifact that is + // internally inconsistent is a trap for the next consumer, not a + // saving. + for decl in &dep.module.type_info.traits { + if let Some(existing) = merged + .module + .type_info + .traits + .iter() + .find(|other| other.name == decl.name) + { + if existing.methods != decl.methods { + anyhow::bail!( + "bundled import '{import_path}': trait `{}` is declared in more than one module — \ + the VM keeps them apart by declaring module, the bundle cannot", + decl.name + ); + } + continue; + } + merged.module.type_info.traits.push(decl.clone()); + } + // The dep's `struct` declarations come across for the same reason, and + // they are what gives a type its *runtime* identity: `trait_env_prescan` + // hands every declared struct a type id, and the id is how the native + // display finds the type's name and field order. Without this an + // imported struct got no id, so `NewObject` skipped `obj_mark` and + // `println(geo.P { x: 4 })` rendered the carrier — `{"x":4}` — where + // the VM prints `P{x:4}`. A wrong answer, not a fallback, because the + // rest of the shape lowered fine. + // + // Same-name refusal as the impls below, and for the same reason: two + // bundled modules declaring `P` are two types to the VM and one id + // here. + for decl in &dep.module.type_info.structs { + if let Some(existing) = merged + .module + .type_info + .structs + .iter() + .find(|other| other.name == decl.name) + { + if existing.fields != decl.fields { + anyhow::bail!( + "bundled import '{import_path}': type `{}` is declared in more than one module — \ + the VM keeps them apart by declaring module, the bundle cannot", + decl.name + ); + } + continue; + } + merged.module.type_info.structs.push(decl.clone()); + } + for decl in &dep.module.type_info.impls { + let mut rewritten = decl.clone(); + for method in &mut rewritten.methods { + method.function = remap + .get(method.function as usize) + .copied() + .flatten() + .ok_or_else(|| anyhow::anyhow!("bundled import '{import_path}': dangling impl method"))?; + } + if let Some(existing) = merged + .module + .type_info + .impls + .iter() + .find(|other| other.type_name == rewritten.type_name && other.trait_name == rewritten.trait_name) + && existing.methods != rewritten.methods + { + anyhow::bail!( + "bundled import '{import_path}': type `{}` is implemented in more than one module — \ + the VM keeps them apart by declaring module, the bundle cannot", + rewritten.type_name + ); + } + merged.module.type_info.impls.push(rewritten); } + let mut fns: std::collections::HashMap = std::collections::HashMap::new(); for (name, fidx) in pairs { let merged_fidx = remap .get(fidx as usize) @@ -1226,29 +1761,156 @@ fn bundle_file_imports(source: &Path, artifact: &ModuleArtifact) -> anyhow::Resu .ok_or_else(|| anyhow::anyhow!("bundled import '{import_path}': dangling fn binding"))?; fns.insert(name, merged_fidx); } - // A bundled module's constants have no initialiser in the merged - // program: its entry — the only code that would have run the - // assignment — is the one function the merge drops. Rather than splice - // an initialiser into the importing entry (which would shift every pc - // and invalidate the pc-keyed facts), fold the value into each read. - // They are constants; substituting them is what `const` means. + canonical_fns.insert(canonical, fns.clone()); + bundles.push(lk_aot::BundledImport { + path: import_path.clone(), + fns, + }); if !dep_consts.is_empty() { - let const_slots: std::collections::HashMap = dep_consts - .into_iter() - .map(|(name, value)| (slot_of(&name, &mut merged.module.globals), value)) - .collect(); - for function in &mut merged.module.functions { - fold_global_constants(function, &const_slots) - .with_context(|| format!("bundled import '{import_path}': folding constants"))?; - } + all_consts.push((import_path, dep_consts)); } + } + for (which, function) in placed.into_iter().enumerate() { + merged.module.functions.push( + function + .ok_or_else(|| anyhow::anyhow!("bundled merge left function {} unfilled", base as usize + which))?, + ); + } - bundled_fns.insert(canonical, fns.clone()); + // A second import path for a file already merged: same functions, its own + // binding table. + for (import_path, canonical) in aliases { + let fns = canonical_fns + .get(&canonical) + .cloned() + .ok_or_else(|| anyhow::anyhow!("bundled import '{import_path}': no merged module to bind to"))?; bundles.push(lk_aot::BundledImport { path: import_path, fns }); } + + // A bundled module's constants have no initialiser in the merged program: + // its entry — the only code that would have run the assignment — is the one + // function the merge drops. Rather than splice an initialiser into the + // importing entry (which would shift every pc and invalidate the pc-keyed + // facts), fold the value into each read. They are constants; substituting + // them is what `const` means. + // + // After every function is in place, which is also a fix: folding used to + // happen as each dep landed, so a dep merged *later* had its reads of an + // earlier dep's constant left as a `GetGlobal` of a slot nothing ever + // initialises. Nothing depended on that yet — a driver importing another + // driver's constant is what would have found it. + for (import_path, dep_consts) in all_consts { + let slot_of = |name: &str, globals: &mut Vec| -> u16 { + match globals.iter().position(|g| g == name) { + Some(slot) => slot as u16, + None => { + globals.push(name.to_string()); + (globals.len() - 1) as u16 + } + } + }; + let mut const_slots: std::collections::HashMap = std::collections::HashMap::new(); + for (name, value) in dep_consts { + // The name every module that did *not* rename it reads. + const_slots.insert(slot_of(&name, &mut merged.module.globals), value.clone()); + // And every name one that did. Skipped when something writes the + // alias's slot: a module of its own with that name shadows the + // import, which is what the VM does, and folding would answer the + // constant where the VM answers the variable. + for (alias, _) in renamed_items.iter().filter(|(_, original)| *original == name) { + let slot = slot_of(alias, &mut merged.module.globals); + let written = merged.module.functions.iter().any(|function| { + function.code.iter().any(|raw| { + Instr::try_from_raw(*raw) + .map(|i| i.opcode() == Opcode::SetGlobal && i.bx() == slot) + .unwrap_or(false) + }) + }); + if !written { + const_slots.insert(slot, value.clone()); + } + } + } + for function in &mut merged.module.functions { + fold_global_constants(function, &const_slots) + .with_context(|| format!("bundled import '{import_path}': folding constants"))?; + } + } Ok(BundleOutcome::Bundled(merged, bundles)) } +/// The `use { name as alias } from "path"` bindings a module declares, as +/// `(alias, name)`. Only renamed ones: an unrenamed item already reads the name +/// the bundle flattened it under. +#[cfg(feature = "aot")] +fn collect_renamed_file_items(imports: &[lk_core::stmt::ImportStmt], out: &mut Vec<(String, String)>) { + use lk_core::stmt::{ImportSource, ImportStmt}; + for import in imports { + if let ImportStmt::Items { + items, + source: ImportSource::File(_), + } = import + { + for item in items { + if let Some(alias) = &item.alias + && alias != &item.name + { + out.push((alias.clone(), item.name.clone())); + } + } + } + } +} + +/// The package dependencies an artifact imports, as `(binding, entry file)`. +/// +/// The file half of this question is [`file_import_paths`]; this is the other +/// half of the same one, and it answers every spelling that names a package: +/// `use dep;`, `use dep as name;`, `use { item } from dep;` and +/// `use * as ns from dep;`. +#[cfg(feature = "aot")] +fn package_import_modules( + source: &Path, + imports: &[lk_core::stmt::ImportStmt], +) -> anyhow::Result> { + use lk_core::stmt::ImportStmt; + + let wanted: Vec<(&str, &str)> = imports + .iter() + .filter_map(|import| match import { + ImportStmt::Module { module } => Some((module.as_str(), module.as_str())), + ImportStmt::ModuleAlias { module, alias } => Some((alias.as_str(), module.as_str())), + // An item or namespace import has no module-object binding of its + // own, so the bundle is keyed by the module's name — which is what + // the lowering looks it up by for these two spellings. + ImportStmt::Items { + source: lk_core::stmt::ImportSource::Module(module), + .. + } + | ImportStmt::Namespace { + source: lk_core::stmt::ImportSource::Module(module), + .. + } => Some((module.as_str(), module.as_str())), + _ => None, + }) + .collect(); + if wanted.is_empty() { + return Ok(Vec::new()); + } + // Discovery walks up for an `Lk.toml`, so a program with only stdlib + // imports pays one stat of its own directory and stops. + let Some(graph) = PackageGraph::discover(source)? else { + return Ok(Vec::new()); + }; + let mut out = Vec::new(); + for (binding, module) in wanted { + if let Some(found) = graph.modules.iter().find(|candidate| candidate.name == module) { + out.push((binding.to_string(), found.root.clone())); + } + } + Ok(out) +} + /// The file imports (`use "path"` in any of its forms) a module declares. #[cfg(feature = "aot")] fn file_import_paths(imports: &[lk_core::stmt::ImportStmt]) -> Vec { @@ -1293,6 +1955,54 @@ fn resolve_bundled_import(base_dir: &Path, import_path: &str) -> anyhow::Result< .ok_or_else(|| anyhow::anyhow!("bundled import not found: {import_path}")) } +/// Whether a method provably neither writes through its receiver nor keeps it. +/// +/// An allow list, and short on purpose: everything not named here is assumed to +/// write, which is the same default the rest of this scan takes. Each of these +/// answers a number or a bool computed from the receiver's current contents and +/// holds on to nothing. +/// +/// `user_methods` is what makes the list safe rather than a guess. A name an +/// `impl` in this module defines could dispatch to anything — a type may have +/// its own `contains` that sorts first — so a name that is also a user method is +/// not treated as the builtin it resembles. +#[cfg(feature = "aot")] +fn reads_only(name: &str, user_methods: &std::collections::HashSet<&str>) -> bool { + // + // Every name here has to be a method the language actually has, or the + // entry is a comment that looks like code: `char_at` and `find` sat in this + // list long after one became `get` (the accessor every sequence spells) and + // the other `index_of`, so neither had matched anything for as long as it + // had been written. `get` does *not* replace `char_at` here — on a list it + // answers an element, and an element can be a handle into the receiver, + // which is exactly the "keeps it" case this list excludes. + const PURE_READS: &[&str] = &[ + "len", + "byte_at", + "starts_with", + "ends_with", + "contains", + "index_of", + "count", + "is_empty", + // The four method names only a `String` has (`builtin_method_sig`'s + // table is the source: every other name is shared with a list, a map or + // a set, and on one of those the same name may hand back a window into + // the receiver). A string is immutable and each of these builds a fresh + // value, so neither the write nor the retain this list guards against + // is possible. + // + // `fn label(c: Cfg) -> String { return c.name.upper(); }` is what + // needed them: reading a field of a parameter taints the result, so an + // ordinary string method on a struct field made the module unbundlable. + "upper", + "lower", + "trim", + "chars", + ]; + PURE_READS.contains(&name) && !user_methods.contains(name) +} + /// Whether any function in a module can mutate or retain a container that came /// in as a parameter. /// @@ -1319,6 +2029,39 @@ fn resolve_bundled_import(base_dir: &Path, import_path: &str) -> anyhow::Result< /// a method on one (the method table is not enumerated here, so an unknown /// method is assumed to mutate), or passing one to a function that does — the /// last of which is why this is a fixpoint over the module's own functions. +#[cfg(feature = "aot")] +/// Builtins that provably cannot keep or write through an argument. +/// +/// Every operator LK desugars into a call, plus the handful that only look at a +/// value. Listed rather than inferred, and in the safe direction: a name missing +/// from here costs a module that is needlessly not bundled, a name wrongly in it +/// costs a wrong answer. +fn builtin_only_reads(name: &str) -> bool { + matches!( + name, + "__lk_shl" + | "__lk_shr" + | "__lk_shr_u" + | "__lk_bit_and" + | "__lk_bit_or" + | "__lk_bit_xor" + | "__lk_bit_not" + | "__lk_lt_u" + | "__lk_div_u" + | "__lk_mod_u" + | "__lk_u64_to_float" + | "__lk_u64_str" + | "typeof" + | "assert" + | "assert_eq" + | "assert_ne" + | "print" + | "println" + | "panic" + | "error" + ) +} + #[cfg(feature = "aot")] fn module_may_mutate_a_parameter(module: &lk_core::vm::ModuleData) -> bool { use lk_core::vm::{Instr, Opcode}; @@ -1371,6 +2114,38 @@ fn module_may_mutate_a_parameter(module: &lk_core::vm::ModuleData) -> bool { }) .collect(); + // Every method name an `impl` in this module defines. + // + // A name in here is not necessarily the builtin it looks like: nothing stops + // a type from having its own `len` that rearranges the receiver, and the + // bytecode carries no types, so a name that could dispatch to user code has + // to be treated as if it does. + let user_methods: std::collections::HashSet<&str> = module + .type_info + .impls + .iter() + .flat_map(|decl| decl.methods.iter().map(|method| method.name.as_str())) + .collect(); + + // Every function index a user method of that name compiles to. + // + // A call to one is not automatically a write: the method's own body is in + // this module, and the fixpoint below is already deciding whether *its* + // receiver is safe. Assuming the worst instead meant that a module with + // one function calling a trait method on a parameter — `fn describe(v: + // Shape) { return v.area(); }`, the whole point of a trait — could not be + // bundled at all, and every name it exports stopped resolving. + let method_bodies: std::collections::HashMap<&str, Vec> = + module.type_info.impls.iter().flat_map(|decl| decl.methods.iter()).fold( + std::collections::HashMap::new(), + |mut acc, method| { + acc.entry(method.name.as_str()) + .or_insert_with(Vec::new) + .push(method.function as usize); + acc + }, + ); + loop { let mut changed = false; for (fi, function) in module.functions.iter().enumerate() { @@ -1379,6 +2154,9 @@ fn module_may_mutate_a_parameter(module: &lk_core::vm::ModuleData) -> bool { .filter_map(|i| u8::try_from(i).ok().map(|reg| (reg, i as usize))) .filter(|(reg, _)| container_regs[fi].contains(reg)) .collect(); + // Which global name a register was loaded from, for the indirect + // call below. + let mut global_of: std::collections::HashMap = std::collections::HashMap::new(); let mark = |slot: usize, unsafe_params: &mut Vec>, changed: &mut bool| { if let Some(flag) = unsafe_params[fi].get_mut(slot) && !*flag @@ -1387,7 +2165,7 @@ fn module_may_mutate_a_parameter(module: &lk_core::vm::ModuleData) -> bool { *changed = true; } }; - for raw in &function.code { + for (pc, raw) in function.code.iter().enumerate() { let Ok(instr) = Instr::try_from_raw(*raw) else { continue; }; @@ -1401,6 +2179,42 @@ fn module_may_mutate_a_parameter(module: &lk_core::vm::ModuleData) -> bool { } else { tainted.remove(&instr.a()); } + // Which global a register came from travels with it: a + // call's callee is *moved* into the window's base, so + // without this the call below cannot name what it calls. + match global_of.get(&instr.b()).copied() { + Some(name) => global_of.insert(instr.a(), name), + None => global_of.remove(&instr.a()), + }; + } + Opcode::GetGlobal => { + // The slot is the compiler's fact where there is one; + // the instruction's `bx` is a placeholder the executor + // also declines to trust + // (`global_slot_from_fact_cache_or_instr`). + let slot = function + .performance + .global_op(pc) + .map(|fact| fact.slot) + .unwrap_or_else(|| instr.bx()); + match module.globals.get(slot as usize) { + Some(name) => global_of.insert(instr.a(), name.as_str()), + None => global_of.remove(&instr.a()), + }; + tainted.remove(&instr.a()); + } + // A container *read out of* a tainted container is part of + // it: `self.items` is the caller's list, so a push through + // it is a write to the parameter. Without this the taint + // stopped at the first field access, and a method whose + // body only ever touches `self.` looked as though it + // left `self` alone. + Opcode::GetFieldK | Opcode::GetIndex | Opcode::GetList | Opcode::GetIndexStrI => { + if let Some(&slot) = tainted.get(&instr.b()) { + tainted.insert(instr.a(), slot); + } else { + tainted.remove(&instr.a()); + } } // Writes through the container in `a`. Opcode::SetIndex | Opcode::SetIndexStrI | Opcode::SetFieldK | Opcode::ListPush => { @@ -1418,11 +2232,91 @@ fn module_may_mutate_a_parameter(module: &lk_core::vm::ModuleData) -> bool { // their own, so what reaches here is the long tail — and // the safe assumption about a method this does not know is // that it writes. + // + // Except for the ones that provably do not. What bundling + // changes is that the module gets the caller's container + // rather than a copy of it, and that difference is + // observable only through a *write* — either this function's + // own, or someone else's through a handle it kept. A method + // that reads and returns a number can do neither. + // + // Without this, a bundled module could not have a function + // that takes a `String` and looks at it: strings are + // immutable, so every string method is a read, and + // `fn log(message: String)` in a driver is the most ordinary + // thing there is. It was `uart_text(text: String)` calling + // `text.byte_at(i)` that found this. Opcode::CallMethodK => { - if let Some(&slot) = tainted.get(&instr.a()) { + // `b`, not `bx`: the receiver is `a` and the argument + // count is `c`, so the method-name constant only has a + // byte to live in. + let name = function + .consts + .strings + .get(instr.b() as usize) + .map(|s| s.as_ref()) + .unwrap_or(""); + // A user method whose every body leaves its receiver + // alone is a read, whatever its name suggests. + let user_method_is_safe = method_bodies.get(name).is_some_and(|bodies| { + bodies.iter().all(|&body| { + unsafe_params + .get(body) + .is_none_or(|params| params.first() != Some(&true)) + }) + }); + if !reads_only(name, &user_methods) + && !user_method_is_safe + && let Some(&slot) = tainted.get(&instr.a()) + { mark(slot, &mut unsafe_params, &mut changed); } } + // A fresh scalar written into a register replaces whatever + // was there, taint included. + // + // Nothing used to say so: taint was dropped only where it + // was also propagated (`Move` and the container reads), so a + // register that had once held an element read out of a + // parameter stayed tainted through every later use of that + // register — and the bytecode reuses registers hard. + // + // Listed, not inferred, and in the safe direction: a missed + // mutation is a wrong answer, an extra one only a refusal. + // So this names opcodes whose `a` is a value they have just + // computed, and leaves alone every opcode whose `a` is a + // *receiver*. + Opcode::LoadInt + | Opcode::LoadFloat + | Opcode::LoadString + | Opcode::AddInt + | Opcode::SubInt + | Opcode::MulInt + | Opcode::DivInt + | Opcode::ModInt + | Opcode::AddIntI + | Opcode::MulIntI + | Opcode::ModIntI + | Opcode::AddFloat + | Opcode::SubFloat + | Opcode::MulFloat + | Opcode::DivFloat + | Opcode::ModFloat + | Opcode::CmpInt + | Opcode::CmpNeInt + | Opcode::CmpLtInt + | Opcode::CmpLeInt + | Opcode::CmpGtInt + | Opcode::CmpGeInt + | Opcode::Not + | Opcode::Neg + | Opcode::Len + | Opcode::Contains + | Opcode::ToString + | Opcode::ConcatString => { + tainted.remove(&instr.a()); + global_of.remove(&instr.a()); + } // A direct call passes registers `b+1..b+1+argc`; taint // flows to the callee's parameter of the same position. Opcode::CallDirect => { @@ -1443,6 +2337,21 @@ fn module_may_mutate_a_parameter(module: &lk_core::vm::ModuleData) -> bool { } // An indirect call could be anything, including a closure // that keeps the handle. + // A call through a register: the callee is whatever that + // register holds, so an argument handed to it is assumed to + // be kept — unless the register can be named and names a + // builtin that provably only reads. + // + // The operators are why this matters. `(bits >> shift) & 1` + // desugars to calls of `__lk_shr` and `__lk_bit_and`, so a + // value read out of a container parameter and then shifted + // looked exactly like one handed to an unknown function — + // and `drivers/text`, whose own comment reads "`font` is + // only ever read, which is what keeps this module + // bundlable", could not be bundled. That is what stopped + // `bare-metal-x86` from building. + Opcode::Call | Opcode::CallNamed + if global_of.get(&instr.a()).copied().is_some_and(builtin_only_reads) => {} Opcode::Call | Opcode::CallNamed => { let base = instr.a(); for offset in 1..=instr.c() { @@ -1476,6 +2385,29 @@ enum BundledConst { Nil, } +/// One integer operand of a top-level fold, or a diagnostic naming what it was. +/// +/// A register holding a float or a string here is not a bug in the scan — it is +/// a module whose top level does arithmetic this does not evaluate, and saying +/// which register held what is the difference between "fix your constant" and +/// "the bundler is broken". +#[cfg(feature = "aot")] +fn int_operand( + reg_const: &std::collections::HashMap, + reg: u8, + import_path: &str, +) -> anyhow::Result { + match reg_const.get(®) { + Some(BundledConst::Int(value)) => Ok(*value), + Some(other) => anyhow::bail!( + "bundled import '{import_path}' does integer arithmetic at its top level on a {other:?}, which is not a constant this can evaluate" + ), + None => anyhow::bail!( + "bundled import '{import_path}' does integer arithmetic at its top level on a value that is not a constant" + ), + } +} + /// Rewrites every `GetGlobal` of a bundled constant into a load of its value. /// /// One instruction replaces one instruction, so pcs — and the facts keyed by diff --git a/cli/src/main_test.rs b/cli/src/main_test.rs index 4819226c..e239b0e4 100644 --- a/cli/src/main_test.rs +++ b/cli/src/main_test.rs @@ -2,38 +2,31 @@ mod tests { use crate::*; use lk_core::vm::VmRuntimeMetrics; + /// A CLI path argument is taken as written, `..` included. + /// + /// There used to be a `sanitize_path` refusing any `..`, and four tests + /// pinning it — including one asserting that `/etc/passwd` **is** allowed. + /// The two halves say the guard stopped nothing: anything `..` reaches, an + /// absolute path reaches too, and every caller is an argument the person + /// running the command typed. What it did stop was `lk ../script.lk` from a + /// subdirectory. #[test] - fn test_sanitize_path_allows_simple_relative() { - let p = sanitize_path("foo/bar.lk").expect("relative path should be allowed"); - assert_eq!(p, PathBuf::from("foo/bar.lk")); - } - - #[test] - fn test_sanitize_path_rejects_parent_dir() { - let err = sanitize_path("foo/../bar.lk").unwrap_err(); - assert!(err.to_string().contains("Parent directory components")); - } - - #[cfg(unix)] - #[test] - fn test_sanitize_path_allows_absolute_unix() { - let p = sanitize_path("/etc/passwd").expect("absolute path should be allowed"); - assert_eq!(p, PathBuf::from("/etc/passwd")); - } - - #[cfg(windows)] - #[test] - fn test_sanitize_path_allows_absolute_windows() { - let p = sanitize_path(r"C:\\Windows").expect("absolute path should be allowed"); - assert_eq!(p, PathBuf::from(r"C:\\Windows")); + fn a_path_argument_is_taken_as_written() { + for raw in ["foo/bar.lk", "../bar.lk", "foo/../bar.lk", "/etc/passwd"] { + assert_eq!(parse_path_arg(raw), Ok(PathBuf::from(raw)), "{raw}"); + } } + /// `lk compile ../bar.lk` compiles `../bar.lk`. + /// + /// This asserted the opposite until the `..` guard came out — see + /// `a_path_argument_is_taken_as_written`. #[test] - fn test_cli_args_rejects_parent_dir_in_compile() { - let args = CliArgs::try_parse_from(["lk", "compile", "foo/../bar.lk"]).expect("should parse"); + fn test_cli_args_accepts_parent_dir_in_compile() { + let args = CliArgs::try_parse_from(["lk", "compile", "../bar.lk"]).expect("should parse"); if let Some(Commands::Compile { positional, .. }) = args.command { - let err = split_compile_args(&positional).expect_err("should reject parent dirs"); - assert!(err.to_string().contains("Parent directory components")); + let (_, file, _) = split_compile_args(&positional).expect("a path is a path"); + assert_eq!(file, PathBuf::from("../bar.lk")); } else { panic!("expected compile command"); } @@ -68,7 +61,10 @@ mod tests { fn test_vm_profile_line_contains_benchmark_fields() { let line = vm_profile_line(VmRuntimeMetrics { opcode_steps: 11, - call_ops: 2, + call_ops: 9, + native_call_ops: 2, + exact_call_ops: 3, + method_call_ops: 1, branch_ops: 3, typed_branch_ops: 4, container_ops: 5, @@ -76,38 +72,52 @@ mod tests { map_ops: 7, string_ops: 8, index_key_metrics: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], - register_write_sources: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - copy_policy_heap_clones: 9, - register_copy_heap_clones: 10, - local_copy_heap_clones: 12, - local_load_heap_clones: 13, - local_store_heap_clones: 14, - const_load_heap_clones: 15, - call_arg_heap_clones: 16, - container_copy_heap_clones: 17, + register_write_sources: [1, 2, 3, 4, 5, 6, 7, 8, 9], + register_writes: 45, ..VmRuntimeMetrics::default() }); assert!(line.starts_with("VM profile: ")); assert!(line.contains("opcode_steps=11")); - assert!(line.contains("calls=2")); + assert!(line.contains("calls=9")); + // The breakdown, and the remainder that makes the parts add up: 2 + 3 + // + 1 classified out of 9, so 3 calls the executor did not classify. + // Without the remainder a reader cannot tell "none of these kinds" from + // "this build does not measure it". + assert!(line.contains("call_kinds=native:2,exact:3,method:1,other:3"), "{line}"); assert!(line.contains("branches=3")); assert!(line.contains("typed_branches=4")); assert!(line.contains("containers=5")); - assert!(line.contains("write_sources=other:10,string:9,global:8,call_return:7,index:6,container:5")); + // No `other:` any more: every dispatch arm that writes a register + // classifies it, so a catch-all bucket could only ever print zero. + assert!(line.contains("write_sources=string:9,global:8,call_return:7,index:6,container:5,compare:4")); assert!(line.contains( "index_keys=known_string_key:12,dynamic_register_key:11,dynamic_int_key:10,dynamic_short_string_key:9,dynamic_object_key:8,dynamic_other_key:7" )); - assert!(line.contains("val_clones=9")); - assert!(line.contains("heap_clones=9")); - assert!(line.contains("copy_policy_heap_clones=9")); - assert!(line.contains("register_copy_heap_clones=10")); - assert!(line.contains("local_copy_heap_clones=12")); - assert!(line.contains("local_load_heap_clones=13")); - assert!(line.contains("local_store_heap_clones=14")); - assert!(line.contains("const_load_heap_clones=15")); - assert!(line.contains("call_arg_heap_clones=16")); - assert!(line.contains("container_copy_heap_clones=17")); + // The ten `*_heap_clones` fields this used to pin are gone. They were + // written only by `record_copy_policy_clone`, which had no caller — and + // because this test builds the struct by hand, it happily printed 9/10/12 + // while every real run printed ten zeros in a row. A formatter test cannot + // tell you a counter is dead; only a caller scan can. + assert!(line.contains("register_writes=45")); + } + + #[test] + fn the_profile_report_says_so_when_it_cannot_profile() { + // `LK_VM_PROFILE=1` used to be answered by a well-formed profile of zeros + // on a binary with no counters compiled in — `opcode_steps=0` right after + // running four thousand of them. The report has to agree with the build it + // is part of, so this test is a `cfg` pair rather than a value check: the + // one that can measure must print numbers, the one that can't must say it + // can't. + let report = vm_profile_report(); + if vm_runtime_metrics_enabled() { + assert!(report.starts_with("VM profile: "), "{report}"); + assert!(!report.contains("unavailable"), "{report}"); + } else { + assert!(report.contains("unavailable"), "{report}"); + assert!(report.contains("--features vm-profile"), "{report}"); + } } #[test] @@ -117,7 +127,7 @@ mod tests { let args = CliArgs::try_parse_from(["lk", "compile", "bytecode", "foo.lk"]).expect("should parse positional target"); if let Some(Commands::Compile { positional, .. }) = args.command { - let (target, file) = split_compile_args(&positional).expect("should split compile args"); + let (target, file, _out) = split_compile_args(&positional).expect("should split compile args"); assert_eq!(target, CompileMode::Bytecode); assert_eq!(file, PathBuf::from("foo.lk")); } else { @@ -162,7 +172,7 @@ mod tests { fn test_cli_args_compile_default_target_is_exe() { let args = CliArgs::try_parse_from(["lk", "compile", "foo.lk"]).expect("should parse default compile"); if let Some(Commands::Compile { positional, .. }) = args.command { - let (target, file) = split_compile_args(&positional).expect("should split compile args"); + let (target, file, _out) = split_compile_args(&positional).expect("should split compile args"); assert_eq!(target, CompileMode::Exe); assert_eq!(file, PathBuf::from("foo.lk")); } else { @@ -186,7 +196,10 @@ mod tests { let main = temp.path().join("main.lk"); std::fs::write(&main, "return 1;\n").expect("write main.lk"); - let (target, file) = split_compile_args_with_cwd(&[], temp.path()).expect("should find main.lk"); + let (target, file, output) = split_compile_args_with_cwd(&[], temp.path()).expect("should find main.lk"); + // A loose `./main.lk` keeps the old rule: `main.lk` -> `main` beside it + // is what naming the file would have done anyway. + assert_eq!(output, None); assert_eq!(target, CompileMode::Exe); assert_eq!(file, main.canonicalize().expect("canonical main")); @@ -205,10 +218,20 @@ mod tests { let main = src.join("main.lk"); std::fs::write(&main, "return 1;\n").expect("write src/main.lk"); - let (target, file) = split_compile_args_with_cwd(&[], temp.path()).expect("should find src/main.lk"); + let (target, file, output) = split_compile_args_with_cwd(&[], temp.path()).expect("should find src/main.lk"); assert_eq!(target, CompileMode::Exe); assert_eq!(file, main.canonicalize().expect("canonical main")); + // A build output does not belong in `src/`. The entry is + // `/src/main.lk` and the output used to be that path without its + // extension — a 20 MB executable dropped next to the source it was + // built from, where the next `git add .` picks it up. It goes to the + // package root, named after the package directory. + let package_root = main.parent().and_then(std::path::Path::parent).expect("package root"); + assert_eq!( + output.expect("a package build has an implicit output"), + package_root.join(package_root.file_name().expect("package directory name")) + ); } #[test] @@ -218,7 +241,7 @@ mod tests { std::fs::write(&main, "return 1;\n").expect("write main.lk"); let args = vec!["bytecode".to_string()]; - let (target, file) = split_compile_args_with_cwd(&args, temp.path()).expect("should find main.lk"); + let (target, file, _out) = split_compile_args_with_cwd(&args, temp.path()).expect("should find main.lk"); assert_eq!(target, CompileMode::Bytecode); assert_eq!(file, main.canonicalize().expect("canonical main")); @@ -268,7 +291,8 @@ mod tests { let main = src.join("main.lk"); std::fs::write(&main, "return 1;\n").expect("write app main"); - let (target, file) = split_compile_args_with_cwd(&[], temp.path()).expect("should find single workspace app"); + let (target, file, _out) = + split_compile_args_with_cwd(&[], temp.path()).expect("should find single workspace app"); assert_eq!(target, CompileMode::Exe); assert_eq!(file, main.canonicalize().expect("canonical main")); diff --git a/cli/src/native_compile.rs b/cli/src/native_compile.rs index 4b5b2157..229f26d3 100644 --- a/cli/src/native_compile.rs +++ b/cli/src/native_compile.rs @@ -2,6 +2,7 @@ use super::*; /// The lk-api C-ABI staticlib (VM + `lk_hybrid_*` bridge), built on demand. /// Shared by the Tier 0 bundle and the Tier 1 hybrid link. +#[cfg(feature = "aot")] pub(super) fn ensure_lk_api_staticlib() -> anyhow::Result { // A caller that supplies its own `lkrt` (`LKRT_STATICLIB`) must be able to // supply a matching `lk-api`. Both archives statically link `std`, so two @@ -18,7 +19,11 @@ pub(super) fn ensure_lk_api_staticlib() -> anyhow::Result { return Ok(path); } let workspace = workspace_root()?; - let staticlib = workspace.join("target/release/liblk_api.a"); + // `lk-api-cabi`, not `lk-api`: the archive was split into its own crate so + // that an ordinary `cargo build`/`cargo test` stops emitting 172MB of it + // for a linker path it never takes. See that crate's docs. The `ffi` + // feature now rides along in its manifest rather than on this command line. + let staticlib = workspace.join("target/release/liblk_api_cabi.a"); if !staticlib.exists() { eprintln!("building lk-api staticlib (one-time)…"); } @@ -27,9 +32,9 @@ pub(super) fn ensure_lk_api_staticlib() -> anyhow::Result { // sub-second no-op under cargo's fingerprinting. let status = std::process::Command::new("cargo") .current_dir(&workspace) - .args(["build", "-p", "lk-api", "--features", "ffi", "--release"]) + .args(["build", "-p", "lk-api-cabi", "--release"]) .status() - .map_err(|e| anyhow::anyhow!("cargo build lk-api: {e}"))?; + .map_err(|e| anyhow::anyhow!("cargo build lk-api-cabi: {e}"))?; if !status.success() { anyhow::bail!("failed to build lk-api staticlib"); } @@ -37,6 +42,7 @@ pub(super) fn ensure_lk_api_staticlib() -> anyhow::Result { } /// Escape a string for embedding as a C double-quoted string literal. +#[cfg(feature = "aot")] pub(super) fn c_escape(s: &str) -> String { let mut out = String::with_capacity(s.len() + 16); for ch in s.chars() { @@ -52,9 +58,12 @@ pub(super) fn c_escape(s: &str) -> String { out } -pub(super) fn compile_instr_module(path: &Path) -> anyhow::Result<()> { +pub(super) fn compile_instr_module(path: &Path, output: Option<&Path>) -> anyhow::Result<()> { let artifact = compile_instr_artifact(path)?; - let output = path.with_extension("lkm"); + // A package build's output belongs at the package root, not in `src/` — + // see `split_compile_args_with_cwd`. `.lkm` is as much a build artifact as + // the executable is. + let output = output.map_or_else(|| path.with_extension("lkm"), |dir| dir.with_extension("lkm")); std::fs::write(&output, artifact.to_json_string()?) .with_context(|| format!("write Instr module {}", output.display()))?; println!("{}", output.display()); @@ -83,6 +92,7 @@ pub(super) fn compile_instr_artifact_with_dependencies(path: &Path) -> anyhow::R // for `lk FILE`. Without this the two paths disagreed on which programs are // valid: `let x: Int = "s"; println(x);` failed at run time under the VM but // compiled and *ran* fine as a native binary, printing `s`. + crate::ensure_stdlib_signatures(); let mut type_checker = lk_core::typ::TypeChecker::new(); // Cross-file signatures first: without them an imported call is unchecked // here and fails much later in the lowering, naming an opcode. @@ -202,7 +212,7 @@ pub(super) fn compile_executable(path: &Path, output: Option<&Path>) -> anyhow:: } // The Cranelift native backend covers only a lowerable subset. // Instead of failing the whole program (the old all-or-nothing — - // plan 问题 2), fall back to the Tier 0 VM bundle, which embeds the + // plan issue 2), fall back to the Tier 0 VM bundle, which embeds the // interpreter and runs any valid program. `lk compile` thus never // rejects a valid program: native when possible, VM-embed otherwise. diagnostic::warning(format!( @@ -245,12 +255,24 @@ pub(super) fn compile_native_executable_from_artifact( artifact: &ModuleArtifact, ) -> anyhow::Result { let bundled = bundle_file_imports(path, artifact)?; + // Why the imports were not bundled, if they were not. + // + // Kept because the failure it causes names a symptom. Without bundling, a + // call into an imported module is a `GetGlobal` that resolves to nothing, so + // the program falls back and the warning says "global `extend` does not + // resolve" — sending the reader to look for a missing import when the cause + // is a module that was deliberately not merged, and for a specific reason + // that was known one function ago. `compile object:` already reports the + // cause because it has no fallback; this path had the same answer and + // discarded it. + let mut declined: Option = None; let (artifact, bundles): (&ModuleArtifact, Vec) = match &bundled { crate::BundleOutcome::Bundled(merged, bundles) => (merged, bundles.clone()), crate::BundleOutcome::Declined(reason) => { if native_trace_enabled() { eprintln!("clif: not bundling imports of {}: {reason}", path.display()); } + declined = Some(reason.clone()); (artifact, Vec::new()) } crate::BundleOutcome::Nothing => (artifact, Vec::new()), @@ -259,7 +281,19 @@ pub(super) fn compile_native_executable_from_artifact( // codegen/validation bug (propagate). let clif = match lk_aot::compile_artifact_to_clif_object(artifact, &bundles)? { Ok(clif) => clif, - Err(reason) => return Ok(NativeOutcome::Unsupported(reason)), + Err(reason) => { + // The decline goes with it. Without bundling, a call into an + // imported module is a `GetGlobal` that resolves to nothing, so what + // the user is about to be told is "global `extend` does not resolve" + // — a symptom that sends them looking for a missing import, when the + // cause is a module deliberately not merged for a reason that was + // known one function ago. `compile object:` already says the cause, + // because it has no fallback to hide behind. + return Ok(NativeOutcome::Unsupported(match declined { + Some(why) => format!("{reason}; the imports were not bundled because {why}"), + None => reason, + })); + } }; if native_trace_enabled() { eprintln!("clif: native object for {}", path.display()); diff --git a/cli/src/paths.rs b/cli/src/paths.rs index e9bf894e..e1ebfe1f 100644 --- a/cli/src/paths.rs +++ b/cli/src/paths.rs @@ -1,5 +1,5 @@ use std::fs; -use std::path::{Component, Path, PathBuf}; +use std::path::{Path, PathBuf}; use anyhow::Context; use lk_core::package::{MANIFEST_FILE, Manifest, PackageGraph, find_manifest}; @@ -11,22 +11,21 @@ fn read_file_content(path: &str) -> anyhow::Result { std::fs::read_to_string(path).map_err(|e| anyhow::anyhow!("Failed to read file '{}': {}", path, e)) } -pub(crate) fn sanitize_path(raw: &str) -> anyhow::Result { - let p = Path::new(raw); - - for comp in p.components() { - if matches!(comp, Component::ParentDir) { - return Err(anyhow::anyhow!( - "Parent directory components ('..') are not allowed in file paths." - )); - } - } - - Ok(p.to_path_buf()) -} - -pub(crate) fn parse_sanitized_path(raw: &str) -> Result { - sanitize_path(raw).map_err(|e| e.to_string()) +/// A CLI path argument, taken as written. +/// +/// This used to refuse any path containing `..`, under the name +/// `sanitize_path`. It protected nobody: an **absolute** path was allowed +/// through the same check, so anything `..` could reach was already reachable — +/// and every call site is an argument the person running the command typed. +/// What it did do was refuse the most ordinary invocation there is: +/// `lk ../script.lk` from a subdirectory, and likewise `lk check ../x.lk`, +/// `lk fmt ../dir` and `lk compile -o ../out`. +/// +/// Traversal guards belong where the path comes from somewhere the user is not +/// choosing — `package::cache_dir_for_source` builds a directory out of a +/// dependency's URL, and *that* refuses `..`. +pub(crate) fn parse_path_arg(raw: &str) -> Result { + Ok(PathBuf::from(raw)) } pub(crate) fn expand_program_file(path: &Path) -> anyhow::Result { @@ -60,30 +59,77 @@ pub(crate) fn parse_options_for_file(path: &Path) -> anyhow::Result anyhow::Result<(CompileMode, PathBuf)> { +pub(crate) fn split_compile_args(args: &[String]) -> anyhow::Result<(CompileMode, PathBuf, Option)> { let cwd = std::env::current_dir().context("read current directory")?; split_compile_args_with_cwd(args, &cwd) } -pub(crate) fn split_compile_args_with_cwd(args: &[String], cwd: &Path) -> anyhow::Result<(CompileMode, PathBuf)> { +/// The third element is the **default output path** when the entry was resolved +/// from a manifest rather than named on the command line. +/// +/// A build output does not belong in `src/`. `lk compile` in a package resolves +/// the entry to `/src/main.lk`, and the output path was "the entry without +/// its extension" — so `lk compile` dropped a 20 MB executable (and +/// `lk compile bytecode` a `.lkm`) *inside the source directory*, next to the +/// file it was built from, where the next `git add .` picks it up. A file the +/// user named keeps that rule (`lk compile foo.lk` → `foo`, which is what +/// naming a file means); a package build goes to the package root, the way +/// `go build` puts its binary in the module directory rather than under `src`. +pub(crate) fn split_compile_args_with_cwd( + args: &[String], + cwd: &Path, +) -> anyhow::Result<(CompileMode, PathBuf, Option)> { match args.len() { - 0 => Ok((CompileMode::Exe, default_compile_entry(cwd)?)), + 0 => { + let (entry, output) = default_compile_entry_with_output(cwd)?; + Ok((CompileMode::Exe, entry, output)) + } 1 => { if let Some(mode) = parse_compile_mode(&args[0])? { - return Ok((mode, default_compile_entry(cwd)?)); + let (entry, output) = default_compile_entry_with_output(cwd)?; + return Ok((mode, entry, output)); } - Ok((CompileMode::Exe, sanitize_path(&args[0])?)) + Ok((CompileMode::Exe, PathBuf::from(&args[0]), None)) } 2 => { let mode = parse_compile_mode(&args[0])?.ok_or_else(|| anyhow::anyhow!("Unknown compile target '{}'", args[0]))?; - let file = sanitize_path(&args[1])?; - Ok((mode, file)) + let file = PathBuf::from(&args[1]); + Ok((mode, file, None)) } _ => anyhow::bail!("compile requires [FILE], [TARGET], or [TARGET FILE]"), } } +/// The implicit entry plus where its output belongs. +/// +/// `None` for the `./main.lk` case: that is a loose file in the current +/// directory, and `main.lk` → `main` beside it is what naming it would have +/// done anyway. +fn default_compile_entry_with_output(cwd: &Path) -> anyhow::Result<(PathBuf, Option)> { + let entry = default_compile_entry(cwd)?; + let root = cwd.canonicalize().unwrap_or_else(|_| cwd.to_path_buf()); + // Resolved through `/src/main.lk`: the output goes to `/`, + // named after the *package directory* rather than "main", which is what a + // built program is called. + let from_src = entry.parent().is_some_and(|dir| dir.ends_with("src")); + if !from_src { + return Ok((entry, None)); + } + let name = entry + .parent() + .and_then(Path::parent) + .and_then(Path::file_name) + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or_else(|| "main".to_string()); + let package_root = entry + .parent() + .and_then(Path::parent) + .map(Path::to_path_buf) + .unwrap_or(root); + Ok((entry, Some(package_root.join(name)))) +} + fn parse_compile_mode(raw: &str) -> anyhow::Result> { let target = raw.to_ascii_lowercase(); match target.as_str() { diff --git a/cli/src/pkg.rs b/cli/src/pkg.rs index f1d23c0a..09eb2523 100644 --- a/cli/src/pkg.rs +++ b/cli/src/pkg.rs @@ -35,6 +35,44 @@ fn load_project_manifest() -> anyhow::Result<(PathBuf, Manifest)> { Ok((manifest_path, manifest)) } +/// What a `` argument names. +/// +/// Decided by shape, and an unrecognised shape is **refused**. It used to be +/// written into the manifest verbatim as a GitHub repo, so +/// `lk pkg add dep ../dep` produced `dep = "../dep"` and the failure arrived +/// much later, from git, as +/// `repository 'https://github.com/../dep.git/' not found`. The manifest has +/// had `path` and `git` since it existed; only `add` could not spell them. +enum AddedSource { + GitHub(String), + Git(String), + Path(String), +} + +fn classify_source(source: &str) -> anyhow::Result { + let trimmed = source.trim(); + if trimmed.contains("://") || trimmed.starts_with("git@") { + return Ok(AddedSource::Git(trimmed.to_string())); + } + if trimmed.starts_with("./") || trimmed.starts_with("../") || trimmed.starts_with('/') || trimmed.starts_with('~') { + return Ok(AddedSource::Path(trimmed.to_string())); + } + // `owner/repo`: exactly one separator, both halves present, no spaces. + let mut parts = trimmed.split('/'); + if let (Some(owner), Some(repo), None) = (parts.next(), parts.next(), parts.next()) + && !owner.is_empty() + && !repo.is_empty() + && !trimmed.contains(char::is_whitespace) + { + return Ok(AddedSource::GitHub(trimmed.to_string())); + } + anyhow::bail!( + "`{source}` is not a dependency source. Write `owner/repo` for GitHub, a URL \ + (`https://…` or `git@…`) for any other git host, or a path starting with `./`, `../` or `/` \ + for a local package" + ) +} + fn add_dependency( name: String, source: String, @@ -43,16 +81,34 @@ fn add_dependency( rev: Option, ) -> anyhow::Result<()> { let (manifest_path, mut manifest) = load_project_manifest()?; - let spec = if branch.is_none() && tag.is_none() && rev.is_none() { - DependencySpec::GitHub(source) - } else { - DependencySpec::Detailed(DetailedDependency { - github: Some(source), + let pinned = branch.is_some() || tag.is_some() || rev.is_some(); + let spec = match classify_source(&source)? { + // A local package has no revision to pin, and silently keeping one in + // the manifest would read as if it did. + AddedSource::Path(path) if pinned => { + anyhow::bail!("--branch/--tag/--rev do not apply to the path dependency `{path}`") + } + AddedSource::Path(path) => DependencySpec::Detailed(DetailedDependency { + path: Some(path), + ..Default::default() + }), + AddedSource::Git(url) => DependencySpec::Detailed(DetailedDependency { + git: Some(url), branch, tag, rev, ..Default::default() - }) + }), + // The bare-string form is the manifest's shorthand for GitHub, and it + // only survives when there is nothing else to say. + AddedSource::GitHub(repo) if !pinned => DependencySpec::GitHub(repo), + AddedSource::GitHub(repo) => DependencySpec::Detailed(DetailedDependency { + github: Some(repo), + branch, + tag, + rev, + ..Default::default() + }), }; manifest.dependencies.insert(name, spec); manifest.write(&manifest_path)?; @@ -84,8 +140,13 @@ fn fetch_dependencies(only: Option) -> anyhow::Result<()> { let source = spec .git_url() .ok_or_else(|| anyhow::anyhow!("dependency '{name}' has no git source"))?; - let dir = cache_dir_for_source(&source); - fetch_git_dependency(&source, &dir, &spec)?; + let dir = cache_dir_for_source(&source)?; + // Named here: `git failed with status exit status: 128` says which + // *process* failed, not which dependency — and with several of them the + // reader has to guess. git's own message above already explains the + // cause; this says what LK was doing when it appeared. + fetch_git_dependency(&source, &dir, &spec) + .with_context(|| format!("fetching dependency `{name}` from {source}"))?; let rev = git_output(&dir, ["rev-parse", "HEAD"])?; locked.insert( name.clone(), @@ -145,7 +206,12 @@ fn fetch_git_dependency(source: &str, dir: &Path, spec: &DependencySpec) -> anyh fn git_status(cmd: &mut Command) -> anyhow::Result<()> { let status = cmd.status().context("run git")?; if !status.success() { - anyhow::bail!("git failed with status {status}"); + // git has already printed its own diagnosis to stderr; repeating the + // exit status adds nothing a reader can act on, so this only names the + // command. The caller supplies which dependency it was for. + let program = cmd.get_program().to_string_lossy().into_owned(); + let args: Vec = cmd.get_args().map(|arg| arg.to_string_lossy().into_owned()).collect(); + anyhow::bail!("`{program} {}` failed (see git's message above)", args.join(" ")); } Ok(()) } @@ -177,7 +243,7 @@ fn print_package_tree() -> anyhow::Result<()> { println!(" {} -> {}", module.name, module.root.display()); } for missing in &graph.missing { - println!(" {} -> ", missing); + println!(" {} -> <{}>", missing.name, missing.advice()); } Ok(()) } @@ -185,12 +251,26 @@ fn print_package_tree() -> anyhow::Result<()> { fn check_package() -> anyhow::Result<()> { let cwd = std::env::current_dir().context("read current directory")?; let graph = PackageGraph::discover(&cwd)?.ok_or_else(|| anyhow::anyhow!("No {MANIFEST_FILE} found"))?; + if let Some(package) = &graph.manifest.package { + lk_core::package::validate_package_section(package)?; + } graph.validate_macro_distribution()?; if graph.missing.is_empty() { println!("package check ok"); } else { - println!( - "package check ok ({} missing dependencies; run lk pkg fetch)", + for missing in &graph.missing { + println!(" {} -> {}", missing.name, missing.advice()); + } + // The per-dependency lines above already say what each one needs; a + // summary that repeats one of the two answers for all of them is how a + // path dependency got told to run `lk pkg fetch`. + // + // And it **fails**. "package check ok (1 dependencies unresolved)" said + // two opposite things in one line and exited 0, so a CI step running + // `lk pkg check` passed on a package that cannot run — which is the one + // question this command exists to answer. + anyhow::bail!( + "{} dependencies unresolved — the package cannot run until they are", graph.missing.len() ); } diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 4275bb18..25cf61a6 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -4,10 +4,12 @@ use std::{ sync::Arc, }; +use lk_core::token::{Token, Tokenizer}; use lk_core::vm::ModuleResolver; use lk_core::{ + macro_system::MacroDefinitions, module::ModuleRegistry, - syntax::{ParseOptions, parse_program_source}, + syntax::{ParseOptions, ProgramExpansion, expand_program_source, parse_program_source}, typ::TypeChecker, vm::{ReplExecutionResult, ReplVmSession, VmContext}, }; @@ -17,6 +19,64 @@ use crate::{ startup_trace, }; +/// What an input would have defined, had it succeeded. +/// +/// Only a declaring input is worth saying "nothing was defined" about: `g()` +/// failing defines nothing either way. And only a *body-bearing* declaration +/// gets the second sentence — for `let q = Q { b: 1 };` the reason is simply +/// that the input failed, and the rule about bodies would be a wrong +/// explanation rather than an unhelpful one. +/// +/// Re-parses, which only happens on the error path — and the input is known to +/// parse, because a parse failure returns before this. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum InputDeclares { + Nothing, + /// A `let` / `:=` / type declaration: a name, no body. + AName, + /// A `fn` or `impl`: a body, compiled against what the session has *now*. + ABody, +} + +fn input_declares(source: &str, options: ParseOptions) -> InputDeclares { + use lk_core::stmt::Stmt; + fn unwrap_attributes(stmt: &Stmt) -> &Stmt { + match stmt { + Stmt::Attributed { item, .. } => unwrap_attributes(item), + other => other, + } + } + // The session's options, not the defaults: an input that uses a macro the + // session defined does not parse without them, and would be reported as + // declaring nothing. + let Ok(program) = parse_program_source(source, options) else { + return InputDeclares::Nothing; + }; + let mut declares = InputDeclares::Nothing; + for stmt in &program.statements { + match unwrap_attributes(stmt) { + // A `struct S` brings a generated `fn S$new` with it + // (`stmt::struct_ctors`), and that body reads nothing but its own + // parameters — it cannot fail for a name the session lacks. Judging + // by the *source* declaration keeps `struct Q { … }` out of the + // body case. + Stmt::Function { name, .. } if lk_core::stmt::struct_ctors::constructed_struct_name(name).is_some() => { + declares = InputDeclares::AName; + } + Stmt::Function { .. } | Stmt::Impl { .. } => return InputDeclares::ABody, + Stmt::Struct { .. } + | Stmt::Trait { .. } + | Stmt::TypeAlias { .. } + | Stmt::Let { .. } + | Stmt::Define { .. } => { + declares = InputDeclares::AName; + } + _ => {} + } + } + declares +} + pub(crate) enum ReplInput { Submit(String), Continue, @@ -32,6 +92,15 @@ enum ReplStep { struct ReplSession { vm: ReplVmSession, completion_state: ReplCompletionState, + /// `macro_rules!` definitions entered so far. + /// + /// Macros are expanded during *parsing*, and the REPL parses each input as + /// its own source text — so a definition used to last exactly as long as + /// the line that made it. `macro_rules! m { … }` was accepted in silence + /// and `m!()` on the next line answered "no macro named `m` is defined", + /// while `fn`, `struct`, `impl` and `let` all persisted. Carried into the + /// next parse, they behave like every other definition the session holds. + macro_definitions: MacroDefinitions, } impl ReplSession { @@ -53,15 +122,28 @@ impl ReplSession { .with_resolver(resolver) .with_type_checker(Some(TypeChecker::new_strict())); startup.step("vm context created"); - let vm = ReplVmSession::new(ctx, TypeChecker::new()); + let mut vm = ReplVmSession::new(ctx, TypeChecker::new()); + // An import path is relative to the working directory here, the same as + // for `lk FILE` — this is what lets `use { Pt } from "lib";` bring the + // *type* and not only the value. + vm.set_base_dir(cwd); startup.step("repl vm session created"); Ok(Self { vm, completion_state: ReplCompletionState::new(), + macro_definitions: MacroDefinitions::default(), }) } + /// Parse options carrying what the session has defined so far. + fn parse_options(&self) -> ParseOptions { + ParseOptions { + carried_macro_definitions: self.macro_definitions.clone(), + ..ParseOptions::default() + } + } + fn completion_state(&self) -> ReplCompletionState { self.completion_state.clone() } @@ -75,10 +157,28 @@ impl ReplSession { return self.execute_command(final_src); } - let Some(result) = (match parse_program_source(final_src, ParseOptions::default()) { - Ok(program) => Some(self.vm.execute_program(&program)), - Err(parse_err) => self.execute_as_expression(final_src, parse_err), - }) else { + // **Expression first.** A REPL's contract is "type a thing, see its + // value", and deciding that by whether the input also happens to be a + // valid *statement* gets it right only by accident. + // + // It used to try the program parse first and fall back to the + // expression wrapper only when that failed. Most inputs need a + // semicolon to be a statement, so most inputs fell through and echoed — + // but everything that is a statement on its own printed nothing at all: + // + // > if true { 1 } else { 2 } (nothing) + // > S { x: 8 } (nothing) + // > match n { 1 => "one", _ => "" } (nothing) + // + // while `[1, 2, 3]` and `x + 1` printed, because a bare list or a bare + // binary expression is not a statement. The value was computed and + // dropped every time. + // + // The wrapper is `return (…)`, so an input carrying a trailing `;`, a + // `let`, a declaration or several statements does not parse as one and + // runs as a program — which is also how `x + 1;` keeps suppressing its + // own echo. + let Some(result) = self.execute_input(final_src) else { return ReplStep::Continue; }; @@ -89,7 +189,39 @@ impl ReplSession { println!("{}", result.display_first_return()); } } - Err(e) => diagnostic::error(&e), + Err(e) => { + diagnostic::error(&e); + let declares = input_declares(final_src, self.parse_options()); + if declares != InputDeclares::Nothing { + // The input takes effect whole or not at all: the session's + // state is only updated after `execute_program` returns. + // Without saying so, a failed `fn` definition produces two + // errors one line apart with nothing connecting them — + // + // > fn g() -> Int { return LATER; } + // Error: undefined name `LATER` + // > g() + // Error: undefined function `g` + // + // and the second reads as a second, unrelated bug. + // + // The rule it explains: a body is compiled when the line is + // entered, so it can only read names that already exist. In + // a file the whole program is compiled at once, so a body + // there may read a binding declared below it. Making the + // REPL match would mean compiling a read of a name that may + // never be bound and answering nil for it — the silent + // wrong answer `stmt::init_order` exists to refuse. + eprint!(" nothing from this input was defined"); + if declares == InputDeclares::ABody { + eprint!( + " — a body is compiled as you enter it, so it can only read names the \ + session already has" + ); + } + eprintln!("."); + } + } } ReplStep::Continue } @@ -108,21 +240,30 @@ impl ReplSession { } } - fn execute_as_expression( - &mut self, - source: &str, - statement_error: lk_core::token::ParseError, - ) -> Option> { - let normalized = normalize_binary_signs(source); - let wrapped = format!("println(({}));", normalized); - match parse_program_source(&wrapped, ParseOptions::default()) { - Ok(program) => Some(self.vm.execute_program(&program)), - Err(_expr_err) => { - diagnostic::parse_error(&statement_error, source); + /// One expression, else a program; `None` once the parse error is reported. + fn execute_input(&mut self, source: &str) -> Option> { + let wrapped = expression_program_source(source); + if let Ok(expansion) = expand_program_source(&wrapped, self.parse_options()) { + return Some(self.run_expansion(expansion)); + } + match expand_program_source(source, self.parse_options()) { + Ok(expansion) => Some(self.run_expansion(expansion)), + // The program error, not the wrapper's: the wrapper's complains + // about a `return (…)` the reader never typed. + Err(program_err) => { + diagnostic::parse_error(&program_err, source); None } } } + + /// Runs an expansion, keeping its macro definitions only if it succeeded — + /// the same "whole or not at all" rule the session's other state follows. + fn run_expansion(&mut self, expansion: ProgramExpansion) -> anyhow::Result { + let result = self.vm.execute_program(&expansion.program)?; + self.macro_definitions = expansion.source.macro_definitions; + Ok(result) + } } impl Drop for ReplSession { @@ -135,101 +276,78 @@ fn print_repl_help() { eprintln!("Commands: :quit | :exit | :q, :help"); } +/// The program the expression fallback runs for a semicolon-less input. +/// +/// `return`, not `println`. Wrapping in `println` made the *program* print the +/// value, so an input that already prints — or that evaluates to nil — printed +/// twice: `println(a)` ran `println((println(a)))` and echoed the inner call's +/// nil under its `1`. Returning the value hands it to the REPL instead, which +/// applies the same nil-suppressing rule as the statement path +/// (`first_return_is_nil`) and renders it with the same `runtime_display_value` +/// that `println` uses, so a real value looks exactly as it did before. +fn expression_program_source(source: &str) -> String { + format!("return ({source});") +} + +/// Is this input still open — should the session read another line? +/// +/// Decided on **tokens**, not characters. Counting raw `(`/`{`/`[` cannot tell +/// a bracket from a bracket inside a string or a comment, so +/// +/// ```text +/// > let s = "("; +/// > s +/// Error: Syntax error: Unexpected tokens at end (found Let) at 2:1-2 +/// ``` +/// +/// — the session went on waiting for a `)` that was never missing, swallowed +/// the next line into the same input, and blamed that line. `// (` at the end +/// of a line did the same. The tokenizer is the thing that decides what a +/// string and a comment are; asking it costs one pass over a line of input. +/// +/// A tokenizer error means the line cannot be read as tokens at all — an +/// unterminated string, say — and that is the parser's message to deliver, not +/// a reason to keep waiting. (Waiting would hang the session on any typo.) +/// +/// Brackets are not the whole story, because a continuation need not open one: +/// +/// ```text +/// > let out = nums +/// > .map(|v| v * 2) +/// ``` +/// +/// The first line closes every bracket it opens, so the session ran it, said +/// "Expected Semicolon, found end of input", and then met a line starting with +/// `.`. That is one statement typed over two lines, and what says so is the +/// *parser*: it ran out of input rather than meeting something wrong. An input +/// that is wrong rather than unfinished does not say that, so a typo still +/// stops instead of waiting for a line that cannot help +/// (`ParseError::wants_more_input`). pub(crate) fn should_continue_multiline(buf: &str) -> bool { - let mut paren = 0i32; - let mut brace = 0i32; - let mut bracket = 0i32; - for ch in buf.chars() { - match ch { - '(' => paren += 1, - ')' => paren -= 1, - '{' => brace += 1, - '}' => brace -= 1, - '[' => bracket += 1, - ']' => bracket -= 1, + if buf.trim_end().ends_with('\\') { + return true; + } + let Ok(tokens) = Tokenizer::tokenize(buf) else { + return false; + }; + let mut depth = 0i32; + for token in &tokens { + match token { + Token::LParen | Token::LBrace | Token::LBracket => depth += 1, + Token::RParen | Token::RBrace | Token::RBracket => depth -= 1, _ => {} } } - let trailing_backslash = buf.trim_end().ends_with('\\'); - paren > 0 || brace > 0 || bracket > 0 || trailing_backslash -} - -fn normalize_binary_signs(src: &str) -> String { - let mut out = String::with_capacity(src.len() + 8); - let chars: Vec = src.chars().collect(); - let mut i = 0usize; - let len = chars.len(); - let mut in_single = false; - let mut in_double = false; - while i < len { - let c = chars[i]; - if !in_single && c == '"' && !is_escaped_quote(&chars, i) { - in_double = !in_double; - out.push(c); - i += 1; - continue; - } - if !in_double && c == '\'' && !is_escaped_quote(&chars, i) { - in_single = !in_single; - out.push(c); - i += 1; - continue; - } - if in_single || in_double { - out.push(c); - i += 1; - continue; - } - - if (c == '+' || c == '-') && i + 1 < len && chars[i + 1].is_ascii_digit() { - let mut j = i as isize - 1; - let mut prev: Option = None; - while j >= 0 { - let pj = chars[j as usize]; - if pj.is_whitespace() { - j -= 1; - continue; - } - prev = Some(pj); - break; - } - let prev_is_value_like = matches!( - prev, - Some(ch) - if ch.is_ascii_alphanumeric() - || ch == '_' - || ch == ')' - || ch == ']' - || ch == '}' - || ch == '"' - || ch == '\'' - ); - - if prev_is_value_like { - out.push(c); - out.push(' '); - i += 1; - continue; - } - } - - out.push(c); - i += 1; + if depth > 0 { + return true; } - out -} - -fn is_escaped_quote(chars: &[char], quote_index: usize) -> bool { - let mut backslashes = 0usize; - let mut index = quote_index; - while index > 0 { - index -= 1; - if chars[index] != '\\' { - break; - } - backslashes += 1; + // The wrapper first, exactly as `execute_input` does: `1 + 1` is a finished + // input even though it is not a statement, and asking the program parser + // about it would answer "unfinished" forever. + if parse_program_source(&expression_program_source(buf), ParseOptions::default()).is_ok() { + return false; } - backslashes % 2 == 1 + parse_program_source(buf, ParseOptions::default()).is_err_and(|error| error.wants_more_input()) } pub fn run(_is_statement_mode: bool) -> anyhow::Result<()> { @@ -362,6 +480,46 @@ fn run_fallback(session: &mut ReplSession) -> anyhow::Result<()> { mod tests { use super::*; + /// A failed input defines nothing, and only a body-bearing declaration + /// gets told *why* it could not see the name. + /// + /// The confusing shape was two unrelated-looking errors one line apart: + /// `fn g() -> Int { return LATER; }` fails, and then `g()` on the next line + /// fails with "undefined function `g`" — because the definition never took. + #[test] + fn only_a_declaring_input_reports_that_nothing_was_defined() { + assert_eq!(input_declares("g()", ParseOptions::default()), InputDeclares::Nothing); + assert_eq!(input_declares("1 + 1", ParseOptions::default()), InputDeclares::Nothing); + assert_eq!( + input_declares("let q = 1;", ParseOptions::default()), + InputDeclares::AName + ); + assert_eq!( + input_declares("struct Q { a: Int }", ParseOptions::default()), + InputDeclares::AName + ); + assert_eq!( + input_declares("type N = Int;", ParseOptions::default()), + InputDeclares::AName + ); + assert_eq!( + input_declares("fn g() -> Int { return 1; }", ParseOptions::default()), + InputDeclares::ABody + ); + assert_eq!( + input_declares("impl Q { fn m(self) -> Int { return 1; } }", ParseOptions::default()), + InputDeclares::ABody + ); + // A body anywhere in the input wins: that is the one that can fail for + // a reason the reader cannot see. + assert_eq!( + input_declares("let a = 1;\nfn g() -> Int { return a; }", ParseOptions::default()), + InputDeclares::ABody + ); + // Unparseable input is reported by the parser, not here. + assert_eq!(input_declares("fn (", ParseOptions::default()), InputDeclares::Nothing); + } + #[test] fn multiline_detects_unclosed_delimiters() { assert!(should_continue_multiline("println((1)\n")); @@ -369,17 +527,229 @@ mod tests { assert!(!should_continue_multiline("println(1)\n")); } + /// A bracket inside a string or a comment is not an open bracket. + /// + /// Counting characters, `let s = "(";` looked unfinished: the session went + /// on reading, swallowed the next line into the same input, and reported + /// `Unexpected tokens at end (found Let)` against it. + #[test] + fn a_bracket_in_a_string_or_comment_does_not_hold_the_line_open() { + assert!(!should_continue_multiline("let s = \"(\";\n")); + assert!(!should_continue_multiline("let t = \"}\";\n")); + assert!(!should_continue_multiline("let u = 1; // (\n")); + assert!(!should_continue_multiline("// [\n")); + // A real open bracket next to a decoy one still holds. + assert!(should_continue_multiline("let xs = [\")\",\n")); + } + + /// Input the tokenizer cannot read is the parser's error to report. + /// + /// Treating it as "keep waiting" would hang the session on a typo — there + /// is no line the reader can type that closes an unterminated string they + /// did not mean to open. + #[test] + fn unlexable_input_does_not_hold_the_line_open() { + assert!(!should_continue_multiline("let s = \"unterminated\n")); + } + #[test] - fn normalize_binary_signs_preserves_unary_signs() { - assert_eq!(normalize_binary_signs("1+2"), "1+ 2"); - assert_eq!(normalize_binary_signs("-2"), "-2"); - assert_eq!(normalize_binary_signs("\"1+2\""), "\"1+2\""); + fn expression_fallback_returns_the_value_rather_than_printing_it() { + // The nesting this asserts against — println((println(a))) — is what + // printed a spurious `nil` after the real output. + assert_eq!(expression_program_source("println(a)"), "return (println(a));"); + // No textual rewriting of the input on the way in: the wrapper is the + // only thing added. `normalize_binary_signs` used to insert a space + // after a binary `+`/`-`, and a with/without differential over fifteen + // inputs (`a-1`, `a--1`, `-a`, `[1,2][0]-1`, `"a-1 ${a-1}"`, …) was + // identical — it was patching a lexer behaviour that is not there. + assert_eq!(expression_program_source("1+1"), "return (1+1);"); } + /// A macro defined on one input is usable on the next. + /// + /// Driven through `execute_input`, which is the path that carries the + /// definitions — parsing an input on its own does not, and that was the + /// defect: the session kept `fn`, `struct`, `impl` and `let`, and dropped + /// `macro_rules!` without saying so. + #[cfg(feature = "stdlib")] #[test] - fn normalize_binary_signs_ignores_escaped_quotes() { - assert_eq!(normalize_binary_signs(r#""a\"+1""#), r#""a\"+1""#); - assert_eq!(normalize_binary_signs(r#"'a\'+1'"#), r#"'a\'+1'"#); + fn a_macro_defined_in_one_input_survives_into_the_next() { + let mut session = ReplSession::new().expect("repl session"); + + session + .execute_input("macro_rules! twice { ($x:expr) => { ($x) * 2 }; }") + .expect("the definition is accepted") + .expect("the definition runs"); + let used = session + .execute_input("twice!(21)") + .expect("the macro resolves on a later input") + .expect("the expansion runs"); + assert_eq!(used.display_first_return(), "42"); + + // Re-entering the name replaces it, the way `let` and `fn` do here. + session + .execute_input("macro_rules! twice { ($x:expr) => { ($x) * 3 }; }") + .expect("the redefinition is accepted") + .expect("the redefinition runs"); + let again = session + .execute_input("twice!(21)") + .expect("the redefined macro resolves") + .expect("the expansion runs"); + assert_eq!(again.display_first_return(), "63"); + + // An *import* is collected into the same set, so it was equally lost: + // `use { vec } from macros;` on its own line left `vec!` undefined, and + // the builtin macro module was unusable from the REPL entirely. + session + .execute_input("use { vec } from macros;") + .expect("the import is accepted") + .expect("the import runs"); + let imported = session + .execute_input("vec![1, 2, 3].len()") + .expect("the imported macro resolves on a later input") + .expect("the expansion runs"); + assert_eq!(imported.display_first_return(), "3"); + } + + /// An unannotated parameter is not pinned by the first call. + /// + /// The checker applies its solved substitutions to everything it has + /// recorded once the program is checked — right for one program, and the + /// REPL checks a sequence of them. `fn f(x) { return x; }` followed by + /// `f(1)` left `f` recorded as `(Int) -> Int`, so `f("a")` on the next input + /// answered "Cannot unify Int with String". The same three lines in a file + /// are fine. + #[cfg(feature = "stdlib")] + #[test] + fn an_open_parameter_is_not_pinned_by_the_first_call() { + let mut session = ReplSession::new().expect("repl session"); + + session + .execute_input("fn f(x) { return x; }") + .expect("the definition is accepted") + .expect("the definition runs"); + for (input, expected) in [("f(1)", "1"), ("f(\"a\")", "a"), ("f([1, 2])", "[1,2]")] { + let result = session + .execute_input(input) + .expect("the call is accepted") + .unwrap_or_else(|error| panic!("`{input}` after an earlier call: {error}")); + assert_eq!(result.display_first_return(), expected); + } + + // A parameter the source *did* annotate still holds its claim. + session + .execute_input("fn h(x: Int) -> Int { return x; }") + .expect("the definition is accepted") + .expect("the definition runs"); + assert!( + session + .execute_input("h(\"a\")") + .expect("the call is accepted") + .is_err(), + "an annotated parameter must still reject a String" + ); + } + + /// A struct declared on one input keeps its field order on the next. + /// + /// Declaration order travels with the type, and the two paths that build an + /// instance read it from the module being executed. Every REPL input is its + /// own module, so a struct built after the line that declared it had no + /// declaration to order by and printed the field map's own iteration. + /// Six fields, deliberately: with fewer the two orders can coincide. + #[cfg(feature = "stdlib")] + #[test] + fn a_struct_keeps_its_field_order_on_a_later_input() { + let mut session = ReplSession::new().expect("repl session"); + + session + .execute_input("struct Reading { zebra: Int, apple: Int, mango: Int, kiwi: Int, pear: Int, fig: Int }") + .expect("the declaration is accepted") + .expect("the declaration runs"); + let built = session + .execute_input("\"{}\".format(Reading { zebra: 1, apple: 2, mango: 3, kiwi: 4, pear: 5, fig: 6 })") + .expect("the construction is accepted") + .expect("the construction runs"); + assert_eq!( + built.display_first_return(), + "Reading{zebra:1,apple:2,mango:3,kiwi:4,pear:5,fig:6}" + ); + + // A spread rebuild goes through the other construction path, which reads + // the same declaration. + session + .execute_input("let base = Reading { zebra: 1, apple: 2, mango: 3, kiwi: 4, pear: 5, fig: 6 };") + .expect("the binding is accepted") + .expect("the binding runs"); + let bumped = session + .execute_input("\"{}\".format(Reading { ..base, apple: 99 })") + .expect("the rebuild is accepted") + .expect("the rebuild runs"); + assert_eq!( + bumped.display_first_return(), + "Reading{zebra:1,apple:99,mango:3,kiwi:4,pear:5,fig:6}" + ); + } + + /// A trait's default method reaches an `impl` written on a later input. + /// + /// The default bodies are copied into the impls that leave them out during + /// *parsing*, over one program's statements. An input carrying the `impl` + /// without the `trait` beside it never saw them, and the checker reported + /// "Method 'tripled' required by trait 'Scaled' not implemented for type + /// 'Rect'" — for a method the source never had to write. + #[cfg(feature = "stdlib")] + #[test] + fn a_trait_default_reaches_an_impl_on_a_later_input() { + let mut session = ReplSession::new().expect("repl session"); + + for input in [ + "struct Rect { w: Int }", + "trait Scaled { fn base(self) -> Int; fn tripled(self) -> Int { return self.w * 3; } }", + "impl Scaled for Rect { fn base(self) -> Int { return self.w; } }", + ] { + session + .execute_input(input) + .expect("the declaration is accepted") + .unwrap_or_else(|error| panic!("`{input}`: {error}")); + } + + let used = session + .execute_input("Rect { w: 4 }.tripled()") + .expect("the call is accepted") + .expect("the default body runs"); + assert_eq!(used.display_first_return(), "12"); + + // The impl's own method still wins over the default. + let own = session + .execute_input("Rect { w: 4 }.base()") + .expect("the call is accepted") + .expect("the impl's method runs"); + assert_eq!(own.display_first_return(), "4"); + } + + #[cfg(feature = "stdlib")] + #[test] + fn expression_fallback_echoes_values_but_not_nil_returns() { + let mut session = ReplSession::new().expect("repl session"); + + let run = |session: &mut ReplSession, src: &str| { + let program = parse_program_source(&expression_program_source(src), ParseOptions::default()) + .expect("expression program parses"); + session.vm.execute_program(&program).expect("expression program runs") + }; + + // println prints its own `1`; the REPL must add nothing after it. + let printed = run(&mut session, "println(1)"); + assert!(printed.first_return_is_nil()); + + let value = run(&mut session, "1+1"); + assert!(!value.first_return_is_nil()); + assert_eq!(value.display_first_return(), "2"); + + // Rendering is unchanged from the println wrapper: strings unquoted. + let text = run(&mut session, "\"x\""); + assert_eq!(text.display_first_return(), "x"); } #[test] diff --git a/cli/src/repl_completion.rs b/cli/src/repl_completion.rs index 08574f71..b6d03e0f 100644 --- a/cli/src/repl_completion.rs +++ b/cli/src/repl_completion.rs @@ -57,6 +57,10 @@ impl ReplCompletion { trigger: lk_completion::CompletionTrigger::Invoked, session_source: session_source.as_deref(), base_dir: self.base_dir.as_deref(), + // The REPL has no checked document to draw on — a line being typed + // is usually not a program yet — so receivers fall back to the + // token-shape guess. + known_types: None, }) } } diff --git a/cli/tests/aot_differential_test.rs b/cli/tests/aot_differential_test.rs index 67fd640a..f01d979c 100644 --- a/cli/tests/aot_differential_test.rs +++ b/cli/tests/aot_differential_test.rs @@ -37,13 +37,32 @@ where cmd } +#[derive(Clone, Copy, PartialEq)] +enum NativePath { + PureNative, + /// A documented lowering gap whose required behavior is a Tier 0 fallback. + MayDegrade, +} + struct Case { name: &'static str, source: &'static str, + native_path: NativePath, } const fn new(name: &'static str, source: &'static str) -> Case { - Case { name, source } + Case { + name, + source, + native_path: NativePath::PureNative, + } +} + +const fn may_degrade(name: &'static str, source: &'static str) -> Case { + Case { + native_path: NativePath::MayDegrade, + ..new(name, source) + } } /// Compile `case` natively with the MIR gate enabled, run it, run the same @@ -64,10 +83,20 @@ fn run_differential(area: &str, cases: &[Case]) { let vm = run_cli(&dir, [file.as_str()]).output().expect("spawn vm run"); let vm_stdout = String::from_utf8_lossy(&vm.stdout).into_owned(); - // Native build + run. - let exe = run_cli(&dir, ["compile", &file]) - .output() - .expect("spawn native compile"); + // Native build + run. Most cases are pure-native even when the caller + // exported `LK_AOT_NO_FALLBACK`; the explicit degradation case clears it + // because the test documents that Tier 0 is the required safe outcome. + let mut compile = run_cli(&dir, ["compile", &file]); + match case.native_path { + NativePath::PureNative => { + compile.env("LK_AOT_NO_FALLBACK", "1"); + } + NativePath::MayDegrade => { + compile.env("LK_AOT_HYBRID", "0"); + compile.env_remove("LK_AOT_NO_FALLBACK"); + } + } + let exe = compile.output().expect("spawn native compile"); assert!( exe.status.success(), "[{area}/{}] native compile failed: {}", @@ -129,6 +158,151 @@ fn differential_scalars() { ); } +/// The shapes `docs/semantics.md` used to exclude from this corpus. +/// +/// They were excluded because the two backends genuinely disagreed: +/// `unique()` had a hand-written equality on each side, and lkrt's still +/// described the VM *of the time* — numerics by `to_bits`, strings "never +/// equal" past seven bytes, lists by handle. Once the VM's equality became +/// heap-aware the two drifted, and being outside the corpus is why nothing +/// said so. One equality now, so these belong here. +#[test] +fn differential_equality_and_unique() { + run_differential( + "equality", + &[ + new("unique_zeros", "let xs = [0.0, -0.0];\nreturn xs.unique();\n"), + new("unique_floats", "let xs = [1.0, 2.0, 1.0];\nreturn xs.unique();\n"), + new( + "unique_long_strings", + "let s = \"abcdefghij\";\nlet xs = [s, s, \"ab\"];\nreturn xs.unique();\n", + ), + new("unique_nested", "let xs = [[1], [1], [2]];\nreturn xs.unique();\n"), + new("eq_across_int_float", "let a = 1;\nlet b = 1.0;\nreturn a == b;\n"), + new( + "in_across_int_float", + "let a = 1;\nlet ys = [1.0, 2.0];\nreturn a in ys;\n", + ), + new( + "in_across_float_int", + "let a = 1.0;\nlet ys = [1, 2];\nreturn a in ys;\n", + ), + new("in_misses", "let ys = [1, 2];\nreturn 1.5 in ys;\n"), + // A miss is nil on every sequence, not -1: -1 is a valid index (the + // last element), so `xs[xs.index_of(v)]` used to answer that + // instead of failing. + // `try` is an expression, so its value has to survive the region on + // both backends — natively that means a cell, and a register seeded + // with nil used to have no way back out of one. + // Int overflow wraps rather than raising, and both backends have to + // wrap the same way. + new( + "int_overflow_wraps", + "let a = 9223372036854775807;\nlet b = -9223372036854775807 - 1;\nreturn [a + 1, a * 2, b - 1];\n", + ), + new( + "try_expression_value", + "fn d(a: Int, b: Int) -> Float {\n if (b == 0) { error(\"zero\"); }\n return a / b;\n}\nlet ok = try { d(10, 2) } catch e { -1.0 };\nlet bad = try { d(1, 0) } catch e { -1.0 };\nreturn [ok, bad];\n", + ), + new( + "try_expression_nil_branch", + "let r = try { 1 % 0 } catch e { let unused = 1; };\nreturn r;\n", + ), + new( + "index_of_miss_is_nil", + "let xs = [1, 2, 3];\nreturn [xs.index_of(9), xs.index_of(2), \"abc\".index_of(\"z\")];\n", + ), + // Strings order lexicographically on both backends. The type + // checker used to refuse `<` on them outright, so `sort()` was the + // only way to ask — and the native lowering, told the VM did not + // support it either, rejected the whole function. + new( + "str_lt_long", + "let a = \"aaaaaaaaa\" + \"a\";\nlet z = \"zzzzzzzzz\" + \"z\";\nreturn a < z;\n", + ), + new( + "str_ge_long", + "let a = \"aaaaaaaaa\" + \"a\";\nlet z = \"zzzzzzzzz\" + \"z\";\nreturn z >= a;\n", + ), + new("str_le_equal", "let a = \"mm\";\nreturn a <= \"mm\";\n"), + new("str_gt_prefix", "let a = \"abc\";\nreturn a > \"ab\";\n"), + // The String read surface, on text with multi-byte characters in + // it. Only `substring`/`find` used to lower, both to byte-indexed + // helpers, so this is exactly where the two backends disagreed — + // and nothing compared them, because the corpus was ASCII. + new( + "str_slice_multibyte", + "let s = \"héllo wörld\";\nreturn s.slice(1, 4);\n", + ), + new( + "str_slice_open_multibyte", + "let s = \"héllo wörld\";\nreturn s.slice(6);\n", + ), + new( + "str_take_skip_multibyte", + "let s = \"héllo wörld\";\nreturn s.take(3) + s.skip(9);\n", + ), + new( + "str_index_of_multibyte", + "let s = \"héllo wörld\";\nreturn s.index_of(\"wörld\");\n", + ), + new("str_index_of_miss", "let s = \"héllo\";\nreturn s.index_of(\"zz\");\n"), + new( + "str_negative_index_multibyte", + "let s = \"中文abc\";\nreturn s[-1] + s[-5];\n", + ), + new( + "str_first_last_multibyte", + "let s = \"中文abc\";\nreturn [s.first(), s.last(), \"\".first()];\n", + ), + // `in` on a heap element. The VM compares these by value and this + // runtime compared them by handle, so each of these answered false + // compiled and true interpreted — and `-` and `index_of`, which + // share the comparison, answered with it. + new( + "in_nested_list", + "return [[1, 2] in [[1, 2], [3]], [] in [[]], [1, 2] in [[1, 2, 3]]];\n", + ), + new("in_nested_map", "return {\"k\": 1} in [{\"k\": 1}];\n"), + new( + "in_two_handles_one_value", + "let a = \"ab\".bytes();\nlet b = \"ab\".bytes();\nreturn [a in [b], a == b];\n", + ), + // A mixed list compares its elements the way `==` does, which + // includes reading an Int and a Float as one number. + new( + "in_mixed_list_across_int_float", + "return [1.0 in [1, \"a\"], 1 in [1.0, \"a\"]];\n", + ), + new( + "sub_removes_nested", + "return [[[1], [2], [3]] - [[2]], [{\"k\": 1}, {\"j\": 2}] - [{\"k\": 1}]];\n", + ), + new( + "index_of_nested_and_across_int_float", + "return [[[1], [2]].index_of([2]), [1, \"x\", 2].index_of(2.0)];\n", + ), + // `count` and `index_of` are one scan in the VM. They were two here + // and had diverged in which carriers exist, so a `List` could + // be searched but not counted. Every carrier, and a boxed receiver + // for each, since that is the spelling that had nothing at all. + new( + "count_every_carrier", + "fn n(xs, v) { return xs.count(v); }\nreturn [\n n([1, 2, 1], 1), n([1.5, 2.5, 1.5], 1.5), n([\"a\", \"b\", \"a\"], \"a\"),\n n([[1], [2], [1]], [1]), n([1, \"a\", 1.0], 1), n([], 1),\n [1, 2, 1].count(1), [\"a\", \"b\", \"a\"].count(\"a\"),\n [1.5, 1.5].count(1.5), [[1], [1]].count([1]),\n];\n", + ), + // The exception, and the reason the comparison is not simply `==` + // everywhere: a byte string holds byte values, so the VM asks for an + // Int and answers false for anything else — where a list of the same + // numbers reads a Float as one of them. A boxed haystack is what + // picks between the two at run time. + new( + "in_bytes_wants_an_int_where_a_list_takes_a_float", + "let c = [\"ab\".bytes(), [97, 98]];\nreturn [97 in c[0], 97.0 in c[0], 99 in c[0], 97 in c[1], 97.0 in c[1]];\n", + ), + ], + ); +} + #[test] fn differential_control_flow() { run_differential( @@ -169,6 +343,33 @@ fn differential_control_flow() { "float_loop", "let s = 0.0;\nlet i = 0;\nwhile (i < 5) { s = s + 1.5; i = i + 1; }\nreturn s;\n", ), + // Every arm returns, so nothing follows the match — the function's + // last block has no terminator, and the catch-all arm is entered + // with no test. Lowering saw a phantom edge off the end and either + // rejected the function or built a `ret void` in an `-> i64` one. + new( + "match_arms_return", + "fn g(n: Int) -> Int {\n match n {\n 0 => { return 7; }\n 1 => { return 8; }\n _ => { return 9; }\n }\n}\nprintln(g(0));\nprintln(g(1));\nprintln(g(2));\nreturn 0;\n", + ), + // Unreachable code: with no predecessors it has a definition for no + // register, and that emptiness used to flow into the blocks it + // falls into, rejecting the function over its own parameter. + new( + "code_after_a_total_if", + "fn h(n: Int) -> Int {\n if n > 0 { return 1; } else { return 2; }\n let z = n + 1;\n return z;\n}\nprintln(h(5));\nprintln(h(-5));\nreturn 0;\n", + ), + // A `return` in one branch of a conditional expression ends that + // branch, not the lowering of what follows the conditional. + new( + "conditional_branch_returns", + "fn f(n: Int) -> Int {\n let a = if n > 0 { return 1; } else { 2 };\n return a + 10;\n}\nprintln(f(5));\nprintln(f(-5));\nreturn 0;\n", + ), + // A binding arm catches every value, nil included — the same rule + // the wildcard follows. + new( + "binding_arm_catches_nil", + "fn f(v: Int?) -> Int {\n return match v { x => 1 };\n}\nprintln(f(nil));\nprintln(f(3));\nreturn 0;\n", + ), ], ); } @@ -214,6 +415,32 @@ fn differential_lists() { run_differential( "lists", &[ + // The same rule across a call: the callee widens a *parameter*, + // and only the caller can build the list that way. Two shapes, + // because they are discovered differently — a parameter two call + // sites disagree about is erased to Dyn and the caller has to be + // pessimistic, while a parameter a single call site pins keeps its + // typed carrier and the callee is the one that reports the push. + new( + "a_callee_widens_a_shared_parameter", + "fn widen(xs: Any) -> Int {\n xs.push(\"z\");\n return xs.len();\n}\nlet a = [1, 2];\nlet b = [1.5, 2.5];\nprintln(widen(a));\nprintln(widen(b));\nprintln(a);\nprintln(b);\nreturn 0;\n", + ), + new( + "a_callee_widens_its_only_caller_s_list", + "fn widen(xs: Any) -> Int {\n xs.push(\"z\");\n return xs.len();\n}\nlet a = [1, 2];\nprintln(widen(a));\nprintln(a);\nreturn 0;\n", + ), + // A list literal whose element type a later push contradicts is + // built as a Dyn list from the start — the same fixpoint retry an + // empty `[]` already used. The VM widens the carrier in place; + // native cannot, so this used to fall back. + new( + "widened_after_a_typed_literal", + "let a: List = [1, 2];\na.push(\"x\");\nprintln(a);\nlet b: List = [1.5, 2.5];\nb.push(\"y\");\nprintln(b);\nlet c: List = [\"p\", \"q\"];\nc.push(7);\nprintln(c);\nreturn 0;\n", + ), + new( + "widened_from_a_register_window", + "let n = 3;\nlet d: List = [n, n + 1];\nd.push(\"z\");\nprintln(d);\nlet f: List = [1, 2];\nfor i in 0..2 { f.push(\"s\"); }\nprintln(f);\nreturn 0;\n", + ), new("len", "let xs = [1, 2, 3, 4];\nreturn xs.len();\n"), new("const_index", "let xs = [10, 20, 30, 40];\nreturn xs[0] + xs[2];\n"), new("oob_nil", "let xs = [10];\nreturn xs[9];\n"), @@ -252,10 +479,31 @@ fn differential_lists() { "str_nil_branch", "let xs = [\"a\"];\nif xs[9] == nil { return 1; }\nreturn 0;\n", ), + // `index_of` on an int list. The VM has it on every sequence; the + // lowering had it only on `Str`, so this dropped its module to the + // VM — same answer, only slower, which no gate can see. + new( + "list_index_of", + "let xs = [10, 20, 30];\nprintln(xs.index_of(20) ?? -1);\nprintln(xs.index_of(99) ?? -1);\nprintln([1].index_of(1) ?? -1);\nreturn 0;\n", + ), new( "nil_branch_oob", "let xs = [1];\nif xs[9] == nil { return 1; }\nreturn 0;\n", ), + // Writing at a negative index means what reading at one means. It + // used to raise in both backends while `xs[-1]` read the last + // element — the same expression, one direction. + new( + "negative_store", + "let xs = [1, 2, 3];\nxs[-1] = 9;\nxs.set(-2, 8);\nprintln(xs);\nreturn 0;\n", + ), + // A window's negative bounds count from the end, like `xs[-1]`. + // The VM raised on them and the native slice raised too, while the + // *string* slice on each side did something different again. + new( + "slice_negative", + "let xs = [1, 2, 3, 4, 5];\nprintln(xs.slice(-2, 5).len());\nprintln(xs.slice(1, -1).len());\nprintln(xs.slice(-99, 99).len());\nprintln(xs.slice(-1, -3).len());\nreturn 0;\n", + ), ], ); } @@ -265,6 +513,42 @@ fn differential_maps() { run_differential( "maps", &[ + // The same rule across a call, for the other container: the + // callee stores a value the parameter's carrier cannot hold, so + // the caller's literal is built with a Dyn carrier. Both shapes, + // as for lists — the erased one stores through `dyn.index_set`. + new( + "a_callee_widens_a_shared_map_parameter", + "fn widen(m: Any) -> Int {\n m[\"k\"] = \"z\";\n return m.len();\n}\nlet a = {\"x\": 1};\nlet b = {\"y\": 1.5};\nprintln(widen(a));\nprintln(widen(b));\nprintln(a);\nprintln(b);\nreturn 0;\n", + ), + // An index store through a boxed receiver, which is the other + // spelling `dyn.index_set` carries: an integer key is a position + // on a list and a key on a map, and the negative-from-end and + // out-of-range rules are the unboxed ones. + new( + "a_boxed_receiver_stores_by_index", + "fn setit(xs: Any) -> Int {\n xs[0] = 9;\n xs[-1] = 8;\n return xs.len();\n}\nlet a = [1, 2];\nlet b = [1.5, 2.5];\nprintln(setit(a));\nprintln(setit(b));\nprintln(a);\nprintln(b);\nreturn 0;\n", + ), + new( + "a_boxed_receiver_store_is_bounds_checked", + "fn setit(xs: Any) -> Int {\n xs[5] = 9;\n return xs.len();\n}\nlet a = [1, 2];\nlet b = [1.5, 2.5];\nprintln(setit(a));\nprintln(setit(b));\nreturn 0;\n", + ), + new( + "a_callee_widens_its_only_caller_s_map", + "fn widen(m: Any) -> Int {\n m[\"k\"] = \"z\";\n return m.len();\n}\nlet a = {\"x\": 1};\nprintln(widen(a));\nprintln(a);\nreturn 0;\n", + ), + // A map literal whose value type a later store contradicts is + // built with a Dyn carrier — the same fixpoint retry the list + // literals use. The VM widens the carrier in place; native cannot, + // so both of these used to fall back. + new( + "widened_after_a_typed_literal", + "let m: Map = {\"a\": 1};\nm[\"b\"] = \"x\";\nprintln(m);\nreturn 0;\n", + ), + new( + "widened_from_an_empty_literal", + "let n: Map = {};\nn[\"a\"] = 1;\nn[\"b\"] = \"y\";\nprintln(n);\nreturn 0;\n", + ), new("str_get", "let m = {\"a\": 1, \"b\": 2};\nreturn m[\"b\"];\n"), new("missing_nil", "let m = {\"a\": 1};\nreturn m[\"z\"];\n"), new( @@ -297,6 +581,301 @@ fn differential_strings() { run_differential( "strings", &[ + // `needle in text` is `text.contains(needle)`. The method + // spelling lowered and the operator sent the whole program back to + // the VM, which is a 3x slowdown with no message. + new( + "in_operator_on_a_string", + "let s = \"abc\";\nprintln(\"b\" in s);\nprintln(\"z\" in s);\nfn f(t: String) -> Bool {\n return \"c\" in t;\n}\nprintln(f(s));\nreturn 0;\n", + ), + // An erased container is a container: `in` refused an `Any` + // operand while indexing, `len`, iteration, method dispatch and + // `push` all took one. Three carriers behind one `Dyn`, so the + // runtime is what picks. + new( + "in_operator_on_an_erased_container", + "fn has(h: Any, n: Any) -> Bool {\n return n in h;\n}\nprintln(has(\"abc\", \"b\"));\nprintln(has(\"abc\", \"z\"));\nprintln(has([1, 2], 2));\nprintln(has([1, 2], 9));\nprintln(has({\"k\": 1}, \"k\"));\nreturn 0;\n", + ), + // The other two container operators followed it too: list + // removal and map merge. Four siblings, one rule. + new( + "remove_and_merge_with_an_erased_operand", + "fn rm(xs: Any) -> Int {\n println(xs - [1]);\n return 0;\n}\nfn mg(m: Any) -> Int {\n println(m + {\"b\": 2});\n return 0;\n}\nrm([1, 2]);\nmg({\"a\": 1});\nreturn 0;\n", + ), + // Concatenation followed the same rule as `in`, and refused the + // same erased operand. + new( + "concat_with_an_erased_operand", + "fn app(xs: Any) -> Int {\n println(xs + [7]);\n return 0;\n}\napp([1, 2]);\napp([1.5, 2.5]);\nreturn 0;\n", + ), + // A list operand absorbs the other one, in position — the VM's + // rule, which `lkrt_dyn_add` states, and which only the checker + // refused. A heterogeneous literal is the case that mattered most: + // it infers to a `Tuple`, so it missed the list rule entirely and + // `"" + [1, "a"]` was typed `String` while both executors answered + // a list. + new( + "a_list_operand_absorbs_the_other", + "println(\"p=\" + [1, 2]);\nprintln([1, 2] + \"x\");\nprintln(\"\" + [1, \"a\"]);\nprintln([1, \"a\"] + \"z\");\nprintln(1 + [2, 3]);\nprintln([2, 3] + 1);\nprintln(nil + [1]);\nprintln([1] + nil);\nprintln([1] + {\"k\": 2});\nprintln({\"k\": 2} + [1]);\nreturn 0;\n", + ), + // …and the element type the checker gives the answer holds up when + // it is written down. + // A container beside a string in `+` renders the way `print` + // renders it. Four ways to print one value and this was the one + // that failed — `println(xs)`, `println("{}", xs)` and + // `println("${xs}")` all worked. A list is not here because a list + // operand *wins* and the answer is a list, which is a different + // operation; what changed is `Set`, a byte string, a window, a + // struct and a map beside a string. + // A raise is *observable output*: `catch e { println(e) }` puts the + // message on stdout, so a guard the runtime does not have is a + // wrong answer and not a diagnostic difference. `repeat` was the + // one of the four negative-count guards that had none — + // `"ab".repeat(-1)` answered `""` compiled and stopped the program + // interpreted. + // `to_bytes` had one runtime helper serving two spellings with a + // message of its own — `bytes.from_list(xs)` *is* `xs.to_bytes()` + // in the interpreter and both say `to_bytes`. So a caught error + // read differently compiled, on a plain `List`, with no boxing + // anywhere. The boxed carrier has to ask whether each element is an + // Int at all, which is the second refusal. + // `datetime.parse` reads three shapes — a full datetime, a date + // alone at midnight, a time alone on the epoch day — and lkrt read + // one, so two of the three answered interpreted and failed + // compiled. The refusal names the value and the format rather than + // repeating chrono's phrasing about its own parser. + new( + "datetime_parse_reads_three_shapes", + "use datetime;\nfn p(v: String, f: String) -> String { try { return \"ok \" + datetime.parse(v, f); } catch e { return \"E: \" + e; } }\nprintln(p(\"2026-08-20 10:30:00\", \"%Y-%m-%d %H:%M:%S\"));\nprintln(p(\"2026-08-20\", \"%Y-%m-%d\"));\nprintln(p(\"10:30:00\", \"%H:%M:%S\"));\nprintln(p(\"nope\", \"%Y\"));\nreturn 0;\n", + ), + new( + "to_bytes_refuses_in_the_interpreters_words", + "fn c(xs) -> String { try { return \"ok \" + xs.to_bytes(); } catch e { return \"E: \" + e; } }\nprintln(c([1, 2]));\nprintln(c([3, \"x\"].take(1)));\nprintln(c([300, \"y\"].take(1)));\nprintln(c([1, \"z\"]));\nprintln(c([1.5, \"w\"].take(1)));\nprintln(c([]));\nreturn 0;\n", + ), + new( + "a_negative_count_raises_on_both_ends", + "fn t(s: String, n: Int) -> String { try { return \"ok[\" + s.take(n) + \"]\"; } catch e { return \"E: \" + e; } }\nfn k(s: String, n: Int) -> String { try { return \"ok[\" + s.skip(n) + \"]\"; } catch e { return \"E: \" + e; } }\nfn r(s: String, n: Int) -> String { try { return \"ok[\" + s.repeat(n) + \"]\"; } catch e { return \"E: \" + e; } }\nfn p(s: String, n: Int) -> String { try { return \"ok[\" + s.pad_right(n, \"-\") + \"]\"; } catch e { return \"E: \" + e; } }\nprintln(t(\"abc\", -1));\nprintln(k(\"abc\", -1));\nprintln(r(\"abc\", -1));\nprintln(p(\"abc\", -1));\nprintln(r(\"abc\", 0));\nprintln(r(\"abc\", 2));\nreturn 0;\n", + ), + // `-` removes a *single* value too: `xs - v` drops the first + // element equal to `v`, `m - k` drops that key. The VM has an arm + // for each beside the two-container ones, and nothing could reach + // either — the checker refused the shape, so the lowering had none + // and `lkrt_dyn_sub` raised. All three had to open together. + // Membership is a *predicate* and answers: a value that cannot be + // a key is not one the map or set holds. It used to depend on the + // map's internal carrier — `1.5 in {"k": 1}` was false and + // `1.5 in {1: 2}` raised, one question with two answers decided by + // something no program can see — and the method spellings + // disagreed with the operator besides. + // + // Building a key still refuses, which is the line: `m.set(1.5, x)`, + // `m.delete(1.5)`, `s.add([1])`, `m[1.5]` and `m - 1.5` all say so. + // The method spellings of the predicates take any value, the way + // their operator spellings always have. A container searched for + // something it cannot hold answers "absent" — and where the type + // settles it, the answer is a constant rather than a call. + // Reading a key of another type is a *miss*: the interpreter + // answers nil, the way it does for a key that is simply absent. + // The checker unified the two types instead — `{"k": 1}[0]` was + // "Cannot unify String with Int", a message about the checker's own + // machinery for a lookup that has an answer. Writing still refuses, + // because it would put a key in the map the type says is not there. + // + // And a `Tuple` slices: each container arm carries a range guard + // and that one did not, so a heterogeneous literal was the one list + // that could not be sliced. + // A value nested past `MAX_VALUE_DEPTH`. The interpreter refuses to + // print it and refuses to compare it, and lkrt had no bound on + // either: a 520-deep value compared `true` compiled and stopped the + // program interpreted, and `println` printed it compiled. + // + // Relying on the stack instead is not the same rule twice — where + // it lands depends on the build and on how much stack was left, so + // the threshold would not be a property of the language. The + // message it gave said so: "a native binary is bounded by the real + // stack, not by LK_MAX_CALL_DEPTH", which is true of LK recursion + // and was not what had happened. + // A boxed value stored into a container the lowering guessed a + // carrier for. An empty `{}` guesses `str -> i64`, so this stored + // an Int and raised "runtime type error" for every other kind while + // the interpreter stored all of them — the guess is meant to cost a + // widening, and unboxing spent it on a raise. + new( + "a_boxed_value_widens_the_container_it_is_stored_in", + "fn m(v: Any) -> String { try { let c = {}; c[\"k\"] = v; return \"map \" + c.len(); } catch e { return \"map E\"; } }\nfn l(v: Any) -> String { try { let c = []; c.push(v); return \"list \" + c.len(); } catch e { return \"list E\"; } }\nfn s(v: Any) -> String { try { let c = [0]; c[0] = v; return \"set \" + c.len(); } catch e { return \"set E\"; } }\nprintln(m(1));\nprintln(m(\"a\"));\nprintln(m([1]));\nprintln(m({\"j\": 1}));\nprintln(m(nil));\nprintln(m(1.5));\nprintln(m(true));\nprintln(l(1));\nprintln(l(\"a\"));\nprintln(l([1]));\nprintln(l(nil));\nprintln(s(1));\nprintln(s(\"a\"));\nprintln(s([1]));\nprintln(s(nil));\nreturn 0;\n", + ), + // A value compared against itself answers without being walked, + // which is what the interpreter does — and without it the depth + // bound above turned `d == d` and `[d, d].unique()` into refusals + // for a value 2000 levels deep. A Float is excluded: `NaN != NaN`, + // and two NaNs are the same bits. + new( + "a_value_equals_itself_without_being_walked", + "fn build(n: Int) -> Any {\n let v: Any = 1;\n let i = 0;\n while i < n { v = [v]; i = i + 1; }\n return v;\n}\nfn t(f: Int, d: Any, e: Any) -> String {\n try {\n if f == 0 { return \"self: \" + (d == d); }\n if f == 1 { return \"unique: \" + [d, d].unique().len(); }\n if f == 2 { return \"other: \" + (d == e); }\n return \"nan: \" + ((0.0 / 0.0) == (0.0 / 0.0));\n } catch err { return \"E\"; }\n}\nlet d = build(2000);\nlet e = build(2000);\nprintln(t(0, d, e));\nprintln(t(1, d, e));\nprintln(t(2, d, e));\nprintln(t(3, d, e));\nreturn 0;\n", + ), + new( + "a_value_too_deep_is_refused_the_same_way", + "fn build(n: Int) -> Any {\n let v: Any = 1;\n let i = 0;\n while i < n { v = [v]; i = i + 1; }\n return v;\n}\nfn t(label: String, f: Int, d: Any, e: Any) -> String {\n try {\n if f == 0 { return label + \": \" + (d == e); }\n if f == 1 { return label + \": \" + [d].contains(e); }\n if f == 2 { return label + \": \" + [d, e].index_of(e); }\n if f == 3 { return label + \": \" + ([d] - [e]).len(); }\n return label + \": \" + [d, e].sort().len();\n } catch err { return label + \": E \" + err; }\n}\nlet a = build(600);\nlet b = build(600);\nprintln(t(\"eq\", 0, a, b));\nprintln(t(\"contains\", 1, a, b));\nprintln(t(\"index_of\", 2, a, b));\nprintln(t(\"sub\", 3, a, b));\nprintln(t(\"sort\", 4, a, b));\nlet shallow = build(100);\nprintln(shallow == build(100));\nreturn 0;\n", + ), + new( + "a_key_of_another_type_is_a_miss", + "println([1, \"a\"][0..2]);\nprintln([1, \"a\"][1..2]);\nprintln([1, 2, 3][0..2]);\nprintln({\"k\": 1}[0]);\nprintln({1: 2}[\"k\"]);\nprintln({\"k\": 1}[\"k\"]);\nreturn 0;\n", + ), + // …and with a key the lowering cannot type, where unboxing it to + // the map's key type used to raise: the map boxes and the tag + // decides, which is what the interpreter does. + new( + "a_key_of_another_type_is_a_miss_erased", + "fn r(m: Any, k: Any) -> Any { return m[k]; }\nprintln(r({\"k\": 1}, 0));\nprintln(r({\"k\": 1}, \"k\"));\nreturn 0;\n", + ), + // The receiver has to *have* the method before "it cannot hold this" + // is an answer. A map has `has` and `delete` and no `contains`, + // `index_of` or `count` at all, so folding those to "absent" made + // `m.contains(x)` answer `false` where the interpreter says "a Map + // has no method `contains`". + // + // The receiver is the *empty* map literal, which is the shape that + // still reaches the fold. `{1: 2}.index_of(k)` used to be here and + // is now a check error — a map whose key type cannot be a string + // and whose value type cannot be a function has no field to call, + // so the checker says so before the program runs. `{}` has neither + // type pinned, so it type-checks and the fold is what decides the + // answer. + new( + "a_fold_needs_the_method_to_exist", + "fn p(f: Int) -> String {\n try {\n if f == 0 { let r: Any = {}.contains([1, 2]); return \"ok \" + r; }\n if f == 1 { let r: Any = {}.index_of({\"k\": 1}); return \"ok \" + r; }\n if f == 2 { let r: Any = {}.count(2.5); return \"ok \" + r; }\n if f == 3 { let r: Any = [1, 2].contains(\"a\"); return \"ok \" + r; }\n let r: Any = {\"a\": 1}.has(1);\n return \"ok \" + r;\n } catch e { return \"E\"; }\n}\nprintln(p(0));\nprintln(p(1));\nprintln(p(2));\nprintln(p(3));\nprintln(p(4));\nreturn 0;\n", + ), + // Removing something that cannot be a key removes nothing — and the + // answer has to come back under the tag the caller unboxes. + // Handing the *typed* map back where `dyn.as_map` wants the boxed + // one made `{"a": 1} - []` raise where the interpreter answered. + new( + "removing_a_non_key_answers_the_map", + "fn p(f: Int) -> String {\n try {\n if f == 0 { let r: Any = {} - []; return \"ok \" + r; }\n if f == 1 { let r: Any = {\"a\": 1} - []; return \"ok \" + r; }\n if f == 2 { let r: Any = {\"a\": 1} - 1.5; return \"ok \" + r; }\n let r: Any = {\"a\": 1} - \"a\";\n return \"ok \" + r;\n } catch e { return \"E\"; }\n}\nprintln(p(0));\nprintln(p(1));\nprintln(p(2));\nprintln(p(3));\nreturn 0;\n", + ), + // A *caught* error's message is stdout, so the two engines have to + // agree on the words. The runtime used to answer "runtime type + // error" for three of these and "value is not callable" for the + // fourth, where the interpreter names the operation and the type. + // + // `cl` is the fine one: which sentence a value gets is its + // *representation*. A scalar — and a string short enough to be + // inline — is named by its display, anything on the heap by its + // type, and the cut is at seven bytes. `"s"` and `"abcdefgh"` are + // both here for that reason, and the map carries the interpreter's + // nudge about imported modules. + new( + "a_caught_error_says_what_the_interpreter_says", + "fn ix(a: Any) { try { let r: Any = a[0]; println(\"ok \" + r); } catch e { println(\"E \" + e); } }\nfn ln(a: Any) { try { let r: Any = a.len(); println(\"ok \" + r); } catch e { println(\"E \" + e); } }\nfn cn(a: Any) { try { let r: Any = 1 in a; println(\"ok \" + r); } catch e { println(\"E \" + e); } }\nfn cl(a: Any) { try { let r: Any = a(); println(\"ok \" + r); } catch e { println(\"E \" + e); } }\nix(nil);\nix(1);\nix(1.5);\nix(true);\nix(Set([1]));\nln(nil);\nln(1);\nln(true);\nln(1.5);\ncn(nil);\ncn(1);\ncn(true);\ncl(nil);\ncl(1);\ncl(true);\ncl(1.5);\ncl(\"s\");\ncl(\"abcdefgh\");\ncl([1]);\ncl({\"k\": 1});\ncl(Set([1]));\nreturn 0;\n", + ), + // A map takes nil, a Bool, an Int and a String as keys alike, and + // which native carrier holds it is a representation choice no + // program asked for. The runtime unbox refused every kind but the + // carrier's, so the same helper stored a string key and raised + // "runtime type error" on an integer one — on an explicit literal, + // not only on the empty-literal guess. + // + // These do not lower, and that is the point: the answer has to be + // the interpreter's, and until a boxed map is generally keyed + // (`docs/aot/aot-gaps-and-lkrt.md` §62) the only way to have it is + // to decline. What this pins is that declining is what happens. + may_degrade( + "a_map_key_of_any_kind_answers_or_declines", + "fn put(m: Any, k: Any) -> Any {\n m[k] = 1;\n return m;\n}\nprintln(put({\"a\": 1}, \"b\"));\nprintln(put({\"a\": 1}, 7));\nprintln(put({\"a\": 1}, nil));\nprintln(put({\"a\": 1}, true));\nprintln(put({1: 2}, 7));\nprintln(put({1: 2}, \"k\"));\nfn build(k: Any) -> Any {\n let m = {};\n m[k] = 1;\n return m;\n}\nprintln(build(\"kk\"));\nprintln(build(7));\nreturn 0;\n", + ), + new( + "a_predicate_takes_any_value", + "println([\"a\", \"b\"].contains(1));\nprintln([\"a\", \"b\"].index_of(1));\nprintln([\"a\", \"b\"].count(1));\nprintln([1, 2].contains(1.5));\nprintln([1, 2].contains(1.0));\nprintln(\"abc\".contains(1));\nprintln(\"abc\".index_of(1));\nprintln(\"ab\".bytes().contains(\"a\"));\nprintln([1, 2, 3].slice(0, 2).contains(\"a\"));\nprintln({1: 2}.has(\"k\"));\nprintln({1: 2}.delete(\"k\"));\nprintln({\"k\": 1}.delete(1));\nprintln(Set([1]).contains(\"a\"));\nprintln(Set([1]).delete(\"a\"));\nprintln([\"a\", \"b\"].contains(\"a\"));\nprintln([1, 2].contains(1));\nprintln(\"abc\".contains(\"b\"));\nprintln({\"k\": 1}.has(\"k\"));\nreturn 0;\n", + ), + new( + "membership_answers_for_a_needle_that_cannot_be_a_key", + "fn i(c: Any, v: Any) -> String { try { let r: Any = v in c; return \"ok \" + r; } catch e { return \"E: \" + e; } }\nfn h(c: Any, v: Any) -> String { try { let r: Any = c.has(v); return \"ok \" + r; } catch e { return \"E: \" + e; } }\nfn c2(c: Any, v: Any) -> String { try { let r: Any = c.contains(v); return \"ok \" + r; } catch e { return \"E: \" + e; } }\nprintln(i({\"a\": 1}, \"a\"));\nprintln(i({\"a\": 1}, 1.5));\nprintln(i({\"a\": 1}, [1]));\nprintln(i([1, 2], 1.5));\nprintln(i(\"abc\", \"b\"));\nprintln(h({\"a\": 1}, \"a\"));\nprintln(h({\"a\": 1}, 1.5));\nprintln(c2([1, 2], 1.5));\nprintln(c2(\"abc\", \"b\"));\nreturn 0;\n", + ), + may_degrade( + "building_a_key_still_refuses", + "fn t(f: Int, bad: Any) -> String {\n let s = Set([1]);\n let m = {\"a\": 1};\n try {\n if f == 0 { s.add(bad); }\n if f == 1 { m.delete(bad); }\n if f == 2 { m.set(bad, 1); }\n if f == 3 { let v: Any = m[bad]; let _ = v; }\n if f == 4 { let d: Any = m.set(bad, 1); let _ = d; }\n return \"ok\";\n } catch e { return \"E: \" + e; }\n}\nprintln(t(0, [1]));\nprintln(t(1, 1.5));\nprintln(t(2, 1.5));\nprintln(t(3, 1.5));\nprintln(t(4, 1.5));\nreturn 0;\n", + ), + new( + "removing_a_single_value", + "println([1, 2, 1] - [1]);\nprintln([1, 2, 1] - 1);\nprintln([\"a\", \"b\"] - \"a\");\nprintln([1.5, 2.5] - 1.5);\nprintln([1] - 1.5);\nprintln([[1], [2]] - [[1]]);\nprintln([1, \"a\"] - 1);\nprintln({\"a\": 1, \"b\": 2} - {\"a\": 1});\nprintln({\"a\": 1, \"b\": 2} - \"a\");\nprintln({\"a\": 1} - 1);\nprintln({\"a\": 1} - nil);\nprintln({\"a\": 1} - true);\nreturn 0;\n", + ), + // …and erased, where a key that cannot be one still raises: a map's + // members are keyed by nil, Bool, Int and String, so `m - 1.5` is a + // question with no answer rather than a removal of nothing. + new( + "removing_a_single_value_erased", + "fn s(a: Any, b: Any) -> String { try { let r: Any = a - b; let _ = r; return \"ok\"; } catch e { return \"E: \" + e; } }\nprintln(s([1, 2, 1], 1));\nprintln(s([1, 2], \"s\"));\nprintln(s([[1], [2]], [1]));\nprintln(s({\"a\": 1}, \"a\"));\nprintln(s({\"a\": 1}, 1.5));\nprintln(s({\"a\": 1}, [1]));\nreturn 0;\n", + ), + new( + "a_container_beside_a_string_renders", + "struct P { x: Int }\nprintln(\"\" + Set([1]));\nprintln(\"\" + \"ab\".bytes());\nprintln(\"v=\" + {\"k\": 1});\nprintln({\"k\": 1} + \"v=\");\nprintln(\"\" + P { x: 1 });\nlet w = [1, 2, 3].slice(0, 1);\nprintln(\"\" + w);\nreturn 0;\n", + ), + // …and through an erased operand, which is the path that already + // answered while the typed one refused to lower. + new( + "a_container_beside_a_string_renders_erased", + "struct P { x: Int }\nfn j(a, b) { return a + b; }\nprintln(j(\"v=\", {\"k\": 1}));\nprintln(j(\"\", Set([1])));\nprintln(j(\"\", \"ab\".bytes()));\nprintln(j(\"\", P { x: 1 }));\nprintln(j({\"k\": 1}, \"v=\"));\nprintln(j(\"\", [1, 2]));\nreturn 0;\n", + ), + new( + "an_absorbed_operand_widens_the_element_type", + "let xs: List = [1, 2] + 3;\nlet ys: List = [1, 2] + \"x\";\nprintln(xs);\nprintln(ys);\nreturn 0;\n", + ), + // Three more names whose `ListDyn` arms were already there and + // whose method-table row was not, so a boxed receiver never + // reached them. + // `sort`, `min` and `max` on a list whose elements are not all one + // carrier. This is the whole of the cross-kind order, and it is the + // gate for it: the order is *imposed* rather than emergent, so a + // corpus shows everything a unit-level mirror would — unlike map + // iteration order, which needed `vm_mirror` because a hash layout + // can drift without any program saying so. + // + // Each line is one of the three things a copy of the VM's + // comparator would have got wrong. The rank tables: the VM keeps + // two and reaches the second only for two heap values, so they have + // to be shown to agree — a short string and a long one sort the + // same way against a list. The window: it is a list by content but + // shares a tag value with the end of the map range. The struct: it + // is a marked map in the runtime and a distinct heap kind in the + // VM, and it sorts *after* a plain map. + new( + "cross_kind_sort_order", + "struct P { x: Int }\nstruct Q { y: Int }\nfn s(xs) { return xs.sort(); }\nprintln(s([nil, true, 1, 2.5, \"ab\", [1], {\"k\": 1}]));\nprintln(s([{\"k\": 1}, [1], \"ab\", 2.5, 1, true, nil]));\nprintln(s([[2], [1, 0], [1], []]));\nprintln(s([[1, 2], [1, 2, 3], [1]]));\nprintln(s([\"b\", \"a\", \"a-long-string-past-seven\", \"B\"]));\nprintln(s([2, 1.5, 1, 2.0, 0.0, -0.0]));\nprintln(s([1, \"1\", true]));\nprintln(s([\"ab\".bytes(), [1], \"zz\"]));\nprintln(s([{\"k\": 1}, P { x: 1 }, [1], \"s\"]));\nprintln(s([P { x: 2 }, P { x: 1 }, Q { y: 1 }]));\nprintln(s([]));\nlet w = [1, 2, 3];\nprintln(s([w.slice(1, 3), [0], [1, 2]]));\nreturn 0;\n", + ), + // The two reductions that share the order, and the empty answer + // that no unboxed carrier can hold. + new( + "cross_kind_min_and_max", + "fn mn(xs) { return xs.min(); }\nfn mx(xs) { return xs.max(); }\nprintln(mn([3, 1.5, \"a\", nil]));\nprintln(mx([3, 1.5, \"a\", nil]));\nprintln(mn([[2], [1]]));\nprintln(mx([[2], [1]]));\nprintln(mn([]) == nil);\nprintln(mx([]) == nil);\nprintln(mn([true, nil, 0]));\nreturn 0;\n", + ), + // `sum` folds with *two* accumulators because the VM does: an Int + // element advances the integer total and the float one both, so + // the float sum runs over every element in written order. + // Promoting on the first float folds a different sequence, and + // float addition is not associative. The refusal names the element + // that is not a number, in the VM's words. + new( + "dyn_receiver_sum", + "fn s(xs) { return \"\" + s2(xs); }\nfn s2(xs) { return xs.sum(); }\nprintln(s([1, 2, 3]));\nprintln(s([1.5, 2.5]));\nprintln(s([1, 2.5]));\nprintln(s([]));\nprintln(s([1e308, 1.0, -1e308]));\nprintln(s([-1, 1]));\nreturn 0;\n", + ), + // `is_empty` takes `dyn.len_of` rather than the method table's + // unbox, because this arm serves maps too and unboxing one to a + // list aborts. It is the dispatch `xs.len()` already takes. + new( + "dyn_receiver_is_empty", + "fn e(c) { return c.is_empty(); }\nprintln(e([1, 2]));\nprintln(e([]));\nprintln(e([1.5]));\nprintln(e({\"k\": 1}));\nprintln(e({}));\nprintln(e(\"ab\"));\nprintln(e(\"\"));\nreturn 0;\n", + ), + // `zip` had the `ListDyn` receiver arm and refused anyway, because + // its *argument* was boxed and only `chain` had written the unbox + // out. It is `to_dyn_list_handle`'s now, so both spellings reach it. + new( + "dyn_receiver_zip", + "fn z(xs, ys) { return xs.zip(ys); }\nprintln(z([1, 2], [3, 4]));\nprintln(z([1, 2], [\"a\", \"b\"]));\nprintln(z([1.5], [2.5]));\nprintln(z([], [1]));\nprintln(z([1, 2, 3], [9]));\nprintln(z([[1]], [[2]]));\nreturn 0;\n", + ), + new( + "dyn_receiver_chunk_enumerate_flatten", + "fn c(xs) { return xs.chunk(2); }\nfn e(xs) { return xs.enumerate(); }\nfn fl(xs) { return xs.flatten(); }\nprintln(c([1, 2, 3]));\nprintln(c([\"a\", \"b\", \"c\"]));\nprintln(c([]));\nprintln(e([1, 2]));\nprintln(e([[1], [2]]));\nprintln(fl([[1], [2, 3]]));\nprintln(fl([[[1]], [[2]]]));\nreturn 0;\n", + ), new("const_ret", "return \"hello\";\n"), new("eq", "return \"hi\" == \"hi\";\n"), new("ne", "return \"hi\" != \"ho\";\n"), @@ -315,6 +894,39 @@ fn differential_strings() { "long_string_var", "let s = \"a-fairly-long-string-literal\";\nreturn s + \"!\";\n", ), + // Text → number: the whole point is that unparseable text answers + // nil rather than guessing, so the two engines must agree on which + // spellings are numbers. `lkrt_str_to_int` is a second + // implementation of `lk_stdlib_string::to_int`'s String arm; this + // is what keeps them the same one. + new( + "to_int_ok", + "use string;\nprintln(string.to_int(\"42\") ?? -1);\nreturn 0;\n", + ), + new( + "to_int_trims", + "use string;\nprintln(string.to_int(\" -7\\n\") ?? -1);\nreturn 0;\n", + ), + new( + "to_int_refuses", + "use string;\nprintln(string.to_int(\"42abc\") ?? -1);\nprintln(string.to_int(\"\") ?? -1);\nprintln(string.to_int(\"42.0\") ?? -1);\nprintln(string.to_int(\"9223372036854775808\") ?? -1);\nreturn 0;\n", + ), + new( + "to_int_base", + "use string;\nprintln(string.to_int(\"ff\", 16) ?? -1);\nprintln(string.to_int(\"-101\", 2) ?? -1);\nprintln(string.to_int(\"9\", 8) ?? -1);\nreturn 0;\n", + ), + // A negative `slice` bound counts from the end, like `[-1]`. The + // four implementations had three answers for it, and the two + // *backends* disagreed: `"abcde".slice(1, -1)` was `""` in the VM + // and `"bcd"` compiled. + new( + "slice_negative", + "println(\"abcde\".slice(-2, 5));\nprintln(\"abcde\".slice(1, -1));\nprintln(\"abcde\".slice(-99, 99));\nprintln(\"abcde\".slice(-1, -3));\nreturn 0;\n", + ), + new( + "to_float_ok", + "use string;\nprintln(string.to_float(\"3.5\") ?? -1.0);\nprintln(string.to_float(\" -2e3 \") ?? -1.0);\nprintln(string.to_float(\"nope\") ?? -1.0);\nreturn 0;\n", + ), ], ); } @@ -673,6 +1285,41 @@ fn differential_dyn_cross_function() { &[ // Disagreeing call-site types join the parameter to Dyn (each // site boxes); the body consumes through the Dyn arms. + // A capture whose type the compiler proved is `Nil`, or a nullable + // one. The function ABI has no word for either, and a call argument + // in the same position has boxed all along — `observe_param` + // widens a nil argument to `Dyn`. The capture refused instead, so + // `let v = nil; let f = || v == nil;` dropped its whole module to + // the VM, which is an ordinary thing to write. + // A `nil` local crossing into a `try` region. The region is + // outlined and its inputs are marshalled as machine words; a `Nil` + // has none of its own, and it does not need one — what it says is + // what was there *going in*, and the body boxes whatever it writes + // back. It crosses boxed now, which is what a cell holding the same + // value already did. + // A `try` region's parked `return` and the function's own returns + // are different arms of one function, and only the second kind was + // joined: the parked value is boxed into the outcome cell and read + // back with the function's return type, so a list parked by the + // `try` arm came back as the `Str` the `catch` arm settled on and + // raised. Both kinds join now, and disagreeing takes the retry that + // two disagreeing direct returns already take. + new( + "a_try_region_return_joins_with_the_functions_own", + "fn a() -> Any { let r: Any = []; try { return \"ok \" + r; } catch e { return \"E\"; } }\nfn b() -> String { let r: Any = []; try { return \"ok \" + r; } catch e { return \"E\"; } }\nfn c() -> Int { try { return 1; } catch e { return 2; } }\nfn d(f: Bool) -> Any { try { if f { return 1; } return \"s\"; } catch e { return nil; } }\nprintln(a());\nprintln(b());\nprintln(c());\nprintln(d(true));\nprintln(d(false));\nreturn 0;\n", + ), + new( + "a_nil_crosses_into_a_try_region", + "fn a() -> String {\n let n = nil;\n try { let c = || n == nil; return \"a\" + c(); }\n catch e { return \"E\"; }\n}\nfn b() -> Int {\n let n = nil;\n try { if n == nil { return 1; } return 2; }\n catch e { return 3; }\n}\nfn c() -> String {\n let m = {\"a\": 1};\n let x = m.get(\"zz\");\n try { let f = || x == nil; return \"a\" + f(); }\n catch e { return \"E\"; }\n}\nprintln(a());\nprintln(b());\nprintln(c());\nreturn 0;\n", + ), + new( + "a_nil_capture_boxes_like_a_nil_argument", + "let m = {\"a\": 1};\nlet v = nil;\nlet x = m.get(\"zz\");\nlet y = m.get(\"a\");\nlet f = || v == nil;\nlet g = || x == nil;\nlet h = || y;\nprintln(f());\nprintln(g());\nprintln(h());\nreturn 0;\n", + ), + new( + "a_nil_capture_inside_a_function", + "fn t() -> Bool {\n let v = nil;\n let f = || v == nil;\n return f();\n}\nprintln(t());\nreturn 0;\n", + ), new( "param_join_int_str", "fn id(x) { return x; }\nprintln(id(1));\nprintln(id(\"s\"));\nprintln(id(2.5));\nprintln(id(true));\nreturn 0;\n", @@ -711,6 +1358,32 @@ fn differential_dyn_cross_function() { "maybe_ret_boxes", "fn lookup(k) {\n let m = {};\n m.set(\"a\", 7);\n return m.get(k);\n}\nprintln(lookup(\"a\"));\nprintln(lookup(\"zz\") == nil);\nreturn 0;\n", ), + // A boxed *receiver* reaches a list method. `chain` accepted a Dyn + // argument and not a Dyn receiver, so a list that is reset on one + // path, extended on another, and handed to a Dyn parameter — which + // is what a line buffer is — refused to lower. The `bare-metal-x86` + // kernel is written exactly this way and stopped compiling for it. + new( + "dyn_receiver_chain", + "fn emit(base, line) { return base + line.len(); }\nfn build(n) {\n let line = [];\n let out = 0;\n let i = 0;\n while (i < n) {\n if (i % 4 == 0) { out = emit(out, line); line = []; }\n else { line = line.chain([i]); }\n i = i + 1;\n }\n return emit(out, line);\n}\nprintln(build(11));\nprintln(build(0));\nreturn 0;\n", + ), + // The rest of the boxed-receiver names whose arms already accepted + // `ListDyn` and whose method-table row was missing, so the receiver + // never reached them. `index_of` is here for its absent answer too: + // a miss is nil, and nil has to survive the unboxed path. + new( + "dyn_receiver_element_methods", + "fn probe(xs) { return \"\" + xs.first() + xs.last() + xs.index_of(1); }\nprintln(probe([3, 1, 2]));\nprintln(probe([3.5, 1.5]));\nprintln(probe([\"a\", \"b\"]));\nreturn 0;\n", + ), + // `join` on a boxed receiver, which is an opcode rather than a + // method and so was never offered the method table's unbox. The + // renderings are the point: `-0.0`, the infinities and a nested + // container are where a second renderer would show, and there is no + // second renderer — the boxed path reaches the same `dyn_join`. + new( + "dyn_receiver_join", + "fn show(xs) { return xs.join(\"|\"); }\nprintln(show([2.0, -0.0, 0.5]));\nprintln(show([1, -1, 0]));\nprintln(show([\"a\", \"\", \"c\"]));\nprintln(show([true, false]));\nprintln(show([nil, 1, \"s\", 2.0, true]));\nprintln(show([[1, 2], [3]]));\nprintln(show([]));\nprintln(show([1.0 / 0.0, -1.0 / 0.0]));\nprintln(show([{\"k\": 1}]));\nreturn 0;\n", + ), // An all-nil branch join must not build a Nil-typed phi: it widens // to Dyn (boxed nil) and compares by tag. new( @@ -733,10 +1406,176 @@ fn differential_trait_dispatch_contract() { // merely skips) into a red test here. run_differential( "trait_contract", - &[new( - "trait_static_dynamic_show", - "struct Rect { w: Int, h: Int }\nstruct Circle { r: Int }\ntrait Area { fn area(self) -> Int; }\nimpl Area for Rect { fn area(self) -> Int { return self.w * self.h; } }\nimpl Area for Circle { fn area(self) -> Int { return 3 * self.r * self.r; } }\ntrait Show { fn show(self) -> String; }\nimpl Show for Rect { fn show(self) -> String { return \"Rect(${self.w}x${self.h})\"; } }\nlet r = Rect { w: 3, h: 4 };\nprintln(r.area());\nprintln(\"${r}\");\nlet shapes = [Rect { w: 1, h: 2 }, Circle { r: 2 }];\nprintln(shapes.map(|s| s.area()));\nreturn 0;\n", - )], + &[ + // `self` inside an impl method is that type, so a method built on + // the type's *other* methods devirtualizes. Without that + // provenance the receiver was an untyped parameter and the whole + // shape — which is what a trait default body always is — fell out + // of the native subset. `run_differential` requires the lowering, + // so this stays honest. + new( + "trait_method_calls_sibling", + "trait Sz {\n fn base(self) -> Int;\n fn doubled(self) -> Int { return self.base() * 2; }\n fn quad(self) -> Int { return self.doubled() * 2; }\n}\nstruct A { v: Int }\nimpl Sz for A { fn base(self) -> Int { return self.v; } }\nprintln(A { v: 5 }.base());\nprintln(A { v: 5 }.doubled());\nprintln(A { v: 5 }.quad());\nreturn 0;\n", + ), + // The same identity guarantee for every other carrier and every + // other way a container reaches a mutator. None of these had + // coverage, which is how the typed-list copy above survived: a + // container's writes being the caller's writes is the single most + // load-bearing thing about a reference type, and only the list + // carrier was ever wrong. + new( + "every_container_carrier_keeps_its_identity", + "struct Box { xs: List }\n\ + trait Sink { fn take(self, n: Int) -> Int; }\n\ + struct S { xs: List }\n\ + impl Sink for S { fn take(self, n: Int) -> Int { self.xs.push(n); return self.xs.len(); } }\n\ + fn put(m: Map, k: String, v: Int) -> Int { m.set(k, v); return m.len(); }\n\ + fn addset(s: Set, n: Int) -> Int { s.add(n); return s.len(); }\n\ + fn bump(p: Box, n: Int) -> Int { p.xs.push(n); return p.xs.len(); }\n\ + fn relay(xs: List, n: Int) -> Int { return inner(xs, n); }\n\ + fn inner(xs: List, n: Int) -> Int { xs.push(n); return xs.len(); }\n\ + let m: Map = {};\nprintln(put(m, \"a\", 1));\nprintln(m.len());\n\ + let st = Set([1]);\nprintln(addset(st, 2));\nprintln(st.len());\n\ + let b = Box { xs: [1] };\nprintln(bump(b, 2));\nprintln(\"${b.xs}\");\n\ + let s = S { xs: [] };\nprintln(s.take(1));\nprintln(s.take(2));\nprintln(\"${s.xs}\");\n\ + let r: List = [];\nprintln(relay(r, 5));\nprintln(\"${r}\");\n\ + let c: List = [1];\nlet g = |n: Int| -> Int { c.push(n); return c.len(); };\n\ + println(g(2));\nprintln(\"${c}\");\nreturn 0;\n", + ), + // A container passed to a function keeps its identity — the + // callee's writes are the caller's. + // + // It did not. The first fixpoint pass observes call arguments while + // every callee's return type is still its `I64` default, and the + // parameter lattice *joins* observations: pass 1's `I64` and pass + // 2's real `list` disagreed, so the parameter became `Dyn` and + // every call site boxed. A typed list boxes by **rebuilding** + // (`list_h.i64_to_dyn`), so the callee held a copy and its `push` + // was lost — a wrong answer that still printed a plausible length. + // `ret_known` already existed for this hazard on the HOF re-route + // path; the parameter lattice never got it. + new( + "a_container_argument_keeps_its_identity", + "fn mk() -> List { return [1]; }\n\ + fn add(xs: List, n: Int) -> Int { xs.push(n); return xs.len(); }\n\ + let xs = mk();\nprintln(add(xs, 2));\nprintln(xs.len());\nprintln(\"${xs}\");\n\ + let ys: List = [];\n\ + println(try { \"${add(ys, 7)}\" } catch e { \"c\" });\nprintln(ys.len());\n\ + println(\"${ys}\");\nreturn 0;\n", + ), + // `typeof` names the struct, and both engines agree about which + // carriers it can decide statically. A struct instance and a plain + // map share `MapStrDyn`, so the static table's `Map` was a wrong + // answer for structs: `typeof(p)` read `Map` compiled and `Object` + // interpreted — two engines, two wrong answers, neither of them the + // struct's name. + new( + "typeof_names_the_struct", + "struct S { a: Int }\nfn name_of(x: Any) -> String { return typeof(x); }\n\ + let m = {\"a\": 1, \"b\": \"x\"};\nlet p = S { a: 1 };\n\ + println(typeof(p));\nprintln(typeof(m));\nprintln(name_of(p));\nprintln(name_of(m));\n\ + println(name_of(1));\nprintln(name_of(\"s\"));\nprintln(name_of([1]));\n\ + println(typeof(1));\nprintln(typeof(1.5));\nprintln(typeof(true));\nprintln(typeof(nil));\n\ + return 0;\n", + ), + // The receiver whose type the lowering *cannot* name — two call + // sites passing different structs into one parameter, or a mixed + // list — dispatches at run time off the arena type mark instead of + // taking the module to the VM. It knows its type then; only the + // already-boxed `Dyn` shape used to reach that path, and only with + // zero arguments. + new( + "a_receiver_of_unknown_struct_type_dispatches_at_run_time", + "struct A { v: Int }\nstruct B { v: Int }\n\ + trait N { fn name(self) -> String; fn scaled(self, k: Int) -> Int;\n\ + fn label(self, p: String, q: String) -> String; }\n\ + impl N for A { fn name(self) -> String { return \"A\"; }\n\ + fn scaled(self, k: Int) -> Int { return self.v * k; }\n\ + fn label(self, p: String, q: String) -> String { return p + \"A\" + q; } }\n\ + impl N for B { fn name(self) -> String { return \"B\"; }\n\ + fn scaled(self, k: Int) -> Int { return self.v + k; }\n\ + fn label(self, p: String, q: String) -> String { return p + \"B\" + q; } }\n\ + fn describe(x: Any, k: Int) -> String { return x.name() + \":${x.scaled(k)}\" + x.label(\"<\", \">\"); }\n\ + println(describe(A { v: 3 }, 4));\nprintln(describe(B { v: 3 }, 4));\n\ + let xs = [A { v: 1 }, B { v: 2 }];\nfor x in xs { println(x.scaled(10)); }\nreturn 0;\n", + ), + // A struct that arrives as an *argument* is that type too. The + // provenance came only from a `NewObject` the lowering saw, so it + // survived a `return` (`ret_structs`) but not a parameter: + // `fn area(q: P) { return q.w * q.h; }` lowered (fields need no + // name) while `fn area(q: P) { return q.norm(); }` could not + // devirtualize and took the whole module to the VM. Covered here + // for a plain function, a lambda, a second argument, and a callee + // that passes its own parameter on. + new( + "a_struct_argument_keeps_its_type", + "struct P { w: Int, h: Int }\ntrait Sz { fn area(self) -> Int; }\nimpl Sz for P { fn area(self) -> Int { return self.w * self.h; } }\n\ + fn area_of(q: P) -> Int { return q.area(); }\nfn relay(q: P) -> Int { return area_of(q); }\n\ + fn tagged(tag: String, q: P) -> String { return tag + \"=\" + \"${q.area()}\"; }\n\ + let f = |q: P| -> Int { return q.area() + 1; };\nlet p = P { w: 2, h: 3 };\n\ + println(area_of(p));\nprintln(relay(p));\nprintln(tagged(\"a\", p));\nprintln(f(p));\n\ + println(area_of(P { w: 4, h: 5 }));\nreturn 0;\n", + ), + // An impl method nobody calls is no longer a lowering root — and + // `show` is the one method reached *without* a call naming it + // (a display site does). Dropping it from the roots leaves a + // dangling callee and the module fails MIR validation, so this + // pins both halves at once: an uncalled `unused` alongside a + // `show` that only `"${…}"` reaches. + // A container in a template renders. `docs/semantics.md` used to + // rule this a loud failure — the VM stopped doing that, and the + // lowering kept mirroring the retired rule, so every template + // holding a list or a struct list dropped its module to the VM. + new( + "container_in_template", + "struct P { v: Int }\nlet xs = [1, 2, 3];\nlet ps = [P { v: 1 }, P { v: 2 }];\nprintln(\"${xs}\");\nprintln(\"a${xs}b\");\nprintln(\"${ps}\");\nprintln(\"n=${xs}, p=${ps}\");\nreturn 0;\n", + ), + // A struct with no `show` renders like the VM's default: + // `Name{f:v,…}`, declaration order, nested values quoted. + // + // **Nesting is the point.** An earlier attempt spelled the + // rendering out at the display site and printed a nested struct as + // a hash-ordered map — a field holding a struct is a bare map by + // then, and the display site cannot tell. The type description now + // lives at runtime, where the mark is, so nesting recurses. + new( + "struct_default_display", + "struct P { name: String, n: Int, ok: Bool, f: Float }\nstruct Outer { inner: P, tag: String }\nstruct WithList { p: P, xs: List, s: String }\nstruct E {}\nlet p = P { name: \"a, b\", n: -3, ok: true, f: 1.5 };\nlet o = Outer { inner: p, tag: \"x\" };\nlet w = WithList { p: p, xs: [1, 2], s: \"z\" };\nlet e = E {};\nprintln(\"${p}\");\nprintln(\"${o}\");\nprintln(\"${w}\");\nprintln(\"${e}\");\nprintln(p);\nreturn 0;\n", + ), + // A function that returns a struct carries the type name out to + // its callers, so a method on the result devirtualizes. The name + // used to stop at the function boundary — `make(3, 4).norm()` had + // an untyped receiver, in one module as much as across two. + new( + "struct_returning_function", + "struct Pt { x: Int, y: Int }\ntrait Norm { fn norm(self) -> Int; }\nimpl Norm for Pt { fn norm(self) -> Int { return self.x + self.y; } }\nfn make(a: Int, b: Int) -> Pt { return Pt { x: a, y: b }; }\nfn pick(c: Bool) -> Pt { if c { return make(1, 2); } return make(3, 4); }\nprintln(make(3, 4).norm());\nprintln(pick(true).norm());\nprintln(pick(false).norm());\nreturn 0;\n", + ), + // A named call devirtualizes like a positional one, plus the + // argument *order*: every name is a constant, so the permutation + // into the callee's frame order is a compile-time fact. The whole + // opcode had no lowering, which mattered once `module.Type { … }` + // started desugaring to one. + new( + "named_call_permutes_arguments", + "fn mk({x: Int, y: Int}) -> Int { return x * 10 + y; }\nfn pos(a: Int, {b: Int}) -> Int { return a * 100 + b; }\nprintln(mk(y: 2, x: 3));\nprintln(mk(x: 1, y: 9));\nprintln(pos(7, b: 4));\nreturn 0;\n", + ), + new( + "trait_show_hook_and_uncalled", + "trait Show { fn show(self) -> String; }\nstruct R { w: Int }\nimpl Show for R { fn show(self) -> String { return \"R!\"; } }\ntrait Extra { fn unused(self, s: String) -> Int; }\nimpl Extra for R { fn unused(self, s: String) -> Int { return s.len(); } }\nlet r = R { w: 3 };\nprintln(\"${r}\");\nreturn 0;\n", + ), + // Two implementors, one of them never calling a method it defines. + // Every impl method is a lowering root, so an *uncalled* one used to + // be lowered with the `I64` parameter default and fail reading a + // field — killing the module from a method nobody calls. + new( + "trait_uncalled_impl_method", + "trait Sz {\n fn base(self) -> Int;\n fn doubled(self) -> Int;\n fn quad(self) -> Int;\n}\nstruct A { v: Int }\nimpl Sz for A {\n fn base(self) -> Int { return self.v; }\n fn doubled(self) -> Int { return self.base() * 2; }\n fn quad(self) -> Int { return self.doubled() * 2; }\n}\nstruct B { v: Int }\nimpl Sz for B {\n fn base(self) -> Int { return self.v; }\n fn doubled(self) -> Int { return self.v * 3; }\n fn quad(self) -> Int { return self.doubled() * 2; }\n}\nprintln(A { v: 5 }.quad());\nprintln(B { v: 5 }.quad());\nreturn 0;\n", + ), + new( + "trait_static_dynamic_show", + "struct Rect { w: Int, h: Int }\nstruct Circle { r: Int }\ntrait Area { fn area(self) -> Int; }\nimpl Area for Rect { fn area(self) -> Int { return self.w * self.h; } }\nimpl Area for Circle { fn area(self) -> Int { return 3 * self.r * self.r; } }\ntrait Show { fn show(self) -> String; }\nimpl Show for Rect { fn show(self) -> String { return \"Rect(${self.w}x${self.h})\"; } }\nlet r = Rect { w: 3, h: 4 };\nprintln(r.area());\nprintln(\"${r}\");\nlet shapes = [Rect { w: 1, h: 2 }, Circle { r: 2 }];\nprintln(shapes.map(|s| s.area()));\nreturn 0;\n", + ), + ], ); } @@ -745,6 +1584,16 @@ fn differential_concurrency_edges() { run_differential( "concurrency_edges", &[ + // The module spelling needs the import — on both ends. `chan` is + // the one name that is a module *and* a bare global (the channel + // constructor), and `chan.new(1)` compiles to the same bytecode + // either way: the import is what replaces the global with the + // module object at run time. Native used to resolve it regardless, + // so an unimported program ran natively and failed under the VM. + new( + "the module spelling after its import", + "use chan;\nlet c = chan.new(1);\nchan.send(c, 7);\nprintln(chan.recv(c));\nreturn 0;\n", + ), // The two try/catch cases that used to live here moved to // `try_catch_differential` in clif_differential_test.rs: this corpus // runs under `LK_AOT_NO_FALLBACK=1` in CI, and a protected region has diff --git a/cli/tests/aot_fuzz_differential_test.rs b/cli/tests/aot_fuzz_differential_test.rs index 12d199c0..eb281091 100644 --- a/cli/tests/aot_fuzz_differential_test.rs +++ b/cli/tests/aot_fuzz_differential_test.rs @@ -18,6 +18,20 @@ use std::io::Write as _; use std::path::PathBuf; use std::process::Command; +/// What a broken *workspace* looks like on `lk compile`'s stderr, as opposed to +/// a program the AOT declined to lower. +/// +/// `rustc`'s own error shapes plus the two the driver prints when the staticlib +/// step fails. Matched rather than parsed: the point is only to tell "your +/// checkout does not compile" from "your program does not lower", and any of +/// these settles that. +const TOOLCHAIN_FAILURES: &[&str] = &[ + "error[E", + "could not compile", + "failed to build lk-api", + "linking with `", +]; + fn bin_path() -> PathBuf { PathBuf::from(env!("CARGO_BIN_EXE_lk")) } @@ -688,6 +702,7 @@ impl Generator { } /// Generates one program; the flag reports whether it carries a hybrid + /// helper (the harness then asserts the bridge actually engaged). /// Generates one program; the flag reports whether it carries a hybrid /// helper (the harness then asserts the bridge actually engaged). fn program(&mut self) -> (String, bool) { let mut out = String::new(); @@ -701,6 +716,64 @@ impl Generator { None }; + // A container the top level declares and the helpers below mutate. + // + // This is the shape the generator could not produce, and it is the shape + // that miscompiled: a `List` written to a global slot was boxed, + // boxing re-represents a container, and the slot ended up holding a + // *second* list while the entry went on reading the first. Both backends + // ran, neither complained, and they printed different numbers — a + // three-line program, found by accident while writing an example for + // something else. + // + // The generator missed it because a helper's body was built with + // `self.vars`, `self.lists` and `self.maps` emptied, so a generated + // function could only ever touch its own parameters. Nothing it wrote + // shared anything with the top level. + let shared_list = if self.rng.chance(60) { + let name = self.fresh("shared_xs"); + let _ = writeln!(out, "let {name}: List = [];"); + Some(name) + } else { + None + }; + let shared_map = if self.rng.chance(40) { + let name = self.fresh("shared_m"); + let _ = writeln!(out, "let {name}: Map = {{}};"); + Some(name) + } else { + None + }; + // The same shape on a *different carrier*, because the carrier is what + // was wrong last time. + // + // `container_ty` in the lowering decides both which globals keep their + // own type and which are refused when a slot joins to `Dyn`, and it + // listed the `List` and `Map` carriers only. A `Bytes` global therefore + // got neither: `let b = "abc".bytes(); fn f(n: Int) -> Int { return + // b[n] ?? -1; }` printed 98 interpreted and died with `runtime type + // error` compiled, for *any* index. This generator already knew to + // build a shared global container — it just only knew two of them, so + // it reproduced the previous bug's carrier and not the next one's. + // + // `Bytes` and `Set` are immutable here (no `push` equivalent that both + // backends lower), so the helpers *read* them; reading is what + // miscompiled. + let shared_bytes = if self.rng.chance(40) { + let name = self.fresh("shared_b"); + let _ = writeln!(out, "let {name} = \"abcdef\".bytes();"); + Some(name) + } else { + None + }; + let shared_set = if self.rng.chance(30) { + let name = self.fresh("shared_s"); + let _ = writeln!(out, "let {name} = Set([1, 2, 3]);"); + Some(name) + } else { + None + }; + for _ in 0..self.rng.below(3) { let name = self.fresh("fn_helper"); let arity = 1 + self.rng.below(2) as usize; @@ -714,6 +787,39 @@ impl Generator { self.vars.push((param.clone(), Ty::I64)); } let body = self.int_expr(2); + // Some helpers reach the top level's container instead of only + // their parameters. Written before the body's `return` so the + // mutation happens on every call. + let touches = match (&shared_list, &shared_map) { + (Some(list), _) if self.rng.chance(50) => { + format!("{list}.push(p0); ") + } + (_, Some(map)) if self.rng.chance(50) => { + // The key interpolates rather than concatenates. `"k" + p0` + // retypes an unannotated `p0` as a String — string + // concatenation is what `+` means once one side is one — and + // the helper then *returns* a String while everything + // generated around it expects an Int. That is a program the + // type checker rightly refuses, and it took a 1500-case run + // on a fresh seed to produce one. + format!("{map}[\"k${{p0}}\"] = p0; ") + } + _ => String::new(), + }; + // A read of a container global, folded into the returned value so a + // wrong answer shows up in stdout rather than only in a crash. The + // index is bounded by the helper's own parameter, which is how a + // *runtime* index (not a constant) reaches the carrier — the + // constant case lowered correctly even while this one did not. + let body = match (&shared_bytes, &shared_set) { + (Some(bytes), _) if self.rng.chance(50) => { + format!("({body}) + ({bytes}[p0 % 6] ?? 0)") + } + (_, Some(set)) if self.rng.chance(50) => { + format!("({body}) + (if {set}.contains(p0 % 4) {{ 1 }} else {{ 0 }})") + } + _ => body, + }; self.vars = saved; self.lists = saved_lists; self.maps = saved_maps; @@ -721,14 +827,196 @@ impl Generator { // A top-level `let f = |…| …` lambda is call-site identical to a // named `fn`, but exercises the zero-capture closure lowering // (MakeClosure → GlobalRef::Lambda devirtualization). - if self.rng.chance(30) { + if touches.is_empty() && self.rng.chance(30) { let _ = writeln!(out, "let {name} = |{}| {body};", params.join(", ")); } else { - let _ = writeln!(out, "fn {name}({}) {{ return {body}; }}", params.join(", ")); + let _ = writeln!(out, "fn {name}({}) {{ {touches}return {body}; }}", params.join(", ")); } self.fns.push(FnSig { name, arity }); } + // Containers that cross a call boundary, in both directions. + // + // The generator could not produce these either: a helper's parameters + // were always `Int`, so a container never travelled into a function and + // never came back out of one. That is the same question the bug found by + // accident was about — whether the two sides are looking at one + // container or at a copy — asked at the other boundary. + // + // Each of these is read *after* the call, because a program that only + // passes a container agrees whichever answer is right. + if self.rng.chance(50) { + let taker = self.fresh("fn_taker"); + let _ = writeln!( + out, + "fn {taker}(xs: List, p0: Int) -> Int {{ xs.push(p0); return xs.len(); }}" + ); + let arg = self.fresh("tl"); + let _ = writeln!(out, "let {arg}: List = [{}];", self.rng.below(40)); + let value = self.rng.below(50); + // Two *siblings* that hand the same container on, not one relay. + // + // That plurality is the shape, and it took bisecting a real + // miscompile to find out. One caller passing a container down does + // not reproduce it; two callers of the same mutator, both reached + // from the top level, do — the container's type is settled from + // whichever call the fixpoint looked at first, and a pass that fails + // to look leaves the other holding a guess. + // + // The bug this reconstructs was mine: an attempt to make the entry + // refuse a call whose callee's return type was not yet known, which + // recovered one shape and made this one print an empty list with no + // fallback and no warning. See the note in `inst/global.rs`. + let relay_a = self.fresh("fn_relay"); + let relay_b = self.fresh("fn_relay"); + let _ = writeln!( + out, + "fn {relay_a}(xs: List, which: Int) -> Int {{ if (which == 1) {{ {taker}(xs, 91); return 0 - 1; }} {taker}(xs, 92); return 33; }}" + ); + let _ = writeln!( + out, + "fn {relay_b}(xs: List) -> Int {{ let r = {taker}(xs, 3); {taker}(xs, 4); return r; }}" + ); + let _ = writeln!(out, "println({relay_a}({arg}, 1));"); + let _ = writeln!(out, "println({relay_b}({arg}));"); + let _ = writeln!(out, "println({taker}({arg}, {value}));"); + let _ = writeln!(out, "println({arg}.len());"); + let _ = writeln!(out, "println({arg}[{arg}.len() - 1]);"); + } + if self.rng.chance(40) { + let maker = self.fresh("fn_maker"); + let _ = writeln!( + out, + "fn {maker}(p0: Int) -> List {{ let out: List = []; out.push(p0); out.push(p0 + 1); return out; }}" + ); + let made = self.fresh("ml"); + let seed = self.rng.below(30); + let _ = writeln!(out, "let {made} = {maker}({seed});"); + let _ = writeln!(out, "println({made}.len());"); + let _ = writeln!(out, "println({made}[1]);"); + // And mutate what came back, which is where a returned handle that + // was really a copy of an already-freed thing would show. + let _ = writeln!(out, "{made}.push(7);"); + let _ = writeln!(out, "println({made}.len());"); + } + // `defer` runs on the way out, whichever way. A generated feature with + // no generated coverage is how the next silent difference gets in. + if self.rng.chance(40) { + let deferred = self.fresh("fn_deferred"); + let _ = writeln!( + out, + "fn {deferred}(xs: List, p0: Int) -> Int {{\n defer xs.push(0 - 1);\n if (p0 % 2 == 0) {{ return p0; }}\n return p0 * 2;\n}}" + ); + let arg = self.fresh("dl"); + let _ = writeln!(out, "let {arg}: List = [];"); + // Both branches, so the release has to happen on both. + let _ = writeln!(out, "println({deferred}({arg}, {}));", self.rng.below(20) * 2); + let _ = writeln!(out, "println({deferred}({arg}, {}));", self.rng.below(20) * 2 + 1); + let _ = writeln!(out, "println({arg}.len());"); + } + // A `try` region with something in it other than a bare call: a + // *nested* region, and a closure built outside the region and called + // inside it. Both are outlined into functions of their own, so what + // crosses the boundary — a write from two frames in, a captured value + // that has no machine word — is decided by machinery no flat + // `try { f(); } catch` exercises. Both shapes shipped a silent wrong + // answer that every other gate passed. + if self.rng.chance(45) { + let probe = self.fresh("fn_tryshape"); + let cap = self.rng.below(9) + 1; + let bump = self.rng.below(5) + 1; + let _ = writeln!( + out, + "fn {probe}(p0: Int) -> Int {{\n let cap = {cap};\n let scaled = || -> Int {{ return p0 * cap; }};\n let plain = || -> Int {{ return {bump}; }};\n let out = 0;\n try {{\n try {{\n if (p0 % 3 == 0) {{ error(\"inner\"); }}\n out = scaled() + plain();\n }} catch e {{ out = 0 - 1; }}\n if (p0 % 5 == 0) {{ error(\"outer\"); }}\n out = out + plain();\n }} catch e {{ out = out - 100; }}\n return out;\n}}" + ); + // Every combination of the two raise conditions, so neither edge + // of either region is left untaken. + for arg in [1u64, 3, 5, 15] { + let _ = writeln!(out, "println({probe}({arg}));"); + } + } + // A `try` with an **empty** handler, and a body that returns on one + // path only. + // + // Every other generated `catch` has a statement in it, and that is what + // hid this: the compiler emits no jump over an empty handler, because + // there is nothing to jump over — so the region's fallthrough *is* its + // handler, which is also what "the body returns on every path" looks + // like. The lowering read the second from the first, skipped the + // did-it-return test, and returned a value nobody parked. + if self.rng.chance(35) { + let probe = self.fresh("fn_emptycatch"); + let at = self.rng.below(4); + let _ = writeln!( + out, + "fn {probe}(p0: Int) -> Int {{\n let acc = 0;\n for v in 0..4 {{\n try {{\n if (v == {at} && p0 > 0) {{ return v * 100; }}\n acc = acc + v;\n }} catch e {{ }}\n }}\n return acc;\n}}" + ); + // Both the path that returns out of the region and the one that + // does not — the second is the one that was wrong. + for arg in [0u64, 1] { + let _ = writeln!(out, "println({probe}({arg}));"); + } + } + // A `try` whose body leaves through a jump that belongs to the loop + // *outside* it. Natively the body is a function of its own, so a `break` + // written there has no loop to leave: it reports which way it left + // through a flag the caller dispatches on. Three exits — `break`, + // `continue`, `return` — plus the ordinary fall-through and a raise, so + // every arm of that dispatch is taken. + // + // The loop kind is drawn because `continue` does not land in the same + // place in each: a `for` range jumps forward to the latch, a `while` + // jumps backward to the condition, and the first version of this got the + // second one wrong. + if self.rng.chance(45) { + let probe = self.fresh("fn_tryescape"); + let brk = self.rng.below(4) + 4; + let skip = self.rng.below(3) + 1; + let bail = self.rng.below(3) + 5; + let header = match self.rng.below(2) { + 0 => "for v in 0..9 {".to_string(), + _ => "let v = 0 - 1;\n while v < 8 {\n v = v + 1;".to_string(), + }; + let _ = writeln!( + out, + "fn {probe}(p0: Int) -> Int {{\n let acc = 0;\n {header}\n try {{\n acc = acc + v;\n if (v == {skip}) {{ continue; }}\n if (v == 7) {{ error(\"raised\"); }}\n if (v == {brk}) {{ break; }}\n if (v == {bail} && p0 > 0) {{ return acc * 10; }}\n acc = acc + 1;\n }} catch e {{\n acc = acc + 100;\n }}\n }}\n return acc;\n}}" + ); + for arg in [0u64, 1] { + let _ = writeln!(out, "println({probe}({arg}));"); + } + } + + // A closure used as a *value* — in a list, pushed, iterated and called + // back. Everything else the generator makes of a lambda is built and + // called where it stands, which is the case the compiler answers + // statically; this is the one that has to go through the runtime. + if self.rng.chance(40) { + let ops = self.fresh("cv"); + let a = self.rng.below(9) + 1; + let b = self.rng.below(9) + 1; + let _ = writeln!(out, "let {ops} = [|x| x + {a}, |x| x * {b}];"); + let _ = writeln!(out, "println({ops}[0]({a}));"); + let _ = writeln!(out, "println({ops}[1]({b}));"); + let built = self.fresh("cb"); + let _ = writeln!(out, "let {built} = [];"); + let _ = writeln!(out, "{built}.push(|x| x - {a});"); + let _ = writeln!(out, "println({built}.len());"); + let _ = writeln!(out, "println(typeof({built}[0]));"); + let sum = self.fresh("cs"); + let _ = writeln!(out, "let {sum} = 0;"); + let _ = writeln!(out, "for f in {ops} {{ {sum} = {sum} + f({b}); }}"); + let _ = writeln!(out, "println({sum});"); + // A closure capturing *another closure*: its environment is all + // static references, which the compiler erases entirely — and a + // value still needs one. That shipped answering "value is not + // callable" for a function that exists. + let base = self.fresh("cbase"); + let wrap = self.fresh("cwrap"); + let _ = writeln!(out, "let {base} = |x| x + {a};"); + let _ = writeln!(out, "let {wrap} = [|y| {base}(y) * {b}];"); + let _ = writeln!(out, "println({wrap}[0]({a}));"); + } + let statements = 3 + self.rng.below(5); for _ in 0..statements { self.statement(&mut out, ""); @@ -751,6 +1039,33 @@ impl Generator { } } + // What the helpers left behind, read from the top level. + // + // Read *here*, after the statements have called them, because the whole + // question is whether the top level and the functions are looking at the + // same container. A program that only wrote it would agree either way. + if let Some(list) = &shared_list { + let _ = writeln!(out, "println({list}.len());"); + let _ = writeln!(out, "if ({list}.len() > 0) {{ println({list}[0]); }}"); + } + if let Some(map) = &shared_map { + let _ = writeln!(out, "println({map}.len());"); + } + if let Some(bytes) = &shared_bytes { + let _ = writeln!(out, "println({bytes}.len());"); + let _ = writeln!(out, "println({bytes}[0] ?? -1);"); + // Both ends of the range: out of range is `nil` at either one, and + // the negative end is where the interpreter used to raise while the + // native build answered nil. + let _ = writeln!(out, "println({bytes}[-1] ?? -1);"); + let _ = writeln!(out, "println({bytes}[99] ?? -1);"); + let _ = writeln!(out, "println({bytes}[-99] ?? -1);"); + } + if let Some(set) = &shared_set { + let _ = writeln!(out, "println({set}.len());"); + let _ = writeln!(out, "println({set}.contains(2));"); + } + // `println` lowers natively now (GetGlobal builtin + format expansion); // exercise several shapes: `{}` formats, plain values, extra args, and // randomized placeholder/argument-count mismatches (the lower-time @@ -823,6 +1138,14 @@ impl Generator { struct CaseOutcome { compared: bool, + /// The program compiled *fully native* — neither bridged nor dropped to the + /// Tier 0 VM bundle. + /// + /// Counted separately from `compared` because a fallback still compiles, + /// still runs, and still answers correctly: a lowering regression is + /// invisible to a differential comparison by construction. `compared` alone + /// would stay at its floor while every generated program ran on the VM. + fully_native: bool, } /// Runs a command to completion with a hard timeout, killing the child on @@ -891,6 +1214,21 @@ fn run_case(dir: &std::path::Path, name: &str, source: &str, seed: u64, expect_h context("AOT compile panicked (lower()/codegen must be total)") ); if !exe.status.success() { + // A *toolchain* failure is not a compiler answer, and reading it as one + // sends the reader to the generated program. + // + // The prebuild above catches a workspace that was already broken when + // the run started. What it cannot catch is one that breaks *during* it: + // `lk compile` rebuilds the `lk-api` staticlib on the way, so an edit + // landing in another crate mid-run arrives here as "the AOT rejected + // your program ungracefully", with `error[E0425]` buried in `stderr` + // under a thousand lines of generated program. That happened, and cost + // two rounds of reading the program instead of the checkout. + assert!( + !TOOLCHAIN_FAILURES.iter().any(|marker| exe_stderr.contains(marker)), + "the toolchain itself did not build, so this says nothing about the generated \ + program. Fix the workspace and re-run.\nstderr: {exe_stderr}" + ); assert!( exe_stderr.contains("does not support"), "{}\nstderr: {exe_stderr}", @@ -904,8 +1242,12 @@ fn run_case(dir: &std::path::Path, name: &str, source: &str, seed: u64, expect_h .trim() .to_string(); println!(" unsupported [{name}]: {reason}"); - return CaseOutcome { compared: false }; + return CaseOutcome { + compared: false, + fully_native: false, + }; } + let fully_native = !exe_stderr.contains("Tier 1 hybrid") && !exe_stderr.contains("falling back"); // A program with a hybrid helper either bridges it ("Tier 1 hybrid") or // falls back whole to Tier 0 for some *other* ineligible shape ("falling // back") — but it must never compile fully native: that means the @@ -946,7 +1288,109 @@ fn run_case(dir: &std::path::Path, name: &str, source: &str, seed: u64, expect_h native.status, String::from_utf8_lossy(&native.stderr) ); - CaseOutcome { compared: true } + CaseOutcome { + compared: true, + fully_native, + } +} + +/// Programs whose features *cross*, compared without a native-coverage floor. +/// +/// Five defects came from a throwaway generator that crossed features and none +/// from the structured probing that moved one axis at a time: a `try` region's +/// parked `return` not joining with the function's own returns; `"a" + v` with +/// `v: Any` typed `String` while a list operand makes the answer a list; +/// `{1: 2} - k` reaching the boxed path through a `Maybe` key; a "cannot hold +/// this" fold applied to a method the receiver does not have; and a removal +/// handing back a typed map where the caller unboxes a boxed one. +/// +/// Separate from `fuzz_differential_vm_vs_native` because these deliberately +/// reach shapes that may not lower — mixing them into that generator dropped +/// its native ratio from 13–19 of 40 to 3, which is exactly what its floor is +/// there to catch. Here the comparison is the whole point and the ratio is not +/// a property worth asserting. +#[test] +fn fuzz_differential_crossed_shapes() { + const ERASED: &[&str] = &[ + "1", + "2.5", + "\"s\"", + "true", + "nil", + "[1, 2]", + "[[1], [2]]", + "{\"k\": 1}", + "{1: 2}", + "Set([1])", + "\"ab\".bytes()", + "[1, \"a\"]", + "[]", + "{}", + ]; + const PROBES: &[&str] = &[ + "a + b", + "a - b", + "b in a", + "a == b", + "\"t=\" + a", + "a.contains(b)", + "a.index_of(b)", + "a.count(b)", + "a.has(\"k\")", + "a.delete(\"k\")", + "a.len()", + "a.first()", + "a.sort()", + "a.sum()", + "a.join(\",\")", + "a[b]", + ]; + + let cases: u64 = std::env::var("LK_FUZZ_CASES") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(40); + let seed: u64 = std::env::var("LK_FUZZ_SEED") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(0xC0FF_EE00); + warm_lk_api_staticlib(); + + let dir = std::env::temp_dir().join(format!("lk_aot_crossed_{}", std::process::id())); + let _ = fs::remove_dir_all(&dir); + fs::create_dir_all(&dir).expect("create tmp dir"); + + let mut compared = 0_u64; + for case in 0..cases { + let case_seed = seed.wrapping_add(case); + let mut rng = Rng(case_seed); + let a = ERASED[rng.below(ERASED.len() as u64) as usize]; + let b = ERASED[rng.below(ERASED.len() as u64) as usize]; + let probe = PROBES[rng.below(PROBES.len() as u64) as usize]; + // Everything is caught and printed, so a program that raises is a + // *result*: the two back ends have to agree on which, and on the + // message. The `try` arm answers whatever the probe is and the `catch` + // arm a string — two arms of one function, disagreeing, which is the + // join a region's parked return has to take part in. The nil capture + // crosses the region with it. + let source = format!( + "fn crossed(a: Any, b: Any) -> Any {{\n \ + let n = nil;\n \ + let f = || n == nil;\n \ + try {{ let r: Any = {probe}; return \"ok \" + (r == nil) + f(); }}\n \ + catch e {{ return \"E\"; }}\n\ + }}\n\ + println(crossed({a}, {b}));\n\ + println(crossed({b}, {a}));\n" + ); + let name = format!("crossed_{case}"); + if run_case(&dir, &name, &source, case_seed, false).compared { + compared += 1; + } + let _ = fs::remove_dir_all(dir.join(&name)); + } + let _ = fs::remove_dir_all(&dir); + println!("crossed shapes: {compared}/{cases} cases compared (seed {seed:#x})"); } /// `lk compile` builds the lk-api staticlib on demand *inside the compile @@ -961,9 +1405,9 @@ fn warm_lk_api_staticlib() { let workspace = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); let status = Command::new("cargo") .current_dir(&workspace) - .args(["build", "-p", "lk-api", "--features", "ffi", "--release"]) + .args(["build", "-p", "lk-api-cabi", "--release"]) .status() - .expect("spawn cargo build lk-api"); + .expect("spawn cargo build lk-api-cabi"); assert!(status.success(), "failed to prebuild the lk-api staticlib"); } @@ -984,6 +1428,7 @@ fn fuzz_differential_vm_vs_native() { fs::create_dir_all(&dir).expect("create tmp dir"); let mut compared = 0_u64; + let mut fully_native = 0_u64; for case in 0..cases { let case_seed = seed.wrapping_add(case); let mut generator = Generator::new(case_seed); @@ -993,6 +1438,9 @@ fn fuzz_differential_vm_vs_native() { if outcome.compared { compared += 1; } + if outcome.fully_native { + fully_native += 1; + } // Drop this case's artifacts before generating the next one. Keeping // them all until the end costs ~30 MB per case under a sanitizer, so a // default 800-case run filled a 24 GB tmpfs and then failed with a @@ -1002,14 +1450,41 @@ fn fuzz_differential_vm_vs_native() { let _ = fs::remove_file(dir.join(format!("{name}.lkm"))); } - println!("fuzz differential: {compared}/{cases} cases natively compared (seed {seed:#x})"); + println!( + "fuzz differential: {compared}/{cases} cases compared, {fully_native} of them fully native (seed {seed:#x})" + ); let _ = fs::remove_dir_all(&dir); - // The generator targets the MIR-lowerable subset; if almost nothing lowers + // The generator targets the MIR-lowerable subset; if almost nothing compiles // any more, the fuzz has silently degraded into a VM-only smoke test. assert!( compared * 4 >= cases, - "only {compared}/{cases} generated programs lowered natively; the generator or the \ + "only {compared}/{cases} generated programs compiled; the generator or the \ MIR pipeline coverage has regressed" ); + // And a second floor on the number that lowered *fully native*. A program + // that drops to the hybrid bridge or the Tier 0 bundle still compiles, still + // runs, and still agrees with the VM — so the comparison above cannot see a + // lowering regression at all, and the count above would not move if every + // generated program started running on the VM. The floor is a fifth, + // deliberately far below what is measured: the generator emits + // deliberately-unlowerable hybrid helpers, so the real ratio is a property + // of the generator rather than a gate. What the floor catches is a + // collapse, which goes to nearly zero rather than drifting. + // + // Asserted only on a *large* run, because the ratio is a sample and a small + // one is noisy: measured on an unchanged tree it was 10, 12, 13, 16, 16, 18 + // and 18 out of 60 across seven seeds — 17% to 30% against a 20% floor, so + // the seed alone decides whether it fires. It cried wolf twice in one + // session here, and both times the change under test was blamed for a + // number the seed had already produced. CI runs 500 cases, where the same + // spread is a few points wide and the floor means something. + // + // The ratio is printed either way, so a small run still reports it. + const FLOOR_NEEDS: u64 = 200; + assert!( + compared < FLOOR_NEEDS || fully_native * 5 >= compared, + "only {fully_native}/{compared} compiled programs lowered fully native; native coverage \ + has regressed behind a fallback that still answers correctly" + ); } diff --git a/cli/tests/boxed_receiver_coverage_test.rs b/cli/tests/boxed_receiver_coverage_test.rs new file mode 100644 index 00000000..2e61d5d7 --- /dev/null +++ b/cli/tests/boxed_receiver_coverage_test.rs @@ -0,0 +1,165 @@ +//! Every read-only list method, called on a receiver the lowering cannot type. +//! +//! `fn show(xs) { return xs.join(", "); }` is how a list method is usually +//! written, and it is the shape that had no coverage: the receiver reaches +//! `lower_method` as `Ty::Dyn`, which unboxes through `dyn.as_list` only when +//! the name's `METHOD_TABLE` row says `unbox_list`. Six names were missing +//! that row or had it wrong, each found separately, each by a program that +//! stopped compiling — `chain` said `false` while `concat`, which shares its +//! dispatch arm, said `true`; `first`, `last`, `index_of` and `count` had no +//! row at all though their arms accepted `ListDyn` already. +//! +//! Nothing gated it. A missing row is not a wrong answer — the program still +//! runs, on the VM, about three times slower and with no diagnostic — so the +//! differential corpora, the coverage gate and the fuzzer are all green for it. +//! This test is the gate: the name list comes from the VM's own +//! `list_dispatch`, and a name that does not lower has to be in [`EXCLUDED`] +//! with a reason. +//! +//! [`EXCLUDED`] is asserted in both directions. A name that starts lowering +//! must leave the list, so it cannot quietly become a place to park failures. +//! +//! And the interpreter has to accept each probe first, which is a check on the +//! *test* rather than on the compiler. Without it a probe with the wrong +//! signature refuses to lower for a reason that has nothing to do with the +//! receiver and lands in [`EXCLUDED`] looking like a finding: `reduce` is +//! `reduce(initial, f)` and was written `reduce(f)`, so a name that lowers +//! correctly sat in the list with an invented excuse; `to_bytes` was probed +//! with floats it refuses. Two of the entries were about the probe. + +use std::path::Path; + +/// `(method, call, first, second)` for every read-only list method the VM +/// dispatches — the call, and the two call-site arguments that join its +/// receiver to `Dyn`. +/// +/// The two arguments have to differ in *carrier*, which is what makes the +/// parameter erase; a single `xs: Any` annotation does not do it, because the +/// lowering specializes on the one call site it can see. `to_bytes` is why the +/// pair is per-name rather than fixed: it wants Ints, so its second site is a +/// mixed list narrowed back to one. +/// +/// Read from `core/src/vm/context/core_methods/list_dispatch.rs`, minus the six +/// that mutate — `clear`, `insert`, `pop`, `push`, `remove_at`, `set` — which +/// must *not* unbox: `dyn.as_list` materializes a copy for three of the four +/// list representations, so a write through it lands on the copy. +/// `no_unbox_list_name_mutates_its_receiver` in the lowering says the same +/// thing from the other side. +const READ_ONLY: &[(&str, &str, &str, &str)] = &[ + ("chunk", "chunk(2)", "[1, 2]", "[1.5, 2.5]"), + ("contains", "contains(1)", "[1, 2]", "[1.5, 2.5]"), + ("count", "count(1)", "[1, 2]", "[1.5, 2.5]"), + ("enumerate", "enumerate()", "[1, 2]", "[1.5, 2.5]"), + ("first", "first()", "[1, 2]", "[1.5, 2.5]"), + ("flatten", "flatten()", "[1, 2]", "[1.5, 2.5]"), + ("get", "get(0)", "[1, 2]", "[1.5, 2.5]"), + ("index_of", "index_of(1)", "[1, 2]", "[1.5, 2.5]"), + ("is_empty", "is_empty()", "[1, 2]", "[1.5, 2.5]"), + ("join", "join(\",\")", "[1, 2]", "[1.5, 2.5]"), + ("last", "last()", "[1, 2]", "[1.5, 2.5]"), + ("reverse", "reverse()", "[1, 2]", "[1.5, 2.5]"), + ("skip", "skip(1)", "[1, 2]", "[1.5, 2.5]"), + ("slice", "slice(0, 1)", "[1, 2]", "[1.5, 2.5]"), + ("sort", "sort()", "[1, 2]", "[1.5, 2.5]"), + ("sum", "sum()", "[1, 2]", "[1.5, 2.5]"), + ("take", "take(1)", "[1, 2]", "[1.5, 2.5]"), + ("to_bytes", "to_bytes()", "[1, 2]", "[3, \"x\"].take(1)"), + ("unique", "unique()", "[1, 2]", "[1.5, 2.5]"), + ("zip", "zip([9])", "[1, 2]", "[1.5, 2.5]"), + // Not in `list_dispatch` — they reach lists through the shared reduction + // and iterator paths — but they are list methods a program writes, and + // they are in the same position. + ("min", "min()", "[1, 2]", "[1.5, 2.5]"), + ("max", "max()", "[1, 2]", "[1.5, 2.5]"), + ("map", "map(|v| v)", "[1, 2]", "[1.5, 2.5]"), + ("filter", "filter(|v| true)", "[1, 2]", "[1.5, 2.5]"), + ("reduce", "reduce(0, |a, b| a)", "[1, 2]", "[1.5, 2.5]"), +]; + +/// Names that do not lower on a boxed receiver, and why. +/// +/// Each is a decision, not a gap left open. Removing a name from here means it +/// now lowers, which the test below also checks — a stale exclusion fails. +const EXCLUDED: &[(&str, &str)] = &[( + "slice", + "answers a *window* over the receiver, and `dyn.as_list` materializes a \ + plain list for three of the four carriers — so unboxing changes the \ + answer's kind. `\"\" + xs.slice(0, 1)` raises in the VM, which is what a \ + window does, and answered a list when the row said `unbox_list`.", +)]; + +#[test] +fn every_read_only_list_method_takes_a_boxed_receiver() { + let dir = tempfile::tempdir().expect("temp dir"); + let mut unexpectedly_refused = Vec::new(); + let mut unexpectedly_lowered = Vec::new(); + + for (name, call, first, second) in READ_ONLY { + // Two call sites with different element carriers join the parameter to + // `Dyn`, which is the receiver type under test. Without the second + // call the parameter would be inferred as one concrete list. + let source = dir.path().join(format!("{name}.lk")); + std::fs::write( + &source, + format!( + "fn probe(xs) {{\n return xs.{call};\n}}\nprintln(probe({first}));\nprintln(probe({second}));\n" + ), + ) + .expect("write probe"); + + // The interpreter has to accept it first. A probe with the wrong + // signature refuses to lower for a reason that has nothing to do with + // the receiver, and lands in `EXCLUDED` looking like a finding — + // `reduce` is `reduce(initial, f)` and was written `reduce(f)`, so a + // name that lowers correctly sat in the list with an invented excuse. + let interpreted = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + assert!( + interpreted.status.success(), + "the probe for `{name}` is not a program the interpreter accepts, so what it \ + measures is the probe: {}", + String::from_utf8_lossy(&interpreted.stderr) + ); + + let lowers = lowers_natively(&source, &dir.path().join(format!("{name}_exe"))); + let excluded = EXCLUDED.iter().any(|(excluded, _)| excluded == name); + match (lowers, excluded) { + (false, false) => unexpectedly_refused.push(*name), + (true, true) => unexpectedly_lowered.push(*name), + _ => {} + } + } + + assert!( + unexpectedly_refused.is_empty(), + "these list methods refuse a boxed receiver and are not in EXCLUDED: {unexpectedly_refused:?}. \ + A program written `fn f(xs) {{ return xs.NAME(); }}` falls to the VM for each of them, \ + silently. Add the `METHOD_TABLE` row (and the `ListDyn` arm, if it is missing), or list \ + the name in EXCLUDED with the reason it cannot." + ); + assert!( + unexpectedly_lowered.is_empty(), + "these are in EXCLUDED but now lower: {unexpectedly_lowered:?}. Remove them — an exclusion \ + that no longer holds is what makes the list stop meaning anything." + ); +} + +/// Whether `source` compiles with the bridge off and fallback forbidden. +/// +/// Both are pinned, because a fallback compiles and runs and prints the right +/// answer — which is exactly why a missing row survived so long. +fn lowers_natively(source: &Path, exe: &Path) -> bool { + std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("run lk compile") + .status + .success() +} diff --git a/cli/tests/broken_pipe_test.rs b/cli/tests/broken_pipe_test.rs new file mode 100644 index 00000000..7d81bc21 --- /dev/null +++ b/cli/tests/broken_pipe_test.rs @@ -0,0 +1,54 @@ +//! `lk prog.lk | head` must stop, not panic. +//! +//! Rust ignores `SIGPIPE` before `main`, which turns a write to a closed pipe +//! into `EPIPE` and then into a `println!` panic — the CLI printed +//! `thread 'main' panicked at library/std/src/io/stdio.rs …` and exited 101. +//! The AOT-compiled binary has a C `main` and never got that startup, so it +//! already died with signal 13. This pins the interpreter to the same answer. +#![cfg(unix)] + +use std::io::{BufRead, BufReader}; +use std::os::unix::process::ExitStatusExt; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +#[test] +fn a_reader_that_goes_away_stops_the_program_instead_of_panicking() { + let dir = std::env::temp_dir().join(format!("lk_pipe_{}", std::process::id())); + std::fs::create_dir_all(&dir).expect("temp dir"); + let script = dir.join("spew.lk"); + // Long enough that the child is certainly still writing when the reader + // goes away, and unbuffered enough that the first line arrives promptly. + std::fs::write(&script, "for i in 1..=200000 { println(\"${i}\"); }\n").expect("write script"); + + let mut child = Command::new(PathBuf::from(env!("CARGO_BIN_EXE_lk"))) + .arg(&script) + .env("LK_FORCE_VM", "1") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("spawn lk"); + + // Read one line, then drop the pipe — this is what `| head -1` does. + let stdout = child.stdout.take().expect("piped stdout"); + let mut reader = BufReader::new(stdout); + let mut first = String::new(); + reader.read_line(&mut first).expect("read first line"); + assert_eq!(first.trim(), "1", "the program should print before the reader leaves"); + drop(reader); + + let output = child.wait_with_output().expect("wait"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !stderr.contains("panicked"), + "a closed pipe must not surface as a Rust panic, got: {stderr}" + ); + assert_eq!( + output.status.signal(), + Some(libc::SIGPIPE), + "expected death by SIGPIPE like any other filter, got {:?} (stderr: {stderr})", + output.status + ); + + let _ = std::fs::remove_dir_all(&dir); +} diff --git a/cli/tests/builtin_method_native_coverage_test.rs b/cli/tests/builtin_method_native_coverage_test.rs new file mode 100644 index 00000000..302b2b15 --- /dev/null +++ b/cli/tests/builtin_method_native_coverage_test.rs @@ -0,0 +1,525 @@ +//! Every declared built-in method, called on a receiver the lowering can type. +//! +//! [`boxed_receiver_coverage_test`] asks the same question about a *boxed* +//! list receiver. This one asks it about the plain case — `"abc".upper()`, +//! `m.keys()`, `s.union(t)` — across all six receiver kinds, and it derives +//! the list from [`BUILTIN_METHODS`] rather than restating it, so a method +//! added to the language is in this gate the moment it is declared. +//! +//! A method that does not lower is not a wrong answer: the program runs, on +//! the VM, roughly three times slower, with no diagnostic. Nothing else in the +//! tree gates it — the differential corpora compare *answers*, the coverage +//! scan only walks `examples/`, and an example is written to demonstrate a +//! feature rather than to reach every method. +//! +//! [`EXCLUDED`] is asserted in both directions, so a name that starts lowering +//! has to leave the list and it cannot become a place to park failures. +//! +//! Each probe must be a program the interpreter accepts before its lowering +//! means anything: a probe with the wrong arity refuses to lower for a reason +//! that has nothing to do with coverage, and would land in [`EXCLUDED`] +//! looking like a finding. That check is on the test, not on the compiler. + +use lk_core::typ::{BUILTIN_METHODS, BuiltinReceiverKind}; +use std::path::Path; + +/// A receiver kind, an expression of that kind, and how to spell the parts its +/// method signatures are written against (`Elem`, `Key`, `Val`). +struct Receiver { + kind: BuiltinReceiverKind, + /// Named in the failure message. + label: &'static str, + expr: &'static str, + elem: &'static str, + key: &'static str, + val: &'static str, +} + +/// One entry per *carrier*, not per kind: the lowering matches on the element +/// and value representation, so `Map` and `Map` are +/// different arms of the same method name. A single receiver per kind is what +/// let `println(m.get(k))` on a `Map` emit ill-typed IR — three +/// separate call sites read a `Maybe`'s value half as a machine word when a +/// `MaybeBool` hands back a `Bool` — while the `Map` probe passed. +const RECEIVERS: &[Receiver] = &[ + Receiver { + kind: BuiltinReceiverKind::List, + label: "List", + expr: "[3, 1, 2]", + elem: "1", + key: "1", + val: "1", + }, + Receiver { + kind: BuiltinReceiverKind::List, + label: "List", + expr: "[3.5, 1.5, 2.5]", + elem: "1.5", + key: "1.5", + val: "1.5", + }, + Receiver { + kind: BuiltinReceiverKind::List, + label: "List", + expr: "[\"c\", \"a\", \"b\"]", + elem: "\"a\"", + key: "\"a\"", + val: "\"a\"", + }, + Receiver { + kind: BuiltinReceiverKind::List, + label: "List", + expr: "[3, \"a\", 2.5]", + elem: "\"a\"", + key: "\"a\"", + val: "\"a\"", + }, + Receiver { + kind: BuiltinReceiverKind::Map, + label: "Map", + expr: "{\"k\": 1.5, \"j\": 2.5}", + elem: "\"k\"", + key: "\"k\"", + val: "1.5", + }, + Receiver { + kind: BuiltinReceiverKind::Map, + label: "Map", + expr: "{\"k\": true, \"j\": false}", + elem: "\"k\"", + key: "\"k\"", + val: "true", + }, + Receiver { + kind: BuiltinReceiverKind::Map, + label: "Map", + expr: "{\"k\": \"v\", \"j\": \"w\"}", + elem: "\"k\"", + key: "\"k\"", + val: "\"v\"", + }, + Receiver { + kind: BuiltinReceiverKind::Map, + label: "Map", + expr: "{1: 2, 3: 4}", + elem: "1", + key: "1", + val: "2", + }, + Receiver { + kind: BuiltinReceiverKind::Set, + label: "Set", + expr: "Set([\"a\", \"b\"])", + elem: "\"a\"", + key: "\"a\"", + val: "\"a\"", + }, + Receiver { + kind: BuiltinReceiverKind::Bytes, + label: "Bytes", + expr: "\"abc\".bytes()", + elem: "98", + key: "98", + val: "98", + }, + Receiver { + kind: BuiltinReceiverKind::Slice, + label: "Slice", + expr: "[3, 1, 2].slice(0, 2)", + elem: "1", + key: "1", + val: "1", + }, + Receiver { + kind: BuiltinReceiverKind::Map, + label: "Map", + expr: "{\"k\": 1, \"j\": 2}", + elem: "\"k\"", + key: "\"k\"", + val: "1", + }, + Receiver { + kind: BuiltinReceiverKind::Set, + label: "Set", + expr: "Set([1, 2])", + elem: "1", + key: "1", + val: "1", + }, + Receiver { + kind: BuiltinReceiverKind::Str, + label: "Str", + expr: "\"abc\"", + elem: "\"a\"", + key: "\"a\"", + val: "\"a\"", + }, +]; + +/// `(receiver, method)` pairs whose generated call is not the call a program +/// would write, with the argument text to use instead. +/// +/// Only for signatures a type text cannot pin down: a format template has to +/// agree with its own argument count, and a callback's body has to return what +/// the method does something with. +const ARG_OVERRIDE: &[(BuiltinReceiverKind, &str, &str)] = &[ + (BuiltinReceiverKind::List, "map", "|v| v + 1"), + (BuiltinReceiverKind::List, "filter", "|v| v > 1"), + (BuiltinReceiverKind::List, "reduce", "0, |a, b| a + b"), + (BuiltinReceiverKind::Slice, "map", "|v| v + 1"), + (BuiltinReceiverKind::Slice, "filter", "|v| v > 1"), + (BuiltinReceiverKind::Slice, "reduce", "0, |a, b| a + b"), + (BuiltinReceiverKind::Bytes, "map", "|v| v + 1"), + (BuiltinReceiverKind::Bytes, "filter", "|v| v > 1"), + (BuiltinReceiverKind::Bytes, "reduce", "0, |a, b| a + b"), +]; + +/// [`ARG_OVERRIDE`] for one carrier rather than a whole kind, consulted first. +/// +/// A callback's body has to type against the *element*, and a kind's carriers +/// do not share one: `|v| v > 1` is a list predicate for three of the four list +/// carriers and a type error for the string one. Without an entry here that +/// carrier's `filter` would simply be skipped as inapplicable, which is the +/// coverage this test exists to have. +const ARG_OVERRIDE_BY_CARRIER: &[(&str, &str, &str)] = &[ + ("List", "filter", "|v| v > \"a\""), + ("List", "reduce", "\"\", |a, b| a + b"), + ("List", "filter", "|v| v == 1"), + ("List", "reduce", "\"\", |a, b| a + b"), + ("List", "reduce", "0.0, |a, b| a + b"), +]; + +/// A receiver expression to use instead of the kind's default, for the methods +/// whose default receiver would raise or answer nothing to lower. +const RECEIVER_OVERRIDE: &[(BuiltinReceiverKind, &str, &str)] = &[ + // A template's placeholder count has to match its arguments. + (BuiltinReceiverKind::Str, "format", "\"v={}\""), +]; + +/// Methods that do not lower on a typed receiver, and why. +/// +/// Each is a decision. A name here that starts lowering fails the test, so the +/// list cannot go stale in the quiet direction. +const EXCLUDED: &[(BuiltinReceiverKind, &str, &str)] = &[]; + +#[test] +fn every_builtin_method_lowers_on_a_typed_receiver() { + let dir = tempfile::tempdir().expect("temp dir"); + let mut bad_probes = Vec::new(); + let mut refused = Vec::new(); + let mut stale_exclusions = Vec::new(); + let mut checked = 0usize; + let mut inapplicable = 0usize; + + // Written and type-checked first, all of them, because whether a refusal is + // a broken probe or a method that does not apply to this carrier is only + // answerable across the kind: `["a"].sum()` is refused and `[1].sum()` is + // not, and the same generator wrote both. + let mut probes = Vec::new(); + for recv in RECEIVERS { + for sig in BUILTIN_METHODS.iter().filter(|s| s.receiver == recv.kind) { + // Both arities, when they differ. Omitting an optional parameter + // is a *different* call for the lowering to match — it matches on + // the argument list — and it is where the first gap this test found + // was: every carrier lowered `xs.slice(a, b)` and a window alone + // refused `xs.slice(a)`. + let override_args = ARG_OVERRIDE_BY_CARRIER + .iter() + .find(|(label, name, _)| *label == recv.label && name == &sig.name) + .map(|(_, _, text)| *text) + .or_else(|| { + ARG_OVERRIDE + .iter() + .find(|(kind, name, _)| *kind == recv.kind && name == &sig.name) + .map(|(_, _, text)| *text) + }); + let arities: Vec = match override_args { + Some(text) => vec![text.to_string()], + None => { + let arg_texts: Vec = sig.params.iter().map(|p| argument_for(p.ty, recv)).collect(); + let required = sig.params.iter().filter(|p| !p.optional).count(); + let mut forms = vec![arg_texts[..required].join(", ")]; + if required < arg_texts.len() { + forms.push(arg_texts.join(", ")); + } + forms + } + }; + let receiver_expr = RECEIVER_OVERRIDE + .iter() + .find(|(kind, name, _)| *kind == recv.kind && name == &sig.name) + .map(|(_, _, expr)| *expr) + .unwrap_or(recv.expr); + + for (form, args) in arities.iter().enumerate() { + let stem = format!( + "{}_{}_{form}", + recv.label + .to_lowercase() + .replace(['<', '>', ',', ' '], "_") + .trim_matches('_'), + sig.name + ); + let source = dir.path().join(format!("{stem}.lk")); + // Through a binding rather than off the literal: that is how a + // program writes it, and a mutating method needs a place to + // write. + std::fs::write( + &source, + format!("let r = {receiver_expr};\nprintln(r.{}({args}));\n", sig.name), + ) + .expect("write probe"); + let accepted = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["check", source.to_str().expect("utf-8 path")]) + .output() + .expect("run lk check"); + probes.push(( + recv, + sig.name, + form, + format!("{}.{}({args})", recv.label, sig.name), + source, + dir.path().join(stem), + accepted.status.success(), + String::from_utf8_lossy(&accepted.stderr).trim().replace('\n', " "), + )); + } + } + } + + for (recv, method, form, label, source, exe, accepted, message) in &probes { + if !accepted { + // Refused for this carrier. If some other carrier of the same kind + // accepts the identical call, the refusal is the language saying + // the method does not apply there. If *none* does, the generator + // wrote something that is not a call, and that is a bug in the test. + let applies_somewhere = probes.iter().any(|(other, other_method, other_form, .., ok, _)| { + other.kind == recv.kind && other_method == method && other_form == form && *ok + }); + if applies_somewhere { + inapplicable += 1; + } else { + bad_probes.push(format!("{label}: {message}")); + } + continue; + } + // Checked but raising means the same thing one step later: `[1, + // "a"].sort()` type-checks and refuses at run time. + let interpreted = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + if !interpreted.status.success() { + inapplicable += 1; + continue; + } + + checked += 1; + let lowers = lowers_natively(source, exe); + let excluded = EXCLUDED + .iter() + .any(|(kind, name, _)| *kind == recv.kind && name == method); + match (lowers, excluded) { + (false, false) => refused.push(label.clone()), + (true, true) => stale_exclusions.push(label.clone()), + _ => {} + } + } + + assert!( + bad_probes.is_empty(), + "`lk check` rejects these probes, so what they measure is the probe rather than the \ + compiler. Fix the generated call (ARG_OVERRIDE / RECEIVER_OVERRIDE):\n {}", + bad_probes.join("\n ") + ); + // A skipped probe reports nothing, so a change that made most of them raise + // would empty the gate quietly and read as full coverage. The floor is well + // under the count at the time of writing (356 compiled, 96 inapplicable) + // and is here to catch a collapse, not to pin the exact number. + assert!( + checked > 250, + "only {checked} probes reached the compiler ({inapplicable} raised under the VM and were \ + skipped as not applying to their carrier). That is far below what this table generates, so \ + the probes are failing for a reason other than coverage." + ); + assert!( + refused.is_empty(), + "{} of {checked} built-in method calls do not lower natively and are not in EXCLUDED:\n {}\n\ + A program calling any of them drops its whole module to the VM, silently and about three \ + times slower. Lower it, or list it in EXCLUDED with the reason it cannot be lowered.", + refused.len(), + refused.join("\n ") + ); + assert!( + stale_exclusions.is_empty(), + "these are in EXCLUDED but now lower: {stale_exclusions:?}. Remove them — an exclusion that \ + no longer holds is what makes the list stop meaning anything." + ); +} + +/// Every built-in method again, on a receiver the lowering cannot type. +/// +/// A parameter reached with two *different carriers of the same kind* is a +/// `Dyn`: `fn empty(c) { c.clear(); }` called with a `Map` and a +/// `Map` has no carrier to specialize to. That is a different +/// set of arms from the typed case above, and only lists had a gate for it +/// (`boxed_receiver_coverage_test`, which also covers the mutating names this +/// deliberately does not re-litigate). +/// +/// It found `clear`, which had an arm for every carrier and none for `Dyn`. +/// +/// Only kinds this table gives two carriers for can be boxed this way; `Str` +/// and `Bytes` have one apiece, so a probe over them would be the typed case +/// again under another name. +#[test] +fn every_builtin_method_lowers_on_a_boxed_receiver() { + let dir = tempfile::tempdir().expect("temp dir"); + let mut refused = Vec::new(); + let mut checked = 0usize; + + for kind in [ + BuiltinReceiverKind::List, + BuiltinReceiverKind::Map, + BuiltinReceiverKind::Set, + ] { + let carriers: Vec<&Receiver> = RECEIVERS.iter().filter(|r| r.kind == kind).collect(); + let [first, second, ..] = carriers.as_slice() else { + continue; + }; + for sig in BUILTIN_METHODS.iter().filter(|s| s.receiver == kind) { + // The first carrier's spelling for the arguments: the receiver is + // erased, so the *call* has to be one both carriers accept, and + // anything else is reported as inapplicable by the run below. + let args = match ARG_OVERRIDE.iter().find(|(k, name, _)| *k == kind && name == &sig.name) { + Some((_, _, text)) => (*text).to_string(), + None => sig + .params + .iter() + .filter(|p| !p.optional) + .map(|p| argument_for(p.ty, first)) + .collect::>() + .join(", "), + }; + let label = format!("Dyn({:?}).{}({args})", kind, sig.name); + let stem = format!("boxed_{:?}_{}", kind, sig.name).to_lowercase(); + let source = dir.path().join(format!("{stem}.lk")); + std::fs::write( + &source, + format!( + "fn probe(r) {{\n return r.{}({args});\n}}\nprintln(probe({}));\nprintln(probe({}));\n", + sig.name, first.expr, second.expr + ), + ) + .expect("write probe"); + + // Checked and run first, for the same reason the typed sweep does + // it: a method that does not apply to one of the two carriers is a + // fact about the language, not a coverage gap. + if !std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["check", source.to_str().expect("utf-8 path")]) + .output() + .expect("run lk check") + .status + .success() + { + continue; + } + if !std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM") + .status + .success() + { + continue; + } + + checked += 1; + // `slice` answers a *window* over the receiver, and reaching a + // boxed one means `dyn.as_list`, which materializes a plain list + // for three of the four carriers — so unboxing would change the + // answer's kind. `boxed_receiver_coverage_test` excludes it for + // the same reason and states it at length. + if sig.name == "slice" { + continue; + } + if !lowers_natively(&source, &dir.path().join(stem)) { + refused.push(label); + } + } + } + + assert!( + checked > 40, + "only {checked} boxed-receiver probes reached the compiler, far below what this table \ + generates — they are failing for a reason other than coverage" + ); + assert!( + refused.is_empty(), + "{} of {checked} built-in methods do not lower on a boxed receiver:\n {}\n\ + A program whose container parameter meets two carriers drops its whole module to the VM \ + for each of them, silently.", + refused.len(), + refused.join("\n ") + ); +} + +/// An expression of the declared parameter type, with the receiver's own +/// spelling substituted for the placeholders the table writes signatures +/// against. +fn argument_for(ty: &str, recv: &Receiver) -> String { + match ty { + "Int" => "1".to_string(), + "Bool" => "true".to_string(), + "String" => "\"a\"".to_string(), + "Any" => recv.elem.to_string(), + "Bytes" => "\"z\".bytes()".to_string(), + "Elem" => recv.elem.to_string(), + "Key" => recv.key.to_string(), + "Val" => recv.val.to_string(), + "Self" => recv.expr.to_string(), + "Set" => format!("Set([{}])", recv.elem), + "List<_>" | "List" | "List" => format!("[{}]", recv.elem), + // `Fn` says only that a callback goes here; the arity and the result + // type come from the method, which is what ARG_OVERRIDE carries. A + // signature reaching this arm is a new callback method with no entry. + "Fn" => panic!("a callback parameter needs an ARG_OVERRIDE entry"), + other => panic!("no probe argument for the declared parameter type `{other}`"), + } +} + +/// Whether `source` compiles with the bridge off and fallback forbidden. +/// +/// Both are pinned, because a fallback compiles and runs and prints the right +/// answer — which is exactly why this class of gap is invisible everywhere +/// else. +fn lowers_natively(source: &Path, exe: &Path) -> bool { + let out = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("run lk compile"); + if out.status.success() { + return true; + } + // A failed compile is only an answer about *coverage* when the compiler + // says so. Everything else — a linker that could not write, a full disk — + // exits non-zero too, and reading that as "does not lower" reports a + // coverage regression for a machine problem. A full `/tmp` did exactly + // that here: the last two receivers in the table failed as a block, which + // is what a resource running out looks like and not what a lowering gap + // looks like. + let message = String::from_utf8_lossy(&out.stderr).to_string() + &String::from_utf8_lossy(&out.stdout); + assert!( + message.contains("native AOT does not support this program yet"), + "`lk compile` failed for a reason that is not a lowering refusal, so this run says nothing \ + about coverage:\n{}", + message.trim() + ); + false +} diff --git a/cli/tests/bundle_container_parameter_test.rs b/cli/tests/bundle_container_parameter_test.rs new file mode 100644 index 00000000..3026c26a --- /dev/null +++ b/cli/tests/bundle_container_parameter_test.rs @@ -0,0 +1,213 @@ +//! What a bundled module may do with a container it was handed. +//! +//! Bundling flattens modules into one, so a container the caller passes arrives +//! by *reference*, where the VM would have given the module its own copy. That +//! difference is real and it is why a module that mutates through a parameter is +//! refused rather than bundled — the two backends would compute different +//! things, and no differential test would catch it because the VM's answer is +//! the only one anybody wrote down. +//! +//! But the difference is observable only through a **write**: this function's +//! own, or someone else's through a handle it kept. A method that reads the +//! receiver and answers a number can do neither, and refusing those cost more +//! than it bought — a bundled module could not have `fn log(message: String)`, +//! because strings are immutable so *every* string method is a read. +//! +//! These tests pin both halves: the reads bundle, the writes still do not, and +//! a user-defined method that merely shares a name with a read is not mistaken +//! for one. + +use std::path::Path; + +/// A module that takes a `String` and looks at it. +/// +/// The case that found this. `uart_text(text: String)` in a bare-metal serial +/// driver walks the string with `byte_at` — no allocation, usable from an +/// interrupt — and the whole module was refused for it. +#[test] +fn a_bundled_module_may_read_a_string_parameter() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("log.lk"), + "fn checksum(message: String) -> Int {\n\ + \x20 let sum = 0;\n\ + \x20 for i in 0..message.len() { sum = sum + message.byte_at(i); }\n\ + \x20 return sum;\n\ + }\n\ + fn shouts(message: String) -> Bool { return message.starts_with(\"!\"); }\n", + ) + .expect("write module"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { checksum, shouts } from \"log\";\n\ + println(checksum(\"net: ok\"));\n\ + println(shouts(\"!boom\"));\n\ + println(shouts(\"quiet\"));\n", + ) + .expect("write main"); + + assert_native_agrees_with_vm(&source, dir.path().join("reads_string")); +} + +/// The same for a list, which is the case the rule exists for — read it, do not +/// write it. +#[test] +fn a_bundled_module_may_read_a_list_parameter() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("stats.lk"), + "fn total(xs: List) -> Int {\n\ + \x20 let sum = 0;\n\ + \x20 for i in 0..xs.len() { sum = sum + (xs[i] as Int); }\n\ + \x20 return sum;\n\ + }\n", + ) + .expect("write module"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { total } from \"stats\";\n\ + let xs = [1, 2, 3];\n\ + println(total(xs));\n\ + xs.push(4);\n\ + println(total(xs));\n", + ) + .expect("write main"); + + assert_native_agrees_with_vm(&source, dir.path().join("reads_list")); +} + +/// A module that writes through a parameter is still refused. +/// +/// This is the guarantee the narrowing must not have weakened. Under the VM the +/// caller's list is untouched — the module got a copy — and under a flattened +/// build it would grow. Refusing is what keeps the two the same program. +#[test] +fn a_bundled_module_that_writes_through_a_parameter_is_still_refused() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("grow.lk"), + "fn extend(xs: List) -> Int {\n\ + \x20 xs.push(99);\n\ + \x20 return xs.len();\n\ + }\n", + ) + .expect("write module"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { extend } from \"grow\";\nlet xs = [1];\nprintln(extend(xs));\nprintln(xs.len());\n", + ) + .expect("write main"); + + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(dir.path().join("grow_exe").to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("run lk compile"); + assert!( + !output.status.success(), + "a module that pushes through a parameter must not be bundled" + ); + // And says *why*. Without bundling, the call into the module is a + // `GetGlobal` that resolves to nothing, so the bare failure names a symptom + // — "global `extend` does not resolve" sends the reader looking for a + // missing import. The decline reason travels with it. + let message = String::from_utf8_lossy(&output.stderr); + assert!( + message.contains("container parameter"), + "the diagnostic should say what it refused, got: {message}" + ); +} + +/// A user-defined method that happens to be called `contains` is not the +/// builtin, and is not assumed to read. +/// +/// The allow list is names, because the bytecode carries no types. What makes +/// that safe is that a name any `impl` in the module defines is excluded from +/// it — nothing stops a type from having a `contains` that rearranges the +/// receiver first, and this is that type. +#[test] +fn a_user_method_sharing_a_read_only_name_is_not_treated_as_one() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("sneaky.lk"), + "struct Bag { items: List }\n\ + impl Bag {\n\ + \x20 fn contains(self, needle: Int) -> Bool {\n\ + \x20 self.items.push(needle);\n\ + \x20 return true;\n\ + \x20 }\n\ + }\n\ + fn probe(bag: Bag) -> Bool { return bag.contains(7); }\n", + ) + .expect("write module"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { probe, Bag } from \"sneaky\";\n\ + let bag = Bag { items: [1] };\n\ + println(probe(bag));\n\ + println(bag.items.len());\n", + ) + .expect("write main"); + + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(dir.path().join("sneaky_exe").to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("run lk compile"); + assert!( + !output.status.success(), + "a user `contains` that mutates must not be mistaken for the builtin read" + ); +} + +/// Compiles `source` natively, runs it, and checks it says what the VM says. +/// +/// Against the VM rather than against numbers, because the failure this whole +/// area is about is a bundled build that computes something the VM does not. +fn assert_native_agrees_with_vm(source: &Path, exe: std::path::PathBuf) { + let vm = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + assert!( + vm.status.success(), + "the VM must run it: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + // Pinned: a fall back to the VM bundle would pass without the module + // ever having been bundled, which is the thing under test. + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "must lower natively"); + + let native = std::process::Command::new(&exe) + .output() + .expect("run the compiled program"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + String::from_utf8_lossy(&native.stdout), + "the VM and the native build disagree" + ); +} diff --git a/cli/tests/bundle_default_output_test.rs b/cli/tests/bundle_default_output_test.rs new file mode 100644 index 00000000..d9fbe2bc --- /dev/null +++ b/cli/tests/bundle_default_output_test.rs @@ -0,0 +1,46 @@ +//! `lk bundle FILE` names the executable itself, the way `lk compile` does. +//! +//! Both commands turn one source file into an executable, and one of them used +//! to work the name out (`path.with_extension("")`) while the other made +//! `--output` mandatory: +//! +//! ```text +//! $ lk bundle app.lk +//! Usage: lk bundle --output +//! ``` +//! +//! That spelling — `lk bundle FILE`, no flag — is the one `CLAUDE.md` and the +//! README document, so the CLI surface and its description disagreed about a +//! required argument. + +use assert_cmd::prelude::*; +use std::error::Error; +use std::process::Command; + +#[test] +fn bundle_defaults_its_output_to_the_source_without_its_extension() -> Result<(), Box> { + let dir = tempfile::tempdir()?; + let source = dir.path().join("greeter.lk"); + std::fs::write(&source, "println(\"bundled\");\n")?; + + Command::cargo_bin("lk")? + .current_dir(dir.path()) + .args(["bundle", "greeter.lk"]) + .assert() + .success(); + + let produced = dir.path().join("greeter"); + assert!(produced.exists(), "`lk bundle greeter.lk` should write ./greeter"); + let output = Command::new(&produced).output()?; + assert_eq!(String::from_utf8(output.stdout)?, "bundled\n"); + + // …and naming it explicitly still works, to the name given. + Command::cargo_bin("lk")? + .current_dir(dir.path()) + .args(["bundle", "--output", "named", "greeter.lk"]) + .assert() + .success(); + assert!(dir.path().join("named").exists()); + + Ok(()) +} diff --git a/cli/tests/bundle_derived_const_test.rs b/cli/tests/bundle_derived_const_test.rs new file mode 100644 index 00000000..b30a72a1 --- /dev/null +++ b/cli/tests/bundle_derived_const_test.rs @@ -0,0 +1,218 @@ +//! What a bundled module may say at its top level, and why the answer is more +//! than "a literal". +//! +//! Bundling flattens an imported module into the importing one, so the module's +//! top level has to be effect-free: there is nowhere for it to *run*. The scan +//! that enforces that used to accept only a load followed by a bind, which made +//! `const FRAME = HEADER + BODY;` a "top-level effect" while `const FRAME = 42;` +//! was fine — and deriving one constant from two others is the ordinary shape of +//! a protocol or register header. The alternative is the same number written +//! twice, in a file whose whole purpose is that it is written once. +//! +//! So the scan folds. These tests pin what it folds, that it folds to the value +//! the VM computes, and that a module whose top level really does have an effect +//! is still refused. + +use std::path::Path; + +/// A constant derived from other constants, through every folded shape. +/// +/// Checked against the VM rather than against numbers written here: the fold is +/// a second implementation of arithmetic the executor already does, and the +/// failure it would produce is a program that compiles and computes something +/// else. +#[test] +fn a_bundled_module_may_derive_a_constant_from_constants() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("proto.lk"), + // Addition, subtraction, multiplication, a chain three deep, and the + // immediate forms the compiler picks when one side is a literal. + "const HEADER = 14;\n\ + const BODY = 28;\n\ + const FRAME = HEADER + BODY;\n\ + const PAYLOAD = FRAME - HEADER;\n\ + const BURST = FRAME * 4;\n\ + const RING = BURST + 1;\n\ + const SLOT = HEADER * BODY - FRAME;\n\ + fn describe() -> Int { return FRAME + PAYLOAD + BURST + RING + SLOT; }\n", + ) + .expect("write module"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { describe, FRAME, PAYLOAD, BURST, RING, SLOT } from \"proto\";\n\ + println(FRAME);\n\ + println(PAYLOAD);\n\ + println(BURST);\n\ + println(RING);\n\ + println(SLOT);\n\ + println(describe());\n", + ) + .expect("write main"); + + assert_native_agrees_with_vm(&source, dir.path().join("derived")); +} + +/// A negative value, and the wrapping the executor does. +/// +/// The fold has to match the VM at the edges as well as in the middle: a fold +/// that saturated or panicked where the executor wraps would reject a program +/// the VM runs, which is worse than computing the wrong answer because it looks +/// like a missing feature. +#[test] +fn a_derived_constant_wraps_where_the_executor_wraps() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("edges.lk"), + "const LOW = 5;\n\ + const HIGH = 9;\n\ + const BELOW = LOW - HIGH;\n\ + const HUGE = 4611686018427387904;\n\ + const OVER = HUGE + HUGE;\n\ + fn edges() -> Int { return BELOW + OVER; }\n", + ) + .expect("write module"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { edges, BELOW, OVER } from \"edges\";\n\ + println(BELOW);\n\ + println(OVER);\n\ + println(edges());\n", + ) + .expect("write main"); + + assert_native_agrees_with_vm(&source, dir.path().join("edges")); +} + +/// A module whose top level does something is still refused, and says so. +/// +/// The fold widens what counts as a *description*; it does not make a module's +/// top level a place where things happen. A bundled module has nowhere to run, +/// so a call there would be silently skipped — which is exactly the failure the +/// scan exists to prevent. +#[test] +fn a_bundled_module_with_a_real_top_level_effect_is_still_refused() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("noisy.lk"), + "fn shout() -> Int { println(\"side effect\"); return 1; }\n\ + const RESULT = shout();\n", + ) + .expect("write module"); + + let source = dir.path().join("main.lk"); + std::fs::write(&source, "use { RESULT } from \"noisy\";\nprintln(RESULT);\n").expect("write main"); + + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(dir.path().join("noisy_exe").to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("run lk compile"); + assert!(!output.status.success(), "a top-level call must not be bundled"); + let message = String::from_utf8_lossy(&output.stderr); + assert!( + message.contains("top-level"), + "the diagnostic should say what it refused, got: {message}" + ); +} + +/// Compiles `source` natively, runs it, and checks it says what the VM says. +/// A constant imported under another name. +/// +/// The fold is keyed by the constant's *own* name, because that is the name the +/// bundle flattens it under. `use { SIZE as TSS_SIZE }` reads a different one, +/// so the read survived as a `GetGlobal` of a slot nothing initialises: an error +/// under `compile object:` (which is how the bare-metal kernel builds) and a +/// silent fall back to the VM bundle otherwise. The VM binds it either way, so +/// what differed was coverage, and the shape that finds it — a driver renaming +/// another driver's constant to say which driver it came from — is the ordinary +/// one. +/// +/// The chain is two deep on purpose: a dep renaming *another dep's* constant is +/// the case the collection has to walk every module to see. +#[test] +fn a_bundled_constant_may_be_imported_under_another_name() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("chip.lk"), + "const LINE_COUNT = 16;\n const RESET = 7;\n", + ) + .expect("write chip"); + std::fs::write( + dir.path().join("board.lk"), + "use { LINE_COUNT as CHIP_LINES } from \"chip\";\n fn lines() -> Int { return CHIP_LINES; }\n", + ) + .expect("write board"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { LINE_COUNT as LINES, RESET } from \"chip\";\n use { lines } from \"board\";\n fn fits(irq: Int) -> Bool { return irq < LINES; }\n println(LINES);\n println(RESET);\n println(fits(3));\n println(fits(20));\n println(lines());\n", + ) + .expect("write main"); + + assert_native_agrees_with_vm(&source, dir.path().join("renamed")); +} + +/// And a binding of its own still shadows the import. +/// +/// The fold substitutes a value for a slot, so it must not touch a slot the +/// program *writes*: a module that declares its own `LINES` means that one, and +/// folding the constant there would answer 16 where the VM answers 3. Under the +/// VM the local binding shadows the import; this pins that the native build +/// agrees. +#[test] +fn a_local_binding_shadows_a_renamed_constant_import() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write(dir.path().join("chip.lk"), "const LINE_COUNT = 16;\n").expect("write chip"); + + let source = dir.path().join("main.lk"); + std::fs::write( + &source, + "use { LINE_COUNT as LINES } from \"chip\";\n let LINES = 3;\n fn fits(irq: Int) -> Bool { return irq < LINES; }\n println(LINES);\n println(fits(5));\n", + ) + .expect("write main"); + + assert_native_agrees_with_vm(&source, dir.path().join("shadowed")); +} + +fn assert_native_agrees_with_vm(source: &Path, exe: std::path::PathBuf) { + let vm = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + assert!( + vm.status.success(), + "the VM must run it: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + // Pinned to the native path: a fall back to the VM bundle would run the + // module's top level for real and pass without the fold existing. + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "must lower natively"); + + let native = std::process::Command::new(&exe) + .output() + .expect("run the compiled program"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + String::from_utf8_lossy(&native.stdout), + "the VM and the native build disagree" + ); +} diff --git a/cli/tests/bundle_function_count_test.rs b/cli/tests/bundle_function_count_test.rs new file mode 100644 index 00000000..200236fc --- /dev/null +++ b/cli/tests/bundle_function_count_test.rs @@ -0,0 +1,166 @@ +//! How many functions a bundled program may hold, and why the answer is not +//! "as many as it likes". +//! +//! A `CallDirect` or `MakeClosure` names its target in the instruction's `b` +//! field, which is a byte. The compiler is not bound by that — past index 255 +//! it lowers the call generically — but a bundled dependency's instructions are +//! already emitted by the time the merge renumbers them, and rewriting one +//! instruction into two would move every jump offset after it. +//! +//! So the merge numbers directly-called functions first. What is bounded is the +//! importing file's functions plus the dep functions a dep calls *directly*, +//! not the total. These tests pin both halves of that: a program past 256 in +//! total compiles and computes, and the diagnostic for a program past the real +//! bound says which bound it crossed. + +use std::path::Path; + +/// A program with more functions than a call instruction can name still +/// compiles natively, and still computes with them. +/// +/// The dep's functions are leaves — nothing there calls anything — so none of +/// them is a `CallDirect` target and all of them may sit above 255. That is the +/// ordinary shape of a driver: it exports what the program calls, and the +/// program reaches it by name, which the lowering resolves through a `u32`. +#[test] +fn a_bundle_may_hold_more_functions_than_a_call_can_name() { + let dir = tempfile::tempdir().expect("temp dir"); + + let mut dep = String::new(); + for index in 0..300 { + dep.push_str(&format!("fn leaf{index}() -> Int {{ return {index}; }}\n")); + } + std::fs::write(dir.path().join("dep.lk"), dep).expect("write dep"); + + let mut main = String::from("use { leaf0, leaf299 } from \"dep\";\n"); + main.push_str("return leaf0() + leaf299();\n"); + let source = dir.path().join("main.lk"); + std::fs::write(&source, main).expect("write main"); + + let exe = dir.path().join("bundle_many"); + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + // Pinned to the native path: falling back to the VM bundle would make + // this pass without the merge having numbered anything. + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "300 bundled functions must lower natively"); + + let output = std::process::Command::new(&exe) + .output() + .expect("run the compiled program"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("299"), + "expected 0 + 299 from the first and last bundled function, got: {stdout}" + ); +} + +/// The *merge's* numbering, isolated. +/// +/// Every index inside the dep is under 256, so the dep compiles with ordinary +/// `CallDirect`s. What crosses the line is the merged index: the importing file +/// has a hundred functions of its own, so numbered as they arrive the dep's +/// chain lands past 255 and its call instructions no longer fit. +/// +/// The chain is declared *last* in the dep, which is the whole test — declaring +/// it first would put it low under either numbering. +#[test] +fn the_merge_numbers_directly_called_functions_first() { + let dir = tempfile::tempdir().expect("temp dir"); + + let mut dep = String::new(); + for index in 0..160 { + dep.push_str(&format!("fn leaf{index}() -> Int {{ return {index}; }}\n")); + } + dep.push_str("fn chain_end() -> Int { return 7; }\n"); + for index in 0..40 { + dep.push_str(&format!( + "fn chain{index}() -> Int {{ return {} + 1; }}\n", + if index == 0 { + "chain_end()".to_string() + } else { + format!("chain{}()", index - 1) + } + )); + } + std::fs::write(dir.path().join("dep.lk"), dep).expect("write dep"); + + let mut main = String::from("use { chain39, leaf0, leaf159 } from \"dep\";\n"); + for index in 0..100 { + main.push_str(&format!("fn own{index}() -> Int {{ return {index}; }}\n")); + } + main.push_str("return chain39() + leaf0() + leaf159() + own0();\n"); + let source = dir.path().join("main.lk"); + std::fs::write(&source, main).expect("write main"); + + assert_native_agrees_with_vm(&source, dir.path().join("bundle_mixed")); +} + +/// A call to a function past index 255, inside one module and with no bundling +/// at all. +/// +/// `CallDirect` names its target in a byte, so the compiler spells this one +/// `LoadFunction` + `Call` instead — correct bytecode that the native lowering +/// used to reject, which made 256 functions a ceiling on the *native* path too. +#[test] +fn a_call_past_the_direct_call_index_lowers_natively() { + let dir = tempfile::tempdir().expect("temp dir"); + + let mut source_text = String::new(); + for index in 0..300 { + source_text.push_str(&format!("fn leaf{index}() -> Int {{ return {index}; }}\n")); + } + // Called from inside a function, so the call is a real one rather than the + // entry's own bookkeeping. + source_text.push_str("fn reach() -> Int { return leaf299() + leaf0(); }\n"); + source_text.push_str("return reach();\n"); + let source = dir.path().join("far_call.lk"); + std::fs::write(&source, source_text).expect("write source"); + + assert_native_agrees_with_vm(&source, dir.path().join("far_call")); +} + +/// Compiles `source` natively, runs it, and checks it says what the VM says. +/// +/// Comparing against the VM rather than against a number: the merge is a +/// native-path transformation — the VM keeps each module in its own namespace — +/// so a numbering it invents wrong is exactly the kind of thing that computes a +/// different answer without failing anything. +fn assert_native_agrees_with_vm(source: &Path, exe: std::path::PathBuf) { + let vm = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + assert!( + vm.status.success(), + "the VM must run it: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + // Pinned to the native path: a fall back to the VM bundle would make + // every one of these pass without proving anything. + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "must lower natively"); + + let native = std::process::Command::new(&exe) + .output() + .expect("run the compiled program"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + String::from_utf8_lossy(&native.stdout), + "the VM and the native build disagree" + ); +} diff --git a/cli/tests/check_oracle_test.rs b/cli/tests/check_oracle_test.rs new file mode 100644 index 00000000..c3cde0ed --- /dev/null +++ b/cli/tests/check_oracle_test.rs @@ -0,0 +1,380 @@ +//! What `lk check` must refuse, and what it must not. +//! +//! Every other differential gate compares the two *engines*; this one compares +//! the checker against the language. A mistake it lets through is a program +//! that fails later — at run time, or on one backend only — and a valid program +//! it refuses is a feature nobody can use. Both directions are here because the +//! two failures look nothing alike and only one of them is loud. +//! +//! Four defects came out of writing this table: a trait impl could carry a +//! method the trait never declared, a struct literal could write one field +//! twice, `use math as m;` made `m.nope()` uncheckable, and `P { ..5 }` was a +//! run-time error with a message naming the desugaring. The cases that pass +//! *by design* are listed too, with the reason — they are the ones a later +//! reader would otherwise "fix". + +use std::process::Command; + +fn check(label: &str, source: &str) -> (bool, String) { + let dir = std::env::temp_dir().join(format!("lk_check_oracle_{}", std::process::id())); + let _ = std::fs::create_dir_all(&dir); + // One file per case: the two tests run in parallel and a shared path makes + // them read each other's source. + let slug: String = label + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect(); + let file = dir.join(format!("{slug}.lk")); + // A case that needs a *second* module writes it beside the first and says + // so with this marker, which stands in for the import line. Only the + // named-default rule needs one: it is about a callee whose declaration the + // caller does not have, which cannot be written in a single file. + let source = if let Some(rest) = source.strip_prefix("IMPORTS_CONF\n") { + std::fs::write( + dir.join("conf.lk"), + "fn configure({host: String, timeout_ms: Int? = 1000}) -> String { return \"${host} ${timeout_ms}\"; }\n", + ) + .expect("write conf"); + format!("use {{ configure }} from \"conf\";\n{rest}") + } else { + source.to_string() + }; + std::fs::write(&file, &source).expect("write case"); + let out = Command::new(env!("CARGO_BIN_EXE_lk")) + .arg("check") + .arg(&file) + .output() + .expect("run lk check"); + let message = String::from_utf8_lossy(&out.stdout).to_string() + &String::from_utf8_lossy(&out.stderr); + let _ = std::fs::remove_file(&file); + (out.status.success(), message) +} + +/// Mistakes the checker has to name, with the shape that produces each. +const MUST_REFUSE: &[(&str, &str)] = &[ + // A named parameter's default is materialized by the compiler at the call + // site, out of the callee's declaration — which is what lets it read an + // earlier argument. A caller in another module does not have that + // declaration, and the runtime path that places named arguments has no + // notion of a default at all, so this worked within a module and failed + // across one *at run time*, reporting `missing required named argument` + // about a parameter that is not required. Said at check time now, where it + // can name the way out. + ( + "an omitted defaulted named argument across a module boundary", + "IMPORTS_CONF\nprintln(configure(host: \"a\"));\n", + ), + // A channel's and a task's operations are *module functions* (`send`, + // `recv`, `task.await`), so neither type has a method surface at all and + // neither has fields a name could fall back to. Every other receiver was + // already checked — container, scalar, struct — and these two were the + // remaining pair: `c.close()`, the spelling another language would have, + // type-checked and raised when the program ran. + ( + "an unknown method on a channel", + "let c = chan(1);\nprintln(c.close());\n", + ), + ( + "an unknown method on a task", + "use task;\nlet t = spawn(|| 1);\nprintln(t.cancel());\n", + ), + // A `let` pattern is a requirement, not a question — unlike a `match` arm. + // These reached the run time and raised `Pattern does not match value` + // while `lk check` said nothing, which is the one thing it is not allowed + // to do: it is documented as the same check the executors run. + ( + "a literal `let` pattern binds nothing", + "let 1 = 2;\nprintln(\"ran\");\n", + ), + ( + "a literal `let` pattern that would match still binds nothing", + "let 1 = 1;\nprintln(\"ran\");\n", + ), + ("a list pattern over a scalar", "let [a] = 5;\nprintln(a);\n"), + ("a map pattern over a scalar", "let { x: v } = 5;\nprintln(v);\n"), + ( + "too few arguments", + "fn f(a: Int, b: Int) -> Int { return a + b; }\nprintln(f(1));\n", + ), + ( + "too many arguments", + "fn f(a: Int) -> Int { return a; }\nprintln(f(1, 2));\n", + ), + ( + "argument type", + "fn f(a: Int) -> Int { return a; }\nprintln(f(\"x\"));\n", + ), + ("return type", "fn f() -> Int { return \"x\"; }\nprintln(f());\n"), + ("undefined name", "println(nope);\n"), + ("unknown method", "println(\"a\".nope());\n"), + // A map is the one container where `m.f(x)` need not be a method: its + // entries are its fields. That reading needs a key spelled like the name, + // and a map keyed by anything but a string can never have one — so + // `m.contains(k)` on a `Map` always raises ("a Map has no method + // `contains`, and this map has no key `contains`"), and `lk check` used to + // wait for the program to start to say it. + ( + "a method no map has, on a map whose keys cannot be names", + "let m: Map = {1: 2};\nprintln(m.contains(1));\n", + ), + ( + "unknown field", + "struct P { x: Int }\nlet p = P { x: 1 };\nprintln(p.y);\n", + ), + ( + "missing field", + "struct P { x: Int, y: Int }\nlet p = P { x: 1 };\nprintln(p.x);\n", + ), + ( + "extra field", + "struct P { x: Int }\nlet p = P { x: 1, z: 2 };\nprintln(p.x);\n", + ), + ( + "repeated field", + "struct P { x: Int, y: Int }\nlet p = P { x: 1, x: 2, y: 3 };\nprintln(p.x);\n", + ), + ("annotation mismatch", "let x: Int = \"s\";\nprintln(x);\n"), + ("nil into non-nullable", "let x: Int = nil;\nprintln(x);\n"), + ( + "unknown type name", + "fn f(a: Nope) -> Int { return 1; }\nprintln(f(1));\n", + ), + ( + "impl of an unknown trait", + "struct P { x: Int }\nimpl Nope for P { fn a(self) -> Int { return 1; } }\nprintln(1);\n", + ), + ( + "impl missing a method", + "trait T { fn a(self) -> Int; }\nstruct P { x: Int }\nimpl T for P { }\nprintln(1);\n", + ), + ( + "impl signature", + "trait T { fn a(self) -> Int; }\nstruct P { x: Int }\nimpl T for P { fn a(self) -> String { return \"s\"; } }\nprintln(1);\n", + ), + ( + "method the trait never declared", + "trait T { fn a(self) -> Int; }\nstruct P { x: Int }\nimpl T for P { fn a(self) -> Int { return 1; } fn b(self) -> Int { return 2; } }\nprintln(1);\n", + ), + ( + "duplicate fn", + "fn f() -> Int { return 1; }\nfn f() -> Int { return 2; }\nprintln(f());\n", + ), + ( + "duplicate struct", + "struct P { x: Int }\nstruct P { y: Int }\nprintln(1);\n", + ), + ( + "duplicate parameter", + "fn f(a: Int, a: Int) -> Int { return a; }\nprintln(f(1,2));\n", + ), + ("break outside a loop", "break;\n"), + ("assignment to a const", "const C = 1;\nC = 2;\nprintln(C);\n"), + ("call a scalar", "let n = 5;\nprintln(n(1));\n"), + ("bit operand", "println(1 & true);\n"), + ("closure arity", "let f = |a, b| a + b;\nprintln(f(1));\n"), + ( + "function-type argument", + "fn take(g: (Int) -> Int) -> Int { return g(1); }\nprintln(take(|a, b| a));\n", + ), + ("assignment changes type", "let x: Int = 1;\nx = \"s\";\nprintln(x);\n"), + ("for over a scalar", "for x in 5 { println(x); }\n"), + ( + "trait declared twice", + "trait T { fn a(self) -> Int; }\ntrait T { fn b(self) -> Int; }\nprintln(1);\n", + ), + ( + "let over a declaration", + "fn pick() -> Int { return 1; }\nlet pick = 2;\nprintln(pick);\n", + ), + ("import of a missing name", "use { nope } from math;\nprintln(1);\n"), + ("member of an aliased module", "use math as m;\nprintln(m.nope(1));\n"), + ( + "spread of a scalar", + "struct P { x: Int }\nlet p = P { ..5 };\nprintln(p.x);\n", + ), + ( + "struct field type", + "struct P { x: Int }\nlet p = P { x: \"s\" };\nprintln(p.x);\n", + ), + ("method on the wrong receiver", "println([1,2].upper());\n"), + // `!` takes a Bool or a Nil — the executors say so at run time + // (`Not expected Bool or Nil, got Int`) and the checker said nothing, so + // `!5` passed here and failed there. Its sibling `&&` was always checked; + // only `!` was waved through, which is why the pair is listed together. + ("not on an Int", "println(!5);\n"), + ("not on a String", "println(!\"a\");\n"), + ( + "not on a function result", + "fn f() -> Int { return 1; }\nprintln(!f());\n", + ), + ("and on an Int", "println(5 && true);\n"), +]; + +/// Valid programs, including the ones a stricter reading would reject. +const MUST_ACCEPT: &[(&str, &str)] = &[ + // Supplying it explicitly is the way out the refusal names, and it must + // keep working — as must the same call inside one module, where the + // compiler does have the declaration. + ( + "a defaulted named argument supplied across a module boundary", + "IMPORTS_CONF\nprintln(configure(host: \"a\", timeout_ms: 5));\n", + ), + ( + "a defaulted named argument omitted within one module", + "fn configure({host: String, timeout_ms: Int? = 1000}) -> String { return \"${host} ${timeout_ms}\"; }\nprintln(configure(host: \"a\"));\n", + ), + // The way a channel and a task are actually operated: module functions, + // which the refusals above must not touch. + ( + "channel and task operations are module functions", + "use task;\nlet c = chan(2);\nsend(c, 1);\nprintln(recv(c));\nlet t = spawn(|| 7);\nprintln(task.await(t));\n", + ), + // The `let`-pattern refusals above are deliberately narrow: a destructure + // whose *shape* is only known at run time is ordinary LK, and raises there + // if it disagrees. These are the neighbours of the four refused cases, and + // a broader rule would take them with it. + ( + "a list pattern whose arity the value may not have", + "fn f() -> List { return [1]; }\nlet [a, b] = f();\nprintln(a + b);\n", + ), + ( + "a list pattern over a string destructures its characters", + "let s = \"ab\";\nlet [c, d] = s;\nprintln(c + d);\n", + ), + ( + "a literal nested inside a `let` pattern is an assertion on one position", + "let xs = [1, 2];\nlet [1, b] = xs;\nprintln(b);\n", + ), + ("empty list annotation", "let xs: List = [];\nprintln(xs);\n"), + // A map pattern destructures a *map*. A struct is not one — the two are + // different heap values, and the interpreter's `is_map` says so — even + // though a struct instance rides the map carrier natively. The pattern + // itself is well-formed, so this is a *run-time* refusal and belongs here + // as a program the checker must accept. + ( + "a map pattern against a struct", + "struct P { p: Int }\nlet p = P { p: 3 };\ntry { let {p: c} = p; println(c); } catch e { println(\"E\"); }\n", + ), + // An assignment target is a *chain*, and the store belongs to its last + // step. The parser used to read the first step and discard the rest, so + // `p.m["b"] = 2` was `p.m = 2` — accepted here whenever the field's type + // left room for it, and silently destroying the map on both engines. + ( + "a store into a field's map", + "struct P { m: Map }\nlet p = P { m: {\"a\": 1} };\np.m[\"b\"] = 2;\nprintln(p.m);\n", + ), + ( + "a store into a field's list", + "struct P { xs: List }\nlet p = P { xs: [1, 2] };\np.xs[0] = 9;\nprintln(p.xs);\n", + ), + ( + "a store two fields deep", + "struct Q { n: Int }\nstruct P { q: Q }\nlet p = P { q: Q { n: 1 } };\np.q.n = 5;\nprintln(p.q.n);\n", + ), + ( + "a store two indexes deep", + "let m = {\"a\": {\"b\": 1}};\nm[\"a\"][\"b\"] = 2;\nprintln(m);\n", + ), + // The other side of the map rule above: a string-keyed map can answer any + // name, because its type does not say which keys it has. The value type is + // not part of the question — a field call does not need a callable, and + // `{"score": 40}.score()` is `40`. + ( + "a field call on a map that can hold the name", + "let m = {\"score\": || 7};\nprintln(m.score());\n", + ), + ( + "a field call whose value is not a function", + "let m: Map = {\"score\": 40};\nprintln(m.score());\n", + ), + // And the empty literal, whose key type is still open. + ( + "a name on a map with nothing pinned", + "let m = {};\nprintln(m.has(1));\n", + ), + ("heterogeneous list", "let xs = [1, \"a\"];\nprintln(xs);\n"), + ( + "nullable field", + "struct P { x: Int? }\nlet p = P { x: nil };\nprintln(p.x);\n", + ), + ( + "Int where Float is declared", + "fn f(x: Float) -> Float { return x; }\nprintln(f(1));\n", + ), + ( + "annotated closure", + "let f: (Int) -> Int = |x| x + 1;\nprintln(f(1));\n", + ), + ( + "trait default", + "trait G { fn hi(self) -> String { return \"h\"; } }\nstruct P { x: Int }\nimpl G for P {}\nprintln(P { x: 1 }.hi());\n", + ), + ( + "inherent impl beside a trait impl", + "trait T { fn a(self) -> Int; }\nstruct P { x: Int }\nimpl T for P { fn a(self) -> Int { return 1; } }\nimpl P { fn b(self) -> Int { return 2; } }\nprintln(P{x:1}.b());\n", + ), + ( + "union parameter", + "fn f(x: Int | String) -> String { return \"{}\".format(x); }\nprintln(f(1));\n", + ), + ( + "try as an expression", + "let v = try { 1 } catch e { 2 };\nprintln(v);\n", + ), + ("member of an alias", "use math as m;\nprintln(m.abs(0 - 3));\n"), + ( + "spread of a struct", + "struct P { x: Int }\nlet b = P { x: 1 };\nlet q = P { ..b, x: 2 };\nprintln(q.x);\n", + ), + // Accepted *by design*, and each for a stated reason. + ("truthiness", "if 5 { println(1); }\n"), + ("repeated map key", "let m = {\"a\": 1, \"a\": 2};\nprintln(m);\n"), + ("top-level return", "return 1;\n"), + ( + "index is not nullable", + "let m = {\"a\": 1};\nlet v: Int = m[\"a\"];\nprintln(v);\n", + ), + // The other direction of the `!` rule: refused only when the operand + // *cannot* be a Bool or a Nil. In a language where most values arrive + // untyped, anything else would refuse the ordinary case. + ( + "not on an untyped parameter", + "fn f(v) -> Bool { return !v; }\nprintln(f(nil));\n", + ), + ( + "not on a nullable Int", + "fn f(v: Int?) -> Bool { return !v; }\nprintln(f(nil));\n", + ), + ( + "not on a container read", + "let m = {\"a\": true};\nprintln(!m[\"a\"]);\n", + ), +]; + +#[test] +fn check_refuses_what_it_must() { + let mut wrong = Vec::new(); + for (label, source) in MUST_REFUSE { + let (accepted, _) = check(label, source); + if accepted { + wrong.push(*label); + } + } + assert!(wrong.is_empty(), "`lk check` accepted these mistakes: {wrong:?}"); +} + +#[test] +fn check_accepts_what_it_must() { + let mut wrong = Vec::new(); + for (label, source) in MUST_ACCEPT { + let (accepted, message) = check(label, source); + if !accepted { + wrong.push(format!("{label}: {}", message.lines().next().unwrap_or("").trim())); + } + } + assert!( + wrong.is_empty(), + "`lk check` refused these valid programs:\n {}", + wrong.join("\n ") + ); +} diff --git a/cli/tests/clif_differential_test.rs b/cli/tests/clif_differential_test.rs index c6aecd90..6348bc16 100644 --- a/cli/tests/clif_differential_test.rs +++ b/cli/tests/clif_differential_test.rs @@ -42,6 +42,16 @@ const fn new(name: &'static str, source: &'static str) -> Case { Case { name, source } } +/// A case whose program is generated rather than written out — a 400-element +/// literal is not something to paste into a test file. The leak lives as long +/// as the test process, which is what `&'static str` here means anyway. +fn generated(name: &'static str, source: String) -> Case { + Case { + name, + source: Box::leak(source.into_boxed_str()), + } +} + /// Compile each case through Cranelift (forced, no fallback), run it, run the /// same source under the VM, and require identical stdout and identical /// success/failure. @@ -169,6 +179,92 @@ fn clif_differential_containers_and_dyn() { ); } +/// `xs.slice(a, b)` is a **window**, and the two engines have to agree on what +/// that means — not merely on the elements it reports. +/// +/// They did not, for a while: the VM returned a view and Cranelift returned a +/// copy of the same elements. Every one of these cases printed the same thing +/// on both engines *except* the one that writes to the source, which is the +/// only one that can tell a view from a copy. That is the shape of divergence a +/// differential corpus exists to catch, so it is pinned here rather than left +/// to whoever next reads both lowerings side by side. +#[test] +fn clif_differential_list_windows() { + run_clif_differential( + "windows", + &[ + new( + "window_reads", + "let xs = [3, 1, 4, 1, 5];\n\ + let w = xs.slice(1, 4);\n\ + println(w.len());\n\ + println(w[0]);\n\ + println(w[-1]);\n\ + println(w);\n\ + return w[2];\n", + ), + // Out of the window is nil on both sides, never the source's + // element at that position. + new( + "window_out_of_range", + "let xs = [3, 1, 4, 1, 5];\n\ + let w = xs.slice(1, 3);\n\ + println(w[2]);\n\ + println(w[-3]);\n\ + return w.is_empty();\n", + ), + // `.get(i)` is `[i]` that answers nil rather than failing — same + // rule for the index, negative included. The dispatch tables in + // `core_methods` used to say a negative was simply out of range, + // which no program could observe (the compiler lowers `.get()` to + // `GetIndex`) and which neither engine did. + new( + "get_indexes_like_brackets", + "let xs = [10, 20, 30, 40];\n\ + let w = xs.slice(1, 3);\n\ + println(xs.get(-1));\n\ + println(xs.get(4));\n\ + println(w.get(0));\n\ + println(w.get(-1));\n\ + return w.get(5);\n", + ), + // The one that distinguishes a view from a copy. + new( + "window_sees_the_source_change", + "let xs = [10, 20, 30, 40];\n\ + let w = xs.slice(1, 3);\n\ + println(w[0]);\n\ + xs[1] = 99;\n\ + println(w[0]);\n\ + xs.push(50);\n\ + return w.len();\n", + ), + new( + "window_iterates_and_copies", + "let xs = [1, 2, 3, 4, 5];\n\ + let w = xs.slice(1, 4);\n\ + let sum = 0;\n\ + for v in w {\n sum = sum + v;\n}\n\ + println(sum);\n\ + println(w.to_list());\n\ + return w.to_list().len();\n", + ), + // A window on a window resolves against the original, and bounds + // past the end clamp rather than raising. + new( + "window_of_a_window", + "let xs = [0, 1, 2, 3, 4];\n\ + let w = xs.slice(1, 99);\n\ + let inner = w.slice(1, 3);\n\ + println(w.len());\n\ + println(inner.len());\n\ + println(inner[0]);\n\ + return inner[1];\n", + ), + ], + ); +} + #[test] fn clif_differential_higher_order() { run_clif_differential( @@ -197,561 +293,4304 @@ fn clif_differential_higher_order() { /// is then interpolated. /// /// Marking a function VM-executed changes the type lattice, and signatures have -/// to re-converge before the module is emitted. They did not: the final pass -/// never wrote `ret_types` back, so `helper` — a native callee whose parameter -/// widens to `Dyn` because its argument is bridge-tainted — kept its -/// pre-marking return type at the call site. Interpolating that result emitted -/// `str.from_i64` on a register pair, which only the Cranelift verifier caught, -/// as an unreadable "Verifier errors". Found by the nightly fresh-seed fuzz -/// (`LK_FUZZ_SEED=30198012768`), reduced here to compile in seconds. +/// A declared width survives a function boundary — and being inlined. +/// +/// Every machine-integer rule is chosen from a compile-time fact about a +/// register, and there were two places that fact was never written down: a +/// *parameter* declared `u8`/`u64`/…, and a `let` inside a body the compiler +/// chose to *inline*. Everything held for an annotated local and an `as` cast, +/// which is what every earlier test and every driver that casts on entry +/// happens to use. +/// +/// So `fn f(a: u8) -> u8 { return a + 1; }` answered 256, `a * 2` on 200 +/// answered 400, and `fn f(a: u64, b: u64) { return a > b; }` compared two +/// addresses as if they were signed. A helper taking a register value is the +/// ordinary shape of driver code. +/// +/// Absolute, and it has to be: the fact is missing in the *compiler*, so both +/// backends are handed the same wrong instruction and agree with each other +/// perfectly. +/// One text, one key representation — otherwise a map disagrees with itself. +/// +/// A string key is stored inline (`ShortStr`) when it fits and behind an `Arc` +/// when it does not, and the enum derives `Eq`/`Hash`, so the two are different +/// keys. Promoting a typed string map to the general carrier wrote the `Arc` +/// variant for every key, short ones included: `m["a"]`, `m.get("a")`, +/// `m.keys()` and `println(m)` all showed the entry, while `"a" in m` and +/// `m.has("a")` answered `false` and `m.delete("a")` removed nothing. #[test] -fn clif_differential_bridge_taint_reconverges_ret_types() { - let dir = unique_tmp_dir("bridge_taint"); +fn a_promoted_map_still_finds_its_short_keys() { + run_differential( + "map_key_shape", + &[ + new( + "promoted_by_an_int_key", + "fn put(m: Any, k: Any, v: Any) -> Nil { m[k] = v; return nil; }\n\ + let m = {\"a\": 1};\n\ + put(m, 3, 9);\n\ + println(m);\n\ + println(m.len());\n\ + println(m[\"a\"]);\n\ + println(\"a\" in m);\n\ + println(m.has(\"a\"));\n\ + println(m.get(\"a\"));\n\ + println(m.keys());\n\ + println(m.delete(\"a\"));\n\ + println(m);\n", + ), + new( + "promoted_by_a_wider_value", + "fn put(m: Any, k: Any, v: Any) -> Nil { m[k] = v; return nil; }\n\ + let counts = {\"hit\": 1, \"a much longer key than fits inline\": 2};\n\ + put(counts, \"miss\", \"none\");\n\ + println(counts.has(\"hit\"));\n\ + println(counts.has(\"a much longer key than fits inline\"));\n\ + println(\"miss\" in counts);\n\ + println(counts.delete(\"hit\"));\n\ + println(counts.len());\n", + ), + ], + NativePath::MayDegrade, + ); +} + +/// A task's raise belongs to whoever awaits it, not to the process. +/// +/// A raise is delivered to the nearest `try` frame, and that stack is +/// thread-local: a spawned task starts with an empty one, so natively the raise +/// took the uncaught path — print and exit — and one failing task killed the +/// program where the interpreter handed the error to `task.await`. A task +/// nobody awaits fails silently on both. +#[test] +fn a_task_hands_its_raise_to_its_awaiter() { + run_clif_differential( + "task_raise", + &[ + new( + "caught_at_await", + "use task;\n\ + let t = spawn(|| { let z = 1 % 0; return 0; });\n\ + try { println(\"awaited \" + task.await(t)); } catch e { println(\"caught \" + e); }\n\ + println(\"still running\");\n", + ), + new( + "unawaited_is_silent", + "use task;\n\ + let t = spawn(|| { let z = 1 % 0; return 0; });\n\ + let done = chan(1);\n\ + go send(done, 1);\n\ + println(recv(done));\n", + ), + new( + "a_returning_task_is_unaffected", + "use task;\n\ + let t = spawn(|| { return 41 + 1; });\n\ + println(task.await(t));\n", + ), + ], + ); +} + +/// A struct's identity has to travel with the value, because the value crosses +/// threads. Both halves of it used to be thread-local: the id → name/field +/// registry (written once by the entry prologue, on the main thread) and a +/// handle → id side table. A task runs on another thread and saw neither, so a +/// struct handed to one arrived as a plain map — `typeof` answered `Map`, and +/// `println` printed `{"p":1,"q":2}` for `P{p:1,q:2}`. +#[test] +fn a_struct_keeps_its_name_across_a_task() { + run_clif_differential( + "struct_across_task", + &[ + new( + "through_a_channel", + "use task;\n\ + struct P { p: Int, q: Int }\n\ + fn describe(v: Any) -> String { return typeof(v) + \" \" + v; }\n\ + let ch = chan(1);\n\ + let t = spawn(|| { return describe(recv(ch)); });\n\ + send(ch, P { p: 1, q: 2 });\n\ + println(task.await(t));\n", + ), + new( + "captured_by_the_closure", + "use task;\n\ + struct P { p: Int, q: Int }\n\ + fn describe(v: Any) -> String { return typeof(v) + \" \" + v; }\n\ + let p = P { p: 1, q: 2 };\n\ + let t = spawn(|| { return describe(p); });\n\ + println(task.await(t));\n", + ), + new( + "nested_and_in_a_list", + "use task;\n\ + struct Inner { v: Int }\n\ + struct Outer { inner: Inner, tag: String }\n\ + let ch = chan(2);\n\ + let t = spawn(|| { return \"\" + recv(ch) + \" | \" + recv(ch); });\n\ + send(ch, Outer { inner: Inner { v: 7 }, tag: \"x\" });\n\ + send(ch, [Inner { v: 1 }, Inner { v: 2 }]);\n\ + println(task.await(t));\n", + ), + new( + "back_out_of_the_task", + "use task;\n\ + struct P { p: Int }\n\ + let out = chan(1);\n\ + let t = spawn(|| { send(out, P { p: 9 }); return 0; });\n\ + let v = recv(out);\n\ + println(typeof(v) + \" \" + v);\n\ + println(task.await(t));\n", + ), + ], + ); +} + +/// A struct instance and a map share one native carrier (`Map`), so +/// nothing in the MIR type says which a word is. The interpreter has two +/// different heap values and refuses every map *collection* operation on a +/// struct, naming it — `len()`, `is_empty()`, `keys()`, `in`. Answering from +/// the carrier gave the field count, the field names, and `true`. +/// +/// `MayDegrade`: where the lowering cannot prove which one it holds it declines, +/// and these programs only ever raise, so the VM answering them costs nothing +/// anyone runs. +#[test] +fn a_struct_is_not_a_map_and_the_collection_methods_say_so() { + run_differential( + "struct_not_map", + &[ + new( + "len_through_any", + "struct P { p: Int, q: Int }\n\ + fn f(v: Any) -> String { try { return \"ok \" + v.len(); } catch e { return \"E \" + e; } }\n\ + println(f(P { p: 1, q: 2 }));\n\ + println(f({\"p\": 1}));\n", + ), + new( + "collection_methods_through_any", + "struct P { p: Int, q: Int }\n\ + fn empty(v: Any) -> String { try { return \"ok \" + v.is_empty(); } catch e { return \"E \" + e; } }\n\ + fn keys(v: Any) -> String { try { return \"ok \" + v.keys(); } catch e { return \"E \" + e; } }\n\ + fn has(v: Any) -> String { try { return \"ok \" + (\"p\" in v); } catch e { return \"E \" + e; } }\n\ + let p = P { p: 1, q: 2 };\n\ + println(empty(p));\n\ + println(keys(p));\n\ + println(has(p));\n\ + println(empty({\"p\": 1}));\n", + ), + new( + "clear_and_get_and_arithmetic", + "struct P { p: Int, q: Int }\n\ + fn describe(v: Any) -> String { return typeof(v) + \" \" + v; }\n\ + fn clear(v: Any) -> String { try { v.clear(); return \"cleared\"; } catch e { return \"E \" + e; } }\n\ + fn get(v: Any) -> String { try { return \"\" + v.get(\"p\", 0); } catch e { return \"E \" + e; } }\n\ + fn add(a: Any, b: Any) -> String { try { return describe(a + b); } catch e { return \"E \" + e; } }\n\ + fn sub(a: Any, b: Any) -> String { try { return describe(a - b); } catch e { return \"E \" + e; } }\n\ + let obj = P { p: 1, q: 2 };\n\ + println(get(obj));\n\ + println(add(obj, {\"z\": 3}));\n\ + println(add({\"z\": 3}, obj));\n\ + println(sub(obj, \"q\"));\n\ + println(clear(obj));\n\ + println(clear({\"p\": 1}));\n\ + println(get({\"p\": 1}));\n\ + println(add({\"a\": 1}, {\"z\": 3}));\n", + ), + new( + "iterating_a_struct", + "struct P { p: Int, q: Int }\n\ + fn f(v: Any) -> String { try { let s = \"\"; for k in v { s = s + k; } return \"ok \" + s; } catch e { return \"E \" + e; } }\n\ + println(f(P { p: 1, q: 2 }));\n\ + println(f({\"p\": 1}));\n", + ), + ], + NativePath::MayDegrade, + ); +} + +/// The other half of the same proof: a map that *is* one keeps its lowering. +/// The fact is a lattice with no "unknown" member, so a plain map has to be +/// recorded as plain — and a parameter every call site hands a map is one. +#[test] +fn a_map_that_is_one_still_lowers_its_collection_methods() { + run_clif_differential( + "plain_map_collections", + &[ + new( + "through_a_parameter", + "fn size(m: Map) -> Int { return m.len(); }\n\ + fn empty(m: Map) -> Bool { return m.is_empty(); }\n\ + fn has(m: Map) -> Bool { return \"a\" in m; }\n\ + let m = {\"a\": 1, \"b\": 2};\n\ + println(size(m));\n\ + println(empty(m));\n\ + println(has(m));\n\ + println(m.keys());\n", + ), + new( + "iterating_a_map", + "let m = {\"a\": 1, \"b\": 2};\n\ + let keys = \"\";\n\ + for pair in m {\n\ + keys = keys + pair[0];\n\ + }\n\ + println(keys);\n", + ), + new( + "across_a_loop_header", + "let m = {\"a\": 1};\n\ + let total = 0;\n\ + let i = 0;\n\ + while i < 3 {\n\ + total = total + m.len();\n\ + i = i + 1;\n\ + }\n\ + println(total);\n", + ), + ], + ); +} + +/// A declared width has to survive every boundary a value crosses, and three +/// of them dropped it. +/// +/// The arithmetic path asks the *register* for its width, and a register that +/// came out of a call or a container had none — while the same value bound to a +/// local first got it right. `250 + 10` at `u8` is 4: +/// +/// | written as | was | +/// | --- | --- | +/// | `bytes[0] + 10`, `bytes: List` | 260 | +/// | `counts["k"] + 10`, `counts: Map` | 260 | +/// | `ret() + 10`, `fn ret() -> u8` | 260 | +/// +/// The shifts are the fourth: they desugar into named calls, and only `~` had +/// been taught to wrap afterwards. +#[test] +fn a_declared_width_survives_a_call_a_container_and_a_shift() { + run_clif_differential( + "machine_int_boundaries", + &[ + new( + "out_of_a_call_and_a_container", + "struct S { f: u8 }\n\ + fn ret() -> u8 { return 250; }\n\ + let bytes: List = [250];\n\ + let counts: Map = {\"k\": 250};\n\ + let s = S { f: 250 };\n\ + let local: u8 = 250;\n\ + println(bytes[0] + 10);\n\ + println(counts[\"k\"] + 10);\n\ + println(s.f + 10);\n\ + println(local + 10);\n\ + println(ret() + 10);\n", + ), + new( + "every_boundary_a_container_crosses", + "struct S { f: u8, buf: List }\n\ + let gbuf: List = [250];\n\ + fn from_param_elem(bytes: List) -> u8 { return bytes[0] + 10; }\n\ + fn from_param_map(m: Map) -> u8 { return m[\"k\"] + 10; }\n\ + fn from_global_elem() -> u8 { return gbuf[0] + 10; }\n\ + fn ret_buf() -> List { return [250]; }\n\ + fn for_over_param(bytes: List) -> u8 { let t: u8 = 0; for b in bytes { t = b + 10; } return t; }\n\ + let s = S { f: 250, buf: [250] };\n\ + println(from_param_elem([250]));\n\ + println(from_param_map({\"k\": 250}));\n\ + println(from_global_elem());\n\ + println(s.buf[0] + 10);\n\ + println(ret_buf()[0] + 10);\n\ + println(for_over_param([250]));\n\ + let cap: List = [250];\n\ + let f = || cap[0] + 10;\n\ + println(f());\n\ + let [head] = gbuf;\n\ + println(head + 10);\n", + ), + new( + "shifts_wrap_to_their_width", + "fn shl8(a: u8, n: u8) -> u8 { return a << n; }\n\ + fn shr8(a: u8, n: u8) -> u8 { return a >> n; }\n\ + fn shl32(a: i32, n: i32) -> i32 { return a << n; }\n\ + println(shl8(1, 3));\n\ + println(shl8(1, 9));\n\ + println(shr8(255, 9));\n\ + println(shl32(1, 30));\n\ + println(shl32(1, 31));\n\ + println(shl32(1, 32));\n", + ), + ], + ); +} + +/// A float narrowed to a fixed width saturates to *that* width's range. +/// +/// Both engines used to saturate to `i64` first and then mask the result, so a +/// value out of range came back as an arbitrary bit pattern. `/` is float +/// division, so dividing by zero is `inf` and lands here: at `i32`, `1 / 0` +/// answered `-1` and `-1 / 0` answered `0`. Two of the four cases were right by +/// coincidence — `u8`'s mask keeps the low byte of `i64::MAX`, which is 255. +#[test] +fn a_float_narrowed_to_a_width_saturates_to_that_width() { + run_clif_differential( + "machine_int_narrowing", + &[ + new( + "division_by_zero", + "fn d8(a: u8, b: u8) -> u8 { return a / b; }\n\ + fn d32(a: i32, b: i32) -> i32 { return a / b; }\n\ + println(d8(1, 0));\n\ + println(d8(0, 0));\n\ + println(d32(1, 0));\n\ + println(d32(0 - 1, 0));\n\ + println(d8(7, 2));\n\ + println(d32(0 - 7, 2));\n", + ), + new( + "out_of_range_casts", + "println(1e300 as u8 as Int);\n\ + println(1e300 as i8 as Int);\n\ + println(1e300 as i32 as Int);\n\ + println((0.0 - 1e300) as i32 as Int);\n\ + println((0.0 / 0.0) as i16 as Int);\n\ + println(2.7 as u8 as Int);\n\ + println((0.0 - 2.7) as i8 as Int);\n", + ), + new( + "integer_casts_still_wrap", + "println(300 as u8 as Int);\n\ + println(200 as i8 as Int);\n\ + println(70000 as u16 as Int);\n\ + println(4294967296 as u32 as Int);\n", + ), + ], + ); +} + +#[test] +fn a_declared_width_crosses_a_function_boundary() { + let dir = unique_tmp_dir("param_width"); let _ = fs::remove_dir_all(&dir); create_dir_all(&dir).expect("create tmp dir"); - let file = "taint.lk"; - let src = "fn bridged(x) { let f = \"v={}\".trim(); println(f, x); return [x, x + 1]; }\n\ - fn helper(a, b) { return (b - 25); }\n\ - let got = bridged(1);\n\ - let picked = helper(2, got[0]);\n\ - println(\"p={}\", picked);\n\ - return 0;\n"; + let file = "params.lk"; + // Each helper is small enough to be inlined at the call site *and* is + // compiled out of line for the module, so both paths are exercised by the + // same source. + let src = "const WIDE: u32 = 0xFFFFFFFF;\n\ + fn from_const() -> u32 { return WIDE + 1; }\n\ + // A closure inherited none of these facts either.\n\ + fn in_closure() -> u32 { let f = || WIDE + 1; return f(); }\n\ + fn add_u8(a: u8) -> u8 { return a + 1; }\n\ + fn mul_u8(a: u8) -> u8 { return a * 2; }\n\ + fn sub_u8(a: u8) -> u8 { return a - 1; }\n\ + fn add_i8(a: i8) -> i8 { return a + 1; }\n\ + fn shr_u64(a: u64) -> u64 { return a >> 32; }\n\ + fn gt_u64(a: u64, b: u64) -> Bool { return a > b; }\n\ + fn half_u64(a: u64) -> u64 { return a / 2; }\n\ + // A `let` inside the body, which is the half that only breaks\n\ + // once the function is inlined.\n\ + fn neg_u32(bits: u32) -> u32 { let zero: u32 = 0; return zero - bits; }\n\ + println(add_u8(255 as u8));\n\ + println(mul_u8(200 as u8));\n\ + println(sub_u8(0 as u8));\n\ + println(add_i8(127 as i8));\n\ + println(shr_u64(0xFFFF800000000000 as u64));\n\ + println(gt_u64(0x8000000000000000 as u64, 1 as u64));\n\ + println(half_u64(0xFFFFFFFFFFFFFFFF as u64));\n\ + println(neg_u32(0xFFFFFF80 as u32));\n\ + // A declared field width, which is the same fact one more hop\n\ + // away: the register a field lands in came out of a container\n\ + // and carries nothing of its own.\n\ + struct Reg { value: u32 }\n\ + let r = Reg { value: 0xFFFFFFFF as u32 };\n\ + println(r.value + 1);\n\ + println(r.value / 2);\n\ + // A top-level `const`, which is what a driver's register map is\n\ + // made of — `drivers/e1000.lk` has seventeen — read through\n\ + // `GetGlobal` into a register that carries nothing.\n\ + println(WIDE + 1);\n\ + println(from_const());\n\ + println(in_closure());\n"; + let expected = concat!( + "0\n144\n255\n-128\n", + "4294934528\ntrue\n9223372036854775807\n128\n", + "0\n2147483647\n", + "0\n0\n0\n", + ); File::create(dir.join(file)) .and_then(|mut f| f.write_all(src.as_bytes())) .expect("write program"); let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); - let vm_stdout = String::from_utf8_lossy(&vm.stdout).into_owned(); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + expected, + "vm stderr: {}", + String::from_utf8_lossy(&vm.stderr) + ); let compile = run_cli(&dir, ["compile", file]) .env("LK_AOT_NO_FALLBACK", "1") - .env("LK_AOT_HYBRID", "1") + .env("LK_AOT_HYBRID", "0") .output() - .expect("hybrid compile"); - let compile_stderr = String::from_utf8_lossy(&compile.stderr).into_owned(); - assert!(compile.status.success(), "hybrid compile failed: {compile_stderr}"); + .expect("native compile"); assert!( - compile_stderr.contains("Tier 1 hybrid"), - "expected the hybrid link path, got: {compile_stderr}" + compile.status.success(), + "native compile failed: {}", + String::from_utf8_lossy(&compile.stderr) ); - - let native = Command::new(dir.join("taint")) + let native = Command::new(dir.join("params")) .env("ASAN_OPTIONS", "detect_leaks=0") .output() .expect("run executable"); assert_eq!( - vm_stdout, String::from_utf8_lossy(&native.stdout), - "stdout must match the VM" + expected, + "native stderr: {}", + String::from_utf8_lossy(&native.stderr) ); - assert_eq!(vm.status.success(), native.status.success()); let _ = fs::remove_dir_all(&dir); } -/// compiled through Cranelift with `LK_AOT_HYBRID` on and forced clif-only, the -/// stderr must show the Cranelift hybrid link (not a fallback) and stdout must -/// match the VM — including native/VM print ordering across the bridge. +/// Sizing a PCI BAR: two's complement at the register's own width. +/// +/// `drivers/pci.lk` asks a device how large its region is the only way the bus +/// allows — write all ones, read back, and the lowest bit the device still +/// leaves set is the size. Turning that into a number is `-bits` at 32 bits, or +/// equivalently `~bits + 1`, and both forms are asserted here because both are +/// what someone writes and they must not disagree. +/// +/// What makes it a test rather than a tautology is the width. On the `i64` +/// carrier every one of these values has 32 high zero bits that the register +/// never had, so a complement or a negation that runs at 64 bits answers +/// something with no relation to a BAR size. The four cases are real region +/// sizes, and the expected values were computed elsewhere. #[test] -fn clif_differential_hybrid_bridge() { - let dir = unique_tmp_dir("hybrid"); +fn a_pci_bar_sizes_at_its_own_width() { + let dir = unique_tmp_dir("bar_size"); let _ = fs::remove_dir_all(&dir); create_dir_all(&dir).expect("create tmp dir"); - let file = "hybrid.lk"; - // `report`/`geti` use `println(fmt, x)` (a bridged shape); `geti`'s result - // flows back through `lk_hybrid_call_r` and feeds native arithmetic. - let src = "fn report(x) { let f = \"acc={}\".trim(); println(f, x); }\n\ - fn geti(x) { let f = \"i={}\".trim(); println(f, x); return x + 1; }\n\ - let acc = 0;\n\ - for i in 0..10 { acc += i; }\n\ - report(acc);\n\ - println(geti(3) + 10);\n\ - println(\"done\");\n\ - return 0;\n"; + let file = "bar.lk"; + let src = "fn size_of(probed: u32, mask: u32) -> u32 {\n\ + \x20 let bits = probed & mask;\n\ + \x20 let zero: u32 = 0;\n\ + \x20 return zero - bits;\n\ + }\n\ + fn size_by_complement(probed: u32, mask: u32) -> u32 {\n\ + \x20 let bits = probed & mask;\n\ + \x20 return (~bits) + 1;\n\ + }\n\ + let mem: u32 = 0xFFFFFFF0;\n\ + let io: u32 = 0xFFFFFFFC;\n\ + println(size_of(0xFFFFFF80 as u32, mem));\n\ + println(size_of(0xFFFFF000 as u32, mem));\n\ + println(size_of(0xFFFFFFE1 as u32, io));\n\ + println(size_of(0xF0000000 as u32, mem));\n\ + println(size_by_complement(0xFFFFFF80 as u32, mem) == size_of(0xFFFFFF80 as u32, mem));\n\ + println(size_by_complement(0xF0000000 as u32, mem) == size_of(0xF0000000 as u32, mem));\n"; + let expected = "128\n4096\n32\n268435456\ntrue\ntrue\n"; File::create(dir.join(file)) .and_then(|mut f| f.write_all(src.as_bytes())) .expect("write program"); let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); - let vm_stdout = String::from_utf8_lossy(&vm.stdout).into_owned(); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + expected, + "vm stderr: {}", + String::from_utf8_lossy(&vm.stderr) + ); let compile = run_cli(&dir, ["compile", file]) .env("LK_AOT_NO_FALLBACK", "1") - .env("LK_AOT_HYBRID", "1") + .env("LK_AOT_HYBRID", "0") .output() - .expect("hybrid compile"); - let compile_stderr = String::from_utf8_lossy(&compile.stderr).into_owned(); - assert!(compile.status.success(), "hybrid compile failed: {compile_stderr}"); + .expect("native compile"); assert!( - compile_stderr.contains("Tier 1 hybrid"), - "expected the hybrid link path, got: {compile_stderr}" + compile.status.success(), + "native compile failed: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let native = Command::new(dir.join("bar")) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run executable"); + assert_eq!( + String::from_utf8_lossy(&native.stdout), + expected, + "native stderr: {}", + String::from_utf8_lossy(&native.stderr) ); + let _ = fs::remove_dir_all(&dir); +} - let native = Command::new(dir.join("hybrid")) +/// A hardware descriptor, packed and unpacked, with the answers computed +/// elsewhere. +/// +/// This is `drivers/idt.lk`'s `set_gate` in miniature: a 64-bit handler address +/// split across three fields that are not adjacent, the two words written, and +/// the address rebuilt from them. Nothing else in the suite exercises that +/// shape, and the only thing that currently notices a mistake in it is a QEMU +/// run that triple-faults — which says the machine died, not which shift was +/// wrong. +/// +/// The address has bit 63 set, which is what a higher-half kernel's handler +/// looks like and what makes the shifts *mean* something: `handler >> 32` is a +/// logical shift because the value is a `u64`, and the same expression on an +/// `Int` carrier would sign-extend. The two masked fields would survive that — +/// the mask hides it — so the unmasked `>> 32` is here as well, which is the +/// shape a driver writes to take the high half of a 64-bit BAR. +/// +/// Expected values computed independently (Python, arbitrary-precision), not +/// read back from this implementation. +#[test] +fn a_gate_descriptor_packs_and_unpacks() { + let dir = unique_tmp_dir("gate_pack"); + let _ = fs::remove_dir_all(&dir); + create_dir_all(&dir).expect("create tmp dir"); + let file = "gate.lk"; + let src = "let handler: u64 = 0xFFFF800012345678;\n\ + let selector: u64 = 0x08;\n\ + let dpl: u64 = 0;\n\ + let kind: u64 = 0x8E;\n\ + let low = (handler & 0xFFFF)\n\ + \x20 | (selector << 16)\n\ + \x20 | ((kind | (dpl << 5)) << 40)\n\ + \x20 | (((handler >> 16) & 0xFFFF) << 48);\n\ + let high = (handler >> 32) & 0xFFFFFFFF;\n\ + println(low);\n\ + println(high);\n\ + let rebuilt = (low & 0xFFFF) | (((low >> 48) & 0xFFFF) << 16) | (high << 32);\n\ + println(rebuilt);\n\ + println(rebuilt == handler);\n\ + println(handler >> 32);\n"; + let expected = "1311829522123347576\n4294934528\n18446603336526616184\ntrue\n4294934528\n"; + File::create(dir.join(file)) + .and_then(|mut f| f.write_all(src.as_bytes())) + .expect("write program"); + + let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + expected, + "vm stderr: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let compile = run_cli(&dir, ["compile", file]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("native compile"); + assert!( + compile.status.success(), + "native compile failed: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let native = Command::new(dir.join("gate")) .env("ASAN_OPTIONS", "detect_leaks=0") .output() - .expect("run hybrid executable"); + .expect("run executable"); assert_eq!( - vm_stdout, String::from_utf8_lossy(&native.stdout), - "hybrid stdout must match the VM (including native/VM ordering)" + expected, + "native stderr: {}", + String::from_utf8_lossy(&native.stderr) ); - assert_eq!(vm.status.success(), native.status.success()); let _ = fs::remove_dir_all(&dir); } -/// try/catch equivalence, *without* requiring the native path. +/// A `Maybe` reaching one call site widens the parameter for all of them. /// -/// Renamed off `clif_differential_*` on purpose: `try`/`catch` is now a real -/// statement lowering to `TryBegin`/`TryEnd`, and the MIR lowering has no -/// handler-region support yet, so these cases degrade to the Tier 0 VM bundle. -/// What this test guards is what it always really guarded — that the native -/// artifact behaves exactly like the VM. The property that lapsed (it went -/// *through Cranelift*) is a recorded debt tracked in todos.md and pinned by -/// `AOT_COVERAGE_ALLOW` in check.yml, not something to be silently dropped here. +/// `Sig::observe_param` records a nullable argument as `Dyn`, and the join is +/// per *parameter* — so one call passing `s.byte_at(i)` makes the parameter +/// `Dyn` for the call that passes `10` as well. Everything inside the callee +/// then has a boxed operand where it wants a number. +/// +/// That is correct and it used to be free, because `byte_at` answered an `I64`. +/// It began answering a `Maybe` — honestly: an index past the end is nil — and +/// `bare-metal-x86` stopped lowering, on `put_char`, a function that never +/// touches a string. What fixes it is unboxing where a scalar is *required*, +/// which is the rule `read_index_scalar` already documented. +/// +/// Absolute rather than differential: both engines agree on the answer either +/// way. What differs is whether the native build exists at all. #[test] -fn try_catch_differential() { - run_differential( - "try_catch", - &[ - // A raise crossing the protected region: the success path runs the - // body, the failure path binds the raised value. - new( - "catch_raise", - "let out = 0;\ntry {\n error(\"boom\");\n out = 1;\n} catch e {\n out = 2;\n}\nreturn out;\n", - ), - new( - "catch_skipped", - "let out = 0;\ntry {\n out = 5;\n} catch e {\n out = 9;\n}\nreturn out;\n", - ), - new( - "catch_with_arg", - // `r` is annotated and the literals match `/`'s Float result: a - // `try` body is now type-checked like any other statement (it used - // to sit inside a closure the checker did not look into), and - // `let r = 0; r = div(10, 0);` is a static type error. The path - // under test — a raise from a nested call, caught, value bound — - // is unchanged. - "fn div(a: Int, b: Int) -> Float {\n if (b == 0) { error(\"zero\"); }\n return a / b;\n}\nlet r: Float = 0.0;\ntry {\n r = div(10, 0);\n} catch e {\n r = -1.0;\n}\nreturn r;\n", - ), - // A raised channel error must not leave any lock held across the - // longjmp: after catching, the registry and channel stay usable - // (regression: `channel()` raised "Channel not found" while the - // registry MutexGuard was live, deadlocking every later op). - new( - "chan_unknown_id_catch_then_use", - // The bad id goes through an `Any` binding: a `try` body is now - // type-checked like any other statement (it used to sit inside a - // closure the checker did not look into), and `recv(999)` is a - // static type error. The runtime path under test — an unknown - // channel id raising, caught, and the channel machinery still - // usable afterwards — is unchanged. - "let bad: Any = 999;\ntry { recv(bad); } catch e { println(\"caught\"); }\nlet c = chan(1);\nsend(c, 41);\nprintln(recv(c) + 1);\nreturn 0;\n", - ), - // Same discipline on the closed-send raise inside select's arm. - new( - "select_closed_send_catch_then_use", - "use chan as ch;\nlet c = chan(1);\nch.close(c);\ntry {\n let x = select {\n case send(c, 1) => \"sent\";\n };\n println(x);\n} catch e { println(\"caught\"); }\nlet d = chan(1);\nsend(d, 6);\nprintln(recv(d) * 7);\nreturn 0;\n", - ), - ], - NativePath::MayDegrade, +fn a_boxed_argument_still_lowers_where_a_number_is_required() { + let dir = unique_tmp_dir("boxed_param"); + let _ = fs::remove_dir_all(&dir); + create_dir_all(&dir).expect("create tmp dir"); + let file = "boxed.lk"; + // `sink` is called with a `Maybe` and with a plain `Int`, which is what + // makes its parameter `Dyn`; the body then does arithmetic, a comparison + // and a shift on it — three separate `read_typed_scalar` consumers. + // + // The parameter is declared `Int?` because that is what `byte_at` answers, + // and an `Int?` argument no longer passes for a declared `Int` (nullability + // used to be erased by the numeric-promotion rule). The lowering under test + // is unchanged: the parameter is still `Dyn` at both call sites. + let src = "fn sink(b: Int?) -> Int {\n\ + \x20 if (b == 8) {\n\ + \x20 return 0;\n\ + \x20 }\n\ + \x20 return (b + 1) >> 1;\n\ + }\n\ + fn walk(text: String) -> Int {\n\ + \x20 let total = 0;\n\ + \x20 for i in 0..text.len() {\n\ + \x20 total = total + sink(text.byte_at(i));\n\ + \x20 }\n\ + \x20 return total + sink(10);\n\ + }\n\ + println(walk(\"hi\"));\n"; + File::create(dir.join(file)) + .and_then(|mut f| f.write_all(src.as_bytes())) + .expect("write program"); + + let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); + let expected = String::from_utf8_lossy(&vm.stdout).into_owned(); + assert!( + !expected.trim().is_empty(), + "the VM printed nothing: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let compile = run_cli(&dir, ["compile", file]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("native compile"); + assert!( + compile.status.success(), + "a boxed parameter must still lower: {}", + String::from_utf8_lossy(&compile.stderr) ); + let native = Command::new(dir.join("boxed")) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run executable"); + assert_eq!(String::from_utf8_lossy(&native.stdout), expected); + let _ = fs::remove_dir_all(&dir); } -/// `as` casts, with the native path pinned: the point of these is that the two -/// backends agree *bit for bit*, not merely that both produce something. -/// -/// The VM masks inside its `i64` carrier and sign-extends back; Cranelift does -/// `ireduce` then `sextend`/`uextend`. Those are different mechanisms, so this -/// is where a divergence would show up. -/// Function pointers: an exported function's address, and a call through it. +/// The whole machine-integer matrix, answers written out. /// -/// Not a *differential* test in the usual sense — the VM refuses both builtins, -/// because an interpreter has no code addresses to hand out and returning a -/// fake one would produce a program that runs interpreted and jumps into -/// nothing when compiled. What is checked is that the native side computes the -/// answer, which is the whole of the feature: a driver table is an array of -/// these. +/// Six rounds of work went into this family one operator at a time — shift, +/// compare, divide, modulo, `as Float`, complement, display — each found by a +/// program that gave a wrong answer rather than by a test. This is the net +/// underneath all of it: every width, signed and unsigned, at the edge where it +/// wraps. Absolute, because a compiler-level mistake is made once and both +/// backends inherit it. #[test] -fn function_pointers_are_native_only() { - use std::process::Command; +fn machine_integer_edges_answer_the_same_on_both_engines() { + let dir = unique_tmp_dir("int_matrix"); + let _ = fs::remove_dir_all(&dir); + create_dir_all(&dir).expect("create tmp dir"); + let file = "matrix.lk"; + // `0 - 1` rather than `-1` where a negative is wanted: this language has no + // unary minus, which is also why a negative literal cannot be told from a + // wide bit pattern by shape. + let src = "let s8: i8 = 127;\n\ + println(s8 + 1);\n\ + let s8min: i8 = 0 - 128;\n\ + println(s8min - 1);\n\ + let s16: i16 = 32767;\n\ + println(s16 + 1);\n\ + let s32: i32 = 2147483647;\n\ + println(s32 + 1);\n\ + let neg: i8 = 0 - 1;\n\ + println(neg >> 1);\n\ + let u8max: u8 = 255;\n\ + println(u8max + 1);\n\ + let u8zero: u8 = 0;\n\ + println(u8zero - 1);\n\ + let u16max: u16 = 65535;\n\ + println(u16max + 1);\n\ + let u32max: u32 = 4294967295;\n\ + println(u32max + 1);\n\ + let u8big: u8 = 200;\n\ + println(u8big * 2);\n\ + let top32: u32 = 0x80000000;\n\ + println(top32 >> 31);\n\ + println(top32 / 2);\n\ + let all64: u64 = 0xFFFFFFFFFFFFFFFF;\n\ + println(all64 / 2);\n\ + println(all64 % 10);\n\ + let top64: u64 = 0x8000000000000000;\n\ + let one64: u64 = 1;\n\ + println(top64 > one64);\n\ + println(one64 < top64);\n\ + println((top64 as u32) as Int);\n\ + println((all64 as u8) as Int);\n\ + println(~top32 as Int);\n"; + let expected = concat!( + "-128\n127\n-32768\n-2147483648\n-1\n", + "0\n255\n0\n0\n144\n", + "1\n1073741824\n9223372036854775807\n5\n", + "true\ntrue\n0\n255\n2147483647\n", + ); + File::create(dir.join(file)) + .and_then(|mut f| f.write_all(src.as_bytes())) + .expect("write program"); - let dir = std::env::temp_dir().join(format!("lk_fnptr_{}", std::process::id())); - let _ = fs::create_dir_all(&dir); - let source = dir.join("fnptr.lk"); - fs::write( - &source, - "#[export(\"probe_add\")]\nfn probe_add(a: Int, b: Int) -> Int {\n return a + b;\n}\n\n\ - let p = unsafe { symbol_address(\"probe_add\") };\nprintln(unsafe { call_address_2(p, 20, 22) });\n", - ) - .expect("write source"); + let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + expected, + "vm stderr: {}", + String::from_utf8_lossy(&vm.stderr) + ); - // The VM refuses, by name. - let vm = Command::new(env!("CARGO_BIN_EXE_lk")) - .arg(&source) + let compile = run_cli(&dir, ["compile", file]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") .output() - .expect("run vm"); - let message = String::from_utf8_lossy(&vm.stderr); - assert!(!vm.status.success(), "the VM must refuse: {message}"); + .expect("native compile"); assert!( - message.contains("symbol_address requires native compilation"), - "the refusal must name the builtin: {message}" + compile.status.success(), + "native compile failed: {}", + String::from_utf8_lossy(&compile.stderr) ); + let native = Command::new(dir.join("matrix")) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run executable"); + assert_eq!( + String::from_utf8_lossy(&native.stdout), + expected, + "native stderr: {}", + String::from_utf8_lossy(&native.stderr) + ); + let _ = fs::remove_dir_all(&dir); +} - // Compiled, it answers. - let exe = dir.join("fnptr"); - let compile = Command::new(env!("CARGO_BIN_EXE_lk")) - .args(["compile"]) - .arg(&source) +/// A 64-bit mask can be *written*, and still cannot be written where it does not +/// belong. +/// +/// `0x8000_0000_0000_0000` used to be refused with "literal +/// -9223372036854775808 is out of range for u64" — a number nobody typed, from +/// the carrier having run out of room. It is a bit pattern at that radix, so it +/// is now parsed as the `u64` it is. The second half of the test is the reason +/// this could not simply relax the range check: `-1` has the same carrier as +/// `0xFFFF_FFFF_FFFF_FFFF` and must stay refused. +#[test] +fn full_width_radix_literals_are_writable() { + let dir = unique_tmp_dir("wide_literal"); + let _ = fs::remove_dir_all(&dir); + create_dir_all(&dir).expect("create tmp dir"); + let file = "wide.lk"; + let src = "const PAGE_NX: u64 = 0x8000000000000000;\n\ + const ALL_ONES: u64 = 0xFFFFFFFFFFFFFFFF;\n\ + println(PAGE_NX);\n\ + println(ALL_ONES);\n\ + let mask: u64 = 0xFFFF000000000000;\n\ + println(PAGE_NX & mask);\n\ + let which = match ALL_ONES { 0xFFFFFFFFFFFFFFFF => 1, _ => 0 };\n\ + println(which);\n\ + let addr: usize = 0xFFFFFFFFFFFFFFFF;\n\ + println(addr);\n\ + let half: usize = 0x8000000000000000;\n\ + println(half / 2);\n"; + // `usize` too, and that is not a freebie: the range check used to probe + // `u32` for pointer widths — "assume the smaller" — so the identical value + // passed as `u64` and was refused as `usize`. + let expected = "9223372036854775808\n18446744073709551615\n9223372036854775808\n1\n\ + 18446744073709551615\n4611686018427387904\n"; + File::create(dir.join(file)) + .and_then(|mut f| f.write_all(src.as_bytes())) + .expect("write program"); + + let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + expected, + "vm stderr: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let compile = run_cli(&dir, ["compile", file]) .env("LK_AOT_NO_FALLBACK", "1") .env("LK_AOT_HYBRID", "0") .output() - .expect("compile"); + .expect("native compile"); assert!( compile.status.success(), - "compile failed: {}", + "native compile failed: {}", String::from_utf8_lossy(&compile.stderr) ); - let run = Command::new(&exe).output().expect("run native"); - assert_eq!(String::from_utf8_lossy(&run.stdout).trim(), "42"); + let native = Command::new(dir.join("wide")) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run executable"); + assert_eq!(String::from_utf8_lossy(&native.stdout), expected); + + // Still refused, and for the widths a bit pattern genuinely does not fit. + for (name, source, wanted) in [ + ("neg.lk", "let y: u8 = -1;\n", "out of range"), + ( + "wide_u32.lk", + "let y: u32 = 0xFFFFFFFFFFFFFFFF;\n", + "out of range for u32", + ), + // Pointer width being 64 bits does not make it signless. + ("neg_usize.lk", "let y: usize = 0 - 1;\n", "out of range for usize"), + ] { + File::create(dir.join(name)) + .and_then(|mut f| f.write_all(source.as_bytes())) + .expect("write program"); + let out = run_cli(&dir, ["check", name]).output().expect("check run"); + let text = format!( + "{}{}", + String::from_utf8_lossy(&out.stdout), + String::from_utf8_lossy(&out.stderr) + ); + assert!(!out.status.success(), "[{name}] should have been refused: {text}"); + assert!(text.contains(wanted), "[{name}] wanted {wanted:?}, got: {text}"); + } let _ = fs::remove_dir_all(&dir); } -/// `<<` and `>>`, which lower to the range-checked `lkrt` helpers rather than -/// to a machine shift. Both halves matter: the values have to agree, and so -/// does the *failure* — a shift amount out of range raises on both sides, and -/// masking it (what the hardware would do) would show up here as a native run -/// that succeeded where the VM refused. +/// What a `u64` above `i64::MAX` prints, absolutely — not just identically. +/// +/// A differential cannot see this one: two backends that both hand the carrier +/// to an `i64` formatter agree with each other perfectly, and print a physical +/// address as a negative number. So the expected digits are written out, and +/// both engines are held to them. #[test] -fn shift_differential() { - run_differential( - "shift", - &[ - new("shl_const", "return 3 << 8;\n"), - new("shr_const", "return 1024 >> 5;\n"), - // Arithmetic, not logical: the sign bit is replicated. - new("shr_negative", "return (0 - 16) >> 2;\n"), - // Variable amounts: the value is not a constant the lowering can fold. - new("shl_variable", "let n = 5;\nreturn 1 << n;\n"), - new("shr_variable", "let n = 3;\nreturn 4096 >> n;\n"), - // Precedence: tighter than comparison, looser than `+` (Rust's). - new("precedence_add", "return 1 << 2 + 3;\n"), - new("precedence_cmp", "if (8 >> 1 == 4) { return 1; }\nreturn 0;\n"), - // Mixed with the other bitwise operators, which lower to machine - // instructions — so this is the two paths meeting. - new("with_mask", "let v = 0xdeadbeef;\nreturn (1 << 12) - 1 & v;\n"), - // The edges of the accepted range. - new("shl_zero", "return 7 << 0;\n"), - new("shl_63", "return 1 << 63;\n"), - // Out of range: both sides must refuse, not mask. - new("shl_out_of_range", "let n = 64;\nreturn 1 << n;\n"), - new("shr_negative_amount", "let n = 0 - 1;\nreturn 1 >> n;\n"), - ], - NativePath::PureCranelift, +fn u64_renders_unsigned_on_both_engines() { + let dir = unique_tmp_dir("u64_render"); + let _ = fs::remove_dir_all(&dir); + create_dir_all(&dir).expect("create tmp dir"); + let file = "u64render.lk"; + // `one << 63` rather than the literal: `9223372036854775808` does not fit an + // `i64`, and the lexer has no way yet to tell a `u64` literal from an + // overflowing one (`let y: u8 = -1` has to stay refused). + let src = "let one: u64 = 1;\n\ + let top = one << 63;\n\ + println(top);\n\ + println(\"${top}\");\n\ + println(top + 5);\n\ + let narrow: u32 = 4294967295;\n\ + println(narrow);\n\ + println((top + 2) >> 1);\n"; + // The last line is the reason arithmetic has to carry the width at all: the + // shift asks its left operand how wide it is, and a bare `a + b` used to + // answer "no idea" — so it shifted arithmetically and produced a *wrong + // value*, not merely a wrong rendering. + let expected = "9223372036854775808\n9223372036854775808\n9223372036854775813\n4294967295\n\ + 4611686018427387905\n"; + File::create(dir.join(file)) + .and_then(|mut f| f.write_all(src.as_bytes())) + .expect("write program"); + + let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + expected, + "vm stderr: {}", + String::from_utf8_lossy(&vm.stderr) ); -} -#[test] -fn machine_int_cast_differential() { - run_differential( - "machine_int_cast", - &[ - // Narrowing truncates rather than erroring: 300 & 0xFF. - new("narrow_u8", "let x = 300 as u8;\nreturn x;\n"), - // Sign extension back into the carrier — the case most likely to - // diverge between a mask and an `ireduce`. - new("sign_extend_i8", "let x = 255 as i8;\nreturn x;\n"), - new("sign_extend_i8_min", "let x = 128 as i8;\nreturn x;\n"), - new("sign_extend_i16", "let x = 65535 as i16;\nreturn x;\n"), - // Negative source, unsigned target: reinterpretation, not clamping. - new("negative_to_u32", "let x = (0 - 1) as u32;\nreturn x;\n"), - new("negative_to_u8", "let x = (0 - 1) as u8;\nreturn x;\n"), - // The second cast must see the first one's result, not the original. - new("chained", "let x = 300 as u8 as u32;\nreturn x;\n"), - // Full width is a no-op on both sides. - new("identity_i64", "let x = (0 - 1) as i64;\nreturn x;\n"), - // Pointer width follows the carrier on a 64-bit host. - new("usize_passthrough", "let x = 42 as usize;\nreturn x;\n"), - // Float and bool sources: the VM converts them (truncating toward - // zero, 0/1) before reducing to width, so the native path needs - // the same conversion rather than only accepting integers. - new("float_source", "let x = 3.9 as i32;\nreturn x;\n"), - new("float_source_negative", "let x = (0.0 - 3.9) as i32;\nreturn x;\n"), - // Out of range: Rust's `as` saturates before the width reduction, - // on both sides — the case a trapping conversion would abort on. - new("float_source_saturates", "let x = 1.0e30 as i64;\nreturn x;\n"), - new("bool_source", "let x = true as u8;\nreturn x;\n"), - // A source that came out of a container is boxed, so the native - // path unboxes through `dyn.cast_to_i64` rather than reading a - // register — a different mechanism from the register case above, - // and the one an output loop in a driver actually hits. - new( - "boxed_source_from_list", - "let xs = [300, 255];\nlet out = 0 as u8;\nfor x in xs { out = out + (x as u8); }\nreturn out;\n", - ), - // The boxed path must truncate a Float toward zero and read a Bool - // as 0/1, exactly as the VM's `cast_source_to_i64` does — the two - // cases where an `as_i64`-style unbox would raise instead. - new( - "boxed_source_float", - "let xs = [3.9, 0.0 - 3.9];\nfor x in xs { println(x as i32); }\nreturn 0;\n", - ), - new( - "boxed_source_bool", - "let xs = [true, false];\nlet out = 0 as u8;\nfor x in xs { out = out + (x as u8); }\nreturn out;\n", - ), - ], - NativePath::PureCranelift, + let compile = run_cli(&dir, ["compile", file]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("native compile"); + assert!( + compile.status.success(), + "native compile failed: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let native = Command::new(dir.join("u64render")) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run executable"); + assert_eq!( + String::from_utf8_lossy(&native.stdout), + expected, + "native stderr: {}", + String::from_utf8_lossy(&native.stderr) ); + let _ = fs::remove_dir_all(&dir); } -/// Machine-int *arithmetic* wraps to its width, on both backends. -/// -/// The wrap is emitted as a normalisation after the 64-bit operation, reusing -/// the same cast path — so what this really checks is that every lowering -/// entry point (plain, lower-into-register, compound assignment) applies it. -/// A missing one produces a plainly wrong number rather than a crash, which is -/// why it needs a test rather than an assertion. +/// to re-converge before the module is emitted. They did not: the final pass +/// never wrote `ret_types` back, so `helper` — a native callee whose parameter +/// widens to `Dyn` because its argument is bridge-tainted — kept its +/// pre-marking return type at the call site. Interpolating that result emitted +/// `str.from_i64` on a register pair, which only the Cranelift verifier caught, +/// as an unreadable "Verifier errors". Found by the nightly fresh-seed fuzz +/// (`LK_FUZZ_SEED=30198012768`), reduced here to compile in seconds. #[test] -fn machine_int_arithmetic_wraps_differential() { +fn clif_differential_bridge_taint_reconverges_ret_types() { + let dir = unique_tmp_dir("bridge_taint"); + let _ = fs::remove_dir_all(&dir); + create_dir_all(&dir).expect("create tmp dir"); + let file = "taint.lk"; + let src = "fn bridged(x) { let f = \"v={}\".trim(); println(f, x); return [x, x + 1]; }\n\ + fn helper(a, b) { return (b - 25); }\n\ + let got = bridged(1);\n\ + let picked = helper(2, got[0]);\n\ + println(\"p={}\", picked);\n\ + return 0;\n"; + File::create(dir.join(file)) + .and_then(|mut f| f.write_all(src.as_bytes())) + .expect("write program"); + + let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); + let vm_stdout = String::from_utf8_lossy(&vm.stdout).into_owned(); + + let compile = run_cli(&dir, ["compile", file]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "1") + .output() + .expect("hybrid compile"); + let compile_stderr = String::from_utf8_lossy(&compile.stderr).into_owned(); + assert!(compile.status.success(), "hybrid compile failed: {compile_stderr}"); + assert!( + compile_stderr.contains("Tier 1 hybrid"), + "expected the hybrid link path, got: {compile_stderr}" + ); + + let native = Command::new(dir.join("taint")) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run executable"); + assert_eq!( + vm_stdout, + String::from_utf8_lossy(&native.stdout), + "stdout must match the VM" + ); + assert_eq!(vm.status.success(), native.status.success()); + let _ = fs::remove_dir_all(&dir); +} + +/// compiled through Cranelift with `LK_AOT_HYBRID` on and forced clif-only, the +/// stderr must show the Cranelift hybrid link (not a fallback) and stdout must +/// match the VM — including native/VM print ordering across the bridge. +#[test] +fn clif_differential_hybrid_bridge() { + let dir = unique_tmp_dir("hybrid"); + let _ = fs::remove_dir_all(&dir); + create_dir_all(&dir).expect("create tmp dir"); + let file = "hybrid.lk"; + // `report`/`geti` use `println(fmt, x)` (a bridged shape); `geti`'s result + // flows back through `lk_hybrid_call_r` and feeds native arithmetic. + let src = "fn report(x) { let f = \"acc={}\".trim(); println(f, x); }\n\ + fn geti(x) { let f = \"i={}\".trim(); println(f, x); return x + 1; }\n\ + let acc = 0;\n\ + for i in 0..10 { acc += i; }\n\ + report(acc);\n\ + println(geti(3) + 10);\n\ + println(\"done\");\n\ + return 0;\n"; + File::create(dir.join(file)) + .and_then(|mut f| f.write_all(src.as_bytes())) + .expect("write program"); + + let vm = run_cli(&dir, [file]).env("LK_FORCE_VM", "1").output().expect("vm run"); + let vm_stdout = String::from_utf8_lossy(&vm.stdout).into_owned(); + + let compile = run_cli(&dir, ["compile", file]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "1") + .output() + .expect("hybrid compile"); + let compile_stderr = String::from_utf8_lossy(&compile.stderr).into_owned(); + assert!(compile.status.success(), "hybrid compile failed: {compile_stderr}"); + assert!( + compile_stderr.contains("Tier 1 hybrid"), + "expected the hybrid link path, got: {compile_stderr}" + ); + + let native = Command::new(dir.join("hybrid")) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run hybrid executable"); + assert_eq!( + vm_stdout, + String::from_utf8_lossy(&native.stdout), + "hybrid stdout must match the VM (including native/VM ordering)" + ); + assert_eq!(vm.status.success(), native.status.success()); + let _ = fs::remove_dir_all(&dir); +} + +/// try/catch equivalence, *without* requiring the native path. +/// +/// Renamed off `clif_differential_*` on purpose: `try`/`catch` is now a real +/// statement lowering to `TryBegin`/`TryEnd`, and the MIR lowering has no +/// handler-region support yet, so these cases degrade to the Tier 0 VM bundle. +/// What this test guards is what it always really guarded — that the native +/// artifact behaves exactly like the VM. The property that lapsed (it went +/// *through Cranelift*) is a recorded debt tracked in todos.md and pinned by +/// `AOT_COVERAGE_ALLOW` in check.yml, not something to be silently dropped here. +/// A container a top-level `let` holds and functions mutate. +/// +/// This is the shape that miscompiled, and it did so in the way that is worst: +/// silently. A `List` boxed into a `Dyn` global is *re-represented* — the +/// two are different memory — so `list_h.i64_to_dyn` built a second container, +/// the global held that one, and the entry went on reading the first. Both +/// backends ran, neither complained, and they printed different numbers. +/// +/// A container global keeps its own type now, so the slot holds the handle and +/// there is one list. Where the slot cannot stay typed the lowering refuses, so +/// the program falls back and is still right — which is why these cases are +/// checked against the VM rather than against a lowering outcome. +#[test] +fn global_container_differential() { run_differential( - "machine_int_arith", + "global_container", &[ - // 300 & 0xFF - new("add_u8", "let a: u8 = 200;\nlet b: u8 = 100;\nreturn a + b;\n"), - // 600 & 0xFF - new("mul_u8", "let a: u8 = 200;\nreturn a * (3 as u8);\n"), - // 200 sign-extended from 8 bits + // The reproduction, at its smallest. new( - "add_i8_overflows_negative", - "let a: i8 = 100;\nreturn a + (100 as i8);\n", + "list_pushed_from_a_function", + "let xs: List = [];\nfn add() { xs.push(1); }\nadd();\nadd();\nreturn xs.len();\n", ), - // Borrowing past zero on an unsigned width. - new("sub_u8_underflows", "let a: u8 = 10;\nreturn a - (20 as u8);\n"), - // 70000 & 0xFFFF - new("add_u16", "let a: u16 = 60000;\nreturn a + (10000 as u16);\n"), - // The wrap has to apply at each step, not just the last one. + // Non-empty, which is where the two views were both visible: the + // native build saw the initial element and none of the pushes. new( - "chained_arithmetic_wraps_each_step", - "let a: u8 = 200;\nlet b: u8 = 100;\nlet c = a + b;\nreturn c + b;\n", + "list_starts_non_empty", + "let xs: List = [9];\nfn add() { xs.push(1); }\nadd();\nreturn xs.len();\n", + ), + // No annotation, so the element type comes from the pushes. + new( + "list_without_an_annotation", + "let xs = [];\nfn add() { xs.push(1); }\nadd();\nreturn xs.len();\n", + ), + // A map, which has the same handle-versus-copy question. + new( + "map_written_from_a_function", + "let m: Map = {};\nfn put(k: String) { m[k] = 1; }\nput(\"a\");\nput(\"b\");\nreturn m.len();\n", + ), + // A container the *entry* owns, handed to two sibling functions that + // both pass it on to a third. + // + // Not a global at all, and that plurality is the point. One caller + // passing a container down does not reproduce anything; two callers + // of the same mutator do, because the container's type is settled + // from whichever call the signature fixpoint looked at first. This + // shape printed an empty list — natively, with no fallback and no + // warning — under an attempted lowering change, and it took + // bisecting `examples/syntax/defer.lk` to find it. `defer` had + // nothing to do with it; the example was just the first program + // with two siblings in it. + new( + "two_siblings_share_the_entrys_container", + "fn note(xs: List, n: Int) -> Int { xs.push(n); return n; }\n fn first(xs: List, which: Int) -> Int {\n if (which == 1) { note(xs, 91); return 0 - 1; }\n note(xs, 92);\n return 33;\n}\n fn second(xs: List) -> Int {\n let r = note(xs, 3);\n note(xs, 4);\n return r;\n}\n let xs: List = [];\nfirst(xs, 1);\nsecond(xs);\nreturn xs.len();\n", + ), + // Read back through the function too, so a build where the two + // views are swapped fails as loudly as one where they are split. + new( + "written_and_read_through_the_function", + "let xs: List = [];\nfn add() { xs.push(7); }\nfn total() -> Int {\n let sum = 0;\n for i in 0..xs.len() { sum = sum + (xs[i] as Int); }\n return sum;\n}\nadd();\nadd();\nreturn total();\n", + ), + ], + // Fallback allowed: a slot the lowering cannot keep typed refuses, and + // the answer still has to be the VM's. That is the guarantee — not that + // every one of these lowers. + NativePath::MayDegrade, + ); +} + +/// Machine integers, which is what a driver's arithmetic is made of. +/// +/// There was no differential coverage for these at all, which is a gap worth +/// closing on its own: a `u32` register write has to be exactly 32 bits and has +/// to *wrap* rather than promote, and both backends have to agree about that or +/// a driver computes a different value depending on how it was built. +/// +/// The wrapping cases are the point. `255u8 + 1u8` is `0`, not `256` — the width +/// decides, not the arithmetic — and the same for `u16` at 65535 and for a `u32` +/// at the top of its range. A build that promoted to `Int` somewhere would pass +/// every non-wrapping case here and fail these. +#[test] +fn machine_int_differential() { + run_differential( + "machine_int", + &[ + new( + "bitwise_or", + "let a: u8 = 0x0f;\nlet b: u8 = 0xf0;\nreturn (a | b) as Int;\n", + ), + new( + "bitwise_and", + "let a: u16 = 0xff0f;\nlet b: u16 = 0x0ff0;\nreturn (a & b) as Int;\n", + ), + new( + "shift_right", + "let a: u16 = 0x1234;\nlet b: u16 = 8;\nreturn ((a >> b) & 0xff) as Int;\n", + ), + new( + "shift_left", + "let a: u8 = 0x0f;\nlet b: u8 = 1;\nreturn (a << b) as Int;\n", + ), + // The width decides, not the arithmetic. + new("u8_wraps", "let a: u8 = 255;\nlet b: u8 = 1;\nreturn (a + b) as Int;\n"), + new( + "u16_wraps", + "let a: u16 = 65535;\nlet b: u16 = 1;\nreturn (a + b) as Int;\n", + ), + new( + "u32_wraps", + "let a: u32 = 4294967295;\nlet b: u32 = 2;\nreturn (a + b) as Int;\n", + ), + // Subtraction under zero wraps the same way, which is how a driver + // computing a ring index one short of the base finds out. + new( + "u8_wraps_down", + "let a: u8 = 0;\nlet b: u8 = 1;\nreturn (a - b) as Int;\n", + ), + // Multiplication past the width, which is where a promotion to + // `Int` would be least visible: the low bits are still right. + new( + "u8_multiplies", + "let a: u8 = 200;\nlet b: u8 = 3;\nreturn (a * b) as Int;\n", + ), + // A literal beside a machine integer takes its width, and wraps at + // it. This is the shape driver code is made of — `reg + 1`, + // `count - 1`, `mask << 1` — and it took two halves: the checker + // accepting it, and the compiler normalising the literal to the + // width *before* the operation. With only the first, `255u8 + 1` + // answered 256 while the type said `u8`. + new("literal_wraps_up", "let a: u8 = 255;\nreturn (a + 1) as Int;\n"), + new("literal_wraps_down", "let a: u8 = 0;\nreturn (a - 1) as Int;\n"), + new("literal_on_the_left", "let a: u8 = 255;\nreturn (1 + a) as Int;\n"), + new("literal_multiplies", "let a: u8 = 200;\nreturn (a * 3) as Int;\n"), + new( + "literal_in_a_wider_width", + "let a: u32 = 4294967295;\nreturn (a + 2) as Int;\n", + ), + // `>>` on a `u64` is a *logical* shift, and this is the one width + // where that is not automatic. + // + // Every value rides an `i64` carrier, so for a `u8`, `u16` or `u32` + // the high bits are zero and an arithmetic shift has no sign to + // replicate — it happens to be right. A `u64` fills the carrier: bit + // 63 *is* the sign bit, and `(1u64 << 63) >> 63` answered -1 instead + // of 1, silently, on both backends. That value is a physical + // address, a page-table entry, the high half of a 64-bit BAR. + new( + "u64_shifts_logically", + "let one: u64 = 1;\nlet top = one << 63;\nreturn (top >> 63) as Int;\n", + ), + new( + "u64_shifts_logically_partway", + "let one: u64 = 1;\nlet top = one << 63;\nreturn (top >> 32) as Int;\n", + ), + // The width has to survive the `<<` for the `>>` to know: until it + // did, there was nothing left to consult by the time the second + // shift was lowered. + new( + "width_survives_a_shift", + "let one: u32 = 1;\nlet top = one << 31;\nreturn (top >> 31) as Int;\n", + ), + // `u64` compares and divides unsigned, and this needed the rewrite + // to reach *three* lowering paths: a comparison producing a value, a + // comparison feeding a call argument, and a condition. Each was + // found by a case the previous fix left failing. + new( + "u64_compares_unsigned", + "let one: u64 = 1;\nlet top = one << 63;\nif (top > one) { return 1; }\nreturn 0;\n", + ), + new( + "u64_compares_unsigned_as_a_value", + "let one: u64 = 1;\nlet top = one << 63;\nif (top < one) { return 1; }\nreturn 0;\n", + ), + new( + "u64_divides_unsigned", + "let one: u64 = 1;\nlet top = one << 63;\nlet two: u64 = 2;\nreturn (top / two) as Int;\n", + ), + new( + "u64_halves_to_one", + "let one: u64 = 1;\nlet n = one << 63;\nlet steps = 0;\nwhile (n > one) { n = n / (one + one); steps = steps + 1; }\nreturn steps;\n", + ), + // A literal beside a `u64` joins the *unsigned* operation. + // + // This is where two correct features composed into a wrong answer. + // The checker gives a literal the width of the operand beside it, so + // `top / 2` type-checks as a `u64` division — and the compiler asked + // for two *proven* operands before choosing the unsigned form, which + // a literal never is. It divided signed and answered a negative. + new( + "u64_divides_a_literal_unsigned", + "let one: u64 = 1;\nlet top = one << 63;\nreturn (top / 2) as Int;\n", + ), + new( + "u64_mods_a_literal_unsigned", + "let one: u64 = 1;\nlet top = one << 63;\nreturn (top % 3) as Int;\n", + ), + new( + "u64_compares_a_literal_unsigned", + "let one: u64 = 1;\nlet top = one << 63;\nif (top > 5) { return 1; }\nreturn 0;\n", + ), + // `reg > 0` and `count < 8` are what driver code is made of, at every + // width. + new( + "u32_compares_a_literal", + "let a: u32 = 7;\nif (a > 3) { return 1; }\nreturn 0;\n", + ), + new( + "u8_compares_a_literal", + "let a: u8 = 0;\nif (a > 0) { return 1; }\nreturn 0;\n", + ), + // `u64 as Float` reads the carrier as unsigned. The last conversion + // in this family, and the one whose result does not *look* wrong + // until it is compared with zero. + new( + "u64_converts_to_float_unsigned", + "let one: u64 = 1;\nlet top = one << 63;\nif ((top as Float) > 0.0) { return 1; }\nreturn 0;\n", + ), + new( + "i64_converts_to_float_signed", + "let a = 0 - 8;\nif ((a as Float) < 0.0) { return 1; }\nreturn 0;\n", + ), + // A mask keeps its width, and so does a reassignment. + // + // Both were found by converting a PCI driver to compute a BAR's size + // at the register's own width. `mask = 0xfffffffc;` on a `u32` was + // refused — the literal-takes-the-width rule reached `let`, addition + // and comparison but not assignment — and `probed & mask` came back + // as `Any`, because `&` desugars to a call whose result the checker + // did not type. Every piece around it checked; the whole did not. + // + // The size itself is the two's complement of the probed bits. In + // `Int` that had to be spelled `((0xffffffff - bits) + 1) & 0xffffffff`: + // a subtraction standing in for a complement and a mask standing in + // for the wrap. At the register's width it is a subtraction from + // zero. + new( + "bar_size_at_the_registers_width", + "fn size(probed_raw: Int, io: Int) -> Int {\n \x20 let mask: u32 = 0xfffffff0;\n \x20 if (io == 1) { mask = 0xfffffffc; }\n \x20 let probed = probed_raw as u32;\n \x20 let bits = probed & mask;\n \x20 if (bits == 0) { return 0; }\n \x20 let zero: u32 = 0;\n \x20 return (zero - bits) as Int;\n }\n return size(0xfff00000, 0) + size(0xffffff00, 0);\n", + ), + new( + "bitwise_keeps_the_width", + "let a: u32 = 0xf0f0f0f0;\nlet b = a & 0xffff;\nlet c = b | 0x10000;\nreturn (c + 1) as Int;\n", + ), + // `~x` on a machine integer is *that width's* complement. + // + // Every value rides an `i64` carrier, so complementing a `u32` set + // the 32 bits above it too: `~(0xff as u32)` answered + // `0xFFFFFFFFFFFFFF00`, which reads back as -256. It went unnoticed + // because the shape people write is `a & ~b`, where the `&` masks + // the strays away — and the one that does not, `~mask` on its own, + // is exactly what a driver writes to clear a field. + new( + "complement_wraps_to_the_width", + "let a: u8 = 0x0f;\nreturn (~a) as Int;\n", + ), + new("complement_u32", "let a: u32 = 0xff;\nreturn (~a) as Int;\n"), + new( + "complement_clears_a_bit", + "let flags: u32 = 0xff;\nlet bit: u32 = 0x80;\nreturn (flags & ~bit) as Int;\n", + ), + // `~x` on a machine integer is *that width's* complement. + // + // Every value rides an `i64` carrier, so complementing a `u32` set + // the 32 bits above it too: `~(0xff as u32)` answered + // `0xFFFFFFFFFFFFFF00`, which reads back as -256. It stayed + // unnoticed because the shape people write is `a & ~b`, where the + // `&` masks the strays away — and the one that does not, `~mask` on + // its own, is exactly what a driver writes to clear a field. + new( + "complement_wraps_to_the_width", + "let a: u8 = 0x0f;\nreturn (~a) as Int;\n", + ), + new("complement_u32", "let a: u32 = 0xff;\nreturn (~a) as Int;\n"), + new( + "complement_clears_a_bit", + "let flags: u32 = 0xff;\nlet bit: u32 = 0x80;\nreturn (flags & ~bit) as Int;\n", + ), + new("complement_u64", "let one: u64 = 1;\nreturn ((~one) >> 32) as Int;\n"), + // A signed comparison is still signed, which is the property the + // change must not have taken away. + new( + "i64_compares_signed", + "let a = 0 - 1;\nif (a < 1) { return 1; }\nreturn 0;\n", + ), + // And a signed shift is still arithmetic, which is the property the + // change must not have taken away. + new( + "i8_shifts_arithmetically", + "let a: i8 = 0 - 128;\nlet s: i8 = 7;\nreturn (a >> s) as Int;\n", + ), + // And through a function, so the width survives a call boundary — + // the shape every driver helper has. + new( + "width_survives_a_call", + "fn combine(hi: u16, lo: u16) -> u16 { return (hi << 8) | lo; }\n let a: u16 = 0x12;\nlet b: u16 = 0x34;\nreturn combine(a, b) as Int;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `s.byte_at(i)` — the one string read that allocates nothing. +/// +/// It exists for freestanding code: `char_at` next to it answers a *string* of +/// one character, which means an allocation, which means it cannot be used from +/// an interrupt handler or before there is a heap. What is pinned here is that +/// the native path answers what the VM answers, including at the two edges +/// where "out of range" has to be a value rather than a raise — a kernel's +/// print loop is bounded by `len`, and a raise per character would be a cost +/// paid on every message that is not a mistake. +#[test] +fn string_byte_at_differential() { + run_differential( + "string_byte_at", + &[ + new( + "sum_of_bytes", + "let s = \"net: ok\";\nlet sum = 0;\nfor i in 0..s.len() { sum = sum + s.byte_at(i); }\nreturn sum;\n", + ), + new("first_byte", "let s = \"net\";\nreturn s.byte_at(0);\n"), + new("last_byte", "let s = \"net\";\nreturn s.byte_at(s.len() - 1);\n"), + // Both edges answer -1 rather than raising, and both have to answer + // the *same* -1 on both backends. + new("past_the_end", "let s = \"net\";\nreturn s.byte_at(99);\n"), + new("before_the_start", "let s = \"net\";\nreturn s.byte_at(0 - 1);\n"), + new("empty_string", "let s = \"\";\nreturn s.byte_at(0);\n"), + // A byte past ASCII: the answer is a byte, not a character, so a + // two-byte character is two answers. + new( + "multibyte_is_bytes", + "let s = \"é\";\nreturn s.len() * 1000 + s.byte_at(0);\n", ), - // Division keeps the width rather than promoting to Float the way - // `Int / Int` does. - new("div_keeps_width", "let a: u8 = 200;\nreturn a / (3 as u8);\n"), ], NativePath::PureCranelift, ); } -/// A volatile read must survive optimisation. +#[test] +fn try_catch_differential() { + run_differential( + "try_catch", + &[ + // The shapes that used to reject, one per fix. Each lowers now, so + // what these check is the part lowering cannot: that the answer is + // the VM's. Two of the answers tried along the way *compiled* and + // computed something else — see `aot/lower/src/try_region.rs`. + // + // A container the body mutates through a handle it shares with the + // parent. It needs no cell: the mutation is already visible. Giving + // it one — which reading `ListPush a=log` as a write to `log` did — + // sent it round `dyn.from_list` / `dyn.as_list`, which is what lost + // the push. + new( + "body_mutates_outer_list", + "let log = [1];\ntry {\n log.push(2);\n log.push(3);\n} catch e {\n log.push(9);\n}\nreturn log.len();\n", + ), + // The same, with a raise partway: the pushes before it must be + // visible, and the handler's must land on the same list. + new( + "body_mutates_then_raises", + "fn boom() { error(\"x\"); return 0; }\nlet log = [1];\ntry {\n log.push(2);\n boom();\n log.push(3);\n} catch e {\n log.push(9);\n}\nreturn log.len();\n", + ), + // A `Bool` parameter in scope. It is 0/1 — a machine word — but was + // missing from the trampoline's word list, so the body got it as + // `I64` and rejected on reading it: a `try` inside *any* function + // taking a bool dropped its module to the VM, while the same + // function with an `Int` parameter lowered. + new( + "body_reads_bool_param", + "fn probe(c: Bool) -> Int {\n let r = try { if c { error(\"boom\"); } 7 } catch e { -1 };\n return r;\n}\nprintln(probe(false));\nprintln(probe(true));\nreturn 0;\n", + ), + // The `try`-as-expression shape that was on file as unlowerable: + // a call, then a value-producing region, then both used. + new( + "value_region_after_a_call", + "fn compute() -> Int { return 10; }\nfn probe(c: Bool) -> Int {\n let q = compute();\n let r = try { if c { error(\"boom\"); } 7 } catch e { -1 };\n return q + r;\n}\nprintln(probe(false));\nprintln(probe(true));\nreturn 0;\n", + ), + // A `Float` parameter. The trampoline's signature is all + // `long long`, so a float arrives in an *integer* register and the + // body reads it back out of those bits (`Inst::BitsToFloat`). + // Declaring the parameter `F64` instead — which is what "a float is + // eight bytes, so it crosses" gets you — compiled and *segfaulted*. + // + // The arithmetic is what pins the bit-cast's direction: a wrong one + // still runs and answers something. `1.5 * 2.0 + 0.5` is `3.5`, not + // a denormal. + new( + "body_reads_float_param", + "fn probe(f: Float, g: Float) -> Float {\n let r = try { if f > 100.0 { error(\"boom\"); } f * g + 0.5 } catch e { -1.5 };\n return r;\n}\nprintln(probe(1.5, 2.0));\nprintln(probe(0.25, 8.0));\nprintln(probe(1000.0, 1.0));\nprintln(probe(-3.5, 2.0));\nreturn 0;\n", + ), + // The same with a mixed parameter list, so the float is not the only + // input crossing. + new( + "body_reads_mixed_params", + "fn probe(f: Float, s: String, xs: List) -> Int {\n let r = try { if f > 1.0 { error(\"boom\"); } xs.len() } catch e { -1 };\n return r + s.len();\n}\nprintln(probe(0.5, \"ab\", [1, 2, 3]));\nprintln(probe(2.0, \"ab\", [1, 2, 3]));\nreturn 0;\n", + ), + // A container the body only *reads*. It travels in as a parameter, + // which needs the trampoline's argument buffer to carry a handle — + // a pointer is a machine word, and declaring every input `I64` + // rejected this on its first instruction. + new( + "body_reads_outer_list", + "let xs = [4, 5, 6];\nlet n = 0;\ntry {\n n = xs.len();\n} catch e {\n n = -1;\n}\nreturn n;\n", + ), + // A value that comes back out of its cell already boxed: nothing to + // unbox, and nothing to reinterpret. + new( + "body_assigns_dyn_then_raises", + "fn boom() { error(\"x\"); return 0; }\nfn pick(f) { if (f) { return 1; } return \"s\"; }\nlet v = pick(true);\ntry {\n v = pick(false);\n boom();\n} catch e {\n v = pick(true);\n}\nreturn typeof(v);\n", + ), + // Two regions in one function, the second's body writing a register + // the first's call window used. The parent's own writes and another + // region's body writes are not the same thing. + new( + "two_regions_sharing_a_temporary", + "fn add(a: Int, b: Int) -> Int { return a + b; }\nlet ok = 0;\ntry {\n ok = add(2, 3);\n} catch e {\n ok = -1;\n}\nlet mid = ok;\ntry {\n ok = add(mid, 1);\n} catch e {\n ok = -2;\n}\nreturn ok;\n", + ), + // A raise crossing the protected region: the success path runs the + // body, the failure path binds the raised value. + new( + "catch_raise", + "let out = 0;\ntry {\n error(\"boom\");\n out = 1;\n} catch e {\n out = 2;\n}\nreturn out;\n", + ), + new( + "catch_skipped", + "let out = 0;\ntry {\n out = 5;\n} catch e {\n out = 9;\n}\nreturn out;\n", + ), + new( + "catch_with_arg", + // `r` is annotated and the literals match `/`'s Float result: a + // `try` body is now type-checked like any other statement (it used + // to sit inside a closure the checker did not look into), and + // `let r = 0; r = div(10, 0);` is a static type error. The path + // under test — a raise from a nested call, caught, value bound — + // is unchanged. + "fn div(a: Int, b: Int) -> Float {\n if (b == 0) { error(\"zero\"); }\n return a / b;\n}\nlet r: Float = 0.0;\ntry {\n r = div(10, 0);\n} catch e {\n r = -1.0;\n}\nreturn r;\n", + ), + // A raised channel error must not leave any lock held across the + // longjmp: after catching, the registry and channel stay usable + // (regression: `channel()` raised "Channel not found" while the + // registry MutexGuard was live, deadlocking every later op). + new( + "chan_unknown_id_catch_then_use", + // The bad id goes through an `Any` binding: a `try` body is now + // type-checked like any other statement (it used to sit inside a + // closure the checker did not look into), and `recv(999)` is a + // static type error. The runtime path under test — an unknown + // channel id raising, caught, and the channel machinery still + // usable afterwards — is unchanged. + "let bad: Any = 999;\ntry { recv(bad); } catch e { println(\"caught\"); }\nlet c = chan(1);\nsend(c, 41);\nprintln(recv(c) + 1);\nreturn 0;\n", + ), + // Same discipline on the closed-send raise inside select's arm. + new( + "select_closed_send_catch_then_use", + "use chan as ch;\nlet c = chan(1);\nch.close(c);\ntry {\n let x = select {\n case send(c, 1) => \"sent\";\n };\n println(x);\n} catch e { println(\"caught\"); }\nlet d = chan(1);\nsend(d, 6);\nprintln(recv(d) * 7);\nreturn 0;\n", + ), + ], + NativePath::MayDegrade, + ); +} + +/// `xs.clear()` on every list carrier, pinned to pure Cranelift. +/// +/// One of the four mutating list methods that lowered for *no* carrier at all +/// (`pop` / `insert` / `remove_at` are the others). `clear` goes first because +/// it does not look at the element type — so it lands as one macro over all four +/// carriers rather than as four functions, three of which would have been +/// forgotten. That is the shape this file keeps recording: an operation whose +/// carriers were filled in one at a time and then not finished. +/// +/// It answers the receiver, which is what the VM's `clear` returns — the same +/// handle, now empty. Pinning the *handle* matters: a copy would print the same +/// thing and leave the original untouched. +#[test] +fn clear_covers_every_list_carrier() { + run_differential( + "list_clear", + &[ + new( + "each_carrier", + "println([1, 2, 3].clear());\nprintln([1.5, 2.5].clear());\nprintln([\"x\", \"y\"].clear());\nprintln([1, \"s\"].clear());\nprintln([].clear());\nreturn 0;\n", + ), + // The receiver is the same list, so the binding sees it emptied. + new( + "clears_in_place", + "let a = [1, 2, 3];\nlet same = a.clear();\nprintln(a);\nprintln(a.len());\nprintln(same);\nprintln(a.is_empty());\nreturn 0;\n", + ), + new( + "clear_then_reuse", + "let a = [1, 2];\na.clear();\na.push(9);\nprintln(a);\nprintln(a.len());\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `reverse` and `index_of` on every list carrier, pinned to pure Cranelift. +/// +/// Both existed for `Int` alone, so `[1.5, 2.5].reverse()` dropped its whole +/// module to the VM — the right answer, three times slower, which is the gap +/// neither the differential corpus nor the coverage gate can see on its own. +/// +/// `index_of` takes exactly the needle its `contains` takes on the same carrier, +/// because in the VM both answer through one `typed_list_position`. That is what +/// the `2` against a float list checks from both sides: `[1.0, 2.0]` finds it +/// (an `Int` needle coerces) and `[1.5, 2.5]` does not (2 is not 2.5) — a +/// lowering that skipped the coercion would answer nil for the first. +#[test] +fn reverse_and_index_of_cover_every_list_carrier() { + run_differential( + "list_reverse_index_of", + &[ + new( + "reverse_each_carrier", + "println([1, 2, 3].reverse());\nprintln([1.5, 2.5, 3.5].reverse());\n\ + println([\"a\", \"b\", \"c\"].reverse());\nprintln([1, \"s\", 2.5].reverse());\n\ + println([].reverse());\nreturn 0;\n", + ), + // Non-mutating: the receiver still reads in its original order. + new( + "reverse_leaves_the_receiver", + "let a = [1.5, 2.5];\nlet b = a.reverse();\nprintln(a);\nprintln(b);\n\ + let s = [\"x\", \"y\"];\nprintln(s.reverse());\nprintln(s);\nreturn 0;\n", + ), + new( + "index_of_each_carrier", + "println([1, 2, 3].index_of(2));\nprintln([1, 2, 3].index_of(9));\n\ + println([1.5, 2.5].index_of(2.5));\nprintln([\"a\", \"bb\"].index_of(\"bb\"));\n\ + println([\"a\", \"bb\"].index_of(\"zz\"));\nprintln([1, \"s\", 2.5].index_of(\"s\"));\n\ + println([1, \"s\", 2.5].index_of(2.5));\nprintln([1, \"s\"].index_of(9));\n\ + println([].index_of(1));\nreturn 0;\n", + ), + new( + "index_of_needle_coercion", + "println([1.0, 2.0].index_of(2));\nprintln([1.5, 2.5].index_of(2));\n\ + println([1.0, 2.0].contains(2));\nprintln([1.5, 2.5].contains(2));\nreturn 0;\n", + ), + // `index_of` answers `Int?`, so its result has to survive the things + // a nullable does: a comparison, `!`, and `??`. + new( + "index_of_result_is_nullable", + "let i = [\"a\", \"bb\"].index_of(\"bb\");\nprintln(i == 1);\nprintln(i!);\n\ + let miss = [1.5].index_of(9.5);\nprintln(miss == nil);\nprintln(miss ?? -1);\nreturn 0;\n", + ), + new( + "take_and_skip_each_carrier", + "println([1, 2, 3, 4].take(2));\nprintln([1.5, 2.5, 3.5].take(2));\n\ + println([\"a\", \"b\", \"c\"].take(2));\nprintln([1, \"s\", 2.5].take(2));\n\ + println([1, 2, 3, 4].skip(2));\nprintln([1.5, 2.5, 3.5].skip(2));\n\ + println([\"a\", \"b\", \"c\"].skip(2));\nprintln([1, \"s\", 2.5].skip(2));\nreturn 0;\n", + ), + // A count past the end clamps; a negative one raises, and the message + // is stdout here because `catch` renders it. + new( + "take_and_skip_edges", + "println([1.5].take(0));\nprintln([1.5].take(99));\nprintln([1.5].skip(99));\n\ + println([].take(1));\n\ + println(try { \"${[1.5, 2.5].take(-1)}\" } catch e { \"caught: ${e}\" });\n\ + println(try { \"${[\"a\"].skip(-2)}\" } catch e { \"caught: ${e}\" });\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `1 + f(x)` evaluates `f(x)` **once**. +/// +/// The immediate form of an int binary op wants the constant on the right, so +/// `const + expr` lowered `expr` first to ask whether its value is a proven +/// `Int`. When the answer was no — which it is for any call without an annotated +/// return type — the code fell through and lowered `expr` *again*, leaving the +/// first lowering's instructions in the stream. So the operand ran twice: `1 + +/// side(7)` called `side` twice, and `return 1 + f(n - 1)` cost 2^n calls — +/// `f(5)` made 63 instead of 6, `f(50)` never finished, and `f(20000)` was a +/// hang where the VM's own depth limit is 100000. +/// +/// It survived because the *answer* stays right for a pure function. Only a +/// counted side effect shows it, which is what these cases count. Both copies of +/// the lowering had it (`lower_bin_op` and `lower_into`), so both are exercised +/// here: `let a = …` goes through one and `println(…)`/`return` through the other. +#[test] +fn a_constant_on_the_left_evaluates_the_other_side_once() { + run_differential( + "commuted_immediate", + &[ + new( + "each_operator_shape", + "let log = [];\nfn side(x) { log.push(x); return x; }\n\ + let a = 1 + side(1);\nlet b = side(2) + 1;\nlet c = 2 * side(3);\n\ + let d = 10 - side(4);\nlet e = 1 + side(5) + 1;\n\ + println(\"${a} ${b} ${c} ${d} ${e}\");\nprintln(log);\nreturn 0;\n", + ), + // The count, not the answer: the answer was always right. + new( + "the_recursive_call_count", + "let calls = 0;\nfn f(n) { calls = calls + 1; if (n <= 0) { return 0; } return 1 + f(n - 1); }\n\ + println(f(5));\nprintln(calls);\nreturn 0;\n", + ), + // A depth the old lowering could not reach. Deliberately *not* the + // f(50) that first showed the bug, and deliberately not an f(1000) + // beside it either: 2^n calls is a hang, and a guard that hangs CI + // instead of failing it is the mute failure mode this whole file + // exists to avoid. 18 is the largest depth whose broken cost (262143 + // calls) is still finite, so this fails fast rather than never — and + // the fixed version reaches the VM's own 100000-frame limit happily, + // which `f(20000)` was checked against by hand. + new( + "a_depth_the_doubling_could_not_reach", + "fn f(n) { if (n <= 0) { return 0; } return 1 + f(n - 1); }\nprintln(f(18));\nreturn 0;\n", + ), + // Through the other lowering: a statement-position expression and an + // argument, neither of which goes through `lower_bin_op`'s `let`. + new( + "the_other_lowering", + "let log = [];\nfn side(x) { log.push(x); return x; }\n\ + println(1 + side(9));\nprintln(log.len());\n\ + fn wrap(v) { return v; }\nprintln(wrap(2 * side(9)));\nprintln(log.len());\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `sort` on the `Float` and `String` carriers — and the NaN that made writing +/// it find a panic on *both* backends. +/// +/// `sort` existed for `Int` alone. The obvious extension is wrong in a way that +/// does not show on ordinary data: the VM's float comparator was +/// `partial_cmp(..).unwrap_or(Equal)`, which is not a total order once a NaN is +/// present (the NaN reads equal to every value while those values stay ordered), +/// and Rust's `sort_by` detects that and panics. So `[NaN, …].sort()` aborted the +/// interpreter — a Rust panic, so `try` could not catch it — and whether it fired +/// depended on the data: 601 elements went through, 60 did not. +/// +/// Both sides now order the NaN (`val::compare_floats`, mirrored by lkrt's +/// `compare_floats`): all NaNs equal, every NaN above every number, `-0.0` and +/// `0.0` still equal because `==` says so. The boxed carrier has no `sort` +/// lowering on purpose — its order spans kinds, which is a mirror that wants its +/// own conformance test. +#[test] +fn sort_covers_the_float_and_string_carriers_including_nan() { + let nan_list = (0..60) + .map(|i| { + if i % 4 == 0 { + "nan".to_string() + } else { + format!("{}.5", 60 - i) + } + }) + .collect::>() + .join(", "); + run_differential( + "list_sort", + &[ + new( + "sort_each_carrier", + "println([3, 1, 2].sort());\nprintln([3.5, 1.5, 2.5].sort());\n\ + println([\"b\", \"a\", \"C\", \"aa\", \"\"].sort());\n\ + println([].sort());\nprintln([1.5].sort());\nreturn 0;\n", + ), + // Non-mutating, like `reverse`. + new( + "sort_leaves_the_receiver", + "let a = [2.5, 1.5];\nprintln(a.sort());\nprintln(a);\nreturn 0;\n", + ), + // Byte order, so uppercase sorts before lowercase and a multi-byte + // character sorts by its UTF-8 bytes. + new( + "string_order_is_by_bytes", + "println([\"é\", \"e\", \"z\", \"Z\"].sort());\nprintln([\"ab\", \"a\", \"b\"].sort());\nreturn 0;\n", + ), + new( + "negative_zero_stays_equal_to_zero", + "println([-0.0, 0.0, -1.5].sort());\nprintln([0.0, -0.0].sort());\n\ + println(-0.0 == 0.0);\nreturn 0;\n", + ), + generated( + "a_nan_no_longer_aborts_either_backend", + format!( + "let z = 0.0;\nlet nan = z / z;\nlet xs = [{nan_list}];\n\ + let sorted = xs.sort();\nprintln(sorted.len());\nprintln(sorted);\nreturn 0;\n" + ), + ), + new( + "nan_sorts_above_every_number", + "let z = 0.0;\nlet nan = z / z;\n\ + println([nan, 1.0].sort());\nprintln([1.0, nan].sort());\nprintln([nan, nan].sort());\n\ + println([nan, 1.0, -1.0].sort());\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `pop` / `insert` / `remove_at` on every list carrier, plus `first` / `last` +/// on the boxed one. Pinned to pure Cranelift. +/// +/// None of the three mutators had a lowering on *any* carrier, so a single +/// `xs.pop()` anywhere in a program dropped the whole module to the VM — and +/// `first`/`last` covered three carriers and left the boxed one out. +/// +/// The interesting parts are the edges, not the happy path: an empty `pop` is +/// nil rather than a raise, `insert` accepts `len` (that is where an append +/// goes) while `remove_at` does not, a negative index counts from the end for +/// both, and each of the four range failures has its own wording — which a +/// `catch` turns into stdout, so the text is part of the answer. +/// +/// The `Int` inserted into a `Float` list is here because the two backends +/// disagree about the *representation* underneath: the VM rebuilds the list as +/// mixed and stores an `Int`, the lowering coerces to `f64` and keeps the +/// carrier. That is unobservable — the static type is `Float` either way, the +/// display agrees, and `/` is float division regardless — and `push`/`set` +/// already made the same choice. Pinned so it stays unobservable. +#[test] +fn pop_insert_and_remove_at_cover_every_list_carrier() { + run_differential( + "list_mutators", + &[ + new( + "pop_each_carrier", + "let a = [1, 2, 3];\nprintln(\"${a.pop()} ${a}\");\n\ + let b = [1.5, 2.5];\nprintln(\"${b.pop()} ${b}\");\n\ + let c = [\"x\", \"y\"];\nprintln(\"${c.pop()} ${c}\");\n\ + let d = [1, \"s\"];\nprintln(\"${d.pop()} ${d}\");\n\ + println([].pop());\nreturn 0;\n", + ), + new( + "insert_each_carrier", + "let a = [1, 2, 3];\na.insert(1, 9);\nprintln(a);\n\ + let b = [1.5];\nb.insert(0, 0.5);\nprintln(b);\n\ + let c = [\"b\"];\nc.insert(0, \"a\");\nprintln(c);\n\ + let d = [1, \"s\"];\nd.insert(1, 2.5);\nprintln(d);\n\ + let e = [1, 2];\ne.insert(2, 9);\nprintln(e);\n\ + let f = [1, 2];\nf.insert(-1, 9);\nprintln(f);\nreturn 0;\n", + ), + new( + "remove_at_each_carrier", + "let a = [1, 2, 3];\nprintln(\"${a.remove_at(1)} ${a}\");\n\ + let b = [1.5, 2.5];\nprintln(\"${b.remove_at(-1)} ${b}\");\n\ + let c = [\"a\", \"b\"];\nprintln(\"${c.remove_at(0)} ${c}\");\n\ + let d = [1, \"s\"];\nprintln(\"${d.remove_at(0)} ${d}\");\nreturn 0;\n", + ), + new( + "first_and_last_including_the_boxed_carrier", + "println([1, 2, 3].first());\nprintln([1, 2, 3].last());\n\ + println([1.5, 2.5].first());\nprintln([\"a\", \"b\"].last());\n\ + println([1, \"s\"].first());\nprintln([1, \"s\"].last());\n\ + println([].first());\nprintln([].last());\nreturn 0;\n", + ), + new( + "mutator_index_edges", + "println(try { \"${[1, 2].insert(-9, 5)}\" } catch e { \"${e}\" });\n\ + println(try { \"${[1, 2].insert(3, 5)}\" } catch e { \"${e}\" });\n\ + println(try { \"${[1.5].remove_at(-9)}\" } catch e { \"${e}\" });\n\ + println(try { \"${[\"a\"].remove_at(2)}\" } catch e { \"${e}\" });\n\ + println(try { \"${[].remove_at(0)}\" } catch e { \"${e}\" });\nreturn 0;\n", + ), + new( + "an_int_into_a_float_list", + "let a = [1.5, 2.5];\na.insert(0, 2);\nprintln(a);\nprintln(a[0]);\n\ + println(a[0] / 4);\nprintln(a[0] == 2);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Container literals past the instruction's operand ceiling. +/// +/// `NewList` names its element window as (u8 base, u8 len) and `NewMap` names +/// twice as many registers, so the compiler refused a literal over 255 elements +/// or 127 entries — with a compiler-internal message, and *only* when constant +/// folding did not apply. An all-literal `[0, …, 399]` became a heap constant +/// and compiled; changing one element to a variable made the same list a +/// compile error. 255 was the operand width, never a rule about lists. +/// +/// Long literals now build empty and push the tail one element at a time +/// through a scratch register that is handed straight back — holding all of +/// them at once hits the same ceiling from the register side, which is what the +/// first attempt here did (`dst` landed at 256). +/// +/// Pinned against the VM because the interesting parts are not the length: the +/// boundary elements either side of 255, left-to-right evaluation order of +/// element expressions that have side effects, element type widening across the +/// boundary, and a duplicate map key still resolving last-wins when the two +/// writes take different routes. +#[test] +fn long_container_literals_lower_and_agree() { + let elements = (0..399).map(|i| (i * 2).to_string()).collect::>().join(", "); + let entries = (0..200) + .map(|i| format!("\"k{i}\": {}", i * 3)) + .collect::>() + .join(", "); + let duplicates = (0..130) + .map(|i| format!("\"d{i}\": {i}")) + .collect::>() + .join(", "); + let mixed = (0..300) + .map(|i| if i % 2 == 0 { i.to_string() } else { format!("\"{i}\"") }) + .collect::>() + .join(", "); + let tapped = (0..300).map(|i| format!("tap({i})")).collect::>().join(", "); + run_differential( + "long_literals", + &[ + generated( + "list_past_the_window", + format!( + "let x = 7;\nlet a = [{elements}, x];\n\ + println(\"${{a.len()}} ${{a[0]}} ${{a[254]}} ${{a[255]}} ${{a[256]}} ${{a[398]}} ${{a[399]}}\");\nreturn 0;\n" + ), + ), + generated( + "map_past_the_window", + format!( + "let x = 7;\nlet m = {{{entries}, \"kx\": x}};\n\ + println(\"${{m.len()}} ${{m[\"k0\"]}} ${{m[\"k126\"]}} ${{m[\"k127\"]}} ${{m[\"k199\"]}} ${{m[\"kx\"]}}\");\nreturn 0;\n" + ), + ), + // The last write wins whether it lands inside `NewMap` or in a + // `SetIndex` after it. + generated( + "duplicate_key_past_the_window", + format!( + "let d = {{{duplicates}, \"d0\": 999}};\nprintln(\"${{d.len()}} ${{d[\"d0\"]}}\");\nreturn 0;\n" + ), + ), + // A list whose elements stop being one type across the boundary. + generated( + "mixed_elements_past_the_window", + format!( + "let s = \"z\";\nlet m = [{mixed}, s];\n\ + println(\"${{m.len()}} ${{m[0]}} ${{m[1]}} ${{m[299]}} ${{m[300]}}\");\nreturn 0;\n" + ), + ), + // Element expressions are evaluated left to right, and the tail is + // no exception. + generated( + "evaluation_order_past_the_window", + format!( + "let log = [];\nfn tap(v) {{ log.push(v); return v; }}\nlet b = [{tapped}];\n\ + println(\"${{b.len()}} ${{log.len()}} ${{log[0]}} ${{log[299]}} ${{b[299]}}\");\nreturn 0;\n" + ), + ), + ], + NativePath::PureCranelift, + ); +} + +/// `slice` and `contains` on the non-`Int` list carriers, pinned to pure +/// Cranelift. +/// +/// A list-method sweep (6 list shapes x 34 methods) found no wrong answers and +/// 62 fallbacks, and the fallbacks were lopsided: `Int` lists lacked 5 methods, +/// `Float` 18 and `String` 15. Two of those were pure dispatch-table gaps — +/// `list_h.{f64,str,dyn}_slice_from` and `{f64,dyn}_contains` had been in the +/// ABI all along, and only the `i64` arm was written. The rest need runtime +/// helpers that do not exist yet. +/// +/// `i64` slices to a *window*; these slice to a fresh list. Both are what +/// `slice` means — the window is an optimisation the other carriers lack, not a +/// different answer, which is what pinning them against the VM checks. +#[test] +fn slice_and_contains_cover_the_other_carriers() { + run_differential( + "list_carrier_methods", + &[ + new( + "slice_from_on_each_carrier", + "println([1.5, 2.5, 3.5].slice(1));\nprintln([\"a\", \"b\", \"c\"].slice(1));\nprintln([1, \"b\", 2.5].slice(1));\nreturn 0;\n", + ), + new( + "slice_edges", + "println([1.5, 2.5].slice(0));\nprintln([1.5, 2.5].slice(9));\nprintln([\"a\"].slice(1));\nreturn 0;\n", + ), + new( + "contains_on_float_and_dyn", + "println([1.5, 2.5].contains(2.5));\nprintln([1.5, 2.5].contains(9.5));\nprintln([1, \"b\"].contains(1));\nprintln([1, \"b\"].contains(\"b\"));\nprintln([1, \"b\"].contains(7));\nreturn 0;\n", + ), + // An Int needle against a Float list coerces, as `==` does. + new( + "contains_coerces_numbers", + "println([1.0, 2.0].contains(2));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `xs.chain(ys)` over every list pairing, pinned to pure Cranelift. +/// +/// `chain` is `+` spelled as a method, and the operator path has always covered +/// every pairing: same-typed keeps its carrier, cross-typed chains boxed. The +/// method path had exactly one arm, `List` twice — so `line.chain([byte])` +/// with a boxed element did not lower. One operation, two implementations, and +/// only one of them complete. +/// +/// The x86 bare-metal kernel is where that showed up: eleven of its eighteen +/// native-lowering blockers were this one method. +#[test] +fn chain_covers_every_list_pairing() { + run_differential( + "list_chain_method", + &[ + // The shape from the kernel: a typed list chained with a one-element + // list whose element is boxed. + new( + "typed_receiver_boxed_argument", + "let xs = [1, \"s\"];\nlet a = [1, 2];\nprintln(a.chain([xs[0]!]));\nreturn 0;\n", + ), + new( + "same_typed_pairings", + "println([1, 2].chain([3]));\nprintln([1.5].chain([2.5]));\nprintln([\"a\"].chain([\"b\"]));\nreturn 0;\n", + ), + // Repeated chaining in a loop, which is how the kernel builds a line. + new( + "chained_in_a_loop", + "let line = [0];\nlet i = 0;\nwhile i < 6 {\n line = line.chain([i * 2]);\n i = i + 1;\n}\nprintln(line);\nprintln(line.len());\nreturn 0;\n", + ), + new( + "empty_operands", + "println([1].chain([]));\nprintln([].chain([1]));\nprintln([].chain([]));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Integer and float edge values, pinned to pure Cranelift. +/// +/// `i64::MIN % -1` **panicked the interpreter**. Integer division overflow is a +/// panic in Rust — in release too, because the hardware traps — so `%` on those +/// operands aborted the process, which no `try` can catch and no differential +/// can compare: the VM side produces no output to diff against. The native side +/// answered `0`. Both `%` (three sites) and `math.floor`'s integer division now +/// wrap, which is what the rest of the language's integer arithmetic already +/// did and what the native side already computed. +#[test] +fn integer_and_float_edges_agree() { + run_differential( + "numeric_edges", + &[ + // The crash, and its floor-division sibling. + new( + "division_overflow_wraps", + "let mn = -9223372036854775808;\nlet d = -1;\nprintln(mn % d);\nuse math;\nprintln(math.floor(mn / d));\nprintln(mn / d);\nreturn 0;\n", + ), + new( + "int_wrapping", + "let mx = 9223372036854775807;\nlet mn = -9223372036854775808;\nprintln(mx + 1);\nprintln(mn - 1);\nprintln(mx * 2);\nprintln(-mn);\nprintln(0 - mn);\nreturn 0;\n", + ), + new( + "signed_remainder_and_floor", + "use math;\nprintln(7 % -3);\nprintln(-7 % 3);\nprintln(math.floor(7 / -3));\nprintln(math.floor(-7 / 3));\nreturn 0;\n", + ), + // Zero, signed zero, the infinities and NaN — including that NaN is + // not equal to itself and that both zeroes compare equal. + new( + "float_specials", + "let z = 0.0;\nlet nz = -0.0;\nprintln(z == nz);\nprintln(1.0 / z);\nprintln(-1.0 / z);\nprintln(z / z);\nprintln(z / z == z / z);\nprintln(\"${z} ${nz} ${1.0 / z} ${z / z}\");\nprintln(1e300 * 1e300);\nreturn 0;\n", + ), + // A float past the integer range, and NaN, cast to Int. + new( + "float_to_int_casts", + "let big = 1e19;\nlet nan = 0.0 / 0.0;\nprintln(big as Int);\nprintln(-big as Int);\nprintln(nan as Int);\nprintln(9223372036854775807 as Float);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Struct update syntax with a typed-map overlay, pinned to pure Cranelift. +/// +/// The sixth instance of one mistake: `to_dyn_map_handle` converted a typed map +/// to the boxed carrier and claimed "iteration order is preserved — the rebuild +/// replays the source order". Re-inserting a table's entries into a fresh table +/// in its *iteration* order is a different insertion sequence from the one that +/// built it, so the copy need not iterate the same way. +/// +/// `P { ..base, x: 42 }` reaches it: the overlay is the `{x: 42}` field literal, +/// a typed map, and the overlay's order is the tail of the merged result's. The +/// overlay is now walked where it lives — nothing is copied, so there is no +/// order to lose. +#[test] +fn a_struct_update_keeps_the_field_order() { + run_differential( + "struct_update_order", + &[ + new( + "int_overlay", + "struct P { a: Int, b: Int, c: Int }\nlet p = P { a: 1, b: 2, c: 3 };\nlet q = P { ..p, b: 9 };\nprintln(q);\nprintln(q.b);\nreturn 0;\n", + ), + // A wide struct, so the field maps grow past one table size and the + // insertion sequence actually matters. + new( + "many_fields", + "struct W { f0: Int, f1: Int, f2: Int, f3: Int, f4: Int, f5: Int, f6: Int, f7: Int, f8: Int, f9: Int }\nlet w = W { f0: 0, f1: 1, f2: 2, f3: 3, f4: 4, f5: 5, f6: 6, f7: 7, f8: 8, f9: 9 };\nprintln(W { ..w, f5: 50 });\nprintln(W { ..w, f0: 100, f9: 900 });\nreturn 0;\n", + ), + // Float and Bool overlays ride different carriers. + new( + "float_and_bool_overlays", + "struct F { x: Float, y: Float }\nlet f = F { x: 1.5, y: 2.5 };\nprintln(F { ..f, y: 9.5 });\nstruct B { p: Bool, q: Bool }\nlet b = B { p: true, q: false };\nprintln(B { ..b, q: true });\nreturn 0;\n", + ), + // A mixed overlay is the boxed carrier, which was always fine — + // here to keep both paths under the same gate. + new( + "mixed_overlay", + "struct M { a: Int, b: String }\nlet m = M { a: 1, b: \"x\" };\nprintln(M { ..m, a: 2, b: \"y\" });\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// A **caught** error's message, pinned to pure Cranelift. +/// +/// The loud-failure contract compares success and stdout, not the text of a +/// failure — and that is right for an *uncaught* one, whose text is the host's +/// wrapper. It says nothing about a caught one, and there the message **is** +/// stdout: `catch e { println(e) }` prints it. +/// +/// They did not match. `assert` differed by a capital letter; every dynamic +/// type error said `runtime type error` where the VM names the operator and +/// both operand kinds; a list store past the end said `runtime error` where the +/// VM says `list index 9 out of bounds`; `Set.add(1.5)` lost the `set.add() +/// value:` prefix. +/// +/// The wording is the VM's, warts included — a string of 8 bytes reports as +/// `Object` because the VM formats a value's *representation* rather than its +/// type. That is filed as its own (VM-side) fix; mirroring it here is what +/// makes the two backends agree in the meantime. +#[test] +fn a_caught_errors_message_matches() { + run_differential( + "caught_error_text", + &[ + new( + "dynamic_type_errors_name_their_operands", + "let xs = [1, \"a\"];\nlet out = try {\n let a = xs[0]!;\n let b = xs[1]!;\n println(a - b);\n \"no-raise\"\n} catch e {\n \"caught: ${e}\"\n};\nprintln(out);\nreturn 0;\n", + ), + new( + "unary_minus_and_not", + "let xs = [\"a\"];\nprintln(try { -xs[0]!; \"no\" } catch e { \"caught: ${e}\" });\nlet ys = [1];\nprintln(try { !ys[0]!; \"no\" } catch e { \"caught: ${e}\" });\nreturn 0;\n", + ), + new( + "ordering_across_kinds", + "let xs = [1, \"a\"];\nlet out = try {\n println(xs[0]! < xs[1]!);\n \"no-raise\"\n} catch e {\n \"caught: ${e}\"\n};\nprintln(out);\nreturn 0;\n", + ), + new( + "list_store_out_of_bounds", + "let xs = [1];\nprintln(try { xs[9] = 2; \"no\" } catch e { \"caught: ${e}\" });\nprintln(try { xs[-9] = 2; \"no\" } catch e { \"caught: ${e}\" });\nreturn 0;\n", + ), + new( + // The key rule is a *check-time* error wherever the key's type is + // certainly wrong (`s.add(1.5)` no longer compiles). It stays a + // run-time one exactly where the checker is deliberately + // conservative — a union may be the Int at run time — so that is + // the shape this reaches it through, and the shape whose message + // has to match on both ends. + "float_member_and_key", + "fn opaque(v: Any) -> Any { return v; }\nlet s = Set([]);\nprintln(try { s.add(opaque(1.5)); \"no\" } catch e { \"caught: ${e}\" });\nlet m = {};\nprintln(try { m[opaque(1.5)] = 1; \"no\" } catch e { \"caught: ${e}\" });\nreturn 0;\n", + ), + new( + "assert_is_lowercase", + "println(try { assert(1 == 2); \"no\" } catch e { \"caught: ${e}\" });\nprintln(try { assert(false, \"nope\"); \"no\" } catch e { \"caught: ${e}\" });\nreturn 0;\n", + ), + // The kinds a message can name, including the representation wart: + // a string of 8 bytes is `Object`, one of 7 is `String`. + new( + "operand_kind_names", + "let xs = [1, \"ab\", \"aaaaaaaaaa\", 2.5, true, [1], {\"a\": 1}];\nprintln(try { xs[0]! - xs[1]!; \"no\" } catch e { \"${e}\" });\nprintln(try { xs[0]! - xs[2]!; \"no\" } catch e { \"${e}\" });\nprintln(try { xs[0]! - xs[4]!; \"no\" } catch e { \"${e}\" });\nprintln(try { xs[0]! - xs[5]!; \"no\" } catch e { \"${e}\" });\nprintln(try { xs[0]! - xs[6]!; \"no\" } catch e { \"${e}\" });\nprintln(try { xs[1]! * xs[3]!; \"no\" } catch e { \"${e}\" });\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `+` and `==` across types, pinned to pure Cranelift. +/// +/// From an operator x type-pair sweep (11 values x 13 operators). Two things +/// came out of it, and only one of them was a missing feature. +/// +/// **`==` across kinds is a constant.** Every arm paired a kind with itself, so +/// `1 == "a"`, `true == [1]`, `nil == 2.5` and a hundred other pairings — each +/// a `false` the VM computes without hesitating — took the whole program down. +/// Both kinds are known at lower time, so the answer folds. +/// +/// **`+` was answering the wrong question.** The `Str + Dyn` arm unboxed the +/// Dyn with `as_str`, which *raises* unless it holds a string, on the belief +/// that the VM "only accepts Str + Str here". `Executor::dynamic_add` says +/// otherwise, in this order: numbers, then two maps merge, then a list on +/// either side concatenates, then a string on either side display-concatenates. +/// So `"v=" + x` with a boxed Int is `v=1` and `"p=" + xs` with a boxed list is +/// the *list* `["p=", 1, 2]` — the old arm aborted both. +#[test] +fn mixed_type_addition_and_equality() { + run_differential( + "mixed_type_ops", + &[ + new( + "cross_kind_equality_is_false", + "println(1 == \"a\");\nprintln(1 != \"a\");\nprintln(true == 1);\nprintln(nil == 0);\nprintln(nil == false);\nprintln([1] == {\"a\": 1});\nprintln(2.5 == \"x\");\nprintln(Set([1]) == [1]);\nreturn 0;\n", + ), + new( + "numeric_kinds_still_coerce", + "println(1 == 1.0);\nprintln(1 != 1.0);\nprintln([1] == [1.0]);\nprintln(nil == nil);\nreturn 0;\n", + ), + new( + "string_plus_scalar_displays", + "println(1 + \"ab\");\nprintln(\"ab\" + 1);\nprintln(2.5 + \"x\");\nprintln(\"x\" + 2.5);\nprintln(true + \"x\");\nprintln(\"x\" + nil);\nprintln(\"a\" + \"b\");\nreturn 0;\n", + ), + // The wrong answer: a boxed operand that is not a string. + new( + "string_plus_boxed_scalar", + "let xs = [1, \"a\", 2.5, true];\nfor x in xs { println(\"v=\" + x); }\nreturn 0;\n", + ), + // A list operand outranks a string one, so this is a list. + new( + "a_list_operand_outranks_a_string", + "let xs = [[1, 2], \"s\"];\nlet a = xs[0]!;\nprintln(\"t=${a}\");\nprintln(\"p=\" + a);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// A typed map boxed into a container keeps its own entry order. +/// +/// Boxing used to mean `str_i64_to_dyn` — rebuilding the map into a +/// `str -> Dyn` one by re-inserting in iteration order. A fresh table filled by +/// a different insertion sequence has a different layout, so once the history +/// includes deletions the copy iterates differently from the original, and +/// `println([m])` listed its entries in an order the VM never produces. A +/// **wrong answer**, not a fallback, and it was reachable two ways: a struct +/// field holding a map (long-standing) and a map inside a list or map (new with +/// the boxable-element work). +/// +/// The rule against it was already written down on `DYN_RAW`: boxing must not +/// re-represent a container. A typed map now boxes in place under a tag naming +/// its carrier. The one surviving rebuild is equality's, which is order-free. +/// +/// The deletions are the point of these cases — without them the two layouts +/// coincide and the bug hides. +/// A typed **list** boxed into a container is the same list, not a copy. +/// +/// The map counterpart (`a_boxed_typed_map_keeps_its_order`) fixed an order; +/// this one fixed two aliasing directions, both of which compiled fully native +/// and answered wrong: +/// +/// * `xs.push(2)` after `let c = [xs]` was invisible through `c[0]` — the box +/// held a rebuilt copy made at boxing time. +/// * `c[0].push(9)` appended to that copy, so `xs` never saw it. +/// +/// A typed list boxes in place under a tag naming its carrier now, so every +/// read goes to the carrier and `push` reaches it through `dyn.list_push`. +/// `dyn.as_list` stays read-only: it has to materialize for a typed carrier, +/// and the names that reach it all build new lists. +/// The pure sequence operations, on every carrier that is a sequence. +/// +/// `Bytes` and a window already had `len`, `is_empty`, `first`, `last`, `get`, +/// `contains`, `index_of`, `take`, `skip`, `slice`, `min`, `max`, `sum`, +/// `map`, `filter` and `reduce` — every read of the list surface except two. +/// `reverse` was on `List` and `Str` alone, and `count` on `Str` alone, so +/// `"aa".count("a")` answered 2 while `[1, 1].count(1)` was "List has no +/// method 'count'". +/// +/// The rule the answers follow: the result is the same carrier when it can be +/// one, and a `List` otherwise. `b.reverse()` is a `Bytes`; `w.reverse()` is a +/// `List`, because a reversed range is not a range of the source. +/// The set operations, and the order their answers iterate in. +/// +/// A `Set` whose whole surface is `add` / `delete` / `contains` / `values` / +/// `len` is a deduplicating bag; these are what make it a set, and none of them +/// existed. +/// +/// The **insertion sequence** is what this case is really pinning. A set's +/// iteration order is its hash order, so two sets with the same members can +/// still print differently — the answer is filled in one stated order (the +/// receiver's members, then the argument's) and both ends replay it. Building +/// the same answer "some other way" would pass a membership test and print +/// differently. +/// `m + n` merges, and the merged map iterates the way the VM's does. +/// +/// Both executors implemented the merge all along and only the checker refused, +/// so this case is new on both ends at once. The **fill sequence** is what it +/// pins: a merge builds a new table, and a new table's iteration order is +/// decided by the order it was filled — the left's entries in the left's order +/// minus the keys the right also has, then the right's in the right's order. +/// The runtime used to merge two *unordered* views into a third, which is three +/// different orders, and was unreachable so nothing said. +/// +/// Five keys on the left because a one-key map cannot show an order. +/// `c[a..b]` is `c.slice(a, b)` written the other way, on every carrier. +/// +/// The two spellings had drifted apart: the method worked on a `Bytes` and a +/// window while the range said "Bytes index must be integer", and the lowering +/// covered `Str` and `List` while `List`, `List`, a mixed list +/// and a `Bytes` fell back — the same operation, decided by which carrier the +/// value happened to have. +/// +/// A window still falls back natively (there is no sub-window symbol), which is +/// a fallback and not a wrong answer. +/// `-` removes: `xs - ys` drops every element of `ys`, `m - n` every key of +/// `n`. +/// +/// The tutorial documents it (`[1, 2, 3] - [2] // [1, 3]`) and the VM has +/// implemented it all along; only the checker refused, so it ran with the types +/// erased to `Any` and was "the left operand must be numeric types" without — +/// the same defect `+` had, in the operator beside it. `lkrt_dyn_sub` had never +/// implemented it either, while its own error text said "expected numbers or +/// list/map lhs". +/// +/// The answer keeps the left's own order, because removal takes entries away +/// and never adds one. +#[test] +fn subtraction_removes_from_a_list_and_a_map() { + run_differential( + "container_removal", + &[ + new( + "lists", + "let z = 0;\nprintln([1 + z, 2, 3] - [2]);\nprintln([1 + z, 2, 3] - []);\nprintln([] - [1 + z]);\nprintln([1 + z, 2, 2, 3] - [2]);\nprintln([\"a\" + \"\", \"b\"] - [\"b\"]);\nreturn 0;\n", + ), + new( + "maps_keep_the_left_order", + "let z = 0;\nlet m = {\"k1\": 1 + z, \"k2\": 2, \"k3\": 3, \"k4\": 4, \"k5\": 5};\nprintln(m - {\"k2\": 0, \"k4\": 0});\nprintln(m);\nprintln(m - {});\nprintln((m - {\"k2\": 0}).len());\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +#[test] +fn a_range_index_answers_what_the_slice_method_does() { + run_differential( + "range_index", + &[ + new( + "every_carrier", + "let z = 0;\nprintln([1 + z, 2, 3][1..3]);\nprintln([1.5 + 0.0, 2.5, 3.5][1..3]);\nprintln([\"a\" + \"\", \"b\", \"c\"][1..3]);\nprintln((\"abcd\" + \"\")[1..3]);\nlet b = \"abcd\".bytes();\nprintln(b[1..3]);\nprintln(b.slice(1, 3));\nprintln(typeof(b[1..3]));\nreturn 0;\n", + ), + // Out of range and counted from the end, which the method clamps + // the same way. + new( + "clamping", + "let z = 0;\nlet b = \"abcd\".bytes();\nprintln(b[-2..4]);\nprintln(b[0..99]);\nprintln(b[3..1]);\nlet xs = [1 + z, 2, 3];\nprintln(xs[-2..3]);\nprintln(xs[0..99]);\nprintln(xs[3..1]);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +#[test] +fn two_maps_merge_in_the_vm_s_order() { + run_differential( + "map_merge", + &[ + new( + "order_and_the_right_side_winning", + "let z = 0;\nlet a = {\"k1\": 1 + z, \"k2\": 2, \"k3\": 3, \"k4\": 4, \"k5\": 5};\nlet b = {\"k9\": 9, \"k2\": 20};\nprintln(a + b);\nprintln(a);\nprintln(b);\nprintln((a + b).len());\nprintln((a + b)[\"k2\"]);\nreturn 0;\n", + ), + // Widened values, and an empty operand on each side. + new( + "widening_and_empty_operands", + "let z = 0;\nlet a = {\"a\": 1 + z, \"b\": 2};\nlet f = {\"x\": 1.5 + 0.0};\nprintln(a + f);\nlet e = {\"gone\": 1 + z};\ne.delete(\"gone\");\nprintln(a + e);\nprintln(e + a);\nprintln(e + e);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +#[test] +fn a_set_can_do_set_things() { + run_differential( + "set_operations", + &[ + new( + "combining", + "let z = 0;\nlet a = Set([1 + z, 2, 3]);\nlet b = Set([2, 3, 4]);\nprintln(a.union(b));\nprintln(a.intersection(b));\nprintln(a.difference(b));\nprintln(a.symmetric_difference(b));\nprintln(a);\nprintln(b);\nreturn 0;\n", + ), + new( + "relating", + "let z = 0;\nlet a = Set([1 + z, 2, 3]);\nlet b = Set([2, 3, 4]);\nprintln(a.is_subset(b));\nprintln(a.is_subset(a));\nprintln(a.is_superset(Set([1 + z])));\nprintln(a.is_superset(b));\nprintln(a.is_disjoint(Set([9])));\nprintln(a.is_disjoint(b));\nreturn 0;\n", + ), + // Empty operands on both sides, and a string carrier — the members + // are keyed by the same `RtKey` whatever they hold. + new( + "edges_and_string_members", + "let z = 0;\nlet e = Set([1 + z]);\ne.delete(1);\nlet a = Set([1 + z, 2]);\nprintln(a.union(e));\nprintln(a.intersection(e));\nprintln(e.difference(a));\nprintln(e.is_subset(a));\nprintln(e.is_disjoint(a));\nlet s = Set([\"a\" + \"\", \"b\"]);\nlet t = Set([\"b\", \"c\"]);\nprintln(s.union(t));\nprintln(s.intersection(t));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +#[test] +fn reverse_and_count_reach_every_sequence_carrier() { + run_differential( + "sequence_surface", + &[ + new( + "reverse_keeps_the_carrier_where_it_can", + "let z = 0;\nlet b = \"abca\".bytes();\nprintln(b.reverse());\nprintln(typeof(b.reverse()));\nlet w = [3 + z, 1, 2].slice(0, 3);\nprintln(w.reverse());\nprintln(typeof(w.reverse()));\nprintln([1 + z, 2].reverse());\nreturn 0;\n", + ), + // Shape-preserving on `Bytes` (ordered scalars, every element still + // a byte), materializing on a window (neither answer is a range of + // the source). + new( + "sort_and_unique_reach_them_too", + "let z = 0;\nlet b = \"cab a\".bytes();\nprintln(b.sort());\nprintln(b.unique());\nprintln(typeof(b.sort()));\nlet w = [3 + z, 1, 3, 2].slice(0, 4);\nprintln(w.sort());\nprintln(w.unique());\nprintln(typeof(w.sort()));\nreturn 0;\n", + ), + // The operations whose answer is a list of the elements: they are + // the list's, reached by materializing once. `join` too, which is a + // fused opcode rather than a method call and needed the carrier + // arms there instead. + new( + "the_list_answering_operations_delegate", + "let z = 0;\nlet b = \"abc\".bytes();\nprintln(b.enumerate());\nprintln(b.chunk(2));\nprintln(b.chain([9]));\nprintln(b.zip([7, 8, 9]));\nprintln(b.join(\"-\"));\nlet w = [1 + z, 2, 3].slice(0, 3);\nprintln(w.enumerate());\nprintln(w.chunk(2));\nprintln(w.concat([9]));\nprintln(w.join(\"-\"));\nprintln([1 + z, 2].join(\"-\"));\nprintln(b.concat(\"d\".bytes()));\nreturn 0;\n", + ), + new( + "count_is_index_of_s_sibling", + "let z = 0;\nlet b = \"abca\".bytes();\nprintln(b.count(97));\nprintln(b.count(122));\nprintln(b.count(300));\nprintln([1 + z, 2, 1].count(1));\nprintln([1.5 + 0.0, 2.5, 1.5].count(1.5));\nlet w = [1 + z, 2, 1].slice(0, 3);\nprintln(w.count(1));\nprintln(\"aa\".count(\"a\"));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +#[test] +fn a_boxed_typed_list_is_the_same_list() { + run_differential( + "typed_list_boxing_identity", + &[ + new( + "writes_cross_the_box_both_ways", + "let z = 0;\nlet xs = [3 + z, 1];\nlet c = [xs];\nxs.push(2);\nprintln(c[0].len());\nc[0].push(9);\nprintln(xs);\nprintln(c[0]);\nreturn 0;\n", + ), + // Every read off the carrier, and the float and string carriers + // alongside the int one — one tag per carrier, so a missing arm + // shows up here rather than in whichever program hits it first. + new( + "reads_off_every_carrier", + "let z = 0;\nlet a = [3 + z, 1];\nlet b = [1.5 + 0.0, 0.5];\nlet s = [\"x\" + \"\", \"y\"];\nlet c = [a, b, s];\nprintln(c[0]);\nprintln(c[1]);\nprintln(c[2]);\nprintln(c[0].len() + c[1].len() + c[2].len());\nprintln(c[0][0]);\nprintln(c[1][-1]);\nprintln(c[2][1]);\nprintln(c[0] == [3, 1]);\nprintln(c[2] == [\"x\", \"y\"]);\nfor x in c[0] { println(x); }\nfor x in c[2] { println(x); }\nprintln(c[0].map(|x| x + 1));\nprintln(c[0].reverse());\nprintln(c[0].unique());\nprintln(c[0] + [7]);\nreturn 0;\n", + ), + // A boxed typed list inside a map, and nested one level deeper — + // the tag has to survive every place a `Dyn` goes. + // Pushing an `Int` into a `List`. The checker accepts it by + // numeric promotion, so the list is still a `List` — the VM + // used to widen to `Mixed` and keep the `Int`, so `typeof` answered + // `Int` there and `Float` natively on a program neither side + // rejects. + new( + "an_int_pushed_into_a_float_list_is_a_float", + "let z = 0.0;\nlet xs = [1.5 + z, 2.5];\nxs.push(9);\nprintln(typeof(xs[2]));\nprintln(xs);\nprintln(xs[2] / 2);\nlet c = [xs];\nc[0].push(7);\nprintln(typeof(xs[3]));\nprintln(xs);\nreturn 0;\n", + ), + new( + "through_maps_and_nesting", + "use encoding;\nlet z = 0;\nlet xs = [1 + z, 2];\nlet m = {\"k\": xs};\nxs.push(3);\nprintln(m[\"k\"]);\nlet outer = [[xs]];\nprintln(outer[0][0].len());\nprintln(encoding.json.stringify(m));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +#[test] +fn a_boxed_typed_map_keeps_its_order() { + run_differential( + "typed_map_boxing_order", + &[ + new( + "in_a_list_after_deletions", + "let m = {};\nlet i = 0;\nwhile i < 200 {\n m[\"key_number_${i}\"] = i;\n i = i + 1;\n}\nlet j = 0;\nwhile j < 60 {\n m.delete(\"key_number_${j * 3}\");\n j = j + 1;\n}\nm[\"late\"] = 1;\nprintln([m]);\nprintln({\"w\": m});\nreturn 0;\n", + ), + new( + "in_a_struct_field_after_deletions", + "struct S { m: Map }\nlet m = {};\nlet i = 0;\nwhile i < 200 {\n m[\"key_number_${i}\"] = i;\n i = i + 1;\n}\nlet j = 0;\nwhile j < 60 {\n m.delete(\"key_number_${j * 3}\");\n j = j + 1;\n}\nprintln(S { m: m });\nreturn 0;\n", + ), + // Boxing in place means the box and the original are one map. + new( + "boxing_keeps_identity", + "let m = {\"a\": 1};\nlet holder = [m];\nm[\"b\"] = 2;\nprintln(holder);\nprintln(m);\nreturn 0;\n", + ), + // Equality still crosses representations: a typed carrier against a + // boxed map is the same map written two ways. + new( + "equality_across_representations", + "println({\"a\": 1} == {\"a\": 1.0});\nprintln([{\"a\": 1}] == [{\"a\": 1}]);\nprintln([{\"a\": 1}] == [{\"a\": 2}]);\nprintln({\"k\": {\"a\": 1}} == {\"k\": {\"a\": 1}});\nreturn 0;\n", + ), + // Int-keyed maps ride the same path: boxed in place, displayed and + // iterated off the carrier. + new( + "int_keyed_maps_box_and_iterate", + "let m = {1: 10, 2: 20, 5: 50};\nprintln([m]);\nprintln({\"w\": m});\nprintln(m == {5: 50, 1: 10, 2: 20});\nlet n = 0;\nfor pair in m { n = n + 1; }\nprintln(n);\nfor pair in m { println(pair); }\nreturn 0;\n", + ), + // Reading a *key* out of the box. Boxing in place means the value + // behind the tag is still a typed carrier, so a read that unboxes + // to a `str_dyn` handle first cannot serve it: `c[0]["a"]` compiled + // fully native and then raised `runtime type error` on a program + // the VM answers. Both key spellings, both representations, and a + // miss on each — a missing key is nil, not a failure. + // Every *method* on a boxed map, and `for` over one. These reached + // `dyn.as_map`, whose answer is a `str_dyn` handle — so a boxed + // `Map` raised `runtime type error` on `keys`, `values`, + // `has`, `delete` and the loop, all of which the VM answers. They + // dispatch on the tag now. `delete` is why the dispatch is per + // operation: materializing a copy inside the guard would have + // answered the four reads and dropped the write. + new( + "methods_on_a_boxed_typed_map", + "let z = 0;\nlet m = {\"a\": 1 + z, \"b\": 2};\nlet c = [m];\nprintln(c[0].keys());\nprintln(c[0].values());\nprintln(c[0].has(\"a\"));\nprintln(c[0].has(\"zz\"));\nfor pair in c[0] { println(pair); }\nprintln(c[0].delete(\"a\"));\nprintln(m);\nprintln(m.len());\nreturn 0;\n", + ), + // `for` over a boxed value of every carrier: the loop lowering + // normalizes by tag now, where it used to demand a list. + new( + "for_over_every_boxed_carrier", + "let z = 0;\nlet s = Set([1 + z, 2]);\nfor x in [s][0] { println(x); }\nlet b = \"ab\".bytes();\nfor x in [b][0] { println(x); }\nlet t = \"ab\" + \"\";\nfor x in [t][0] { println(x); }\nlet xs = [1 + z, 2];\nfor x in [xs][0] { println(x); }\nreturn 0;\n", + ), + // `needle in v` on a boxed haystack. What membership means is the + // tag's answer — a map tests keys, everything else tests elements + // — and the lowering had no `Dyn` arm at all, so the whole program + // fell back. + // `Bytes` and a window as haystacks. Both index, both report a + // `len`, both iterate, and `Bytes` even has a `contains` method — + // `in` was the one place they were not containers, and it was + // missing in all three of the checker, the VM and the lowering. + // A byte value outside `u8` is `false`, not an error, which is what + // a list of Ints searched for a string already answers. + new( + "in_over_bytes_and_a_window", + "let z = 0;\nlet b = \"ab\".bytes();\nprintln(97 + z in b);\nprintln(3 in b);\nprintln(300 in b);\nprintln(-1 in b);\nlet xs = [1 + z, 2, 3];\nlet w = xs.slice(0, 2);\nprintln(1 in w);\nprintln(3 in w);\nreturn 0;\n", + ), + new( + "in_over_a_boxed_haystack", + "let z = 0;\nlet m = {\"a\": 1 + z};\nlet c = [m];\nprintln(\"a\" in c[0]);\nprintln(\"zz\" in c[0]);\nlet xs = [1 + z, 2];\nlet d = [xs];\nprintln(2 in d[0]);\nprintln(9 in d[0]);\nlet s = Set([1 + z]);\nlet e = [s];\nprintln(1 in e[0]);\nprintln(4 in e[0]);\nreturn 0;\n", + ), + new( + "reading_a_key_out_of_a_boxed_typed_map", + "let z = 0;\nlet m = {\"a\": 1 + z};\nlet c = [m];\nprintln(c[0][\"a\"]);\nprintln(c[0][\"zz\"]);\nlet n = {3: 4 + z};\nlet d = [n];\nprintln(d[0][3]);\nprintln(d[0][9]);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `for x in s` over a `Set`, and `for b in bytes`, pinned to pure Cranelift. +/// +/// A set's *iteration* order is its hash order — unlike its display order, +/// which is imposed — so this needs the mirror discipline. It could not have it +/// while `lkset` kept its own four-variant key that folded both string shapes +/// into one: membership agreed with the VM and the **hash** did not, and the +/// way that showed up was iteration never being lowered at all. One `RtKey`, +/// one hash, and `set_iteration_order_matches_the_vm` can then say something. +/// +/// `Bytes` iterates its byte values in order — no hash anywhere. +#[test] +fn sets_and_bytes_iterate_natively() { + run_differential( + "set_bytes_iter", + &[ + // Enough members to force several table growths, so the order is a + // real check rather than a small set's coincidence. + new( + "many_int_members", + "let s = Set([]);\nlet i = 0;\nwhile i < 40 {\n s.add(i * 3 - 7);\n i = i + 1;\n}\nlet out = [];\nfor x in s { out.push(x); }\nprintln(out);\nreturn 0;\n", + ), + // Short (inline) and long (heap) keys mixed: the two shapes hash + // differently, which is exactly what one shared key type buys. + new( + "short_and_long_string_members", + "let s = Set([\"alpha\", \"b\", \"gamma_long_key\", \"d\", \"another_long_one\"]);\nfor y in s { println(y); }\nreturn 0;\n", + ), + new( + "iterate_after_mutation", + "let s = Set([1, 2, 3]);\ns.delete(2);\ns.add(9);\nfor x in s { println(x); }\nprintln(s.len());\nreturn 0;\n", + ), + new( + "bytes_iterate_in_order", + "use bytes;\nfor b in bytes.from_string(\"hey\") { println(b); }\nlet n = 0;\nfor b in bytes.from_string(\"\") { n = n + 1; }\nprintln(n);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `Set` and `Bytes` as boxed values, pinned to pure Cranelift. +/// +/// Neither had a `LkDyn` tag, so neither could be *boxed* — and boxing is how a +/// value enters a mixed container, a struct field, or a returned position. So +/// `[s]` and `{"k": b}` had no lowering, for a reason that had nothing to do +/// with sets or byte buffers: the dynamic carrier did not cover every value the +/// language has. `DYN_SET` and `DYN_BYTES` close that, and each tags the handle +/// in place — no rebuild, so identity and any mutation ride along. +#[test] +fn sets_and_bytes_are_boxable() { + run_differential( + "dyn_set_bytes", + &[ + new( + "set_in_containers", + "let s = Set([2, 1]);\nprintln([s]);\nprintln({\"k\": s});\nprintln([s, s]);\nreturn 0;\n", + ), + new( + "bytes_in_containers", + "use bytes;\nlet b = bytes.from_string(\"hi\");\nprintln([b]);\nprintln({\"k\": b});\nreturn 0;\n", + ), + // Boxing tags in place, so a mutation after the box is visible + // through it — the same handle, not a copy. + new( + "boxing_keeps_identity", + "let s = Set([1]);\nlet holder = [s];\ns.add(2);\nprintln(holder);\nprintln(s);\nreturn 0;\n", + ), + new( + "returned_and_compared", + "fn id(a) { return a; }\nlet s = Set([1, 2]);\nprintln(id(s));\nprintln(id(s) == Set([2, 1]));\nuse bytes;\nlet b = bytes.from_string(\"ab\");\nprintln(id(b));\nprintln(id(b) == bytes.from_string(\"ab\"));\nreturn 0;\n", + ), + // Mixed with other element types, and nested one level down. + new( + "mixed_and_nested", + "use bytes;\nlet s = Set([1]);\nlet b = bytes.from_string(\"x\");\nprintln([1, s, \"t\", b]);\nprintln({\"a\": [s], \"b\": b});\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `Set` display and `==`, pinned to pure Cranelift. +/// +/// A set displays sorted, because its hash iteration order is not something to +/// show. It sorted the *rendered text* rather than the members, so +/// `Set([1, 2, 10, 20, 3])` printed `Set([1,10,2,20,3])` — an order that is +/// neither insertion, nor value, nor anything a reader can use. That is fixed +/// in the VM (`RuntimeMapKey::display_order`) and mirrored natively. +/// +/// Mirrored is the easy word here: the display order is *imposed*, not the hash +/// order, and imposed on the members' values — so both sides compare content +/// and no hasher or layout can drift them apart. This is the one container +/// display that needs no mirror discipline. +#[test] +fn sets_display_sorted_and_compare_natively() { + run_differential( + "set_display_eq", + &[ + // The shape that was wrong: numbers whose decimal texts sort + // differently from their values, and negatives. + new( + "numbers_sort_by_value", + "println(Set([1, 2, 10, 20, 3]));\nprintln(Set([-1, -2, 5]));\nprintln(Set([100, 99, 9]));\nreturn 0;\n", + ), + // Strings sort lexicographically across the 7-byte short/long + // split, which a variant-order comparison would get wrong. + new( + "strings_sort_by_content", + "println(Set([\"ab\", \"aaaaaaaaaa\", \"z\"]));\nprintln(Set([\"b\", \"a\"]));\nreturn 0;\n", + ), + // Kinds group before values compare. + new( + "mixed_kinds_group", + "let s = Set([]);\ns.add(nil);\ns.add(true);\ns.add(1);\ns.add(\"a\");\ns.add(false);\ns.add(-5);\nprintln(s);\nprintln(s.len());\nreturn 0;\n", + ), + new( + "empty_and_duplicates", + "println(Set([]));\nprintln(Set([1, 1, 2]));\nprintln(Set([1, 1, 2]).len());\nreturn 0;\n", + ), + new( + "equality_is_order_free", + "println(Set([1, 2]) == Set([2, 1]));\nprintln(Set([1, 2]) == Set([1, 3]));\nprintln(Set([1]) == Set([1, 2]));\nprintln(Set([]) == Set([]));\nreturn 0;\n", + ), + // In a template and after a mutation. + new( + "template_and_mutation", + "let s = Set([3, 1]);\nprintln(\"s=${s}\");\ns.add(2);\ns.delete(3);\nprintln(s);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `==` over maps and structs, pinned to pure Cranelift. +/// +/// Every *list* pairing compared natively; no *map* pairing did, not even +/// `{"a": 1} == {"a": 1}`. Both sides now box to a `Dyn` map and `dyn.eq` +/// decides — order-free, key by key, with the VM's numeric coercion. +/// +/// That route was not usable as it stood: a struct instance is a marked map, +/// and `dyn_eq_inner` compared only the entries, so it would have answered +/// `true` for `P{x:1} == Q{x:1}` and `P{x:1} == {"x":1}` where the VM answers +/// `false`. The mark decides all three cases at once — every declared struct +/// has an id and a plain map has none — so it is read first. +#[test] +fn maps_and_structs_compare_natively() { + run_differential( + "map_struct_eq", + &[ + new( + "typed_and_boxed_maps", + "println({\"a\": 1} == {\"a\": 1});\nprintln({\"a\": 1} == {\"a\": 2});\nprintln({\"a\": 1} == {\"a\": 1, \"b\": 2});\nprintln({\"a\": 1, \"b\": 2} == {\"b\": 2, \"a\": 1});\nprintln({\"a\": 1} == {\"b\": 1});\nprintln({} == {});\nreturn 0;\n", + ), + // A value's *number* coerces across the two maps' element types, + // but a bool is not a number. + new( + "numeric_coercion_and_bools", + "println({\"a\": 1} == {\"a\": 1.0});\nprintln({\"a\": 1.5} == {\"a\": 1.5});\nprintln({\"a\": true} == {\"a\": true});\nprintln({\"a\": 1} == {\"a\": true});\nprintln({\"a\": 1, \"b\": \"x\"} == {\"a\": 1, \"b\": \"x\"});\nreturn 0;\n", + ), + new( + "nested_values", + "println({\"a\": [1, 2]} == {\"a\": [1, 2]});\nprintln({\"a\": [1, 2]} == {\"a\": [1, 3]});\nprintln({\"a\": {\"b\": 1}} == {\"a\": {\"b\": 1}});\nreturn 0;\n", + ), + // The struct mark: same shape, different type, and struct against + // the bare map with the same fields. + new( + "struct_identity", + "struct P { x: Int }\nstruct Q { x: Int }\nprintln(P{x:1} == P{x:1});\nprintln(P{x:1} == P{x:2});\nprintln(P{x:1} == Q{x:1});\nprintln(P{x:1} == {\"x\": 1});\nprintln({\"x\": 1} == P{x:1});\nreturn 0;\n", + ), + // Through a container, where `dyn_eq` recurses into the arm rather + // than being called on it directly. + new( + "struct_identity_nested", + "struct P { x: Int }\nstruct Q { x: Int }\nprintln([P{x:1}] == [P{x:1}]);\nprintln([P{x:1}] == [Q{x:1}]);\nprintln({\"k\": P{x:1}} == {\"k\": Q{x:1}});\nreturn 0;\n", + ), + // `Map.len()` was missing from the `Len` table, though + // it rides the same carrier as `Map`. + new( + "bool_map_len", + "let m = {\"a\": true, \"b\": false};\nprintln(m.len());\nprintln(m);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// A map literal whose values are computed, and the display of a typed map. +/// +/// Two holes that met in the middle. `NewMap` — the opcode for a literal whose +/// values are not all constants, `{"k": a}` — had no lowering at all, so a +/// program that built a record from anything it had computed fell back whole; +/// the list spelling `[a, a + 1]` always lowered, which is what kept it +/// invisible. And displaying a *typed* map was refused on a ruling that +/// predates `lkrt/src/vm_mirror.rs`: the ruling said the two runtimes do not +/// share a map's iteration order, and the mirror's entire job is that they do. +/// The `MapStrDyn` arm had already been let through, so `println({"a": 1})` +/// cost a program its lowering while `println({"a": 1, "b": "x"})` did not. +/// +/// Pinned to pure Cranelift: byte-exact display is the acceptance criterion — +/// key quoting, `,`/`:` separators, and above all the entry order. +#[test] +fn a_computed_map_literal_lowers_and_a_typed_map_displays() { + run_differential( + "map_literal_and_display", + &[ + new( + "computed_int_values", + "let a = 5;\nlet m = {\"i\": a, \"j\": a + 1};\nprintln(m);\nprintln(m[\"j\"] ?? 0);\nreturn 0;\n", + ), + new( + "computed_from_calls", + "fn f(x: Int) -> Int { return x * 2; }\nlet m = {\"a\": f(3), \"b\": f(4)};\nprintln(m);\nreturn 0;\n", + ), + new( + "float_and_bool_values", + "let f = 1.5;\nlet b = true;\nprintln({\"f\": f, \"g\": f * 2.0});\nprintln({\"b\": b, \"c\": !b});\nreturn 0;\n", + ), + new( + "heterogeneous_values_box", + "let a = 5;\nlet s = \"v\";\nlet f = 1.5;\nprintln({\"i\": a, \"s\": s, \"f\": f, \"n\": nil});\nreturn 0;\n", + ), + // Int keys, whose order is the *stage-1* table's: the VM runs no + // stage 2 for a non-string key, so the native carrier is keyed by + // `vm_mirror::IntKey` (hashing as `RtKey::Int`) and filled in + // literal order. Rehashing into an `FxMap`, which is what + // it used to do, made `{1: 1.5, 2: 2.5}` come out `1,2` against + // the VM's `2,1`. + new( + "int_keys", + "let a = 5;\nlet m = {1: a, 3: a + 1};\nprintln(m);\nprintln(m[1] ?? 0);\nprintln({1: 1.5, 2: 2.5});\nprintln({7: 1, 2: 2, 9: 3, 4: 4, 1: 5});\nprintln({-3: 1.5, 7: 2.5, 0: 0.5});\nreturn 0;\n", + ), + // The same, built by runtime stores rather than a literal: the + // insertion sequence is the program's, and both sides replay it. + new( + "int_keys_stored_one_by_one", + "let m = {10: 1};\nlet i = 0;\nwhile i < 20 {\n m[i * 7] = i;\n i = i + 1;\n}\nprintln(m);\nprintln(m.len());\nreturn 0;\n", + ), + // Enough keys to force several table growths, so the order is a + // real check rather than one small map's coincidence. + new( + "many_keys_keep_the_vm_order", + "let m = {};\nlet i = 0;\nwhile i < 40 {\n m[\"k${i}\"] = i * 3;\n i = i + 1;\n}\nprintln(m);\nprintln(m.len());\nreturn 0;\n", + ), + // The constant spelling of the same map, which took the display + // refusal too. + new( + "constant_map_displays", + "println({\"a\": 1, \"b\": 2});\nprintln({\"a\": 1.5});\nprintln({\"a\": true});\nprintln({});\nreturn 0;\n", + ), + // A map inside a template and inside a list. The list case is the + // one that printed `{\"a\":1}` where the VM printed `[{\"a\":1}]`: + // no arm of `NewList` could box a typed map, so the destination + // kept only the argument-pack view and the call read *that*. + new( + "nested_in_a_template_and_a_list", + "let a = 1;\nlet m = {\"a\": a};\nprintln(\"m=${m}\");\nprintln([m]);\nprintln([m, m]);\nreturn 0;\n", + ), + // A duplicate key keeps the last value, both spellings. + new( + "duplicate_key_keeps_the_last", + "let a = 5;\nprintln({\"d\": a, \"d\": a + 1});\nreturn 0;\n", + ), + new( + "container_values_box", + "let a = 5;\nprintln({\"n\": [a, a + 1], \"m\": {\"k\": a}});\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// A container reassigned inside a `try` body, pinned to pure Cranelift. +/// +/// A register the body assigns crosses back through an output cell, and a +/// container had no way out of one — so `try { xs = […]; } catch e { }` dropped +/// the program to the VM. An *already boxed* container round-trips by pointer +/// (`dyn.from_list` only tags the handle), which is what makes this sound; a +/// typed one still refuses, because its boxing is an element-wise copy and the +/// round trip would hand back a different list. +#[test] +fn a_dyn_container_crosses_a_try_region() { + run_differential( + "try_container_cell", + &[ + new( + "dyn_list_reassigned", + "let xs = [1, \"a\"];\ntry { xs = [2, \"b\"]; } catch e { }\nprintln(xs);\nreturn 0;\n", + ), + new( + "dyn_map_reassigned", + "let m = {\"a\": 1, \"b\": \"x\"};\ntry { m = {\"a\": 2, \"b\": \"y\"}; } catch e { }\nprintln(m);\nreturn 0;\n", + ), + // The typed containers, which need the *raw* cell: their boxing is + // an element-wise copy, so a boxed round trip would hand back a + // different handle. + new( + "typed_list_reassigned", + "let xs = [1];\ntry { xs = [2, 3]; } catch e { }\nprintln(xs);\nreturn 0;\n", + ), + new( + "typed_map_reassigned", + "let m = {\"k\": 1};\ntry { m = {\"k\": 2}; } catch e { }\nprintln(m[\"k\"] ?? 0);\nreturn 0;\n", + ), + new( + "set_reassigned", + "let s = Set([1, 2]);\ntry { s = Set([3]); } catch e { }\nprintln(s.len());\nreturn 0;\n", + ), + new( + "bytes_reassigned", + "use bytes;\nlet b = bytes.from_string(\"a\");\ntry { b = bytes.from_string(\"bc\"); } catch e { }\nprintln(bytes.len(b));\nreturn 0;\n", + ), + // The body raises before assigning: the cell still holds the value + // the caller seeded it with, which is what the VM shows. + new( + "raised_before_assigning", + "let xs = [1, \"a\"];\ntry { error(\"boom\"); xs = [2, \"b\"]; } catch e { }\nprintln(xs);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Several regions in one function, pinned to pure Cranelift. +/// +/// A body that writes a register and does not carry it back leaves the parent's +/// copy poisoned, and a later read of one is how the fixpoint discovers which +/// registers need a cell. The read used to say only *which register*, so the +/// cell went to every region in the function — including regions whose body had +/// merely reused that register number as a scratch. The parent has no +/// definition for such a register at its own region's start, so seeding its +/// cell read it before pc 0 and the whole function fell back: adding a second +/// `try` to a function that had one cost the *first* one its lowering. +/// +/// The poison now names the body that left it, so the cell goes to that one. +#[test] +fn several_try_regions_share_a_function() { + run_differential( + "try_multi_region", + &[ + // The shape that failed: a container region, then an unrelated + // scalar one. The container body's literal lands in a scratch + // register that the scalar variable happens to reuse. + new( + "container_region_then_scalar_region", + "let a = [1];\ntry { a = [2]; } catch e { }\nlet b = 3;\ntry { b = 4; } catch e { }\nprintln(a);\nprintln(b);\nreturn 0;\n", + ), + new( + "three_regions_three_types", + "let a = [1];\nlet m = {\"k\": 1};\nlet s = \"x\";\ntry { a = [2, 3]; } catch e { }\ntry { m = {\"k\": 9, \"j\": 2}; } catch e { }\ntry { s = \"y\"; a = [7]; } catch e { }\nprintln(\"${a} ${m[\"k\"] ?? 0} ${s}\");\nreturn 0;\n", + ), + // A region inside a loop, after a region outside it: the poison is + // per block, and the loop header's phi has to see the cell's value. + new( + "region_then_region_in_a_loop", + "let a = [1];\ntry { a = [5]; } catch e { }\nlet n = 0;\nfor i in 0..4 {\n try { n = n + i + a[0]!; } catch e { }\n}\nprintln(n);\nreturn 0;\n", + ), + // Both regions raise: each cell keeps what the parent seeded. + new( + "both_regions_raise", + "let a = [1];\nlet b = 3;\ntry { error(\"x\"); a = [2]; } catch e { }\ntry { error(\"y\"); b = 4; } catch e { }\nprintln(a);\nprintln(b);\nreturn 0;\n", + ), + // Inside a called function rather than the entry, and the second + // region reads what the first one wrote. + new( + "regions_in_a_function_chained", + "fn f(k: Int) -> Int {\n let acc = [0];\n try { acc = [k, k + 1]; } catch e { }\n let t = 0;\n try { t = acc[1]! * 2; } catch e { t = -1; }\n return t + acc[0]!;\n}\nprintln(f(3));\nprintln(f(10));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// A `try` body that `return`s from the enclosing function, pinned to pure +/// Cranelift. +/// +/// The body is outlined into a function of its own, so a `return` written in it +/// would return from *that* function. It used to be a rejection, which made the +/// statement form fall back while the value form lowered — the same function, +/// two spellings, one three times slower. The body now has a third channel +/// beside "the value" and "it raised": a flag cell and a value cell, checked on +/// the ok edge. +#[test] +fn a_try_body_may_return_from_its_function() { + run_differential( + "try_body_return", + &[ + new( + "int_return_and_fallthrough", + "fn f(n: Int) -> Int {\n let v = try { if (n > 0) { return 10; } 0 } catch e { -1 };\n return v;\n}\nprintln(f(1));\nprintln(f(-1));\nprintln(f(0));\nreturn 0;\n", + ), + // Every carrier the value cell has to hand back. + new( + "string_return", + "fn f(n: Int) -> String {\n let v = try { if (n > 0) { return \"big\"; } \"small\" } catch e { \"err\" };\n return v;\n}\nprintln(f(1));\nprintln(f(0));\nreturn 0;\n", + ), + new( + "bool_return", + "fn f(n: Int) -> Bool {\n let v = try { if (n > 0) { return true; } false } catch e { false };\n return v;\n}\nprintln(f(1));\nprintln(f(0));\nreturn 0;\n", + ), + // A return *and* a raise from the same body: the two channels must + // not be confused for each other. + new( + "return_or_raise", + "fn f(n: Int) -> Int {\n let v = try { if (n > 0) { return 10; } error(\"neg\"); 0 } catch e { -1 };\n return v;\n}\nprintln(f(1));\nprintln(f(-1));\nreturn 0;\n", + ), + // A body where *every* path returns. It has no ok edge in the + // bytecode (the compiler emits no jump over the handler), which I + // first read as needing its own protocol — it does not: the ok edge + // simply always takes the return branch. + new( + "every_path_returns", + "fn f(n: Int) -> Int {\n try { return n * 2; } catch e { return -1; }\n}\nprintln(f(3));\nreturn 0;\n", + ), + new( + "every_path_returns_or_raises", + "fn f(n: Int) -> Int {\n try { if (n < 0) { error(\"neg\"); } return n; } catch e { return -1; }\n}\nprintln(f(3));\nprintln(f(-1));\nreturn 0;\n", + ), + // A handler that falls through while the body returns. + new( + "body_returns_handler_falls_through", + "fn f(n: Int) -> Int {\n try { return n; } catch e { }\n return 0;\n}\nprintln(f(7));\nreturn 0;\n", + ), + // Two returns and a fallthrough in one body. + new( + "two_returns_and_a_fallthrough", + "fn f(n: Int) -> Int {\n let v = try { if (n > 0) { return n; } if (n < -5) { return -n; } 0 } catch e { -1 };\n return v;\n}\nprintln(f(3));\nprintln(f(-9));\nprintln(f(-1));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `task.join_all` over task handles, pinned to pure Cranelift. +/// +/// Variadic, so no ABI row can describe it — a row has one arity — and it was +/// the last thing in the concurrency surface that dropped a program to the VM. +/// The element display is the point of the string case: a `Dyn` list has to +/// quote exactly as the VM's typed list does. +#[test] +fn join_all_over_handles_lowers_natively() { + run_differential( + "join_all", + &[ + new( + "several_tasks", + "use task;\nlet a = spawn(|| 1);\nlet b = spawn(|| 2);\nprintln(task.join_all(a, b));\nreturn 0;\n", + ), + new( + "one_task", + "use task;\nlet a = spawn(|| 1);\nprintln(task.join_all(a));\nreturn 0;\n", + ), + new( + "string_and_mixed_elements", + "use task;\nlet a = spawn(|| \"x\");\nlet b = spawn(|| \"y z\");\nprintln(task.join_all(a, b));\nlet c = spawn(|| 1);\nlet d = spawn(|| \"s\");\nprintln(task.join_all(c, d));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// A closure that calls another closure, pinned to pure Cranelift. +/// +/// Composing two lambdas is most of what having them is for, and it dropped the +/// whole program to the VM: a captured `f` lives in a *cell*, and what goes into +/// that cell is a lowering-time reference rather than a value, so the store had +/// nothing to read and the callee's capture had nothing to mean. +#[test] +fn a_closure_may_call_another_closure() { + run_differential( + "closure_composition", + &[ + new( + "compose_two", + "let f = |x| x + 1;\nlet g = |x| f(x) * 2;\nprintln(g(1));\nreturn 0;\n", + ), + // A named `fn` is the same kind of reference. + new( + "call_a_named_function", + "fn inc(x: Int) -> Int { return x + 1; }\nlet g = |x| inc(x) * 2;\nprintln(g(1));\nreturn 0;\n", + ), + // Twice in one body, and a three-deep chain. + new( + "call_it_twice", + "let f = |x| x + 1;\nlet g = |x| f(f(x));\nprintln(g(1));\nreturn 0;\n", + ), + new( + "chain_of_three", + "let f = |x| x + 1;\nlet g = |x| x * 2;\nlet h = |x| g(f(x));\nprintln(h(1));\nreturn 0;\n", + ), + // Calling one *and* writing a capture, the two closure facts at once. + new( + "call_and_assign_a_capture", + "let acc = 0;\nlet f = |x| x + 1;\nlet g = |x| { acc = acc + f(x); };\ng(1);\ng(2);\nprintln(acc);\nreturn 0;\n", + ), + // A plain alias of a lambda. + new( + "alias_a_lambda", + "let f = |x| x + 1;\nlet g = f;\nprintln(g(1));\nreturn 0;\n", + ), + // A lambda *argument* whose body calls a captured lambda. Its whole + // environment is static, so it is erased and the typed `map_fn` fast + // path — which calls the callback with the element and nothing else — + // accepts it. + new( + "captured_lambda_inside_a_map_callback", + "let f = |x| x + 1;\nprintln([1,2,3].map(|x| f(x)));\nprintln([1,2,3].filter(|x| f(x) > 2));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Closures that **assign** to what they captured. +/// +/// A capture travelled as a hidden trailing argument holding the cell's content +/// at the call site — right for one the body reads, with nowhere to put a +/// write. So an accumulating closure, which is most of what closures are for, +/// dropped the whole program to the VM. Pinned to pure Cranelift: the bug is a +/// silent fallback, so "both print 7" is not the property under test. +#[test] +fn a_closure_may_assign_to_its_capture() { + run_differential( + "mutable_captures", + &[ + new( + "accumulate_int", + "let acc = 0;\nlet add = |v| { acc = acc + v; };\nadd(3);\nadd(4);\nprintln(acc);\nreturn 0;\n", + ), + // The closure both reads and returns the capture it wrote. + new( + "read_write_and_return", + "let hits = 0;\nlet bump = |n| { hits = hits + n; return hits; };\nprintln(bump(1));\nprintln(bump(2));\nprintln(hits);\nreturn 0;\n", + ), + // Non-integer carriers: a string rebuilt, a bool flipped, a float + // scaled. Each boxes through the cell and comes back. + new( + "string_bool_float_captures", + "let log = \"\";\nlet flag = false;\nlet f = 0.5;\nlet step = |s| { log = log + s + \";\"; flag = !flag; f = f * 2.0; };\nstep(\"a\");\nstep(\"b\");\nprintln(log);\nprintln(flag);\nprintln(f);\nreturn 0;\n", + ), + // One capture written, one only read — the read-only one must keep + // passing by value rather than being dragged into a cell. + new( + "written_and_read_only_captures", + "let base = 10;\nlet total = 0;\nlet add = |v| { total = total + v + base; };\nadd(1);\nadd(2);\nprintln(total);\nprintln(base);\nreturn 0;\n", + ), + // Two closures sharing one cell, and a call inside a loop (the + // write-back has to survive the loop-header phi). + new( + "two_closures_one_cell_in_a_loop", + "let n = 0;\nlet inc = || { n = n + 1; };\nlet dec = || { n = n - 1; };\nfor i in 0..5 { inc(); }\ndec();\nprintln(n);\nreturn 0;\n", + ), + // A rebinding write, not a mutation through the handle: the cell + // carries a whole new list. + new( + "capture_rebound_to_a_new_list", + "let xs = [1];\nlet reset = || { xs = [9, 9]; };\nreset();\nprintln(xs);\nreturn 0;\n", + ), + // A closure nested in a closure writes what its parent captured: + // the cell has to pass *through* the parent by pointer, and the + // parent's own capture becomes a cell because of the child's write. + new( + "nested_closure_writes_the_outer_capture", + "let total = 0;\nlet outer = |v| {\n let inner = |w| { total = total + w; };\n inner(v);\n inner(v);\n};\nouter(3);\nprintln(total);\nreturn 0;\n", + ), + // Three levels: the requirement propagates the whole chain. + new( + "three_levels_of_nesting", + "let total = 0;\nlet l1 = |a| {\n let l2 = |b| {\n let l3 = |c| { total = total + c; };\n l3(b);\n };\n l2(a);\n};\nl1(5);\nl1(2);\nprintln(total);\nreturn 0;\n", + ), + // The inner closure writes one capture and reads another, so only + // one of them may become a cell. + new( + "nested_writes_one_capture_reads_another", + "let base = 100;\nlet acc = 0;\nlet outer = |v| {\n let inner = |w| { acc = acc + w + base; };\n inner(v);\n};\nouter(1);\nouter(2);\nprintln(acc);\nprintln(base);\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Both arities of `slice`, in both spellings, pinned to pure Cranelift. +/// +/// A string had one- and two-argument forms; a list had only the two-argument +/// one, so `xs.slice(1)` dropped the program to the VM. And `string.slice(s, a, +/// b)` — the *module* spelling of what the method path had always lowered — had +/// no row at all: two spellings of one operation, only one of them fast. +#[test] +fn every_slice_spelling_lowers_natively() { + run_differential( + "slice_spellings", + &[new( + "list_string_and_bytes", + "use string;\nuse bytes;\nlet xs = [1,2,3];\nprintln(xs.slice(1));\nprintln(xs.slice(1, 3));\nprintln(string.slice(\"hello\", 1, 3));\nprintln(string.slice(\"hello\", 1));\nprintln(\"hello\".slice(1));\nlet b = bytes.from_string(\"abcde\");\nprintln(b.slice(1));\nreturn 0;\n", + )], + NativePath::PureCranelift, + ); +} + +/// A window answers the whole read surface *through* itself. +/// +/// `xs.slice(a, b)` is a range of its source, not a copy — and nine of its +/// seventeen methods dropped the module to the VM, so a program that took a +/// window to avoid copying paid for the window and then ran interpreted. The +/// same "almost native receiver" shape `Bytes` had before it, and the list +/// carriers before that. +/// +/// `sum`/`min`/`contains` read through the window rather than materializing it, +/// which is the point of having one; `take`/`skip` are sub-windows, and keep +/// the count guard (a count is not a position, so a negative one is a refusal); +/// `map`/`filter`/`reduce` join the list channel by becoming a list first, and +/// answer a **List** — a filtered window is not a range of anything. +#[test] +fn a_window_answers_its_whole_read_surface_natively() { + run_clif_differential( + "slice_surface", + &[ + new( + "reads_through_the_window", + "let z = 0;\nlet xs = [3, 1, 2, 5, 4];\nlet w = xs.slice(1 + z, 4);\n\ + println(w.len());\nprintln(w.is_empty());\nprintln(w.first());\nprintln(w.last());\n\ + println(w.get(0));\nprintln(w.get(-1));\nprintln(w.get(9));\n\ + println(w.sum());\nprintln(w.min());\nprintln(w.max());\n\ + println(w.contains(2));\nprintln(w.contains(99));\n\ + println(w.index_of(2));\nprintln(w.index_of(99));\n\ + println(w.to_list());\nprintln(w.slice(1, 3).to_list());\nreturn 0;\n", + ), + new( + "sub_windows_and_the_count_guard", + "let z = 0;\nlet xs = [3, 1, 2, 5, 4];\nlet w = xs.slice(0 + z, 5);\n\ + println(w.take(2).to_list());\nprintln(w.skip(2).to_list());\n\ + println(w.take(99).to_list());\nprintln(w.skip(99).to_list());\n\ + println(w.take(0).to_list());\nreturn 0;\n", + ), + new( + "an_empty_window_reads_as_nil", + "let z = 0;\nlet w = [1, 2, 3].slice(1 + z, 1);\n\ + println(w.len());\nprintln(w.is_empty());\nprintln(w.first());\nprintln(w.last());\n\ + println(w.min());\nprintln(w.max());\nprintln(w.sum());\n\ + println(w.index_of(1));\nprintln(w.contains(1));\nprintln(w.to_list());\nreturn 0;\n", + ), + new( + "the_closure_methods_answer_lists", + "let z = 0;\nlet w = [3, 1, 2].slice(0 + z, 3);\n\ + println(w.map(|x: Int| -> Int { x * 2 }));\n\ + println(w.filter(|x: Int| -> Bool { x > 1 }));\n\ + println(w.reduce(0, |a: Int, b: Int| -> Int { a + b }));\nreturn 0;\n", + ), + ], + ); +} + +/// Every closed-channel refusal reads the same on both ends. +/// +/// A caught message is printed output. `chan.try_send` decorated the runtime's +/// error into "Failed to send to channel: Channel is closed" where `lkrt::chan` +/// raises "send on closed channel" — one operation, two answers, and the +/// interpreter did not even agree with its own `chan.send`. +#[test] +fn closed_channel_refusals_read_the_same_on_both_ends() { + run_clif_differential( + "chan_closed_text", + &[new( + "every_spelling", + "use chan;\nlet z = 0;\nlet c = chan.new(1 + z);\nchan.close(c);\n\ + println(try { \"${chan.try_send(c, 1)}\" } catch e { \"${e}\" });\n\ + println(try { \"${chan.send(c, 1)}\" } catch e { \"${e}\" });\n\ + println(try { \"${chan.try_recv(c)}\" } catch e { \"${e}\" });\n\ + println(try { \"${chan.recv(c)}\" } catch e { \"${e}\" });\n\ + println(chan.is_closed(c));\nprintln(chan.len(c));\nreturn 0;\n", + )], + ); +} + +/// A window is a value: it boxes, so it can enter a list, a map or a `try`. +/// +/// A carrier with no `Dyn` tag cannot be boxed at all, and boxing is how a +/// value enters anything that holds a dynamic value — so `[w]`, `{"k": w}`, +/// `typeof(w)` and `let out = try { … } catch e { … };` each dropped the whole +/// program to the VM. `Set` and `Bytes` had the same hole once, for the same +/// reason. +/// +/// In place, not materialized: the box holds the window, so it still reads +/// through to the list it windows. And it compares by *content* against a plain +/// list, because `xs.slice(0, 2) == [3, 1]` is true in the VM — a window is a +/// range of a list, not a distinct kind of value. +#[test] +fn a_window_is_a_boxable_value() { + run_clif_differential( + "slice_boxing", + &[ + new( + "into_containers_and_comparisons", + "let z = 0;\nlet xs = [3, 1, 2];\nlet w = xs.slice(0 + z, 2);\n\ + println(w);\nprintln([w]);\nprintln([w, w]);\n\ + println(w == [3, 1]);\nprintln(w == [1, 3]);\nprintln(w == xs.slice(0, 2));\n\ + println({\"k\": w});\nprintln(typeof(w));\nreturn 0;\n", + ), + // `typeof` over every proven type, which is the same table: it knew + // only the five scalars, so `typeof([1, 2])` fell back too. + new( + "typeof_names_every_proven_type", + "use bytes;\nlet z = 0;\n\ + println(typeof([1 + z, 2]));\nprintln(typeof([1.5]));\nprintln(typeof([\"a\"]));\n\ + println(typeof({\"a\": 1}));\nprintln(typeof(Set([1])));\n\ + println(typeof(bytes.from_string(\"ab\")));\nprintln(typeof([1, 2].slice(0, 1)));\n\ + println(typeof(1));\nprintln(typeof(1.5));\nprintln(typeof(\"s\"));\n\ + println(typeof(true));\nprintln(typeof(nil));\nreturn 0;\n", + ), + // The cell a `try` region writes back through: its *kind* is one + // decision, and both sides used to make it. A register the caller + // saw as `nil` got a value cell; the body then stored a raw handle + // into it because the value it assigned was a container, and the + // read raised "runtime type error" where the VM printed the bytes. + new( + "a_nil_seeded_cell_survives_a_container_assignment", + "use bytes;\nlet z = 0;\nlet b = bytes.from_string(\"abc\");\n\ + let out = nil;\ntry { out = b.take(1 + z); } catch e { }\nprintln(out);\n\ + let ob = try { b.skip(1) } catch e { b };\nprintln(ob);\n\ + let os = try { Set([1, 2]) } catch e { Set([9]) };\nprintln(os.len());\nreturn 0;\n", + ), + ], + ); +} + +/// `Bytes` as a native value, pinned to pure Cranelift. +/// +/// It had no carrier at all, so `"hi".bytes()`, every `bytes` module member, and +/// `base64.decode` / `hex.decode` dropped the whole program to the VM. Display +/// (`Bytes([104,105])`) and equality (by *content*) are the two things a bare +/// handle integer could not have expressed. +#[test] +fn bytes_are_a_native_value() { + run_differential( + "bytes_value", + &[ + new( + "module_surface", + "use bytes;\nlet b = bytes.from_string(\"hi\");\nprintln(b);\nprintln(bytes.len(b));\nprintln(bytes.is_empty(b));\nprintln(bytes.to_string_utf8(b));\nprintln(bytes.to_string_lossy(b));\nprintln(bytes.get(b, 0) ?? -1);\nprintln(bytes.get(b, -1) ?? -1);\nprintln(bytes.get(b, 9) ?? -1);\nprintln(bytes.concat(b, b));\nreturn 0;\n", + ), + // The method spellings, and `len` through the container fast path. + new( + "method_surface", + "println(\"hi\".bytes());\nprintln(\"hi\".bytes().len());\nprintln(\"\".bytes().is_empty());\nprintln(\"\".bytes());\nreturn 0;\n", + ), + // Indexing, which is also what `b.get(i)` compiles to, and the + // two-argument `slice`. Negative counts from the end and out of + // range is nil — the same rule every container reads by, which the + // `bytes` *module* did not have until now (`b.slice(1, -1)` through + // the method dispatch answered while `bytes.slice(b, 1, -1)` raised). + new( + "indexing_and_slicing", + "use bytes;\nlet b = bytes.from_string(\"abcde\");\nprintln(b[0]);\nprintln(b[-1]);\nprintln(b[9] ?? -1);\nprintln(b.get(-1) ?? -1);\nprintln(bytes.slice(b, 1));\nprintln(bytes.slice(b, 1, -1));\nprintln(b.slice(1, -1));\nreturn 0;\n", + ), + // List interop, the last two members of the module — and the + // out-of-range raise, which is the whole point of `from_list` + // taking bytes rather than truncating whatever it is handed. + new( + "list_interop", + "use bytes;\nlet b = bytes.from_list([104,105]);\nprintln(b);\nprintln(bytes.to_list(b));\nprintln(bytes.from_list([]));\nprintln(bytes.to_list(bytes.from_string(\"\")));\ntry { bytes.from_list([300]); println(\"no\"); } catch e { println(\"caught\"); }\ntry { bytes.from_list([-1]); println(\"no\"); } catch e { println(\"caught\"); }\nreturn 0;\n", + ), + // Content equality, not handle identity. + new( + "content_equality", + "use bytes;\nlet a = bytes.from_string(\"hi\");\nprintln(a == bytes.from_string(\"hi\"));\nprintln(a == bytes.from_string(\"ho\"));\nprintln(a != bytes.from_string(\"ho\"));\nreturn 0;\n", + ), + // The decoders answer `Bytes`, and raise catchably on bad input. + new( + "decoders_answer_bytes", + "use bytes;\nuse encoding;\nlet d = encoding.base64.decode(\"aGk=\");\nprintln(d);\nprintln(bytes.to_string_utf8(d));\nprintln(encoding.hex.decode(\"6869\") == d);\ntry { encoding.base64.decode(\"!!!\"); println(\"no\"); } catch e { println(\"caught\"); }\ntry { encoding.hex.decode(\"zz\"); println(\"no\"); } catch e { println(\"caught\"); }\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `base64` / `hex` / `url` text, pinned to pure Cranelift. +/// +/// lkrt uses the same crates the stdlib module does, so the bytes are identical +/// rather than merely equivalent — and the `url` pair is the one that had to be +/// fixed before it could be mirrored: the encoder was form-encoding (a space +/// became `+`) while the decoder only undid `%XX`, so it did not round-trip. +#[test] +fn text_codecs_lower_natively() { + run_differential( + "text_codec", + &[ + new( + "base64_and_hex_encode", + "use encoding;\nprintln(encoding.base64.encode(\"hi\"));\nprintln(encoding.hex.encode(\"hi\"));\nreturn 0;\n", + ), + new( + "url_component_round_trip", + "use encoding;\nlet s = \"a b&c=d\";\nlet e = encoding.url.encode_component(s);\nprintln(e);\nprintln(encoding.url.decode_component(e));\nprintln(encoding.url.decode_component(e) == s);\nreturn 0;\n", + ), + // A malformed escape raises, catchably, with the same three messages. + new( + "url_decode_raises_on_a_bad_escape", + "use encoding;\ntry { println(encoding.url.decode_component(\"%\")); } catch e { println(\"caught1\"); }\ntry { println(encoding.url.decode_component(\"%zz\")); } catch e { println(\"caught2\"); }\nprintln(encoding.url.decode_component(\"%41\"));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// A submodule reached through its parent, pinned to pure Cranelift. +/// +/// `encoding.json.parse(s)` compiles to a `CallMethodK` whose *receiver* is the +/// module object `encoding.json`, and two things were missing: reading a +/// submodule off its parent gave a module *function* rather than another module, +/// and a module-object receiver had no arm at all. So the chain stopped at the +/// first dot and the program fell back, while `use { json } from encoding;` +/// lowered — same answer, three times slower, which no differential test can +/// see. +#[test] +fn a_submodule_reached_through_its_parent_lowers_natively() { + run_differential( + "nested_module", + &[ + new( + "encoding_json_through_its_parent", + "use encoding;\nprintln(encoding.json.parse(\"[1,2]\"));\nreturn 0;\n", + ), + // The same member through the selective import, which always + // lowered: both spellings, one answer. + new( + "encoding_json_through_a_selective_import", + "use { json } from encoding;\nprintln(json.parse(\"[1,2]\"));\nreturn 0;\n", + ), + // `io.std` is the other shape: a submodule whose parent had no row + // at all, so even the name did not bind. + new( + "io_std_through_its_parent", + "use io;\nlet out = io.std.stdout();\nprintln(io.std.write(out, \"a\"));\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// The `chan` module's own spelling, pinned to pure Cranelift. +/// +/// The surrounding channel cases allow degradation because raises through +/// `select` are recorded debt; these must not, because the bug they cover is +/// exactly a *silent* drop to the VM. `chan` is both a builtin constructor and +/// a module under one name, `builtin_for_name` claimed it first, and the member +/// read found no value — so `chan.new(1)` fell back while `chan(1)` lowered, +/// with both printing the same answer. +#[test] +fn chan_module_lowers_natively() { + run_differential( + "chan_module", + &[ + // The module spelling of the whole channel surface, including the + // blocking pair the module used to lack: `chan` resolves to a + // builtin constructor *and* a module under the same name, and the + // member read was losing to the constructor, so `chan.new(1)` was + // dropping the program to the VM while `chan(1)` lowered. + new( + "chan_module_spelling_blocking_and_polling", + "use chan;\nlet c = chan.new(2);\nchan.send(c, 7);\nprintln(chan.try_send(c, 8));\nprintln(chan.recv(c));\nprintln(chan.try_recv(c) ?? -1);\nprintln(chan.len(c));\nprintln(chan.capacity(c));\nprintln(chan.is_closed(c));\nchan.close(c);\nprintln(chan.is_closed(c));\nreturn 0;\n", + ), + // `0` is unbuffered, not unbounded — lkrt had kept the retired rule, + // and answered `true` to both sends. + new( + "chan_capacity_zero_is_unbuffered", + "use chan;\nlet c = chan.new(0);\nprintln(chan.try_send(c, 1));\nprintln(chan.try_send(c, 2));\nprintln(chan.len(c));\ntry { chan.new(-1); println(\"no\"); } catch e { println(\"caught\"); }\nreturn 0;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// `as` casts, with the native path pinned: the point of these is that the two +/// backends agree *bit for bit*, not merely that both produce something. +/// +/// The VM masks inside its `i64` carrier and sign-extends back; Cranelift does +/// `ireduce` then `sextend`/`uextend`. Those are different mechanisms, so this +/// is where a divergence would show up. +/// `try` regions that lower natively: the body becomes a function, and the +/// `setjmp` happens in `lkrt`'s C frame because Cranelift cannot emit one. +/// +/// Both outcomes are here. A body that raises must reach the handler with the +/// raised value intact, and a body that returns must skip it — a region that +/// only ever worked on one of those paths would pass half a test. +#[test] +fn try_region_differential() { + run_differential( + "try_region", + &[ + new( + "caught", + "fn boom() { error(404); return 0; }\nlet b = 0;\ntry { boom(); } catch code { b = code; }\nreturn b;\n", + ), + new( + "not_raised", + "fn fine() { return 7; }\nlet b = 0;\ntry { fine(); } catch e { b = 1; }\nreturn b;\n", + ), + // The body reads the enclosing function's locals. They are its + // *parameters* once it is outlined, discovered by lowering it and + // seeing which registers had no definition inside — so an argument + // arriving in the wrong order or under the wrong number shows up + // here as the wrong branch being taken. + new( + "reads_outer_locals", + "fn checked(a: Int, b: Int) -> Int {\n if (b == 0) { error(\"zero\"); }\n return a - b;\n}\n\ + let x = 10;\nlet y = 0;\nlet out = 0;\ntry { checked(x, y); } catch e { out = 1; }\nreturn out;\n", + ), + new( + "reads_outer_locals_no_raise", + "fn checked(a: Int, b: Int) -> Int {\n if (b == 0) { error(\"zero\"); }\n return a - b;\n}\n\ + let x = 10;\nlet y = 3;\nlet out = 0;\ntry { checked(x, y); } catch e { out = 1; }\nreturn out;\n", + ), + // The body *assigns* an enclosing local. It cannot travel in a + // register — the body runs in a frame of its own — so it goes + // through a cell, written as the body goes rather than on the way + // out: a raise half way through must leave behind what was already + // assigned, which is what the VM shows. + new( + "writes_outer_local", + "fn fine() { return 7; }\nlet a = 0;\ntry { fine(); a = 1; } catch e { a = 2; }\nreturn a;\n", + ), + new( + "writes_then_raises", + "fn boom() { error(\"x\"); return 0; }\nlet a = 0;\ntry { a = 5; boom(); a = 9; } catch e { }\nreturn a;\n", + ), + // Not just integers: what crosses back out of a cell is decided per + // type, and a type with no unboxer rejects rather than guesses. + new( + "writes_outer_bool", + "fn boom() { error(\"x\"); return 0; }\nlet flag = false;\n\ + try { flag = true; boom(); } catch e { }\nif (flag) { return 1; }\nreturn 0;\n", + ), + new( + "writes_outer_string", + "fn boom() { error(\"x\"); return 0; }\nlet s = \"before\";\n\ + try { s = \"during\"; boom(); } catch e { }\nreturn s.len();\n", + ), + // A raise from two frames down still lands in the nearest handler: + // the trampoline's frame is what `longjmp` targets, not the body's. + new( + "deep", + "fn inner() { error(\"deep\"); return 0; }\nfn outer() { return inner(); }\n\ + let b = 0;\ntry { outer(); } catch e { b = 1; }\nreturn b;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Function pointers: an exported function's address, and a call through it. +/// +/// Not a *differential* test in the usual sense — the VM refuses both builtins, +/// because an interpreter has no code addresses to hand out and returning a +/// fake one would produce a program that runs interpreted and jumps into +/// nothing when compiled. What is checked is that the native side computes the +/// answer, which is the whole of the feature: a driver table is an array of +/// these. +#[test] +fn function_pointers_are_native_only() { + use std::process::Command; + + let dir = std::env::temp_dir().join(format!("lk_fnptr_{}", std::process::id())); + let _ = fs::create_dir_all(&dir); + let source = dir.join("fnptr.lk"); + fs::write( + &source, + "#[export(\"probe_add\")]\nfn probe_add(a: Int, b: Int) -> Int {\n return a + b;\n}\n\n\ + let p = unsafe { symbol_address(\"probe_add\") };\nprintln(unsafe { call_address_2(p, 20, 22) });\n", + ) + .expect("write source"); + + // The VM refuses, by name. + let vm = Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(&source) + .output() + .expect("run vm"); + let message = String::from_utf8_lossy(&vm.stderr); + assert!(!vm.status.success(), "the VM must refuse: {message}"); + assert!( + message.contains("symbol_address requires native compilation"), + "the refusal must name the builtin: {message}" + ); + + // Compiled, it answers. + let exe = dir.join("fnptr"); + let compile = Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile"]) + .arg(&source) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("compile"); + assert!( + compile.status.success(), + "compile failed: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let run = Command::new(&exe).output().expect("run native"); + assert_eq!(String::from_utf8_lossy(&run.stdout).trim(), "42"); + let _ = fs::remove_dir_all(&dir); +} + +/// `<<` and `>>`, which lower to the range-checked `lkrt` helpers rather than +/// to a machine shift. Both halves matter: the values have to agree, and so +/// does the *failure* — a shift amount out of range raises on both sides, and +/// masking it (what the hardware would do) would show up here as a native run +/// that succeeded where the VM refused. +#[test] +fn shift_differential() { + run_differential( + "shift", + &[ + new("shl_const", "return 3 << 8;\n"), + new("shr_const", "return 1024 >> 5;\n"), + // Arithmetic, not logical: the sign bit is replicated. + new("shr_negative", "return (0 - 16) >> 2;\n"), + // Variable amounts: the value is not a constant the lowering can fold. + new("shl_variable", "let n = 5;\nreturn 1 << n;\n"), + new("shr_variable", "let n = 3;\nreturn 4096 >> n;\n"), + // Precedence: tighter than comparison, looser than `+` (Rust's). + new("precedence_add", "return 1 << 2 + 3;\n"), + new("precedence_cmp", "if (8 >> 1 == 4) { return 1; }\nreturn 0;\n"), + // Mixed with the other bitwise operators, which lower to machine + // instructions — so this is the two paths meeting. + new("with_mask", "let v = 0xdeadbeef;\nreturn (1 << 12) - 1 & v;\n"), + // The edges of the accepted range. + new("shl_zero", "return 7 << 0;\n"), + new("shl_63", "return 1 << 63;\n"), + // Out of range: both sides must refuse, not mask. + new("shl_out_of_range", "let n = 64;\nreturn 1 << n;\n"), + new("shr_negative_amount", "let n = 0 - 1;\nreturn 1 >> n;\n"), + ], + NativePath::PureCranelift, + ); +} + +#[test] +fn machine_int_cast_differential() { + run_differential( + "machine_int_cast", + &[ + // Narrowing truncates rather than erroring: 300 & 0xFF. + new("narrow_u8", "let x = 300 as u8;\nreturn x;\n"), + // Sign extension back into the carrier — the case most likely to + // diverge between a mask and an `ireduce`. + new("sign_extend_i8", "let x = 255 as i8;\nreturn x;\n"), + new("sign_extend_i8_min", "let x = 128 as i8;\nreturn x;\n"), + new("sign_extend_i16", "let x = 65535 as i16;\nreturn x;\n"), + // Negative source, unsigned target: reinterpretation, not clamping. + new("negative_to_u32", "let x = (0 - 1) as u32;\nreturn x;\n"), + new("negative_to_u8", "let x = (0 - 1) as u8;\nreturn x;\n"), + // The second cast must see the first one's result, not the original. + new("chained", "let x = 300 as u8 as u32;\nreturn x;\n"), + // Full width is a no-op on both sides. + new("identity_i64", "let x = (0 - 1) as i64;\nreturn x;\n"), + // Pointer width follows the carrier on a 64-bit host. + new("usize_passthrough", "let x = 42 as usize;\nreturn x;\n"), + // Float and bool sources: the VM converts them (truncating toward + // zero, 0/1) before reducing to width, so the native path needs + // the same conversion rather than only accepting integers. + new("float_source", "let x = 3.9 as i32;\nreturn x;\n"), + new("float_source_negative", "let x = (0.0 - 3.9) as i32;\nreturn x;\n"), + // Out of range: Rust's `as` saturates before the width reduction, + // on both sides — the case a trapping conversion would abort on. + new("float_source_saturates", "let x = 1.0e30 as i64;\nreturn x;\n"), + new("bool_source", "let x = true as u8;\nreturn x;\n"), + // A source that came out of a container is boxed, so the native + // path unboxes through `dyn.cast_to_i64` rather than reading a + // register — a different mechanism from the register case above, + // and the one an output loop in a driver actually hits. + new( + "boxed_source_from_list", + "let xs = [300, 255];\nlet out = 0 as u8;\nfor x in xs { out = out + (x as u8); }\nreturn out;\n", + ), + // The boxed path must truncate a Float toward zero and read a Bool + // as 0/1, exactly as the VM's `cast_source_to_i64` does — the two + // cases where an `as_i64`-style unbox would raise instead. + new( + "boxed_source_float", + "let xs = [3.9, 0.0 - 3.9];\nfor x in xs { println(x as i32); }\nreturn 0;\n", + ), + new( + "boxed_source_bool", + "let xs = [true, false];\nlet out = 0 as u8;\nfor x in xs { out = out + (x as u8); }\nreturn out;\n", + ), + ], + NativePath::PureCranelift, + ); +} + +/// Machine-int *arithmetic* wraps to its width, on both backends. +/// +/// The wrap is emitted as a normalisation after the 64-bit operation, reusing +/// the same cast path — so what this really checks is that every lowering +/// entry point (plain, lower-into-register, compound assignment) applies it. +/// A missing one produces a plainly wrong number rather than a crash, which is +/// why it needs a test rather than an assertion. +#[test] +fn machine_int_arithmetic_wraps_differential() { + run_differential( + "machine_int_arith", + &[ + // 300 & 0xFF + new("add_u8", "let a: u8 = 200;\nlet b: u8 = 100;\nreturn a + b;\n"), + // 600 & 0xFF + new("mul_u8", "let a: u8 = 200;\nreturn a * (3 as u8);\n"), + // 200 sign-extended from 8 bits + new( + "add_i8_overflows_negative", + "let a: i8 = 100;\nreturn a + (100 as i8);\n", + ), + // Borrowing past zero on an unsigned width. + new("sub_u8_underflows", "let a: u8 = 10;\nreturn a - (20 as u8);\n"), + // 70000 & 0xFFFF + new("add_u16", "let a: u16 = 60000;\nreturn a + (10000 as u16);\n"), + // The wrap has to apply at each step, not just the last one. + new( + "chained_arithmetic_wraps_each_step", + "let a: u8 = 200;\nlet b: u8 = 100;\nlet c = a + b;\nreturn c + b;\n", + ), + // Division keeps the width rather than promoting to Float the way + // `Int / Int` does. + new("div_keeps_width", "let a: u8 = 200;\nreturn a / (3 as u8);\n"), + ], + NativePath::PureCranelift, + ); +} + +/// A volatile read must survive optimisation. +/// +/// Reading one address twice has to produce two accesses: a device register can +/// return different values on consecutive reads, and reading it can have side +/// effects. This is a *disassembly* test rather than a differential one because +/// the failure is invisible at the value level — with the reads collapsed the +/// program still returns a plausible number, just one derived from a single +/// access. +/// +/// The failure it guards is not hypothetical and not historical: removing the +/// `sequence_point` that `Inst::VolatileLoad` emits still compiles this to one +/// `mov` and a `lea` doubling it. Cranelift has no volatile flag; what keeps +/// both accesses is that its alias analysis keys every access by the last store +/// before it, and a sequence point — which assembles to nothing — moves that +/// key. So the assertion counts *instructions*, which is the thing at risk. It +/// used to count calls into `lkrt`, back when a device read was a call. +#[test] +fn volatile_reads_are_not_collapsed() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("volatile_twice.lk"); + std::fs::write( + &source, + "fn read_twice(addr: usize) -> Int {\n\ + \x20 let reg = addr as *mut u32;\n\ + \x20 let a = unsafe { volatile_read_u32(reg) } as Int;\n\ + \x20 let b = unsafe { volatile_read_u32(reg) } as Int;\n\ + \x20 return a + b;\n\ + }\n\ + return read_twice(0x1000);\n", + ) + .expect("write source"); + + let exe = dir.path().join("volatile_twice"); + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "volatile must lower natively"); + + let disassembly = std::process::Command::new("objdump") + .args(["-d", exe.to_str().expect("utf-8 path")]) + .output(); + let Ok(disassembly) = disassembly else { + // objdump is not everywhere; the compile above is still meaningful. + return; + }; + let text = String::from_utf8_lossy(&disassembly.stdout); + let body: String = text + .lines() + .skip_while(|line| !line.contains(":")) + .take_while(|line| !line.trim().is_empty()) + .collect::>() + .join("\n"); + let accesses = count_loads(&body); + assert_eq!(accesses, 2, "expected two volatile reads, got {accesses}:\n{body}"); +} + +/// Volatile writes to *different* addresses keep their order. +/// +/// The two tests around this one guard against accesses being *removed*. This +/// one guards the property `drivers/e1000.lk` calls "the entire transmit +/// protocol": a descriptor is filled in, and only then is the card's tail +/// register bumped to tell it to look. Swap those and the card transmits a +/// descriptor that was not finished being written — on real hardware, and quite +/// possibly not in QEMU, which is the worst way for a bug to be shaped. +/// +/// x86-64 does not reorder stores in hardware, so what is being pinned here is +/// the *compiler* half: nothing in the MIR passes or in Cranelift's scheduling +/// may move one volatile store past another. It holds today; nothing else +/// notices if it stops. +#[test] +fn volatile_writes_keep_their_order() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("volatile_order.lk"); + // Distinct immediates, so the order is readable off the disassembly without + // having to decode which address each store names. + std::fs::write( + &source, + "#[export]\n\ + fn tx(desc: usize, tail: usize) -> Int {\n\ + \x20 unsafe { volatile_write_u64(desc as *mut u64, 0x1111 as u64); };\n\ + \x20 unsafe { volatile_write_u32((desc + 8) as *mut u32, 0x2222 as u32); };\n\ + \x20 unsafe { volatile_write_u32(tail as *mut u32, 0x3333 as u32); };\n\ + \x20 return 0;\n\ + }\n\ + println(0);\n", + ) + .expect("write source"); + + let exe = dir.path().join("volatile_order"); + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "volatile must lower natively"); + + let Ok(disassembly) = std::process::Command::new("objdump") + .args(["-d", exe.to_str().expect("utf-8 path")]) + .output() + else { + // objdump is not everywhere; the compile above is still meaningful. + return; + }; + let text = String::from_utf8_lossy(&disassembly.stdout); + // `#[export]` so the symbol is the source's own name rather than a numbered + // one that renumbers whenever a function is added above it. + let body: String = text + .lines() + .skip_while(|line| !line.contains(":")) + .skip(1) + .take_while(|line| !line.trim().is_empty() && !line.contains(">:")) + .collect::>() + .join("\n"); + + let positions: Vec> = ["0x1111", "0x2222", "0x3333"] + .iter() + .map(|needle| body.find(needle)) + .collect(); + for (needle, position) in ["0x1111", "0x2222", "0x3333"].iter().zip(&positions) { + assert!(position.is_some(), "{needle} was not written at all:\n{body}"); + } + let positions: Vec = positions.into_iter().flatten().collect(); + assert!( + positions[0] < positions[1] && positions[1] < positions[2], + "the three volatile writes were reordered:\n{body}" + ); +} + +/// Two identical writes to one address must stay two writes. +/// +/// The mirror image of `volatile_reads_are_not_collapsed`, and a distinct +/// mechanism: what eliminates this one is the alias pass's *idempotent store* +/// rule, which drops a store of a value the location is already known to hold. +/// It compares SSA values, not runtime ones, so two `write(port, 7)` lines in a +/// row are exactly its target — and a command register that counts writes is +/// exactly the device for which one write is not two. Measured: without the +/// sequence point, `movb $0x7,(%rdi)` is emitted once for a source that says it +/// twice. +#[test] +fn identical_volatile_writes_are_not_collapsed() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("volatile_write_twice.lk"); + std::fs::write( + &source, + "fn kick_twice(addr: usize) {\n\ + \x20 let reg = addr as *mut u8;\n\ + \x20 unsafe { volatile_write_u8(reg, 7 as u8); };\n\ + \x20 unsafe { volatile_write_u8(reg, 7 as u8); };\n\ + }\n\ + kick_twice(0x1000);\n\ + return 0;\n", + ) + .expect("write source"); + + let exe = dir.path().join("volatile_write_twice"); + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "volatile writes must lower natively"); + + let Some(body) = native_body(&exe, "lk_fn_1") else { + return; // objdump is not everywhere; the compile above still ran. + }; + let writes = count_stores(&body); + assert_eq!(writes, 2, "expected two volatile writes, got {writes}:\n{body}"); +} + +/// The disassembly of one function of a compiled executable, or `None` when +/// there is no `objdump` to ask. +fn native_body(exe: &std::path::Path, symbol: &str) -> Option { + let output = std::process::Command::new("objdump") + .args(["-d", exe.to_str().expect("utf-8 path")]) + .output() + .ok()?; + let text = String::from_utf8_lossy(&output.stdout); + let marker = format!("<{symbol}>:"); + Some( + text.lines() + .skip_while(|line| !line.contains(&marker)) + .take_while(|line| !line.trim().is_empty()) + .collect::>() + .join("\n"), + ) +} + +/// Counts loads through a bare register-indirect address — `mov (%rdi),%esi` +/// and its width variants. +/// +/// Deliberately narrow. A device access is emitted as exactly this shape, while +/// the prologue and epilogue move between registers (`mov %rsp,%rbp`) and a +/// spill would carry a frame-pointer offset. Counting every `mov` would pass +/// for the wrong reason. +fn count_loads(body: &str) -> usize { + body.lines() + .filter_map(mov_operands) + .filter(|operands| operands.starts_with("(%r") && operands.contains("),%")) + .count() +} + +/// The operand text of one `objdump -d` line, when its mnemonic is a `mov`. +/// +/// The line is `address:\tbytes\tmnemonic operands`, so the instruction is the +/// last tab-separated field and the operands are what follows its first run of +/// whitespace. Splitting on the *first* tab instead lands in the middle of the +/// raw bytes, which is a silent zero rather than an error. +/// +/// The `mov` restriction is not decoration: `lea (%r8,%rdi,1),%rax` has operands +/// shaped exactly like a load's and touches no memory at all. Matching on the +/// operand shape alone counted the address arithmetic that *follows* two device +/// reads as a third read. +fn mov_operands(line: &str) -> Option<&str> { + let instruction = line.rsplit('\t').next()?.trim(); + let (mnemonic, rest) = instruction.split_once(char::is_whitespace)?; + mnemonic.starts_with("mov").then(|| rest.trim()) +} + +/// Counts stores to a bare register-indirect address — `movb $0x7,(%rdi)`. +fn count_stores(body: &str) -> usize { + body.lines() + .filter_map(mov_operands) + .filter(|operands| operands.ends_with(')') && operands.contains(",(%r")) + .count() +} + +/// A critical section lowers to the right sequence, in the right order. +/// +/// Order is the whole point and it is invisible in the return value: masking +/// interrupts *after* the register write, or dropping the barrier, produces a +/// program that returns the same number and races on real hardware. So this +/// checks the emitted call sequence rather than the result. +#[test] +fn critical_section_emits_its_instructions_in_order() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("critical.lk"); + std::fs::write( + &source, + "fn critical(addr: usize) -> Int {\n\ + \x20 let reg = addr as *mut u32;\n\ + \x20 let saved = unsafe { cpu_irq_save() };\n\ + \x20 unsafe { volatile_write_u32(reg, 1 as u32); };\n\ + \x20 unsafe { cpu_barrier(); };\n\ + \x20 let v = unsafe { volatile_read_u32(reg) } as Int;\n\ + \x20 unsafe { cpu_irq_restore(saved); };\n\ + \x20 return v;\n\ + }\n\ + return critical(0x1000);\n", + ) + .expect("write source"); + + let exe = dir.path().join("critical"); + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "a critical section must lower natively"); + + let Ok(disassembly) = std::process::Command::new("objdump") + .args(["-d", exe.to_str().expect("utf-8 path")]) + .output() + else { + return; // objdump is not everywhere; the compile above still ran. + }; + let text = String::from_utf8_lossy(&disassembly.stdout); + let body: Vec<&str> = text + .lines() + .skip_while(|line| !line.contains(":")) + .take_while(|line| !line.trim().is_empty()) + .collect(); + + // Two of the five are instructions rather than calls, which is the whole + // change: a device access no longer goes through `lkrt`. What is being + // checked is unchanged — that the mask, the write, the barrier, the read + // and the restore appear in the order the source puts them. + enum Step { + Call(&'static str), + Load, + Store, + } + let expected = [ + Step::Call("lkrt_cpu_irq_save"), + Step::Store, + Step::Call("lkrt_cpu_barrier"), + Step::Load, + Step::Call("lkrt_cpu_irq_restore"), + ]; + let mut remaining = expected.iter(); + let mut wanted = remaining.next(); + for line in &body { + let matched = match wanted { + Some(Step::Call(name)) => line.contains(name), + Some(Step::Load) => count_loads(line) == 1, + Some(Step::Store) => count_stores(line) == 1, + None => false, + }; + if matched { + wanted = remaining.next(); + } + } + let wanted = wanted.map(|step| match step { + Step::Call(name) => name, + Step::Load => "a load through a register-indirect address", + Step::Store => "a store through a register-indirect address", + }); + assert!( + wanted.is_none(), + "missing or out-of-order: still looking for {wanted:?} in:\n{}", + body.join("\n") + ); +} + +/// Port I/O lowers to opaque `lkrt` calls, and two reads of one port stay two. +/// +/// The same reasoning as `volatile_reads_are_not_collapsed`, for a different +/// address space: a UART's status port answers differently on each read, so +/// collapsing a poll loop's read is a hang rather than a wrong number. The ABI +/// marks the reads `WritesHost` to prevent it; this checks that it holds after +/// lowering, not merely that the annotation is present. +#[test] +fn port_reads_are_not_collapsed() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("ports.lk"); + std::fs::write( + &source, + "fn poll(port: Int) -> Int {\n\ + \x20 let a = unsafe { port_in_u8(port) };\n\ + \x20 let b = unsafe { port_in_u8(port) };\n\ + \x20 return (a as Int) + (b as Int);\n\ + }\n\ + return poll(0x3f8);\n", + ) + .expect("write source"); + + let exe = dir.path().join("ports"); + let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .status() + .expect("run lk compile"); + assert!(status.success(), "port I/O must lower natively"); + + let Ok(disassembly) = std::process::Command::new("objdump") + .args(["-d", exe.to_str().expect("utf-8 path")]) + .output() + else { + // objdump is not everywhere; the compile above is still meaningful. + return; + }; + let text = String::from_utf8_lossy(&disassembly.stdout); + let body: String = text + .lines() + .skip_while(|line| !line.contains(":")) + .take_while(|line| !line.trim().is_empty()) + .collect::>() + .join("\n"); + let accesses = body.matches("lkrt_port_in_u8").count(); + assert_eq!(accesses, 2, "expected two port reads, got {accesses}:\n{body}"); +} + +/// A file import that carries constants as well as functions. +/// +/// The native path bundles imports at compile time, and a bundled module's +/// entry — the only code that would run its top-level assignments — is the one +/// function the merge drops. So its constants are folded into each read +/// instead. That is a rewrite of the program, and the only thing that shows it +/// was faithful is the two backends still agreeing. +#[test] +fn bundled_import_constants_match_the_vm() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("dep.lk"), + "const BASE = 0x3f8;\n\ + const SCALE = 2.5;\n\ + const LABEL = \"dep\";\n\ + const ON = true;\n\ + fn offset(n: Int) -> Int { return BASE + n; }\n\ + fn scaled(n: Int) -> Float { return n * SCALE; }\n\ + fn label() -> String { return LABEL; }\n\ + fn flag() -> Bool { return ON; }\n", + ) + .expect("write dep"); + let main = dir.path().join("main.lk"); + std::fs::write( + &main, + "use { offset, scaled, label, flag, BASE } from \"dep\";\n\ + println(offset(8));\n\ + println(scaled(4));\n\ + println(label());\n\ + println(flag());\n\ + println(BASE);\n\ + return 0;\n", + ) + .expect("write main"); + + let vm = Command::new(bin_path()) + .current_dir(dir.path()) + .arg("main.lk") + .output() + .expect("spawn vm run"); + assert!( + vm.status.success(), + "vm run failed: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let exe = dir.path().join("main"); + let compile = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["compile", "main.lk"]) + .arg("--output") + .arg(&exe) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("spawn native compile"); + assert!( + compile.status.success(), + "a module of constants and functions must lower natively: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let native = Command::new(&exe).output().expect("spawn compiled executable"); + + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + String::from_utf8_lossy(&native.stdout), + "bundled constants diverged between the backends" + ); +} + +/// A bundled module may import another file. +/// +/// The bundler walks the import graph rather than one level of it, and the +/// lowering resolves a nested module's names — which never appear in the +/// importing file's own import list — through the flattened namespace the +/// merge produces. +#[test] +fn nested_bundled_imports_match_the_vm() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::create_dir(dir.path().join("lib")).expect("mkdir lib"); + std::fs::write( + dir.path().join("lib/bits.lk"), + "const MASK = 0xff;\nfn low_byte(v: Int) -> Int { return v & MASK; }\n", + ) + .expect("write bits"); + std::fs::write( + dir.path().join("lib/dev.lk"), + "use { low_byte } from \"bits\";\n\ + const BASE = 0x3f8;\n\ + fn reg(offset: Int) -> Int { return low_byte(BASE + offset); }\n", + ) + .expect("write dev"); + std::fs::write( + dir.path().join("main.lk"), + "use { reg } from \"lib/dev\";\n\ + use { low_byte } from \"lib/bits\";\n\ + println(reg(5));\n\ + println(low_byte(0x1234));\n\ + return 0;\n", + ) + .expect("write main"); + + let vm = Command::new(bin_path()) + .current_dir(dir.path()) + .arg("main.lk") + .output() + .expect("spawn vm run"); + assert!( + vm.status.success(), + "vm run failed: {}", + String::from_utf8_lossy(&vm.stderr) + ); + + let exe = dir.path().join("main"); + let compile = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["compile", "main.lk"]) + .arg("--output") + .arg(&exe) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("spawn native compile"); + assert!( + compile.status.success(), + "a module importing another module must lower natively: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let native = Command::new(&exe).output().expect("spawn compiled executable"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + String::from_utf8_lossy(&native.stdout), + "a nested import diverged between the backends" + ); +} + +/// A container at a bundled module's top level is refused, not flattened. /// -/// Reading one address twice has to produce two accesses: a device register can -/// return different values on consecutive reads, and reading it can have side -/// effects. This is a *disassembly* test rather than a differential one because -/// the failure is invisible at the value level — with the reads collapsed the -/// program still returns a plausible number, just one derived from a single -/// access. That is exactly how the first implementation passed by inspection -/// and failed here: inline Cranelift loads compiled to one `mov` and a `lea` -/// doubling it, because Cranelift has no volatile flag and its egraph pass -/// proved the two loads equal. +/// Bundling merges modules into one, which would *share* the container with +/// the importer; the VM gives each module its own copy. The two answers differ +/// as soon as anything mutates it, so the bundler rejects the shape rather +/// than producing a program that computes something the VM would not. #[test] -fn volatile_reads_are_not_collapsed() { +fn bundled_module_container_constants_are_refused() { let dir = tempfile::tempdir().expect("temp dir"); - let source = dir.path().join("volatile_twice.lk"); std::fs::write( - &source, - "fn read_twice(addr: usize) -> Int {\n\ - \x20 let reg = addr as *mut u32;\n\ - \x20 let a = unsafe { volatile_read_u32(reg) };\n\ - \x20 let b = unsafe { volatile_read_u32(reg) };\n\ - \x20 return a + b;\n\ - }\n\ - return read_twice(0x1000);\n", + dir.path().join("table.lk"), + "const NAMES = [\"zero\", \"one\"];\nfn get() -> List { return NAMES; }\n", ) - .expect("write source"); + .expect("write dep"); + std::fs::write( + dir.path().join("main.lk"), + "use { get } from \"table\";\nprintln(get().len());\nreturn 0;\n", + ) + .expect("write main"); - let exe = dir.path().join("volatile_twice"); - let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) - .args(["compile", source.to_str().expect("utf-8 path")]) + let compile = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["compile", "main.lk"]) .arg("--output") - .arg(exe.to_str().expect("utf-8 path")) + .arg(dir.path().join("main")) .env("LK_AOT_NO_FALLBACK", "1") .env("LK_AOT_HYBRID", "0") - .status() - .expect("run lk compile"); - assert!(status.success(), "volatile must lower natively"); - - let disassembly = std::process::Command::new("objdump") - .args(["-d", exe.to_str().expect("utf-8 path")]) - .output(); - let Ok(disassembly) = disassembly else { - // objdump is not everywhere; the compile above is still meaningful. - return; - }; - let text = String::from_utf8_lossy(&disassembly.stdout); - let body: String = text - .lines() - .skip_while(|line| !line.contains(":")) - .take_while(|line| !line.trim().is_empty()) - .collect::>() - .join("\n"); - let accesses = body.matches("lkrt_mmio_read_u32").count(); - assert_eq!(accesses, 2, "expected two volatile reads, got {accesses}:\n{body}"); + .output() + .expect("spawn native compile"); + assert!( + !compile.status.success(), + "a shared container must not compile silently" + ); + let stderr = String::from_utf8_lossy(&compile.stderr); + assert!( + stderr.contains("container at its top level"), + "the refusal should say what is wrong: {stderr}" + ); } -/// A critical section lowers to the right sequence, in the right order. +/// An exported-but-unused function in a bundled module does not fail the build. /// -/// Order is the whole point and it is invisible in the return value: masking -/// interrupts *after* the register write, or dropping the barrier, produces a -/// program that returns the same number and races on real hardware. So this -/// checks the emitted call sequence rather than the result. +/// Bundled functions are reached by name, which the bytecode reachability scan +/// cannot follow, so they were all rooted. A module exports more than any one +/// importer uses, and lowering a function nothing calls can fail the whole +/// module for a shape that never runs — its parameter types have no call site +/// to be observed from, so they are not even known. #[test] -fn critical_section_emits_its_instructions_in_order() { +fn an_unused_bundled_function_does_not_fail_the_module() { let dir = tempfile::tempdir().expect("temp dir"); - let source = dir.path().join("critical.lk"); std::fs::write( - &source, - "fn critical(addr: usize) -> Int {\n\ - \x20 let reg = addr as *mut u32;\n\ - \x20 let saved = unsafe { cpu_irq_save() };\n\ - \x20 unsafe { volatile_write_u32(reg, 1 as u32); };\n\ - \x20 unsafe { cpu_barrier(); };\n\ - \x20 let v = unsafe { volatile_read_u32(reg) };\n\ - \x20 unsafe { cpu_irq_restore(saved); };\n\ - \x20 return v;\n\ - }\n\ - return critical(0x1000);\n", + dir.path().join("lib.lk"), + // `each` is never called: its list parameter has no observed type. + "fn used(n: Int) -> Int { return n + 1; }\n\ + fn each(xs: List) -> Int { let s = 0; for x in xs { s = s + x; } return s; }\n", ) - .expect("write source"); + .expect("write dep"); + std::fs::write( + dir.path().join("main.lk"), + "use { used } from \"lib\";\nprintln(used(1));\nreturn 0;\n", + ) + .expect("write main"); - let exe = dir.path().join("critical"); - let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) - .args(["compile", source.to_str().expect("utf-8 path")]) + let compile = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["compile", "main.lk"]) .arg("--output") - .arg(exe.to_str().expect("utf-8 path")) + .arg(dir.path().join("main")) .env("LK_AOT_NO_FALLBACK", "1") .env("LK_AOT_HYBRID", "0") - .status() - .expect("run lk compile"); - assert!(status.success(), "a critical section must lower natively"); - - let Ok(disassembly) = std::process::Command::new("objdump") - .args(["-d", exe.to_str().expect("utf-8 path")]) .output() - else { - return; // objdump is not everywhere; the compile above still ran. - }; - let text = String::from_utf8_lossy(&disassembly.stdout); - let body: Vec<&str> = text - .lines() - .skip_while(|line| !line.contains(":")) - .take_while(|line| !line.trim().is_empty()) - .collect(); - - let expected = [ - "lkrt_cpu_irq_save", - "lkrt_mmio_write_u32", - "lkrt_cpu_barrier", - "lkrt_mmio_read_u32", - "lkrt_cpu_irq_restore", - ]; - let mut remaining = expected.iter(); - let mut wanted = remaining.next(); - for line in &body { - if let Some(name) = wanted - && line.contains(name) - { - wanted = remaining.next(); - } - } + .expect("spawn native compile"); assert!( - wanted.is_none(), - "missing or out-of-order: still looking for {wanted:?} in:\n{}", - body.join("\n") + compile.status.success(), + "an unused export must not fail the module: {}", + String::from_utf8_lossy(&compile.stderr) ); } -/// Port I/O lowers to opaque `lkrt` calls, and two reads of one port stay two. +/// A boxed container index lowers, rather than failing the module. /// -/// The same reasoning as `volatile_reads_are_not_collapsed`, for a different -/// address space: a UART's status port answers differently on each read, so -/// collapsing a poll loop's read is a hang rather than a wrong number. The ABI -/// marks the reads `WritesHost` to prevent it; this checks that it holds after -/// lowering, not merely that the annotation is present. +/// Iterating a list yields a `Maybe` carrier; passing that as an argument +/// boxes it. So `fn at(xs, i) { return xs[i]; }` called from `for i in idx` +/// sees a `Dyn` index — an ordinary shape that had no lowering, which made a +/// two-function library fail to compile with an error naming neither function. #[test] -fn port_reads_are_not_collapsed() { +fn a_boxed_container_index_matches_the_vm() { + run_clif_differential( + "boxed_index", + &[ + new( + "read", + "fn at(xs: List, i: Int) -> Int { return xs[i]; }\n\ + let xs = [10, 20, 30];\n\ + let idx = [0, 2];\n\ + let total = 0;\n\ + for i in idx { total = total + at(xs, i); }\n\ + return total;\n", + ), + new( + "write", + "fn put(xs: List, i: Int, v: Int) { xs[i] = v; }\n\ + let xs = [0, 0, 0];\n\ + let idx = [0, 2];\n\ + for i in idx { put(xs, i, 7); }\n\ + return xs[0] + xs[2];\n", + ), + // A non-integer index is rejected by the type checker before it + // reaches the lowering, so the unbox only ever sees an integer in + // a well-typed program. It still goes through the runtime's tag + // check rather than reading the payload blind, because `Dyn` is + // also what an untyped path produces. + ], + ); +} + +/// A module that writes through a container parameter is not bundled. +/// +/// Bundling flattens the modules together, so the callee would get the +/// caller's container by reference; the VM runs them as separate modules with +/// separate heaps and copies arguments across the boundary (see +/// `copy_runtime_positional_args_to_frame`). The two disagree the moment the +/// callee writes — `xs[0]` reads 0 under the VM and 7 under a flattened build +/// — and nothing reports it. So the bundler declines. +/// +/// What is checked here is the refusal and its wording. That the fallback then +/// produces the VM's answer is the Tier 0 path's own guarantee, and exercising +/// it here would drag a cargo build of the embedded runtime into a unit test. +#[test] +fn a_module_that_mutates_a_parameter_is_not_bundled() { let dir = tempfile::tempdir().expect("temp dir"); - let source = dir.path().join("ports.lk"); std::fs::write( - &source, - "fn poll(port: Int) -> Int {\n\ - \x20 let a = unsafe { port_in_u8(port) };\n\ - \x20 let b = unsafe { port_in_u8(port) };\n\ - \x20 return (a as Int) + (b as Int);\n\ - }\n\ - return poll(0x3f8);\n", + dir.path().join("m.lk"), + "fn put(xs: List, i: Int, v: Int) { xs[i] = v; }\n", ) - .expect("write source"); - - let exe = dir.path().join("ports"); - let status = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) - .args(["compile", source.to_str().expect("utf-8 path")]) - .arg("--output") - .arg(exe.to_str().expect("utf-8 path")) - .env("LK_AOT_NO_FALLBACK", "1") - .env("LK_AOT_HYBRID", "0") - .status() - .expect("run lk compile"); - assert!(status.success(), "port I/O must lower natively"); - - let Ok(disassembly) = std::process::Command::new("objdump") - .args(["-d", exe.to_str().expect("utf-8 path")]) - .output() - else { - // objdump is not everywhere; the compile above is still meaningful. - return; - }; - let text = String::from_utf8_lossy(&disassembly.stdout); - let body: String = text - .lines() - .skip_while(|line| !line.contains(":")) - .take_while(|line| !line.trim().is_empty()) - .collect::>() - .join("\n"); - let accesses = body.matches("lkrt_port_in_u8").count(); - assert_eq!(accesses, 2, "expected two port reads, got {accesses}:\n{body}"); + .expect("write dep"); + std::fs::write( + dir.path().join("main.lk"), + "use { put } from \"m\";\nlet xs = [0, 0, 0];\nput(xs, 0, 7);\nprintln(xs[0]);\nreturn 0;\n", + ) + .expect("write main"); + + // An object build has no fallback to take, so the refusal has to name the + // cause rather than the unlowerable instruction it would otherwise become. + let strict = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["compile", "object:x86_64-unknown-none", "main.lk"]) + .arg("--output") + .arg(dir.path().join("main.o")) + .output() + .expect("spawn object compile"); + assert!(!strict.status.success(), "a shared container must not compile silently"); + let stderr = String::from_utf8_lossy(&strict.stderr); + assert!( + stderr.contains("container parameter"), + "the refusal should say what is wrong: {stderr}" + ); } -/// A file import that carries constants as well as functions. +/// A module that only *reads* its container parameters still bundles. /// -/// The native path bundles imports at compile time, and a bundled module's -/// entry — the only code that would run its top-level assignments — is the one -/// function the merge drops. So its constants are folded into each read -/// instead. That is a rewrite of the program, and the only thing that shows it -/// was faithful is the two backends still agreeing. +/// The refusal above has to be narrow, or every library that takes a list +/// stops compiling natively. #[test] -fn bundled_import_constants_match_the_vm() { +fn a_module_that_only_reads_a_parameter_still_bundles() { let dir = tempfile::tempdir().expect("temp dir"); std::fs::write( - dir.path().join("dep.lk"), - "const BASE = 0x3f8;\n\ - const SCALE = 2.5;\n\ - const LABEL = \"dep\";\n\ - const ON = true;\n\ - fn offset(n: Int) -> Int { return BASE + n; }\n\ - fn scaled(n: Int) -> Float { return n * SCALE; }\n\ - fn label() -> String { return LABEL; }\n\ - fn flag() -> Bool { return ON; }\n", + dir.path().join("m.lk"), + "fn total(xs: List) -> Int { let s = 0; for x in xs { s = s + x; } return s; }\n\ + fn at(xs: List, i: Int) -> Int { return xs[i]; }\n\ + fn size(xs: List) -> Int { return xs.len(); }\n", ) .expect("write dep"); - let main = dir.path().join("main.lk"); std::fs::write( - &main, - "use { offset, scaled, label, flag, BASE } from \"dep\";\n\ - println(offset(8));\n\ - println(scaled(4));\n\ - println(label());\n\ - println(flag());\n\ - println(BASE);\n\ + dir.path().join("main.lk"), + "use { total, at, size } from \"m\";\n\ + let xs = [1, 2, 3];\n\ + println(total(xs));\n\ + println(at(xs, 1));\n\ + println(size(xs));\n\ return 0;\n", ) .expect("write main"); @@ -761,12 +4600,6 @@ fn bundled_import_constants_match_the_vm() { .arg("main.lk") .output() .expect("spawn vm run"); - assert!( - vm.status.success(), - "vm run failed: {}", - String::from_utf8_lossy(&vm.stderr) - ); - let exe = dir.path().join("main"); let compile = Command::new(bin_path()) .current_dir(dir.path()) @@ -779,53 +4612,146 @@ fn bundled_import_constants_match_the_vm() { .expect("spawn native compile"); assert!( compile.status.success(), - "a module of constants and functions must lower natively: {}", + "a read-only module must still bundle: {}", String::from_utf8_lossy(&compile.stderr) ); let native = Command::new(&exe).output().expect("spawn compiled executable"); - assert_eq!( String::from_utf8_lossy(&vm.stdout), String::from_utf8_lossy(&native.stdout), - "bundled constants diverged between the backends" + "a read-only bundled module diverged" ); } -/// A bundled module may import another file. +/// An imported function's signature is visible to the type checker. /// -/// The bundler walks the import graph rather than one level of it, and the -/// lowering resolves a nested module's names — which never appear in the -/// importing file's own import list — through the flattened namespace the -/// merge produces. +/// Without it the name is `Any`: a range bound, a condition and a cast all +/// need better than that, so a program that reads perfectly well needs +/// annotations that say nothing — and a call with the wrong number of +/// arguments is not checked at all, surfacing much later from the native +/// lowering as "opcode CallDirect is not natively lowerable", which names +/// neither the call nor the reason. #[test] -fn nested_bundled_imports_match_the_vm() { +fn an_imported_signature_is_checked() { let dir = tempfile::tempdir().expect("temp dir"); - std::fs::create_dir(dir.path().join("lib")).expect("mkdir lib"); std::fs::write( - dir.path().join("lib/bits.lk"), - "const MASK = 0xff;\nfn low_byte(v: Int) -> Int { return v & MASK; }\n", + dir.path().join("lib.lk"), + "fn add(a: Int, b: Int) -> Int { return a + b; }\nfn count() -> Int { return 3; }\n", ) - .expect("write bits"); + .expect("write dep"); + // A range bound and a cast, neither of which accepts `Any`. std::fs::write( - dir.path().join("lib/dev.lk"), - "use { low_byte } from \"bits\";\n\ - const BASE = 0x3f8;\n\ - fn reg(offset: Int) -> Int { return low_byte(BASE + offset); }\n", + dir.path().join("ok.lk"), + "use { add, count } from \"lib\";\n\ + let total = 0;\n\ + for i in 0..count() { total = total + add(i, 1); }\n\ + return total;\n", ) - .expect("write dev"); + .expect("write ok"); + std::fs::write(dir.path().join("bad.lk"), "use { add } from \"lib\";\nreturn add(1);\n").expect("write bad"); + + let ok = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["check", "ok.lk"]) + .output() + .expect("spawn check"); + assert!( + ok.status.success(), + "an imported signature should make annotations unnecessary: {}", + String::from_utf8_lossy(&ok.stderr) + ); + + let bad = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["check", "bad.lk"]) + .output() + .expect("spawn check"); + assert!( + !bad.status.success(), + "a wrong-arity call across a module must be caught" + ); + let stderr = String::from_utf8_lossy(&bad.stderr); + assert!( + stderr.contains("arguments"), + "the error should be about the call, not an opcode: {stderr}" + ); +} + +/// An argument's type is checked against an *annotated* parameter. +/// +/// The distinction matters more than the check: an unannotated parameter also +/// ends up with a type, because inference gives it one from the body, but that +/// is a derivation rather than a claim. `fn scale(x) { return x * 2.5; }` may +/// settle on `Int` for `x`, and rejecting `scale(4.0)` against it would reject +/// on something the program never said. +#[test] +fn argument_types_are_checked_against_annotations() { + let dir = tempfile::tempdir().expect("temp dir"); + let check = |name: &str, source: &str| { + std::fs::write(dir.path().join(name), source).expect("write source"); + Command::new(bin_path()) + .current_dir(dir.path()) + .args(["check", name]) + .output() + .expect("spawn check") + }; + + let annotated = check( + "annotated.lk", + "fn add(a: Int, b: Int) -> Int { return a + b; }\nreturn add(1, \"x\");\n", + ); + assert!(!annotated.status.success(), "a wrong argument type must be caught"); + let stderr = String::from_utf8_lossy(&annotated.stderr); + assert!( + stderr.contains("Argument 2") && stderr.contains("expected Int"), + "the error should name the position and the types: {stderr}" + ); + + let inferred = check("inferred.lk", "fn scale(x) { return x * 2.5; }\nreturn scale(4.0);\n"); + assert!( + inferred.status.success(), + "an inferred parameter type is not a claim to check against: {}", + String::from_utf8_lossy(&inferred.stderr) + ); + + // A machine-integer parameter takes an integer literal without a cast. + // They do not convert implicitly — that is what makes `u8 + Int` an error + // — but a literal has no type of its own to preserve. + let literal = check( + "literal.lk", + "fn port(number: u16) -> Int { return number as Int; }\nreturn port(0x3f8);\n", + ); + assert!( + literal.status.success(), + "an integer literal should reach a machine-int parameter: {}", + String::from_utf8_lossy(&literal.stderr) + ); +} + +/// `#[extern]` names a function implemented outside the program. +/// +/// The mirror of `#[export]`. A native build calls the symbol and never emits +/// the body; the interpreter, which cannot reach outside, runs the body. That +/// asymmetry is the point and also the cost: this is the one construct whose +/// two back ends are not checked against each other, because the thing being +/// called is not in the program. +#[test] +fn an_extern_function_calls_the_named_symbol() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("ext.lk"); std::fs::write( - dir.path().join("main.lk"), - "use { reg } from \"lib/dev\";\n\ - use { low_byte } from \"lib/bits\";\n\ - println(reg(5));\n\ - println(low_byte(0x1234));\n\ + &source, + "#[extern(\"kernel_double\")]\n\ + fn kernel_double(value: Int) -> Int { return value * 2; }\n\ + println(kernel_double(21));\n\ return 0;\n", ) - .expect("write main"); + .expect("write source"); + // The interpreter runs the body. let vm = Command::new(bin_path()) .current_dir(dir.path()) - .arg("main.lk") + .arg("ext.lk") .output() .expect("spawn vm run"); assert!( @@ -833,398 +4759,917 @@ fn nested_bundled_imports_match_the_vm() { "vm run failed: {}", String::from_utf8_lossy(&vm.stderr) ); + assert_eq!(String::from_utf8_lossy(&vm.stdout), "42\n0\n"); + + // The object refers to the symbol and leaves it to the linker. + let object = dir.path().join("ext.o"); + let compile = Command::new(bin_path()) + .current_dir(dir.path()) + .args(["compile", "object:x86_64-unknown-none", "ext.lk"]) + .arg("--output") + .arg(&object) + .output() + .expect("spawn object compile"); + assert!( + compile.status.success(), + "an extern call must lower: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let bytes = std::fs::read(&object).expect("read object"); + let needle = b"kernel_double"; + assert!( + bytes.windows(needle.len()).any(|window| window == needle), + "the object should name the symbol it calls" + ); +} + +/// `join` writes every element the way the language writes it. +/// +/// It used to raise "ListJoin list must contain only strings" for any carrier +/// but `Str` — so `[1, 2].join(",")` type-checked and failed at run time, while +/// `"${[1, 2]}"` had been printing `[1,2]` all along. Worse, the AOT lowering +/// declined `join` on the numeric carriers *citing that rule*, which is how one +/// arbitrary restriction becomes two. +/// +/// The values below are the ones where two renderers drift apart if there are +/// two: a float that is integral (`2.0` → `2`), a negative zero, an exponent, +/// and a NaN. Both ends go through the renderer their own display path uses, so +/// this is the test that says they are the same renderer. +#[test] +fn join_covers_every_carrier_and_agrees_on_how_a_value_looks() { + run_clif_differential( + "list_join", + &[ + new( + "join_each_carrier", + "println([1, 2, 3].join(\"-\"));\nprintln([1.5, 2.0].join(\",\"));\n\ + println([\"a\", \"b\"].join(\", \"));\nprintln([1, \"a\", nil, true].join(\"|\"));\n\ + return 0;\n", + ), + new( + "join_of_an_empty_list_is_an_empty_string", + "println([].join(\",\"));\nprintln([1].join(\",\"));\n\ + println(([1, 2].join(\"\")).len());\nreturn 0;\n", + ), + // Where a second renderer would show: integral floats, signed zero, + // exponents, NaN and infinity. + new( + "join_writes_floats_the_way_display_does", + "println([2.0, -0.0, 1e20, 1e-7].join(\" \"));\n\ + println([0.0 / 0.0, 1.0 / 0.0, -1.0 / 0.0].join(\" \"));\nreturn 0;\n", + ), + // The separator is not a delimiter the elements may contain. + new( + "a_separator_that_occurs_in_the_elements", + "println([\"a,b\", \"c\"].join(\",\"));\nprintln([11, 1].join(\"1\"));\nreturn 0;\n", + ), + ], + ); +} + +/// The carrier a list happens to have does not decide which methods stay native. +/// +/// A sweep of every list method against every carrier found three holes, each of +/// a different kind: +/// +/// * `contains` on `Str` — `str_contains` was declared in the ABI and reached +/// only from the `in` operator, so `"a" in xs` lowered and `xs.contains("a")` +/// did not. The comment beside those arms states the invariant it broke: a +/// carrier whose `index_of` lowers and whose `contains` does not makes +/// `xs.contains(v)` and `xs.index_of(v) != nil` disagree about which programs +/// stay native. +/// * two-argument `slice` — only `Int` had it, and the rule it needs (negative +/// counts from the tail, everything clamps) now lives in one `slice_bounds` +/// that all four carriers share, rather than being written out four times. +/// * `flatten` — only the boxed carrier. A typed list cannot nest, so flatten +/// there is a copy, which `slice_from(0)` already is. +#[test] +fn every_carrier_answers_contains_slice_and_flatten() { + run_clif_differential( + "list_carrier_parity", + &[ + new( + "contains_on_every_carrier", + "let n = 3;\nlet i = [1, 2, n];\nlet f = [1.5, 2.5, 3.5];\nlet s = [\"a\", \"b\"];\n\ + println(i.contains(2));\nprintln(f.contains(9.5));\nprintln(s.contains(\"b\"));\n\ + println(s.contains(\"z\"));\nreturn 0;\n", + ), + // The spelling that already lowered, so the two agree. + new( + "contains_agrees_with_the_in_operator", + "let s = [\"a\", \"b\"];\nprintln(s.contains(\"a\") == (\"a\" in s));\n\ + println(s.contains(\"z\") == (\"z\" in s));\nreturn 0;\n", + ), + // Every branch of the shared bounds rule: negative, clamped, inverted. + new( + "two_argument_slice_on_every_carrier", + "let n = 4;\nlet i = [1, 2, 3, n];\nlet f = [1.5, 2.5, 3.5];\nlet s = [\"a\", \"b\", \"c\"];\n\ + println(i.slice(1, 3));\nprintln(f.slice(0, 2));\nprintln(s.slice(1, 3));\n\ + println(s.slice(-2, 3));\nprintln(f.slice(0, 99));\nprintln(i.slice(3, 1));\n\ + println(s.slice(-99, 99));\nreturn 0;\n", + ), + // A typed list has nothing to flatten, and the result is a copy: the + // receiver must not move when the answer is pushed to. + new( + "flatten_of_a_typed_list_copies_it", + "let n = 3;\nlet xs = [1, 2, n];\nlet ys = xs.flatten();\nys.push(9);\n\ + println(xs);\nprintln(ys);\nprintln([\"a\"].flatten());\n\ + println([1.5].flatten());\nreturn 0;\n", + ), + ], + ); +} - let exe = dir.path().join("main"); - let compile = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["compile", "main.lk"]) - .arg("--output") - .arg(&exe) - .env("LK_AOT_NO_FALLBACK", "1") - .env("LK_AOT_HYBRID", "0") - .output() - .expect("spawn native compile"); - assert!( - compile.status.success(), - "a module importing another module must lower natively: {}", - String::from_utf8_lossy(&compile.stderr) - ); - let native = Command::new(&exe).output().expect("spawn compiled executable"); - assert_eq!( - String::from_utf8_lossy(&vm.stdout), - String::from_utf8_lossy(&native.stdout), - "a nested import diverged between the backends" +/// `m.clear()` lowers, like the list's and the set's. +/// +/// It was the one container method the map lacked natively, so a function using +/// it dropped to the VM for a reason no program can see. `Map` rides +/// the `str_i64` carrier, so five helpers cover the six map types the MIR +/// distinguishes — and every one of them is exercised here, because a carrier +/// wired to the wrong helper would still compile. +#[test] +fn clear_lowers_on_every_map_carrier() { + run_clif_differential( + "map_clear", + &[ + new( + "clear_each_carrier", + "let n = 2;\nlet si = {\"a\": 1, \"b\": n};\nlet sf = {\"a\": 1.5, \"b\": 2.5};\n\ + let sb = {\"a\": true, \"b\": false};\nlet ii = {1: 10, 2: 20};\nlet if_ = {1: 1.5, 2: 2.5};\n\ + si.clear();\nsf.clear();\nsb.clear();\nii.clear();\nif_.clear();\n\ + println(si.len());\nprintln(sf.len());\nprintln(sb.len());\n\ + println(ii.len());\nprintln(if_.len());\nreturn 0;\n", + ), + // Clearing is in place: the receiver is empty afterwards, and still + // usable. + new( + "a_cleared_map_is_empty_and_still_a_map", + "let n = 2;\nlet m = {\"a\": 1, \"b\": n};\nprintln(m.len());\nm.clear();\n\ + println(m.len());\nprintln(m.has(\"a\"));\nm[\"c\"] = 7;\n\ + println(m.len());\nprintln(m[\"c\"]);\nreturn 0;\n", + ), + ], ); } -/// A container at a bundled module's top level is refused, not flattened. +/// Which spelling you use, and which carrier the list happens to have, do not +/// decide whether a program stays native. /// -/// Bundling merges modules into one, which would *share* the container with -/// the importer; the VM gives each module its own copy. The two answers differ -/// as soon as anything mutates it, so the bundler rejects the shape rather -/// than producing a program that computes something the VM would not. +/// A full sweep of the 29 declared list methods against the four carriers left +/// two holes after the earlier round: +/// +/// * `concat` — the same operation as `chain` under a second name, with its own +/// two narrower arms. `xs.chain(ys)` lowered on all four carriers and +/// `xs.concat(ys)` on two, so the choice of word decided the outcome. Both +/// narrow arms were subsumed by the general one; deleting them is the fix. +/// * `xs[i] = v` — `Int` and `Float` had arms, `Str` and the boxed carrier did +/// not, and `xs.set(i, v)` is the same opcode, so both spellings fell together. +/// +/// The out-of-bounds store is included because it is the one place these can +/// disagree loudly: the VM halts, and every carrier's helper has to halt with +/// the same words. #[test] -fn bundled_module_container_constants_are_refused() { - let dir = tempfile::tempdir().expect("temp dir"); - std::fs::write( - dir.path().join("table.lk"), - "const NAMES = [\"zero\", \"one\"];\nfn get() -> List { return NAMES; }\n", - ) - .expect("write dep"); - std::fs::write( - dir.path().join("main.lk"), - "use { get } from \"table\";\nprintln(get().len());\nreturn 0;\n", - ) - .expect("write main"); +fn concat_and_index_assignment_do_not_depend_on_the_carrier() { + run_clif_differential( + "list_carrier_parity_2", + &[ + new( + "concat_agrees_with_chain_on_every_carrier", + "let n = 3;\nlet i = [1, n];\nlet f = [1.5, 2.5];\nlet s = [\"a\", \"b\"];\n\ + println(i.concat(i) == i.chain(i));\nprintln(f.concat(f) == f.chain(f));\n\ + println(s.concat(s) == s.chain(s));\nprintln(s.concat(s));\n\ + println(f.concat(f));\nreturn 0;\n", + ), + new( + "index_assignment_on_every_carrier", + "let n = 3;\nlet i = [1, 2, n];\nlet f = [1.5, 2.5];\nlet s = [\"a\", \"b\"];\n\ + i[0] = 9;\nf[1] = 9.5;\ns[0] = \"z\";\ns.set(1, \"y\");\n\ + println(i);\nprintln(f);\nprintln(s);\n\ + s[-1] = \"tail\";\nprintln(s);\nreturn 0;\n", + ), + // A store past the end halts on both ends, with the same words. + new( + "a_store_out_of_bounds_halts_the_same_way", + "let s = [\"a\"];\nprintln(try { s[5] = \"x\"; \"no\" } catch e { \"caught: ${e}\" });\n\ + println(try { s[-9] = \"x\"; \"no\" } catch e { \"caught: ${e}\" });\n\ + println(s);\nreturn 0;\n", + ), + ], + ); +} - let compile = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["compile", "main.lk"]) - .arg("--output") - .arg(dir.path().join("main")) - .env("LK_AOT_NO_FALLBACK", "1") - .env("LK_AOT_HYBRID", "0") - .output() - .expect("spawn native compile"); - assert!( - !compile.status.success(), - "a shared container must not compile silently" +/// Every sequence carrier answers the same question the same way at the edges. +/// +/// The edges are where the four carriers had drifted before, one pair at a +/// time: `bytes.slice(b, 2, 1)` raised where the method clamped, `s[-1]` +/// counted back from the *byte* length, `index_of` answered `-1` on one side +/// and nil on the other. Each was found by writing that one case down; this is +/// the grid, so the next one is found by the corpus instead. +/// +/// A reversed window is empty, a position past either end is nil, a *count* +/// past the end clamps — and a negative count is a refusal, because a count is +/// not a position. The programs print values rather than comparisons: two +/// carriers agreeing on a wrong answer is exactly what a comparison hides. +#[test] +fn every_sequence_reads_the_same_edge_positions() { + run_clif_differential( + "sequence_edges", + &[ + new( + "reversed_and_out_of_range_windows", + "use bytes;\nlet z = \"\";\nlet s = \"abcde\" + z;\nlet xs = [1, 2, 3, 4, 5];\n\ + let b = bytes.from_string(s);\nlet w = xs.slice(0, 5);\n\ + println(s.slice(3, 1));\nprintln(xs.slice(3, 1).to_list());\n\ + println(b.slice(3, 1));\nprintln(w.slice(3, 1).to_list());\n\ + println(s.slice(-1, -3));\nprintln(xs.slice(-1, -3).to_list());\n\ + println(b.slice(-1, -3));\n\ + println(s.slice(-99, 99));\nprintln(xs.slice(-99, 99).to_list());\n\ + println(b.slice(-99, 99));\nreturn 0;\n", + ), + new( + "positions_past_either_end_are_nil", + "use bytes;\nlet z = \"\";\nlet s = \"abc\" + z;\nlet xs = [1, 2, 3];\n\ + let b = bytes.from_string(s);\n\ + println(s.get(-1));\nprintln(xs.get(-1));\nprintln(b.get(-1));\n\ + println(s.get(-99));\nprintln(xs.get(-99));\nprintln(b.get(-99));\n\ + println(s.get(99));\nprintln(xs.get(99));\nprintln(b.get(99));\n\ + println(s.index_of(\"z\"));\nprintln(xs.index_of(99));\nprintln(b.index_of(122));\n\ + println(\"\".first());\nprintln([].first());\nprintln(\"\".bytes().first());\n\ + println(\"\".last());\nprintln([].last());\nprintln(\"\".bytes().last());\nreturn 0;\n", + ), + new( + "a_count_clamps_past_the_end_and_refuses_a_negative", + "use bytes;\nlet z = \"\";\nlet s = \"abc\" + z;\nlet xs = [1, 2, 3];\n\ + let b = bytes.from_string(s);\n\ + println(s.take(99));\nprintln(xs.take(99));\nprintln(b.take(99));\n\ + println(s.skip(99));\nprintln(xs.skip(99));\nprintln(b.skip(99));\n\ + println(try { \"${s.take(0 - 1)}\" } catch e { \"${e}\" });\n\ + println(try { \"${xs.take(0 - 1)}\" } catch e { \"${e}\" });\n\ + println(try { \"${b.take(0 - 1)}\" } catch e { \"${e}\" });\n\ + println(try { \"${s.skip(0 - 1)}\" } catch e { \"${e}\" });\n\ + println(try { \"${xs.skip(0 - 1)}\" } catch e { \"${e}\" });\n\ + println(try { \"${b.skip(0 - 1)}\" } catch e { \"${e}\" });\nreturn 0;\n", + ), + ], ); - let stderr = String::from_utf8_lossy(&compile.stderr); - assert!( - stderr.contains("container at its top level"), - "the refusal should say what is wrong: {stderr}" +} + +/// `Bytes` is a receiver kind, not four methods and a carrier. +/// +/// It had `len`, `is_empty`, `get` and `slice`; the other ten of its fourteen +/// declared methods dropped the whole module to the VM. A receiver that is +/// *almost* native is the shape a coverage percentage cannot show — the corpus +/// compiles, the number stays 60/60, and every program touching bytes is slow. +/// +/// `first`/`last` reuse `get` (a negative position already counts from the end). +/// `take`/`skip` do *not* reuse `slice`: a count is not a position, so a negative +/// one is the loud error the VM gives rather than something measured from the +/// tail — which is exactly what the last two cases here pin. `index_of` answers +/// nil on a miss, never -1, because -1 is a legal position and +/// `b[b.index_of(v)]` would quietly read the last byte instead of failing. +#[test] +fn bytes_answers_its_whole_method_surface_natively() { + run_clif_differential( + "bytes_methods", + &[ + new( + "reads_and_windows", + "let b = \"abcde\".bytes();\nprintln(b.len());\nprintln(b.first());\n\ + println(b.last());\nprintln(b.take(2));\nprintln(b.skip(2));\n\ + println(b.take(99));\nprintln(b.skip(99));\nprintln(b.to_list());\n\ + println(b.slice(1, 3));\nreturn 0;\n", + ), + new( + "membership_answers_nil_on_a_miss", + "let b = \"abc\".bytes();\nprintln(b.contains(97));\nprintln(b.contains(122));\n\ + println(b.index_of(98));\nprintln(b.index_of(122));\n\ + println(b.index_of(-1));\nprintln(b.index_of(300));\n\ + println(b.contains(300));\nreturn 0;\n", + ), + // The module spelling of every one of them, plus the three that + // used to have no method at all. `bytes.slice(b, 2, 1)` is the one + // that had two answers: the module raised, the method clamped to an + // empty window, and the module forwards to the method now. + new( + "the_module_spelling_agrees", + "use bytes;\nlet z = \"\";\nlet b = bytes.from_string(\"abcde\" + z);\n\ + println(bytes.len(b) == b.len());\nprintln(bytes.is_empty(b) == b.is_empty());\n\ + println(bytes.get(b, -1) == b.get(-1));\nprintln(bytes.first(b) == b.first());\n\ + println(bytes.last(b) == b.last());\nprintln(bytes.contains(b, 98) == b.contains(98));\n\ + println(bytes.index_of(b, 98) == b.index_of(98));\nprintln(bytes.sum(b) == b.sum());\n\ + println(bytes.min(b) == b.min());\nprintln(bytes.max(b) == b.max());\n\ + println(bytes.take(b, 2) == b.take(2));\nprintln(bytes.skip(b, 2) == b.skip(2));\n\ + println(bytes.slice(b, 1, 3) == b.slice(1, 3));\n\ + println(bytes.to_list(b) == b.to_list());\n\ + println(bytes.to_string_utf8(b) == b.to_string_utf8());\n\ + println(bytes.to_string_lossy(b) == b.to_string_lossy());\n\ + println(bytes.concat(b, b) == b.concat(b));\n\ + println(bytes.from_string(\"xy\") == \"xy\".bytes());\n\ + println(bytes.from_list([1, 2]) == [1, 2].to_bytes());\n\ + println(bytes.slice(b, 2, 1));\nprintln(b.slice(2, 1));\n\ + println(b.to_string_utf8());\nprintln(b.concat(b));\n\ + println([65, 66].to_bytes());\nreturn 0;\n", + ), + new( + "an_empty_bytes_reads_as_nil", + "let b = \"\".bytes();\nprintln(b.len());\nprintln(b.is_empty());\n\ + println(b.first());\nprintln(b.last());\nprintln(b.take(3));\n\ + println(b.index_of(97));\nreturn 0;\n", + ), + // `map`/`filter`/`reduce` reach the callback channel by *becoming* an + // `Int` list first — byte values lose nothing in the conversion. The + // shapes differ on the way back and that asymmetry is the VM's: + // `map` may produce anything so it answers a list, `filter` only + // removes so it answers `Bytes`, `reduce` answers a scalar. + new( + "closures_over_bytes_keep_the_vm_result_shapes", + "let b = \"abc\".bytes();\nprintln(b.map(|v| v + 1));\nprintln(b.filter(|v| v > 97));\n\ + println(b.reduce(0, |a, x| a + x));\nprintln(b.filter(|v| false));\n\ + println(b.map(|v| v * 2).len());\nprintln(\"\".bytes().map(|v| v));\n\ + println(\"\".bytes().reduce(7, |a, x| a + x));\nreturn 0;\n", + ), + // A count is not a position: negative raises, on both ends, with the + // same words. + new( + "a_negative_count_is_the_same_loud_error", + "let b = \"abc\".bytes();\n\ + println(try { b.take(-1); \"no\" } catch e { \"caught: ${e}\" });\n\ + println(try { b.skip(-2); \"no\" } catch e { \"caught: ${e}\" });\nreturn 0;\n", + ), + ], ); } -/// An exported-but-unused function in a bundled module does not fail the build. +/// `s.values()` is the members in iteration order, and it lowers. /// -/// Bundled functions are reached by name, which the bytecode reachability scan -/// cannot follow, so they were all rooted. A module exports more than any one -/// importer uses, and lowering a function nothing calls can fail the whole -/// module for a shape that never runs — its parameter types have no call site -/// to be observed from, so they are not even known. +/// It was the one Set method with no arm, so a function calling it dropped to +/// the VM while a `for` loop over the same set stayed native — two ways of +/// asking for the same sequence, one of them native. `set.iter` already builds +/// exactly that list; the order is a hash order, so this rides the same mirror +/// discipline that makes set iteration lowerable at all. #[test] -fn an_unused_bundled_function_does_not_fail_the_module() { - let dir = tempfile::tempdir().expect("temp dir"); - std::fs::write( - dir.path().join("lib.lk"), - // `each` is never called: its list parameter has no observed type. - "fn used(n: Int) -> Int { return n + 1; }\n\ - fn each(xs: List) -> Int { let s = 0; for x in xs { s = s + x; } return s; }\n", - ) - .expect("write dep"); - std::fs::write( - dir.path().join("main.lk"), - "use { used } from \"lib\";\nprintln(used(1));\nreturn 0;\n", - ) - .expect("write main"); +fn set_values_is_the_iteration_order_and_lowers() { + run_clif_differential( + "set_values", + &[ + new( + "values_agrees_with_iteration", + "let s = Set([5, 1, 9, 3, 7, 2]);\nlet out = [];\nfor x in s { out.push(x); }\n\ + println(s.values() == out);\nprintln(s.values().len());\n\ + println(Set([]).values());\nreturn 0;\n", + ), + // Mixed kinds, because the order spans them. + new( + "values_over_mixed_members", + "let s = Set([1, \"a\", true, nil]);\nprintln(s.values().len());\n\ + println(s.values().contains(\"a\"));\nprintln(s.values().contains(1));\nreturn 0;\n", + ), + ], + ); +} - let compile = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["compile", "main.lk"]) - .arg("--output") - .arg(dir.path().join("main")) - .env("LK_AOT_NO_FALLBACK", "1") - .env("LK_AOT_HYBRID", "0") - .output() - .expect("spawn native compile"); - assert!( - compile.status.success(), - "an unused export must not fail the module: {}", - String::from_utf8_lossy(&compile.stderr) +/// `math` answers the same values *and* the same errors on both ends. +/// +/// Nine of the module's twenty functions did not lower: `tan` while `sin` and +/// `cos` did, the whole inverse and logarithm families, and `clamp`. The split +/// was not a rule — it was where someone stopped. +/// +/// The domain guards matter more than the values. `math.sqrt(-1.0)` used to +/// print its real reason to *stderr* and raise `"runtime error"`, so +/// `try { math.sqrt(-1.0) } catch e { e }` was `"sqrt() argument must be +/// non-negative"` interpreted and `"runtime error"` compiled — and a caught +/// error's text is the program's output, not a diagnostic. Every guard added +/// here raises the stdlib module's own sentence, and this test is what pins +/// them word for word. +#[test] +fn math_agrees_on_values_and_on_domain_errors() { + run_clif_differential( + "math_surface", + &[ + new( + "the_whole_module_lowers", + "use math;\nlet n = 2.0;\nprintln(math.tan(0.0));\nprintln(math.asin(0.5));\n\ + println(math.acos(0.5));\nprintln(math.atan(1.0));\nprintln(math.atan2(1.0, n));\n\ + println(math.log(1.0));\nprintln(math.log10(100.0));\nprintln(math.log2(8.0));\n\ + println(math.clamp(5, 1, 3));\nprintln(math.clamp(0, 1, 3));\n\ + println(math.clamp(2, 1, 3));\nreturn 0;\n", + ), + // The words, not just the fact that it raised. + new( + "a_domain_error_carries_the_modules_own_words", + "use math;\n\ + println(try { math.sqrt(-1.0); \"no\" } catch e { \"${e}\" });\n\ + println(try { math.asin(2.0); \"no\" } catch e { \"${e}\" });\n\ + println(try { math.acos(-2.0); \"no\" } catch e { \"${e}\" });\n\ + println(try { math.log(0.0); \"no\" } catch e { \"${e}\" });\n\ + println(try { math.log10(-1.0); \"no\" } catch e { \"${e}\" });\n\ + println(try { math.log2(0.0); \"no\" } catch e { \"${e}\" });\n\ + println(try { math.clamp(5, 3, 1); \"no\" } catch e { \"${e}\" });\nreturn 0;\n", + ), + // Edges the two ends could disagree on quietly. + new( + "edges_of_the_domains", + "use math;\nprintln(math.asin(1.0));\nprintln(math.acos(-1.0));\n\ + println(math.log(1.0));\nprintln(math.atan2(0.0, 0.0));\n\ + println(math.sqrt(0.0));\nprintln(math.clamp(1, 1, 1));\nreturn 0;\n", + ), + ], ); } -/// A boxed container index lowers, rather than failing the module. +/// `string.f(s, …)` and `s.f(…)` are the same call, so they lower the same way. /// -/// Iterating a list yields a `Maybe` carrier; passing that as an argument -/// boxes it. So `fn at(xs, i) { return xs[i]; }` called from `for i in idx` -/// sees a `Dyn` index — an ordinary shape that had no lowering, which made a -/// two-function library fail to compile with an error naming neither function. +/// The forwarder that makes a module spelling reach the method arm was gated on +/// `matches!(module, "iter" | "stream")` — a list of two, not a rule. So every +/// one of the `string` module's functions fell back to the VM while its method +/// spelling lowered, and which spelling a program happened to use decided +/// whether it stayed native. The VM routes both through the same +/// `core_methods`; all thirteen pairs below were checked to be equal there +/// before the gate was widened. +/// +/// `split` needed one thing more: it is an *intrinsic* in the bytecode compiler, +/// so the method spelling becomes `Opcode::StringSplit` and never reaches the +/// method table at all. The module spelling does, so the arm it forwards to had +/// to exist — pointed at the same helper the opcode uses, so the two cannot +/// drift. #[test] -fn a_boxed_container_index_matches_the_vm() { +fn the_string_module_spelling_lowers_like_the_method() { run_clif_differential( - "boxed_index", + "string_module_spelling", &[ new( - "read", - "fn at(xs: List, i: Int) -> Int { return xs[i]; }\n\ - let xs = [10, 20, 30];\n\ - let idx = [0, 2];\n\ - let total = 0;\n\ - for i in idx { total = total + at(xs, i); }\n\ - return total;\n", + "each_pair_agrees", + "use string;\nlet z = \"z\";\nlet s = \" Hello World \" + z;\n\ + println(string.trim(s) == s.trim());\nprintln(string.upper(s) == s.upper());\n\ + println(string.lower(s) == s.lower());\nprintln(string.len(s) == s.len());\n\ + println(string.reverse(s) == s.reverse());\n\ + println(string.contains(s, \"Hello\") == s.contains(\"Hello\"));\n\ + println(string.index_of(s, \"World\") == s.index_of(\"World\"));\n\ + println(string.starts_with(s, \" \") == s.starts_with(\" \"));\n\ + println(string.ends_with(s, \"z\") == s.ends_with(\"z\"));\n\ + println(string.slice(s, 0, 4) == s.slice(0, 4));\n\ + println(string.repeat(\"ab\", 2) == \"ab\".repeat(2));\nreturn 0;\n", ), + // The intrinsic pair, and the values themselves rather than only the + // equality — a bug that made both sides equally wrong would pass the + // comparisons above. new( - "write", - "fn put(xs: List, i: Int, v: Int) { xs[i] = v; }\n\ - let xs = [0, 0, 0];\n\ - let idx = [0, 2];\n\ - for i in idx { put(xs, i, 7); }\n\ - return xs[0] + xs[2];\n", + "split_and_replace_by_value", + "use string;\nlet z = \",\";\nprintln(string.split(\"a,b,c\", z));\n\ + println(string.replace(\"aaa\", \"a\", \"b\"));\nprintln(string.trim(\" x \"));\n\ + println(string.upper(\"aBc\"));\nprintln(string.index_of(\"abc\", \"zz\"));\n\ + println(string.slice(\"abcde\", 1, 3));\nreturn 0;\n", + ), + // Multibyte, because every string position in this language is a + // character position and the module spelling must not forget. + new( + "module_spelling_counts_characters_too", + "use string;\nlet s = \"中文abc\";\nprintln(string.len(s));\n\ + println(string.slice(s, 1, 3));\nprintln(string.index_of(s, \"a\"));\n\ + println(string.reverse(s));\nreturn 0;\n", + ), + // The members that used to exist only as module functions, now + // methods that the module forwards to. `count("")` is the one that + // had two answers: `str::matches("")` counts one match between every + // pair of *characters*, and the native helper counted bytes + 1, so + // `string.count("中中", "")` was 7 compiled and 3 interpreted. + new( + "the_members_that_used_to_be_module_only", + "use string;\nlet z = \"\";\nlet s = \"aB cD\" + z;\n\ + println(string.capitalize(s) == s.capitalize());\n\ + println(string.title(s) == s.title());\n\ + println(string.count(s, \"D\") == s.count(\"D\"));\n\ + println(string.strip_prefix(s, \"a\") == s.strip_prefix(\"a\"));\n\ + println(string.strip_suffix(s, \"z\") == s.strip_suffix(\"z\"));\n\ + println(s.capitalize());\nprintln(s.title());\n\ + println(s.count(\"\"));\nprintln(\"中中\".count(\"\"));\n\ + println(\"中中\".count(\"中\"));\n\ + println(s.strip_prefix(\"a\"));\nprintln(s.strip_prefix(\"z\"));\n\ + println(s.strip_suffix(\"D\"));\nprintln(s.strip_suffix(\"z\"));\nreturn 0;\n", + ), + // `strip` and the two pads: character-counted, and the fill repeats + // from its start on both sides. A byte-sliced fill used to cut + // inside a character and take the process down. + new( + "strip_and_pad", + "use string;\nlet z = \"\";\nlet s = \"--a--\" + z;\n\ + println(string.strip(s, \"-\") == s.strip(\"-\"));\n\ + println(string.pad_left(\"a\", 5) == \"a\".pad_left(5));\n\ + println(string.pad_right(\"a\", 5, \"中\") == \"a\".pad_right(5, \"中\"));\n\ + println(s.strip(\"-\"));\nprintln(\"xxaybyxx\".strip(\"xy\"));\n\ + println(\"abc\".strip(\"-\"));\nprintln(\"---\".strip(\"-\"));\n\ + println(\"a\".pad_left(5, \"中\"));\nprintln(\"a\".pad_right(5, \"中\"));\n\ + println(\"中文\".pad_left(4, \"-\"));\nprintln(\"a\".pad_left(5, \"xy\"));\n\ + println(\"abcdef\".pad_left(3, \"-\"));\nprintln(\"a\".pad_right(4));\nreturn 0;\n", + ), + // `format` was the last `string` member lowering on neither + // spelling — variadic, with arguments of differing types. It is the + // same compile-time expansion `println` does with its own template, + // so the interesting cases are the *leftovers*: an unfilled `{}` + // stays literal, and an unconsumed argument appends space + // separated (with the leading space only when the rendered + // template is non-empty). + new( + "format_expands_like_println", + "use string;\nprintln(\"a {} b {}\".format(1, \"x\"));\n\ + println(\"{}\".format(3.5));\nprintln(\"{} {} {}\".format(1));\n\ + println(\"no holes\".format(7, 8));\nprintln(\"\".format(9));\n\ + println(\"{}{}\".format(true, false));\n\ + println(string.format(\"{}-{}\", 2, 3));\n\ + let n = 42;\nprintln(\"n={} m={}\".format(n, n * 2));\n\ + println(\"{}\".format(\"\"));\nreturn 0;\n", + ), + // The refusals, whose text is stdout once it is caught. + new( + "pad_refusals_read_the_same", + "fn main() {\n let s = \"abc\";\n let w = 0 - 1;\n\ + println(\"${try { s.pad_left(w) } catch e { \"${e}\" }}\");\n\ + println(\"${try { s.pad_right(5, \"\") } catch e { \"${e}\" }}\");\n}\nmain();\nreturn 0;\n", ), - // A non-integer index is rejected by the type checker before it - // reaches the lowering, so the unbox only ever sees an integer in - // a well-typed program. It still goes through the runtime's tag - // check rather than reading the payload blind, because `Dyn` is - // also what an untyped path produces. ], ); } -/// A module that writes through a container parameter is not bundled. +/// The `path` module's fixed-arity members answer natively, and answer the same. /// -/// Bundling flattens the modules together, so the callee would get the -/// caller's container by reference; the VM runs them as separate modules with -/// separate heaps and copies arguments across the boundary (see -/// `copy_runtime_positional_args_to_frame`). The two disagree the moment the -/// callee writes — `xs[0]` reads 0 under the VM and 7 under a flattened build -/// — and nothing reports it. So the bundler declines. +/// The module is `std::path` on both ends — the same discipline that keeps the +/// base64/hex text and the datetime formatting byte-identical: share the crate +/// underneath, do not write the rule twice. /// -/// What is checked here is the refusal and its wording. That the fallback then -/// produces the VM's answer is the Tier 0 path's own guarantee, and exercising -/// it here would drag a cargo build of the embedded runtime into a unit test. +/// The `String?` members are the sharp edge. `path.parent("c.txt")` is the empty +/// string while `path.parent("/")` is nil, and `path.extension("a")` and +/// `path.extension(".bashrc")` are both nil for different reasons — a boxed +/// result that got the empty-vs-nil distinction wrong would look right on the +/// common cases. #[test] -fn a_module_that_mutates_a_parameter_is_not_bundled() { - let dir = tempfile::tempdir().expect("temp dir"); - std::fs::write( - dir.path().join("m.lk"), - "fn put(xs: List, i: Int, v: Int) { xs[i] = v; }\n", - ) - .expect("write dep"); - std::fs::write( - dir.path().join("main.lk"), - "use { put } from \"m\";\nlet xs = [0, 0, 0];\nput(xs, 0, 7);\nprintln(xs[0]);\nreturn 0;\n", - ) - .expect("write main"); +fn path_members_answer_the_same_on_both_ends() { + run_clif_differential( + "path_members", + &[ + new( + "the_optional_parts", + "use path;\nlet z = \"\";\nprintln(path.parent(\"a/b/c.txt\" + z));\n\ + println(path.parent(\"c.txt\"));\nprintln(path.parent(\"/\"));\n\ + println(path.file_name(\"a/b/c.txt\"));\nprintln(path.file_name(\"a/b/\"));\n\ + println(path.file_stem(\"a/b/c.tar.gz\"));\nprintln(path.extension(\"a/b/c.tar.gz\"));\n\ + println(path.extension(\"a\"));\nprintln(path.extension(\".bashrc\"));\nreturn 0;\n", + ), + new( + "the_total_parts", + "use path;\nlet z = \"\";\nprintln(path.with_extension(\"a/b.txt\" + z, \"md\"));\n\ + println(path.with_extension(\"a\", \"txt\"));\nprintln(path.is_absolute(\"/a\"));\n\ + println(path.is_absolute(\"a\"));\nprintln(path.components(\"a/b/c\"));\n\ + println(path.components(\"/a/b\"));\nprintln(path.components(\"\"));\n\ + println(path.sep());\nprintln(path.delimiter());\nreturn 0;\n", + ), + ], + ); +} - // An object build has no fallback to take, so the refusal has to name the - // cause rather than the unlowerable instruction it would otherwise become. - let strict = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["compile", "object:x86_64-unknown-none", "main.lk"]) - .arg("--output") - .arg(dir.path().join("main.o")) - .output() - .expect("spawn object compile"); - assert!(!strict.status.success(), "a shared container must not compile silently"); - let stderr = String::from_utf8_lossy(&strict.stderr); - assert!( - stderr.contains("container parameter"), - "the refusal should say what is wrong: {stderr}" +/// `hash` answers the same digest natively, on both carriers. +/// +/// `sha256`/`sha1`/`crc32` come from the same crates the stdlib module uses, so +/// this mostly pins the *hex rendering* and the string→UTF-8-bytes rule. The +/// case that carries real weight is `fnv64`: no crate in either graph provides +/// FNV-1a, so its loop and its two constants exist twice, and a transcription +/// slip in either one is invisible until something compares the numbers. +#[test] +fn hash_members_answer_the_same_on_both_ends() { + run_clif_differential( + "hash_members", + &[ + new( + "string_carrier", + "use hash;\nlet z = \"\";\nprintln(hash.sha256(\"hello\" + z));\n\ + println(hash.sha1(\"hello\"));\nprintln(hash.crc32(\"hello\"));\n\ + println(hash.fnv64(\"hello\"));\nprintln(hash.fnv64(\"\"));\n\ + println(hash.crc32(\"\"));\nprintln(hash.fnv64(\"abc\"));\n\ + println(hash.sha256(\"\"));\nprintln(hash.fnv64(\"中文\"));\nreturn 0;\n", + ), + new( + "bytes_carrier", + "use hash;\nuse bytes;\nuse encoding;\nlet b = bytes.from_string(\"hello\");\n\ + println(hash.sha256(b));\nprintln(hash.sha1(b));\nprintln(hash.crc32(b));\n\ + println(hash.fnv64(b));\nprintln(encoding.base64.encode(b));\n\ + println(encoding.hex.encode(b));\nprintln(encoding.base64.encode(\"hello\"));\n\ + return 0;\n", + ), + ], ); } -/// A module that only *reads* its container parameters still bundles. +/// `uuid` parses, validates and raises identically on both ends. /// -/// The refusal above has to be narrow, or every library that takes a list -/// stops compiling natively. +/// The last one is the reason the native side shares the `uuid` crate rather +/// than re-implementing the parse: `uuid.parse("nope")` raises `invalid UUID: +/// invalid character: found `n` at 0`, where everything after the colon is the +/// crate's own `Display` — and a caught error's message is program output. +/// +/// `v4` cannot be compared directly (that is the point of it), so what the +/// second case compares is everything about it that *is* fixed: the length, the +/// canonical shape as `is_valid` judges it, and that two calls differ — which +/// also pins the ABI classification, since a `Pure` `v4` would be CSE'd into +/// one call and print `false`. #[test] -fn a_module_that_only_reads_a_parameter_still_bundles() { - let dir = tempfile::tempdir().expect("temp dir"); - std::fs::write( - dir.path().join("m.lk"), - "fn total(xs: List) -> Int { let s = 0; for x in xs { s = s + x; } return s; }\n\ - fn at(xs: List, i: Int) -> Int { return xs[i]; }\n\ - fn size(xs: List) -> Int { return xs.len(); }\n", - ) - .expect("write dep"); - std::fs::write( - dir.path().join("main.lk"), - "use { total, at, size } from \"m\";\n\ - let xs = [1, 2, 3];\n\ - println(total(xs));\n\ - println(at(xs, 1));\n\ - println(size(xs));\n\ - return 0;\n", - ) - .expect("write main"); - - let vm = Command::new(bin_path()) - .current_dir(dir.path()) - .arg("main.lk") - .output() - .expect("spawn vm run"); - let exe = dir.path().join("main"); - let compile = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["compile", "main.lk"]) - .arg("--output") - .arg(&exe) - .env("LK_AOT_NO_FALLBACK", "1") - .env("LK_AOT_HYBRID", "0") - .output() - .expect("spawn native compile"); - assert!( - compile.status.success(), - "a read-only module must still bundle: {}", - String::from_utf8_lossy(&compile.stderr) - ); - let native = Command::new(&exe).output().expect("spawn compiled executable"); - assert_eq!( - String::from_utf8_lossy(&vm.stdout), - String::from_utf8_lossy(&native.stdout), - "a read-only bundled module diverged" +fn uuid_members_answer_the_same_on_both_ends() { + run_clif_differential( + "uuid_members", + &[ + new( + "parse_and_validate", + "use uuid;\nlet z = \"\";\n\ + println(uuid.parse(\"550E8400-E29B-41D4-A716-446655440000\" + z));\n\ + println(uuid.parse(\"550e8400e29b41d4a716446655440000\"));\n\ + println(uuid.is_valid(\"550e8400-e29b-41d4-a716-446655440000\"));\n\ + println(uuid.is_valid(\"nope\"));\nprintln(uuid.is_valid(\"\"));\n\ + let r = try { uuid.parse(\"nope\") } catch e { e };\nprintln(r);\nreturn 0;\n", + ), + new( + "v4_shape", + "use uuid;\nlet a = uuid.v4();\nlet b = uuid.v4();\n\ + println(a.len());\nprintln(uuid.is_valid(a));\nprintln(a != b);\n\ + println(uuid.parse(a) == a);\nreturn 0;\n", + ), + ], ); } -/// An imported function's signature is visible to the type checker. +/// The `fs` surface: same values, same booleans, and the same error text. /// -/// Without it the name is `Any`: a range bound, a condition and a cast all -/// need better than that, so a program that reads perfectly well needs -/// annotations that say nothing — and a call with the wrong number of -/// arguments is not checked at all, surfacing much later from the native -/// lowering as "opcode CallDirect is not natively lowerable", which names -/// neither the call nor the reason. +/// Half of these had an lkrt implementation and an ABI row already but no +/// lowering row, so nothing could call them — and three of the messages +/// (`read_to_string`, `write`, `canonicalize`) had drifted from the stdlib +/// module's wording in the meantime. That is what an unreachable code path is: +/// unverified, not spare. +/// +/// The cases pin the things that are easy to get subtly wrong: `remove_*` +/// answers `false` for an absent path instead of raising, `copy` answers a byte +/// count rather than a bool, `read_dir` sorts, and a raise names the path with +/// the stdlib's exact sentence. #[test] -fn an_imported_signature_is_checked() { - let dir = tempfile::tempdir().expect("temp dir"); - std::fs::write( - dir.path().join("lib.lk"), - "fn add(a: Int, b: Int) -> Int { return a + b; }\nfn count() -> Int { return 3; }\n", - ) - .expect("write dep"); - // A range bound and a cast, neither of which accepts `Any`. - std::fs::write( - dir.path().join("ok.lk"), - "use { add, count } from \"lib\";\n\ - let total = 0;\n\ - for i in 0..count() { total = total + add(i, 1); }\n\ - return total;\n", - ) - .expect("write ok"); - std::fs::write(dir.path().join("bad.lk"), "use { add } from \"lib\";\nreturn add(1);\n").expect("write bad"); - - let ok = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["check", "ok.lk"]) - .output() - .expect("spawn check"); - assert!( - ok.status.success(), - "an imported signature should make annotations unnecessary: {}", - String::from_utf8_lossy(&ok.stderr) +fn fs_members_answer_the_same_on_both_ends() { + run_clif_differential( + "fs_members", + &[ + new( + "files", + "use fs;\nuse bytes;\nlet d = fs.temp_dir() + \"/lk_diff_fs_files\";\n\ + fs.remove_dir_all(d);\nprintln(fs.create_dir_all(d + \"/sub\"));\n\ + println(fs.write(d + \"/b.txt\", \"bbb\"));\n\ + println(fs.write(d + \"/a.txt\", bytes.from_string(\"aaa\")));\n\ + println(fs.append(d + \"/a.txt\", \"!\"));\n\ + println(fs.read_to_string(d + \"/a.txt\"));\nprintln(fs.read_dir(d));\n\ + println(fs.is_file(d + \"/a.txt\"));\nprintln(fs.is_dir(d));\n\ + println(fs.is_file(d + \"/nope\"));\n\ + println(fs.copy(d + \"/a.txt\", d + \"/c.txt\"));\n\ + println(fs.rename(d + \"/c.txt\", d + \"/e.txt\"));\n\ + println(fs.remove_file(d + \"/e.txt\"));\n\ + println(fs.remove_file(d + \"/e.txt\"));\nprintln(fs.read_dir(d));\n\ + println(fs.remove_dir_all(d));\nreturn 0;\n", + ), + new( + "errors_and_env", + "use fs;\nuse env;\nlet d = fs.temp_dir() + \"/lk_diff_fs_missing\";\n\ + let a = try { fs.read_to_string(d) } catch e { e };\nprintln(a);\n\ + let b = try { fs.canonicalize(d) } catch e { e };\nprintln(b);\n\ + let c = try { fs.write(d + \"/x/y\", \"a\") } catch e { e };\nprintln(c);\n\ + let f = try { fs.rename(d, d + \"2\") } catch e { e };\nprintln(f);\n\ + println(fs.exists(d));\nprintln(env.has(\"PATH\"));\n\ + println(env.has(\"LK_NO_SUCH_VAR_XYZ\"));\n\ + let m = fs.temp_dir() + \"/lk_diff_fs_meta.txt\";\n\ + fs.write(m, \"0123456789\");\nprintln(fs.metadata(m));\n\ + fs.remove_file(m);\n\ + let vars = env.vars();\nprintln(vars[\"PATH\"] == env.get_or(\"PATH\", \"\"));\n\ + println(vars.len() > 3);\nreturn 0;\n", + ), + ], ); +} - let bad = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["check", "bad.lk"]) - .output() - .expect("spawn check"); - assert!( - !bad.status.success(), - "a wrong-arity call across a module must be caught" - ); - let stderr = String::from_utf8_lossy(&bad.stderr); - assert!( - stderr.contains("arguments"), - "the error should be about the call, not an opcode: {stderr}" +/// `regex`, and the named-argument spelling of a stdlib member. +/// +/// Named arguments are a `CallNamed`, and that opcode only ever resolved a +/// *user* function — so a stdlib member called by name dropped the whole +/// program to the VM. Every parameter after the subject is named-eligible, and +/// `regex.replace("banana", "a", "X")` is three strings a reader cannot tell +/// apart, so the spelling that lowered was the one nobody is meant to write. +/// +/// The permutation is what this case is really testing: `pattern` and +/// `replacement` swapped would still compile, still run, and answer `banana` +/// instead of `bXnXnX`. +#[test] +fn regex_and_named_arguments_answer_the_same_on_both_ends() { + run_clif_differential( + "regex_named", + &[ + new( + "regex_members", + "use regex;\nlet z = \"\";\nprintln(regex.is_match(\"baaa\" + z, \"a+\"));\n\ + println(regex.is_match(\"baaa\", \"^z\"));\nprintln(regex.split(\"a,b;c\", \"[,;]\"));\n\ + println(regex.split(\"abc\", \"x\"));\n\ + println(regex.replace(\"baaa\", \"a+\", \"X\"));\n\ + println(regex.find(\"xbaaay\", \"a+\"));\nprintln(regex.find(\"abc\", \"z\"));\n\ + println(regex.find_all(\"banana\", \"a\"));\n\ + println(regex.captures(\"xaq\", \"(a)(z)?\"));\n\ + println(regex.captures(\"abc\", \"z\"));\n\ + let e = try { regex.is_match(\"x\", \"(\") } catch err { err };\nprintln(e);\nreturn 0;\n", + ), + new( + "named_arguments", + "use regex;\nuse string;\nuse bytes;\nuse math;\nlet z = \"\";\n\ + println(regex.replace(\"banana\" + z, pattern: \"a\", replacement: \"X\"));\n\ + println(string.replace(\"banana\", pattern: \"a\", with: \"X\"));\n\ + println(string.slice(\"hello\", start: 1, end: 3));\n\ + println(bytes.slice(bytes.from_string(\"hello\"), start: 1, end: 3));\n\ + println(math.clamp(5, min: 1, max: 3));\n\ + println(math.clamp(0, min: 1, max: 3));\n\ + println(math.pow(2 + math.abs(0), exponent: 10));\n\ + println(math.atan2(1, x: 1));\nreturn 0;\n", + ), + // The *mixed* spelling: some named-eligible parameters written + // positionally and the rest by name. The VM has always taken all + // three spellings (`named_and_positional_spellings_mix_freely`), + // and this one alone used to fall back — the row's names were + // indexed from the call's positional count instead of from where + // the declaration's named block starts, so `end` landed past the + // end of a three-argument frame. + new( + "mixed_positional_and_named", + "use regex;\nuse string;\nuse bytes;\nuse math;\nlet z = \"\";\n\ + println(string.slice(\"hello\" + z, 1, end: 3));\n\ + println(string.replace(\"banana\", \"a\", with: \"X\"));\n\ + println(bytes.slice(bytes.from_string(\"hello\"), 1, end: 3));\n\ + println(math.clamp(5, 1, max: 3));\n\ + println(regex.replace(\"banana\", \"a\", replacement: \"X\"));\nreturn 0;\n", + ), + ], ); } -/// An argument's type is checked against an *annotated* parameter. +/// `random`: everything about it that is *not* random has to match. /// -/// The distinction matters more than the check: an unannotated parameter also -/// ends up with a type, because inference gives it one from the body, but that -/// is a derivation rather than a claim. `fn scale(x) { return x * 2.5; }` may -/// settle on `Int` for `x`, and rejecting `scale(4.0)` against it would reject -/// on something the program never said. +/// The values cannot be compared — that is the point of them — so the case +/// pins the frame around them: `int` is inclusive at both ends (hence +/// `random.int(4, 4)`), `float` is the half-open unit interval, `bool(1.0)` and +/// `bool(0.0)` are decided, `choice([])` is nil rather than a raise, `shuffle` +/// keeps the length, and each refusal is the stdlib's exact sentence — the +/// negative-length one included, which is a *type* complaint from the module's +/// shared argument reader and not this member's own wording. #[test] -fn argument_types_are_checked_against_annotations() { - let dir = tempfile::tempdir().expect("temp dir"); - let check = |name: &str, source: &str| { - std::fs::write(dir.path().join(name), source).expect("write source"); - Command::new(bin_path()) - .current_dir(dir.path()) - .args(["check", name]) - .output() - .expect("spawn check") - }; - - let annotated = check( - "annotated.lk", - "fn add(a: Int, b: Int) -> Int { return a + b; }\nreturn add(1, \"x\");\n", - ); - assert!(!annotated.status.success(), "a wrong argument type must be caught"); - let stderr = String::from_utf8_lossy(&annotated.stderr); - assert!( - stderr.contains("Argument 2") && stderr.contains("expected Int"), - "the error should name the position and the types: {stderr}" +fn random_members_behave_the_same_on_both_ends() { + run_clif_differential( + "random_members", + &[ + new( + "bounds_and_shapes", + "use random;\nlet z = 0;\nlet a = random.int(1 + z, 6);\n\ + println(a >= 1 && a <= 6);\nprintln(random.int(4, 4));\n\ + let f = random.float();\nprintln(f >= 0.0 && f < 1.0);\n\ + println(random.bool(1.0));\nprintln(random.bool(0.0));\n\ + let b = random.bool();\nprintln(b == true || b == false);\n\ + println(random.bytes(8).len());\nprintln(random.choice([7]));\n\ + println(random.choice([]));\nprintln(random.shuffle([1, 2, 3]).len());\n\ + println(random.shuffle([\"a\", \"b\"]).len());\n\ + println(random.shuffle([1.5, 2.5]).len());\nreturn 0;\n", + ), + new( + "refusals", + "use random;\nlet z = 0;\n\ + let e = try { random.int(5 + z, 1) } catch err { err };\nprintln(e);\n\ + let e2 = try { random.bool(2.0) } catch err { err };\nprintln(e2);\n\ + let e3 = try { random.bytes(0 - 1) } catch err { err };\nprintln(e3);\n\ + let e4 = try { random.bytes(99999999) } catch err { err };\nprintln(e4);\nreturn 0;\n", + ), + ], ); +} - let inferred = check("inferred.lk", "fn scale(x) { return x * 2.5; }\nreturn scale(4.0);\n"); - assert!( - inferred.status.success(), - "an inferred parameter type is not a claim to check against: {}", - String::from_utf8_lossy(&inferred.stderr) +/// `process`: the child-process members, and `exit`. +/// +/// `id()` is deliberately only compared as `> 0` — the two runs are two +/// processes. What is comparable is everything else: the exit code of a child, +/// its captured stdout, the four-key `output` map (whose key order is the +/// stdlib's insertion order), and the refusals. +/// +/// The `exit` case is the one that needed care: `std::process::exit` runs no +/// destructors, so an unterminated `print` behind a line-buffered stdout is +/// lost unless it is flushed first — and the harness compares stdout *and* the +/// exit status, so both halves of that show up here. +#[test] +fn process_members_answer_the_same_on_both_ends() { + run_clif_differential( + "process_members", + &[ + new( + "children", + "use process;\nlet z = \"\";\nprintln(process.id() > 0);\n\ + println(process.status(\"true\" + z));\nprintln(process.status(\"false\"));\n\ + println(process.output_string(\"echo\", [\"hi\"]));\n\ + println(process.output(\"echo\", [\"hi\"]));\n\ + println(process.output(\"true\"));\n\ + let e = try { process.status(\"lk_no_such_cmd_xyz\") } catch err { err };\nprintln(e);\n\ + let e2 = try { process.set_cwd(\"/lk_no_such_dir_xyz\") } catch err { err };\nprintln(e2);\n\ + return 0;\n", + ), + new( + "exit_flushes", + "use process;\nlet z = 0;\nprint(\"partial\");\nprocess.exit(3 + z);\n", + ), + ], ); +} - // A machine-integer parameter takes an integer literal without a cast. - // They do not convert implicitly — that is what makes `u8 + Int` an error - // — but a literal has no type of its own to preserve. - let literal = check( - "literal.lk", - "fn port(number: u16) -> Int { return number as Int; }\nreturn port(0x3f8);\n", - ); - assert!( - literal.status.success(), - "an integer literal should reach a machine-int parameter: {}", - String::from_utf8_lossy(&literal.stderr) +/// `encoding.*.stringify`: the write direction of the three formats. +/// +/// Object keys come out sorted on both sides because both build a +/// `serde_json::Map`, which is a `BTreeMap` — so this is the one encoding +/// member a map's iteration order does not reach, and the case says so by +/// writing the map with its keys out of order. +/// +/// The refusals are the load-bearing part: TOML has no top-level scalar, a +/// non-string object key would silently collapse `1` and `"1"` onto one entry, +/// and NaN has no JSON form. Each is the stdlib's sentence, prefixed with the +/// member's name the way its `write_format` wrapper does it. +#[test] +fn encoding_stringify_answers_the_same_on_both_ends() { + run_clif_differential( + "encoding_stringify", + &[ + new( + "values", + "use encoding;\nlet z = 1;\nlet m = {\"z\": z, \"a\": [1, 2], \"m\": {\"k\": true}};\n\ + println(encoding.json.stringify(m));\n\ + println(encoding.json.stringify([1, \"two\", 3.5, nil, true]));\n\ + println(encoding.json.stringify(\"plain\"));\n\ + println(encoding.json.stringify(42));\nprintln(encoding.json.stringify(nil));\n\ + println(encoding.yaml.stringify(m));\n\ + println(encoding.toml.stringify({\"a\": 1, \"b\": \"x\"}));\nreturn 0;\n", + ), + new( + "refusals", + "use encoding;\nlet z = 1;\n\ + let a = try { encoding.toml.stringify([z]) } catch e { e };\nprintln(a);\n\ + let b = try { encoding.json.stringify({1: 2}) } catch e { e };\nprintln(b);\n\ + let c = try { encoding.json.stringify(0.0 / 0.0) } catch e { e };\nprintln(c);\nreturn 0;\n", + ), + ], ); } -/// `#[extern]` names a function implemented outside the program. +/// `time.timeout` / `time.after`: a capacity-1 channel that fires once. /// -/// The mirror of `#[export]`. A native build calls the symbol and never emits -/// the body; the interpreter, which cannot reach outside, runs the body. That -/// asymmetry is the point and also the cost: this is the one construct whose -/// two back ends are not checked against each other, because the thing being -/// called is not in the program. +/// Nothing about a timer is byte-comparable except its *shape*, so that is what +/// this pins: `timeout` delivers nil, `after` delivers epoch milliseconds not +/// earlier than the moment it was armed, and the wait really waited. The two +/// implementations are different underneath — a tokio timer on the VM side, a +/// sleeping thread on lkrt's, because lkrt's channels are thread-backed — which +/// is exactly why the observable part needs saying out loud. #[test] -fn an_extern_function_calls_the_named_symbol() { - let dir = tempfile::tempdir().expect("temp dir"); - let source = dir.path().join("ext.lk"); - std::fs::write( - &source, - "#[extern(\"kernel_double\")]\n\ - fn kernel_double(value: Int) -> Int { return value * 2; }\n\ - println(kernel_double(21));\n\ - return 0;\n", - ) - .expect("write source"); - - // The interpreter runs the body. - let vm = Command::new(bin_path()) - .current_dir(dir.path()) - .arg("ext.lk") - .output() - .expect("spawn vm run"); - assert!( - vm.status.success(), - "vm run failed: {}", - String::from_utf8_lossy(&vm.stderr) +fn time_timers_behave_the_same_on_both_ends() { + run_clif_differential( + "time_timers", + &[new( + "timeout_and_after", + "use time;\nuse chan;\nlet z = 0;\nlet t0 = time.now();\n\ + let c = time.timeout(30 + z);\nprintln(chan.recv(c));\n\ + println(time.since(t0, time.now()) >= 25);\n\ + let a = time.after(20);\nlet fired = chan.recv(a);\nprintln(fired >= t0);\n\ + return 0;\n", + )], ); - assert_eq!(String::from_utf8_lossy(&vm.stdout), "42\n0\n"); +} - // The object refers to the symbol and leaves it to the linker. - let object = dir.path().join("ext.o"); - let compile = Command::new(bin_path()) - .current_dir(dir.path()) - .args(["compile", "object:x86_64-unknown-none", "ext.lk"]) - .arg("--output") - .arg(&object) - .output() - .expect("spawn object compile"); - assert!( - compile.status.success(), - "an extern call must lower: {}", - String::from_utf8_lossy(&compile.stderr) - ); - let bytes = std::fs::read(&object).expect("read object"); - let needle = b"kernel_double"; - assert!( - bytes.windows(needle.len()).any(|window| window == needle), - "the object should name the symbol it calls" +/// `min` / `max` / `sum` across the carriers that lower. +/// +/// The values are easy; the edges are the point. An empty sequence answers +/// **nil** for the two extremes and **0** for the sum, so the extremes come +/// back boxed and the sum does not — a carrier that got that backwards would +/// print `0` where the VM prints `nil`. Floats use `sort`'s total order, so a +/// NaN is not an artifact of which comparison ran, and a `Bytes` answers the +/// same three questions a list does. +#[test] +fn sequence_reductions_answer_the_same_on_both_ends() { + run_clif_differential( + "sequence_reductions", + &[ + new( + "carriers", + "use bytes;\nlet z = 0;\nlet xs = [3, 1 + z, 2];\nlet fs = [1.5, 0.5, 2.5];\n\ + let ss = [\"b\", \"a\", \"c\"];\nlet b = bytes.from_list([3, 1, 2]);\n\ + println(xs.sum());\nprintln(xs.min());\nprintln(xs.max());\n\ + println(fs.sum());\nprintln(fs.min());\nprintln(fs.max());\n\ + println(ss.min());\nprintln(ss.max());\n\ + println(b.sum());\nprintln(b.min());\nprintln(b.max());\nreturn 0;\n", + ), + new( + "empty_and_ties", + "use bytes;\nlet z = 0;\nlet e: List = [];\nlet ef: List = [];\n\ + let es: List = [];\nlet eb = bytes.from_list([]);\n\ + println(e.sum());\nprintln(e.min());\nprintln(e.max());\n\ + println(ef.sum());\nprintln(ef.min());\n\ + println(es.min());\nprintln(eb.sum());\nprintln(eb.min());\n\ + println([2 + z, 2].min());\nprintln([-0.0, 0.0].min());\nreturn 0;\n", + ), + ], ); } diff --git a/cli/tests/compile_cli_test.rs b/cli/tests/compile_cli_test.rs index 061b6716..2edf6e0b 100644 --- a/cli/tests/compile_cli_test.rs +++ b/cli/tests/compile_cli_test.rs @@ -64,7 +64,10 @@ return id!(7); ); let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); assert!(stdout.contains("# macro id at"), "expected trace line, got: {stdout}"); - assert!(stdout.contains("return 7;"), "expected expanded return, got: {stdout}"); + assert!( + stdout.contains("return (7);"), + "expected expanded return, got: {stdout}" + ); let _ = fs::remove_dir_all(&dir); } @@ -102,7 +105,7 @@ return answer!(); ); let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); assert!( - stdout.contains("return 42;"), + stdout.contains("return (42);"), "expected imported macro expansion, got: {stdout}" ); @@ -307,7 +310,9 @@ return generated() + decorated() + proc_value!() + user.value(); "expected manifest attribute provider output in AST expansion, got: {stdout}" ); assert!( - stdout.contains("+ 5"), + // `(5)`: an expansion in expression position is one expression, and the + // rendering carries the grouping. + stdout.contains("+ (5)"), "expected manifest function-like provider output in token expansion, got: {stdout}" ); assert!( @@ -413,7 +418,7 @@ return answer!(); ); let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); assert!( - stdout.contains("return 42;"), + stdout.contains("return (42);"), "expected package macro expansion, got: {stdout}" ); @@ -663,20 +668,38 @@ fn test_compile_struct_constructs_to_module_artifact() { ); } +/// `..` in a path argument is a path, not an attack. +/// +/// This asserted the opposite: a `sanitize_path` refused every `..`, while +/// letting an **absolute** path through — so it stopped nothing (anything `..` +/// reaches, `/…` reaches) and refused `lk compile ../x.lk` from a +/// subdirectory. Now the only failure left is the honest one: the file is not +/// there. #[test] -fn test_compile_rejects_parent_directory_argument() { +fn compile_takes_a_parent_directory_argument_as_a_path() { let dir = unique_tmp_dir("compile_parent"); ensure_clean_dir(&dir); + let nested = dir.join("nested"); + create_dir_all(&nested).expect("nested dir"); + write_file(&dir, "escape.lk", "return 7;\n"); - let out = run_cli(&dir, ["compile", "../escape.lk"]) + let out = run_cli(&nested, ["compile", "bytecode", "../escape.lk"]) .output() .expect("spawn compile with parent dir"); - assert!(!out.status.success()); - let stderr = String::from_utf8_lossy(&out.stderr); assert!( - stderr.contains("Parent directory components"), - "expected sanitize error, got: {stderr}" + out.status.success(), + "compiling `../escape.lk` failed: {}", + String::from_utf8_lossy(&out.stderr) ); + + // And a `..` that really is not there fails for that reason, not for its + // shape. + let out = run_cli(&nested, ["compile", "bytecode", "../nope.lk"]) + .output() + .expect("spawn compile with a missing parent-dir file"); + assert!(!out.status.success()); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!(stderr.contains("Failed to read file"), "{stderr}"); } #[test] @@ -744,6 +767,104 @@ fn test_compile_rejects_what_run_and_check_reject() { let _ = fs::remove_dir_all(&dir); } +/// What `lk check FILE` accepts, `lk FILE` must run. +/// +/// It did not. The run path type-checks the program *twice*: the CLI does it with +/// the imports seeded, and `execute_with_ctx` then does it again with a fresh +/// checker and `None` for the directory — so the second one cannot open the files +/// the program imports and rejects every name that crosses a module boundary. +/// `lk check` passed this file and `lk` answered `Unknown type 'P' in parameter +/// 'p'`, which makes the pre-flight command a liar about the one thing it is for. +/// +/// The CLI's own check stays: it is the only one the sandboxed (`LK_FUEL`) and +/// bytecode-cache branches get. That this path now checks twice is a startup +/// cost, not a correctness one. +#[test] +fn what_check_accepts_the_run_path_accepts() { + let dir = unique_tmp_dir("check_and_run_agree"); + ensure_clean_dir(&dir); + write_file( + &dir, + "lib.lk", + "struct P { x: Int, y: Int }\n\ + impl P { fn sum(self) -> Int { return self.x + self.y; } }\n\ + fn make() -> P { return P { x: 10, y: 20 }; }\n", + ); + // The parameter annotation names an imported type, and the body calls a + // method the imported `impl` declares — the two things the unseeded check + // could not resolve. + write_file( + &dir, + "main.lk", + "use \"./lib\";\nfn take(p: P) -> Int { return p.sum(); }\nprintln(take(lib.make()));\n", + ); + + let checked = run_cli(&dir, ["check", "main.lk"]).output().expect("spawn check"); + assert!( + checked.status.success(), + "`lk check` rejected it: {}", + String::from_utf8_lossy(&checked.stderr) + ); + + let out = run_cli(&dir, ["main.lk"]).output().expect("spawn run"); + assert!( + out.status.success(), + "`lk check` passed but running failed: {}", + String::from_utf8_lossy(&out.stderr) + ); + assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "30"); + + let _ = fs::remove_dir_all(&dir); +} + +/// An imported `impl`'s method signatures reach the checker. +/// +/// A method becomes known to the checker by being *type-checked* — the `Impl` +/// arm sets the self type and each method body's check registers its signature — +/// and that only ever happens for the program's own statements. So a call on an +/// imported type was not merely unchecked, it was *unknown*, and unknown falls +/// through to `Any`: in one file `impl Show for Int` made `a.show(1, 2)` an +/// error, and with the impl one `use` away the same call passed the checker and +/// died at run time. +/// +/// The signature is read from the declaration, so this only makes the arity and +/// the annotated types visible — an unannotated parameter stays `Any`, exactly as +/// it is for an imported free function. +#[test] +fn an_imported_impls_signatures_are_checked() { + let dir = unique_tmp_dir("imported_impl_sigs"); + ensure_clean_dir(&dir); + write_file( + &dir, + "lib.lk", + "struct P { x: Int }\n\ + impl P { fn scaled(self, k: Int) -> Int { return self.x * k; } }\n\ + fn make() -> P { return P { x: 2 }; }\n", + ); + + for (body, expected) in [ + ("println(lib.make().scaled());", "Method expects 1 arguments"), + ("println(lib.make().scaled(1, 2));", "Method expects 1 arguments"), + ("println(lib.make().scaled(\"s\"));", "wrong type"), + ] { + write_file(&dir, "main.lk", &format!("use \"./lib\";\n{body}\n")); + let out = run_cli(&dir, ["check", "main.lk"]).output().expect("spawn check"); + let stderr = String::from_utf8_lossy(&out.stderr).into_owned(); + assert!( + !out.status.success() && stderr.contains(expected), + "`{body}` should be refused with {expected:?}, got: {stderr}" + ); + } + + // And the correct call still passes both. + write_file(&dir, "main.lk", "use \"./lib\";\nprintln(lib.make().scaled(3));\n"); + let out = run_cli(&dir, ["main.lk"]).output().expect("spawn run"); + assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); + assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "6"); + + let _ = fs::remove_dir_all(&dir); +} + /// A `trait` implemented in an imported file must dispatch in the importer. /// /// It did not: the importer executes an imported file in a throwaway @@ -780,6 +901,123 @@ fn test_trait_impl_from_imported_file_dispatches() { let _ = fs::remove_dir_all(&dir); } +/// A trait used as a **type** must accept an implementor from another file. +/// +/// The trait, the struct and the impl's *methods* all crossed the boundary +/// already; the relation "this type implements this trait" did not, because +/// only the importing program's own statements were walked for it. So +/// `render(v: Shape)` in an imported file reported "expected Shape, got Sq" +/// for the very type that file declares an impl for — the feature worked +/// within one file and nowhere else. +#[test] +fn test_trait_as_a_type_accepts_an_imported_implementor() { + let dir = unique_tmp_dir("cross_module_trait_type"); + ensure_clean_dir(&dir); + write_file( + &dir, + "shape.lk", + "trait Area { fn area(self) -> Int; }\n\ + struct Sq { s: Int }\n\ + impl Area for Sq { fn area(self) -> Int { return self.s * self.s; } }\n\ + fn make(n: Int) -> Sq { return Sq { s: n }; }\n\ + fn describe(v: Area) -> Int { return v.area(); }\n", + ); + write_file( + &dir, + "main.lk", + "use { make, describe } from \"./shape.lk\";\nprintln(describe(make(5)));\n", + ); + + let out = run_cli(&dir, ["main.lk"]).output().expect("spawn run"); + assert!( + out.status.success(), + "a trait-typed parameter refused an imported implementor: {}", + String::from_utf8_lossy(&out.stderr) + ); + assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "25"); + + let _ = fs::remove_dir_all(&dir); +} + +/// `lk check` follows a module alias when it checks a member. +/// +/// `use math as m;` recorded the name it bound and not the module behind it, +/// so `m.nope(1)` was checked against a module called `m` — which does not +/// exist, so nothing was checked and the program died at run time with "nil is +/// not a function". The unaliased spelling had been reporting this properly for +/// a while, which is what made the gap easy to miss: one of the two forms +/// worked. +/// +/// A CLI test rather than a `core` one because the member table only exists +/// where the standard library is linked. +#[test] +fn test_check_follows_a_module_alias() { + let dir = unique_tmp_dir("check_module_alias"); + ensure_clean_dir(&dir); + write_file(&dir, "bad.lk", "use math as m;\nprintln(m.nope(1));\n"); + write_file(&dir, "good.lk", "use math as m;\nprintln(m.abs(0 - 3));\n"); + + let bad = run_cli(&dir, ["check", "bad.lk"]).output().expect("spawn check"); + assert!(!bad.status.success(), "`m.nope` should not check"); + let message = String::from_utf8_lossy(&bad.stderr).to_string() + &String::from_utf8_lossy(&bad.stdout); + assert!(message.contains("has no member `nope`"), "{message}"); + // Named as `math`: the alias is how it was written, not what it is. + assert!(message.contains("`math`"), "{message}"); + + let good = run_cli(&dir, ["check", "good.lk"]).output().expect("spawn check"); + assert!( + good.status.success(), + "`m.abs` should check: {}", + String::from_utf8_lossy(&good.stderr) + ); + + let _ = fs::remove_dir_all(&dir); +} + +/// A module whose functions call a *read-only* user method on a parameter is +/// still bundlable. +/// +/// Bundling declines a module that could write through a container parameter, +/// because it hands the caller's container over by reference where the VM +/// hands a copy. A call to a user method counted as a write on the grounds +/// that the bytecode carries no types — but the method's body is *in the same +/// module*, and the same fixpoint is already deciding whether its receiver is +/// safe. Assuming the worst meant `fn describe(v: Shape) { return v.area(); }` +/// — the whole point of a trait — made the module unbundlable, so every name it +/// exported stopped resolving natively. +#[test] +fn test_a_read_only_trait_method_does_not_block_bundling() { + let dir = unique_tmp_dir("bundle_trait_method"); + ensure_clean_dir(&dir); + write_file( + &dir, + "shape.lk", + "trait Area { fn area(self) -> Int; }\n\ + struct Sq { s: Int }\n\ + impl Area for Sq { fn area(self) -> Int { return self.s * self.s; } }\n\ + fn make(n: Int) -> Sq { return Sq { s: n }; }\n\ + fn describe(v: Area) -> Int { return v.area(); }\n", + ); + write_file( + &dir, + "main.lk", + "use { make, describe } from \"./shape.lk\";\nprintln(describe(make(5)));\n", + ); + + let out = run_cli(&dir, ["compile", "main.lk"]) + .env("LK_AOT_HYBRID", "0") + .env("LK_AOT_NO_FALLBACK", "1") + .output() + .expect("spawn compile"); + assert!( + out.status.success(), + "a read-only trait method blocked bundling: {}", + String::from_utf8_lossy(&out.stderr) + ); + + let _ = fs::remove_dir_all(&dir); +} + /// One trait may be implemented for a builtin type only once across the whole /// program. /// @@ -860,7 +1098,11 @@ fn test_try_catch_is_a_statement_not_a_closure() { write_file( &dir, "outer.lk", - "let t = 0;\nfor i in 0..100 {\n try { t += i / 0; } catch e { t += 1; }\n}\nprintln(t);\n", + // `% 0` rather than `/ 0`: `/` yields a Float, so dividing by zero is + // an infinity now and raises nothing. Integer remainder still has no + // answer at zero, which is what this case needs — it is about try's + // scoping, not about division. + "let t = 0;\nfor i in 0..100 {\n try { t += i % 0; } catch e { t += 1; }\n}\nprintln(t);\n", ); let out = run_cli(&dir, ["outer.lk"]).output().expect("spawn run"); assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); @@ -905,7 +1147,7 @@ fn test_try_catch_is_a_statement_not_a_closure() { &dir, "shadow.lk", "fn f() {\n let e = 0;\n let bump = || { e = e + 1; };\n bump();\n\ - try { 1 / 0; } catch e { println(\"caught\"); }\n println(e);\n}\nf();\n", + try { 1 % 0; } catch e { println(\"caught\"); }\n println(e);\n}\nf();\n", ); let out = run_cli(&dir, ["shadow.lk"]).output().expect("spawn run"); assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); @@ -920,7 +1162,7 @@ fn test_try_catch_is_a_statement_not_a_closure() { write_file( &dir, "bind.lk", - "try { error([1, 2]); } catch e { println(typeof(e)); }\ntry { 1 / 0; } catch e { println(typeof(e)); }\n", + "try { error([1, 2]); } catch e { println(typeof(e)); }\ntry { 1 % 0; } catch e { println(typeof(e)); }\n", ); let out = run_cli(&dir, ["bind.lk"]).output().expect("spawn run"); assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); @@ -1019,7 +1261,7 @@ fn test_local_trait_impl_dispatches_inside_an_imported_function() { /// for the whole context: `A.mk(1).tag()` answered `"B"`. A locally declared /// `Point` hijacked the imported one the same way. Both halves of a declared /// type's identity — the declaring module and the name — now travel with the -/// value (`lk_core::vm::TypeScope`). +/// value (`lk_core::val::TypeScope`). #[test] fn test_same_type_name_in_two_modules_dispatches_separately() { let dir = unique_tmp_dir("type_scope_collision"); @@ -1168,3 +1410,254 @@ fn compile_object_rejects_an_unknown_triple() { let _ = std::fs::remove_dir_all(&dir); } + +/// One expression's scratch registers are handed back as it goes. +/// +/// A register VM needs *one* temporary for `a + b + c + …`, not one per term: +/// the result is written over the left operand, which is what `x += 1` has +/// always compiled to. Every intermediate kept its own register instead, so a +/// single expression could exhaust the 256 a frame has — and the failure was a +/// refusal to compile a program that is nothing unusual. 300 terms and 40 list +/// elements are both well past where it used to stop (~250 and 27). +/// +/// The answers are checked, not just the exit status: reusing an operand's +/// register is only safe because the opcodes read both operands before writing +/// the destination, and a compiler that got that wrong would still compile. +#[test] +fn one_expression_reuses_its_scratch_registers() { + let dir = tempfile::tempdir().expect("temp dir"); + let path = dir.path().join("wide_expr.lk"); + let chain = (1..=300).map(|i| format!("({i} * 2)")).collect::>().join(" + "); + let elements = (0..40) + .map(|i| format!("(\"abc\".count(\"a\") + {i})")) + .collect::>() + .join(", "); + std::fs::write( + &path, + format!( + "let total = {chain}; +let xs = [{elements}]; +println(\"${{total}} ${{xs.len()}} ${{xs[39]}}\"); +" + ), + ) + .expect("write"); + + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(&path) + .output() + .expect("run lk"); + + assert!( + output.status.success(), + "stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + // 2 * (1 + … + 300) = 90300; the last element is 1 + 39. + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "90300 40 40"); +} + +/// The same, for the two windows an expression can be lowered into: a call's +/// arguments and a template string's parts. +/// +/// Both pre-allocate a contiguous window and then lower into it, and both let +/// every part's scratch pile up behind the window. Two programs, because the +/// two halves fail differently and one program does not separate them: +/// +/// - 60 interpolations of `${s.count(t) + i}` **refuse to compile** without the +/// template half. +/// - a 60-argument call nested inside a template still compiles without the +/// call half — it just costs 191 registers where 132 are needed, which is why +/// the count is asserted rather than the exit status. +#[test] +fn a_call_window_and_a_template_reuse_their_scratch_too() { + let dir = tempfile::tempdir().expect("temp dir"); + let params = (0..60).map(|i| format!("a{i}: Int")).collect::>().join(", "); + let args = (0..60) + .map(|i| format!("(\"aaa\".count(\"a\") + \"b\".len() + {i})")) + .collect::>() + .join(", "); + let template = (0..60) + .map(|i| format!("${{\"a\".count(\"a\") + {i}}}")) + .collect::>() + .join("-"); + + // `a0` is 3 + 1 + 0 and `a59` is 3 + 1 + 59. + let call = dir.path().join("wide_call.lk"); + std::fs::write( + &call, + format!("fn many({params}) -> Int {{ return a0 + a59; }}\nprintln(\"${{many({args})}}\");\n"), + ) + .expect("write"); + let rendered = dir.path().join("wide_template.lk"); + std::fs::write(&rendered, format!("println(\"{template}\");\n")).expect("write"); + + let expected = ["67", &(1..=60).map(|i| i.to_string()).collect::>().join("-")]; + for (path, expected) in [(&call, expected[0]), (&rendered, expected[1])] { + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(path) + .output() + .expect("run lk"); + assert!( + output.status.success(), + "{}: {}", + path.display(), + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), expected); + } + + // The count itself, not just "it compiled": a call window that stops + // recycling is still under the ceiling at this width, so success alone + // would not notice. Measured 132 with the reuse and 191 without. + let counted = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["coverage", "--disassemble"]) + .arg(&call) + .output() + .expect("run lk coverage"); + let listing = String::from_utf8_lossy(&counted.stdout); + let registers: usize = listing + .lines() + .find_map(|line| line.trim().strip_prefix("registers: ")) + .and_then(|count| count.trim().parse().ok()) + .unwrap_or_else(|| panic!("no register count in the listing: {listing}")); + assert!( + registers < 160, + "the call window stopped reusing its scratch: {registers} registers" + ); +} + +/// A struct literal is not capped at a number nobody could reach. +/// +/// The guard said "max 127 fields", but `NewObject` reads its fields from a +/// window of *two* registers each plus one for the type name, so 127 fields +/// need 255 window registers and `dst` has nowhere to go. In practice it broke +/// around 84, and what came out was "this function needs more than 256 +/// registers" — a message about the enclosing function, for a limit belonging to +/// one literal. Two diagnostics, one real ceiling, neither of them naming it. +/// +/// 200 is chosen to sit past every one of those numbers: past 84, past 127, and +/// past the 255-register window the old path needed. +#[test] +fn a_struct_literal_is_not_capped_at_an_unreachable_field_count() { + let dir = tempfile::tempdir().expect("temp dir"); + let path = dir.path().join("wide.lk"); + let fields = (0..200).map(|i| format!("f{i}: Int")).collect::>().join(", "); + let values = (0..200).map(|i| format!("f{i}: {i}")).collect::>().join(", "); + std::fs::write( + &path, + format!("struct Wide {{ {fields} }}\nlet w = Wide {{ {values} }};\nprintln(\"${{w.f199}} ${{w.f0}}\");\n"), + ) + .expect("write"); + + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(&path) + .output() + .expect("run lk"); + + assert!( + output.status.success(), + "stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "199 0"); +} + +/// Too many fields is reported as too many fields. +/// +/// A `struct` declaration emits no code of its own, but every one gets a +/// generated constructor taking one *named parameter* per field — and parameters +/// are locals. So a 254-field struct failed with "this function needs more than +/// 256 registers … split the body into smaller functions": a body the program +/// does not contain, and advice that cannot be followed, for a limit that is +/// real and worth stating plainly. +#[test] +fn a_struct_too_wide_to_construct_says_so() { + let dir = tempfile::tempdir().expect("temp dir"); + let path = dir.path().join("too_wide.lk"); + let fields = (0..254).map(|i| format!("f{i}: Int")).collect::>().join(", "); + std::fs::write(&path, format!("struct TooWide {{ {fields} }}\n")).expect("write"); + + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg("check") + .arg(&path) + .output() + .expect("run lk check"); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + !output.status.success(), + "stdout: {}", + String::from_utf8_lossy(&output.stdout) + ); + assert!(stderr.contains("struct `TooWide` has 254 fields"), "{stderr}"); + assert!(stderr.contains("253 is the most one can have"), "{stderr}"); + // The register message named the wrong thing entirely. + assert!(!stderr.contains("split the body into smaller functions"), "{stderr}"); +} + +/// A package dependency bundles like a file import, in every spelling that +/// names one. +/// +/// Before, the bundler queued file imports only, so a call into a dependency +/// fell to the stdlib-only module lowering and the whole program ran on the +/// Tier 0 VM bundle — about 3x slower, with nothing said. The sweep pins the +/// `use dep;` spelling through the workspace example; the other three have no +/// corpus program, and each is a separate arm of the binding table. +#[test] +fn a_package_dependency_lowers_natively_in_every_import_spelling() { + let dir = unique_tmp_dir("pkg_bundle_spellings"); + ensure_clean_dir(&dir); + write_file( + &dir, + "Lk.toml", + "[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2026\"\n\n[dependencies]\nmathlib = { path = \"mathlib\" }\n", + ); + create_dir_all(dir.join("mathlib/src")).expect("create dep dir"); + write_file( + &dir.join("mathlib"), + "Lk.toml", + "[package]\nname = \"mathlib\"\nversion = \"0.1.0\"\nedition = \"2026\"\n", + ); + write_file( + &dir.join("mathlib/src"), + "mod.lk", + "fn double(n: Int) -> Int {\n return n * 2;\n}\n", + ); + create_dir_all(dir.join("src")).expect("create src dir"); + + for source in [ + "use mathlib;\nprintln(mathlib.double(7));\n", + "use mathlib as ml;\nprintln(ml.double(7));\n", + "use { double } from mathlib;\nprintln(double(7));\n", + "use * as m from mathlib;\nprintln(m.double(7));\n", + ] { + write_file(&dir.join("src"), "main.lk", source); + // Strict: no fallback, no hybrid bridge — "compiles" means "lowered". + let compiled = run_cli(&dir, ["compile", "src/main.lk"]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("spawn compile"); + assert!( + compiled.status.success(), + "{source} did not lower: {}", + String::from_utf8_lossy(&compiled.stderr) + ); + + let native = Command::new(dir.join("src/main")) + .current_dir(&dir) + .output() + .expect("run the native binary"); + let vm = run_cli(&dir, ["src/main.lk"]) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + assert_eq!( + String::from_utf8_lossy(&native.stdout), + String::from_utf8_lossy(&vm.stdout), + "{source}: the two executors disagree" + ); + assert_eq!(String::from_utf8_lossy(&native.stdout).trim(), "14", "{source}"); + } +} diff --git a/cli/tests/construct_native_coverage_test.rs b/cli/tests/construct_native_coverage_test.rs new file mode 100644 index 00000000..6e195a31 --- /dev/null +++ b/cli/tests/construct_native_coverage_test.rs @@ -0,0 +1,517 @@ +//! Every construct the language has, lowered natively. +//! +//! The AOT coverage gate walks `examples/`, so it measures the *programs* the +//! repository happens to contain. That is not the same as measuring the +//! language: `?.` had no native lowering at all — the nil test it compiles to +//! had no case for a container receiver, so every field form of the operator +//! dropped its whole program to the interpreter — and no gate saw it, because +//! no example used `?.` on a field. +//! +//! This walks the constructs instead. One minimal program per form, compiled +//! with fallback forbidden, so "compiles" means "lowered fully native". +//! +//! A construct that legitimately cannot lower belongs in `EXPECTED_FALLBACK` +//! with a reason, never quietly removed from the table. + +use std::process::Command; + +/// Constructs that do not lower yet, each with why. Empty: every form in the +/// table below is native. +const EXPECTED_FALLBACK: &[(&str, &str)] = &[]; + +/// `(name, source)` — one minimal program per language form. +const CONSTRUCTS: &[(&str, &str)] = &[ + ( + "add", + "let a = 1;\n\ + let b = 2;\n\ + println(a + b);\n", + ), + ( + "and", + "let a = true;\n\ + println(a && false);\n", + ), + ( + "bitand", + "let a = 6;\n\ + println(a & 3);\n", + ), + ( + "bitnot", + "let a: u8 = 1;\n\ + println(~a);\n", + ), + ( + "bitor", + "let a = 6;\n\ + println(a | 3);\n", + ), + ( + "bitxor", + "let a = 6;\n\ + println(a ^ 3);\n", + ), + ( + "break", + "let i = 0;\n\ + while true { i = i + 1;\n\ + if (i > 2) { break;\n\ + } } println(i);\n", + ), + ( + "cast", + "let a = 300;\n\ + println(a as u8);\n", + ), + ( + "closure", + "let f = |x| x + 1;\n\ + println(f(1));\n", + ), + ( + "closure_capture", + "let n = 1;\n\ + let f = || n + 1;\n\ + println(f());\n", + ), + ( + "coalesce", + "let m = {\"k\": 1};\n\ + println(m.get(\"z\") ?? -1);\n", + ), + ( + "compound_assign", + "let a = 1;\n\ + a += 2;\n\ + println(a);\n", + ), + ( + "continue", + "let t = 0;\n\ + for i in 1..=4 { if ((i % 2) == 0) { continue;\n\ + } t = t + i;\n\ + } println(t);\n", + ), + ( + "defer", + "fn f() -> Int { defer println(\"d\");\n\ + return 1;\n\ + } println(f());\n", + ), + ( + "destructure_list", + "let [a, b] = [1, 2];\n\ + println(a + b);\n", + ), + ( + "destructure_map", + "let { k: v } = {\"k\": 1};\n\ + println(v);\n", + ), + ( + "destructure_rest", + "let m = {\"a\": 1, \"b\": 2};\n\ + let { a: x, ..rest } = m;\n\ + println(rest.len());\n", + ), + ( + "div", + "let a = 3;\n\ + println(a / 2);\n", + ), + ( + "eq", + "let a = 1;\n\ + println(a == 2);\n", + ), + ( + "field", + "struct P { p: Int } let x = P { p: 1 };\n\ + println(x.p);\n", + ), + ( + "field_assign", + "struct P { p: Int } let x = P { p: 1 };\n\ + x.p = 5;\n\ + println(x.p);\n", + ), + ( + "for_list", + "let t = 0;\n\ + for x in [1, 2] { t = t + x;\n\ + } println(t);\n", + ), + ( + "for_map", + "let t = 0;\n\ + for p in ({\"a\": 1}) { t = t + 1; }\n\ + println(t);\n", + ), + ( + "for_range", + "let t = 0;\n\ + for i in 1..=3 { t = t + i;\n\ + } println(t);\n", + ), + ( + "for_set", + "let t = 0;\n\ + for x in Set([1, 2]) { t = t + 1;\n\ + } println(t);\n", + ), + ( + "for_str", + "let t = 0;\n\ + for c in \"ab\" { t = t + 1;\n\ + } println(t);\n", + ), + ( + "if_else", + "let a = 1;\n\ + if (a > 0) { println(\"y\");\n\ + } else { println(\"n\");\n\ + }\n", + ), + ( + "impl_method", + "struct P { p: Int } impl P { fn twice(self) -> Int { return self.p * 2;\n\ + } } println(P { p: 2 }.twice());\n", + ), + ( + "in_list", + "let xs = [1, 2];\n\ + println(1 in xs);\n", + ), + ( + "in_map", + "let m = {\"k\": 1};\n\ + println(\"k\" in m);\n", + ), + ( + "in_str", + "let s = \"abc\";\n\ + println(\"b\" in s);\n", + ), + ( + "index_assign", + "let xs = [1];\n\ + xs[0] = 5;\n\ + println(xs[0]);\n", + ), + ( + "index_list", + "let xs = [1];\n\ + println(xs[0]);\n", + ), + ( + "index_map", + "let m = {\"k\": 1};\n\ + println(m[\"k\"]);\n", + ), + ( + "le", + "let a = 1;\n\ + println(a <= 2);\n", + ), + ( + "list_concat", + "let a = [1];\n\ + println(a + [2]);\n", + ), + ("list_lit", "println([1, 2, 3]);\n"), + ( + "lt", + "let a = 1;\n\ + println(a < 2);\n", + ), + ("map_lit", "println({\"a\": 1});\n"), + ( + "map_merge", + "let a = {\"x\": 1};\n\ + println(a + {\"y\": 2});\n", + ), + ( + "method", + "let xs = [1];\n\ + println(xs.len());\n", + ), + ( + "mod", + "let a = 3;\n\ + println(a % 2);\n", + ), + ( + "mul", + "let a = 3;\n\ + println(a * 2);\n", + ), + ( + "ne", + "let a = 1;\n\ + println(a != 2);\n", + ), + ( + "neg", + "let a = 1;\n\ + println(0 - a);\n", + ), + ( + "nested_assign", + "struct P { m: Map } let x = P { m: {\"a\": 1} };\n\ + x.m[\"a\"] = 5;\n\ + println(x.m[\"a\"]);\n", + ), + ( + "not", + "let a = true;\n\ + println(!a);\n", + ), + ( + "optional_field", + "let m = {\"k\": 1};\n\ + println(m?.k ?? -1);\n", + ), + ( + "or", + "let a = true;\n\ + println(a || false);\n", + ), + ( + "range_lit", + "let r = 0..3;\n\ + println(r.len());\n", + ), + ( + "recursion", + "fn f(n: Int) -> Int { if (n <= 0) { return 0;\n\ + } return n + f(n - 1);\n\ + } println(f(5));\n", + ), + ( + "set_ops", + "let a = Set([1]);\n\ + println(a.union(Set([2])).len());\n", + ), + ( + "shl", + "let a = 1;\n\ + println(a << 3);\n", + ), + ( + "shr", + "let a = 8;\n\ + println(a >> 3);\n", + ), + ( + "spread", + "struct P { p: Int, q: Int } let a = P { p: 1, q: 2 };\n\ + println(P { ..a, q: 3 }.q);\n", + ), + ( + "string_concat", + "let a = \"x\";\n\ + println(a + \"y\");\n", + ), + ("struct_lit", "struct P { p: Int } println(P { p: 1 }.p);\n"), + ( + "sub", + "let a = 3;\n\ + println(a - 1);\n", + ), + ( + "template", + "let n = 1;\n\ + println(\"v=${n}\");\n", + ), + ( + "ternary", + "let a = 1;\n\ + println(a > 0 ? \"y\" : \"n\");\n", + ), + ( + "trait_dispatch", + "trait S { fn s(self) -> Int; }\n\ + struct P { p: Int }\n\ + impl S for P { fn s(self) -> Int { return self.p; } }\n\ + fn render(v: Any) -> Int { return v.s(); }\n\ + println(render(P { p: 7 }));\n", + ), + ("try_catch", "println(try { 1 % 0 } catch e { -1 });\n"), + // A condition is truthiness, not `Bool` (docs/semantics.md): only nil and + // false are falsy, so `0`, `""` and `[]` all take the branch. Nothing + // exercised the non-Bool carriers natively, and the lowering carried a + // refusal variant saying it could not do them. + ( + "truthiness", + "fn pick(x) { if x { return 1; } return 0; }\n\ + println(pick(0));\n\ + println(pick(nil));\n\ + println(pick(\"\"));\n\ + println(pick([]));\n\ + let n = 3;\n\ + println(n ? \"y\" : \"n\");\n", + ), + ( + "unsafe_block", + "let a = 1;\n\ + println(a);\n", + ), + ( + "unwrap", + "let xs = [1];\n\ + println(xs[0]!);\n", + ), + ( + "while", + "let i = 0;\n\ + while i < 3 { i = i + 1;\n\ + } println(i);\n", + ), +]; + +#[test] +fn every_language_construct_lowers_natively() { + let dir = tempfile::tempdir().expect("temp dir"); + let mut refused = Vec::new(); + let mut stale = Vec::new(); + let mut diverged = Vec::new(); + + for (name, source) in CONSTRUCTS { + let path = dir.path().join(format!("{name}.lk")); + std::fs::write(&path, source).expect("write construct"); + + // A malformed probe would look like a lowering gap, so the type check + // is asserted separately and loudly. + let checked = Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["check", path.to_str().expect("utf-8 path")]) + .output() + .expect("run lk check"); + assert!( + checked.status.success(), + "the `{name}` probe does not type-check, so it measures nothing: {}", + String::from_utf8_lossy(&checked.stderr) + ); + + let compiled = Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", path.to_str().expect("utf-8 path")]) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("run lk compile"); + let expected = EXPECTED_FALLBACK.iter().find(|(listed, _)| listed == name); + match (compiled.status.success(), expected) { + (true, None) | (false, Some(_)) => {} + (false, None) => { + let message = String::from_utf8_lossy(&compiled.stderr).trim().to_string(); + // A linker or disk failure is not a lowering refusal, and + // reading it as one has wasted a whole investigation before. + assert!( + message.contains("native AOT does not support this program yet"), + "`{name}` failed to compile for a reason that is not a lowering refusal: {message}" + ); + refused.push(format!("{name}: {message}")); + } + (true, Some((_, reason))) => stale.push(format!("{name} (listed as: {reason})")), + } + + // Lowering is half the question; the other half is whether it lowers to + // the *same answer*. Running both is nearly free here — the programs are + // one line each — and it makes this table a differential corpus of the + // language's forms rather than only a coverage list. + if compiled.status.success() { + let interpreted = Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(path.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run on the VM"); + let native = Command::new(dir.path().join(name)) + .output() + .expect("run the native build"); + if interpreted.stdout != native.stdout { + diverged.push(format!( + "{name}: vm={:?} native={:?}", + String::from_utf8_lossy(&interpreted.stdout), + String::from_utf8_lossy(&native.stdout) + )); + } + } + } + + assert!( + refused.is_empty(), + "constructs that stopped lowering natively:\n{}", + refused.join("\n") + ); + assert!( + stale.is_empty(), + "listed as unable to lower, but they do — drop them from EXPECTED_FALLBACK:\n{}", + stale.join("\n") + ); + assert!( + diverged.is_empty(), + "constructs whose native build answers differently from the VM:\n{}", + diverged.join("\n") + ); +} + +/// The same table, through the *serialization* boundary. +/// +/// `ModuleArtifact` encode/decode is a second implementation of the module: a +/// construct whose encoding drops something runs correctly from source and +/// wrongly from a `.lkm`. `vm_bytecode_differential_test` is the oracle for +/// that, and like the AOT coverage gate it walks `examples/` — so it measures +/// the programs the repository happens to contain, which is what the table +/// above exists because of. Running the table through it too costs one +/// `lk compile bytecode` per construct and no link. +#[test] +fn every_language_construct_survives_a_bytecode_round_trip() { + let dir = tempfile::tempdir().expect("temp dir"); + let mut rejected = Vec::new(); + let mut diverged = Vec::new(); + + for (name, source) in CONSTRUCTS { + let path = dir.path().join(format!("{name}.lk")); + std::fs::write(&path, source).expect("write construct"); + let source_arg = path.to_str().expect("utf-8 path"); + + let compiled = Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", "bytecode", source_arg]) + .output() + .expect("run lk compile bytecode"); + if !compiled.status.success() { + rejected.push(format!("{name}: {}", String::from_utf8_lossy(&compiled.stderr).trim())); + continue; + } + + let from_source = Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source_arg) + .env("LK_FORCE_VM", "1") + .output() + .expect("run from source"); + let module = dir.path().join(format!("{name}.lkm")); + let from_bytecode = Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(module.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run the serialized module"); + if from_source.stdout != from_bytecode.stdout { + diverged.push(format!( + "{name}: source={:?} bytecode={:?}", + String::from_utf8_lossy(&from_source.stdout), + String::from_utf8_lossy(&from_bytecode.stdout) + )); + } + } + + assert!( + rejected.is_empty(), + "constructs the bytecode compiler will not serialize:\n{}", + rejected.join("\n") + ); + assert!( + diverged.is_empty(), + "constructs whose serialized module answers differently from its source:\n{}", + diverged.join("\n") + ); +} diff --git a/cli/tests/cross_module_function_value_test.rs b/cli/tests/cross_module_function_value_test.rs new file mode 100644 index 00000000..0a5cc100 --- /dev/null +++ b/cli/tests/cross_module_function_value_test.rs @@ -0,0 +1,219 @@ +//! Passing a function to a function in another file. +//! +//! `apply(double, 5)` is the shape: a higher-order helper in one module, the +//! function it works on in another. It used to fail at run time — a bare +//! closure is a `function_index` into *its own* module's table, so the copy into +//! the callee's heap refused it outright. +//! +//! It now promotes instead: the value crossing the boundary becomes a callable +//! that carries the module it came from, so the index still means what it meant. +//! The promoted callable runs against a **fresh** state, which is what the +//! refusals below are about — a function that needs its module's globals cannot +//! be handed to another module, and says which global and why. + +use std::process::Command; + +fn lk() -> Command { + Command::new(env!("CARGO_BIN_EXE_lk")) +} + +fn run(dir: &std::path::Path, main: &str) -> (String, String, bool) { + let source = dir.join("main.lk"); + std::fs::write(&source, main).expect("write main"); + let output = lk().arg(&source).output().expect("run lk"); + ( + String::from_utf8_lossy(&output.stdout).into_owned(), + String::from_utf8_lossy(&output.stderr).into_owned(), + output.status.success(), + ) +} + +fn with_helper(dir: &std::path::Path) { + std::fs::write( + dir.join("helper.lk"), + "fn apply(f: (Int) -> Int, x: Int) -> Int { return f(x); }\n\ + fn apply_str(f: (String) -> String, s: String) -> String { return f(s); }\n\ + fn twice(f: (Int) -> Int, x: Int) -> Int { return f(f(x)); }\n", + ) + .expect("write helper"); +} + +/// The shapes that cross: a named function, one that calls another function of +/// its own module, a lambda, a lambda with a capture, and a function applied +/// more than once. +#[test] +fn a_function_can_be_passed_to_a_function_in_another_module() { + let dir = tempfile::tempdir().expect("temp dir"); + with_helper(dir.path()); + let (stdout, stderr, ok) = run( + dir.path(), + "use { apply, apply_str, twice } from \"helper\";\n\ + fn double(x: Int) -> Int { return x * 2; }\n\ + fn plus_one(x: Int) -> Int { return x + 1; }\n\ + fn chained(x: Int) -> Int { return plus_one(x) * 2; }\n\ + let n = 7;\n\ + println(apply(double, 5));\n\ + println(apply(chained, 5));\n\ + println(apply(|x| x + n, 1));\n\ + println(apply_str(|s| s + \"!\", \"hi\"));\n\ + println(twice(double, 3));\n", + ); + assert!(ok, "the program should run: {stderr}"); + assert_eq!(stdout, "10\n12\n8\nhi!\n12\n", "stderr: {stderr}"); +} + +/// A function that reads one of its module's globals is refused, by the name of +/// the global. +/// +/// The promoted callable runs against a fresh state, so that global is nil +/// there. Answering with nil would be a wrong answer that looks like a working +/// program — `x * factor` silently becoming `x * nil`'s error, or worse, zero. +#[test] +fn a_function_that_reads_a_module_global_is_refused_by_name() { + let dir = tempfile::tempdir().expect("temp dir"); + with_helper(dir.path()); + let (_, stderr, ok) = run( + dir.path(), + "use { apply } from \"helper\";\n\ + let factor = 10;\n\ + fn scaled(x: Int) -> Int { return x * factor; }\n\ + println(apply(scaled, 5));\n", + ); + assert!(!ok, "reading a module global across the boundary must not run"); + assert!( + stderr.contains("reads the module global `factor`"), + "the refusal should name the global: {stderr}" + ); +} + +/// A function whose body makes a call this check cannot follow — `println` is +/// the everyday one — is refused separately, and told apart from a write. +/// +/// It is not known to be wrong, only unproven: a builtin arrives through a +/// register, and nothing says what it reaches. Saying "it writes a global" +/// would have been a guess dressed as a fact. +#[test] +fn a_function_whose_calls_cannot_be_followed_says_so() { + let dir = tempfile::tempdir().expect("temp dir"); + with_helper(dir.path()); + let (_, stderr, ok) = run( + dir.path(), + "use { apply } from \"helper\";\n\ + fn noisy(x: Int) -> Int { println(\"called\"); return x; }\n\ + println(apply(noisy, 5));\n", + ); + assert!(!ok, "an unprovable body must not cross"); + assert!( + stderr.contains("makes a call this check cannot follow"), + "the refusal should name the real reason: {stderr}" + ); + assert!( + !stderr.contains("it writes a module global"), + "an unfollowable call is not a write, and saying so would be a guess: {stderr}" + ); +} + +/// A function that writes a module global gets the write's own refusal. +#[test] +fn a_function_that_writes_a_module_global_is_refused_as_a_write() { + let dir = tempfile::tempdir().expect("temp dir"); + with_helper(dir.path()); + let (_, stderr, ok) = run( + dir.path(), + "use { apply } from \"helper\";\n\ + let seen = 0;\n\ + fn record(x: Int) -> Int { seen = x; return x; }\n\ + println(apply(record, 5));\n", + ); + assert!(!ok, "a write across the boundary must not run"); + assert!( + stderr.contains("it writes a module global"), + "the refusal should name the write: {stderr}" + ); +} + +/// The way back: a function *returned* by an imported function. +/// +/// `make_adder(5)` builds a closure inside the other module and hands it over. +/// This direction needs no help from the executor — the module is the callee's +/// own — but it was refused for exactly as long as the argument direction was, +/// and fixing one without the other would have made `apply(make_adder(5), 1)` +/// half-work. +#[test] +fn a_function_can_be_returned_from_another_module() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("mk.lk"), + "fn make_adder(n: Int) -> (Int) -> Int { return |x| x + n; }\n\ + fn apply(f: (Int) -> Int, x: Int) -> Int { return f(x); }\n\ + fn pass_through(f: (Int) -> Int, x: Int) -> Int { return apply(f, x); }\n", + ) + .expect("write module"); + let (stdout, stderr, ok) = run( + dir.path(), + "use { make_adder, apply, pass_through } from \"mk\";\n\ + fn double(x: Int) -> Int { return x * 2; }\n\ + let add5 = make_adder(5);\n\ + println(add5(1));\n\ + println(apply(add5, 1));\n\ + println(pass_through(double, 4));\n\ + let fs = [double, double];\n\ + println(apply(fs[0], 3));\n\ + struct Box { f: (Int) -> Int }\n\ + let b = Box { f: double };\n\ + println(apply(b.f, 6));\n", + ); + assert!(ok, "the program should run: {stderr}"); + assert_eq!(stdout, "6\n6\n8\n6\n12\n", "stderr: {stderr}"); +} + +/// Named arguments cross too — including a function passed by name. +/// +/// Two separate things had to be true. The compiler collects named-call +/// signatures from *this* program's declarations, so an imported function had +/// none and the call failed to compile with a sentence about the compiler's +/// bookkeeping (`Compiler missing named-call signature`) — while the identical +/// call to a local function worked. And the named argument path had its own +/// copy of the argument copying, which still refused a function. +#[test] +fn named_arguments_cross_a_module_boundary() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("named.lk"), + "fn scale({value: Int, by: Int}) -> Int { return value * by; }\n\ + fn apply({f: (Int) -> Int, x: Int}) -> Int { return f(x); }\n", + ) + .expect("write module"); + let (stdout, stderr, ok) = run( + dir.path(), + "use { scale, apply } from \"named\";\n\ + fn double(x: Int) -> Int { return x * 2; }\n\ + println(scale(value: 3, by: 4));\n\ + println(apply(f: double, x: 5));\n", + ); + assert!(ok, "the program should run: {stderr}"); + assert_eq!(stdout, "12\n10\n", "stderr: {stderr}"); +} + +/// A crossing that cannot name its source module still refuses — and says which +/// crossing it was. +/// +/// A channel payload is copied by a function that is handed two heaps and +/// nothing else, so there is no module to promote against. The message says so +/// rather than describing the argument case it no longer applies to. +#[test] +fn a_channel_payload_still_refuses_a_function_and_says_why() { + let dir = tempfile::tempdir().expect("temp dir"); + let (_, stderr, ok) = run( + dir.path(), + "use chan;\n\ + fn double(x: Int) -> Int { return x * 2; }\n\ + let c = chan.new(1);\n\ + chan.send(c, double);\n", + ); + assert!(!ok, "a function through a channel must not run"); + assert!( + stderr.contains("a channel payload"), + "the refusal should name the crossing it is about: {stderr}" + ); +} diff --git a/cli/tests/hybrid_compile_test.rs b/cli/tests/hybrid_compile_test.rs index 1b3964f7..cbd849a3 100644 --- a/cli/tests/hybrid_compile_test.rs +++ b/cli/tests/hybrid_compile_test.rs @@ -229,7 +229,13 @@ fn hybrid_bridged_containers_deep_convert_and_match_the_vm() { fn user(name) { let f = \"u={}\".trim(); println(f, name); \ return {\"name\": name, \"score\": 95, \"tags\": [1, 2]}; }\n\ fn mkp(x: Int) { let f = \"p={}\".trim(); println(f, x); return P { tag: x }; }\n\ - mkp(7);\n\ + fn strs(n) { let f = \"s={}\".trim(); println(f, n); return [\"a\", \"b\"]; }\n\ + let p = mkp(7);\n\ + let ss = strs(1);\n\ + println(ss);\n\ + println(ss[1]);\n\ + println(typeof(p));\n\ + println(p);\n\ let r = rows(3);\n\ println(r);\n\ println(r[1]);\n\ @@ -280,13 +286,9 @@ fn hybrid_bridged_containers_deep_convert_and_match_the_vm() { /// first-class value — string and container payloads, consumed-result position /// included — byte-identical to the VM. /// -/// It no longer checks that the enclosing `try` is a *native* frame reached by -/// longjmp across the bridge. `try`/`catch` became a real statement lowering to -/// `TryBegin`/`TryEnd`, the MIR lowering has no handler region yet, and a -/// top-level `try` makes the entry function unlowerable — so the whole module -/// degrades to the Tier 0 bundle and there is no native try frame to reach. -/// Restore the `Tier 1 hybrid` / no-fallback assertions below when the region -/// outlining lands (todos.md). +/// The enclosing `try` stays native while each `boom` helper runs on the bridge; +/// pinning fallback off proves the raise crosses that boundary rather than being +/// handled inside a Tier 0 VM bundle. #[test] fn raises_reach_the_enclosing_try_like_the_vm() { let dir = std::env::temp_dir().join(format!("lk_hybrid_cli_raise_{}", std::process::id())); @@ -317,10 +319,15 @@ fn raises_reach_the_enclosing_try_like_the_vm() { .current_dir(&dir) .args(["compile", "raise.lk"]) .env("LK_AOT_HYBRID", "1") + .env("LK_AOT_NO_FALLBACK", "1") .output() .expect("hybrid compile"); let compile_stderr = String::from_utf8_lossy(&compile.stderr).into_owned(); assert!(compile.status.success(), "compile: {compile_stderr}"); + assert!( + compile_stderr.contains("Tier 1 hybrid"), + "expected the hybrid link path, got: {compile_stderr}" + ); let native = native_run(&dir, "raise"); assert_eq!( @@ -333,7 +340,7 @@ fn raises_reach_the_enclosing_try_like_the_vm() { } #[test] -fn hybrid_uncaught_vm_error_exits_nonzero_like_the_vm() { +fn an_uncaught_error_exits_and_reads_the_same_on_both_backends() { let dir = std::env::temp_dir().join(format!("lk_hybrid_cli_err_{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).expect("create tmp dir"); @@ -372,10 +379,123 @@ fn hybrid_uncaught_vm_error_exits_nonzero_like_the_vm() { !native.status.success(), "the bridged uncaught error must fail the hybrid binary too" ); + // Not just "nonzero": the same status. An uncaught error used to abort + // natively, so a script forgetting a `catch` died with SIGABRT (134) once + // compiled and with exit 1 under the VM. + assert_eq!( + native.status.code(), + vm.status.code(), + "an uncaught error must exit with the same status on both backends" + ); + assert_eq!(vm.status.code(), Some(1), "the VM reports an uncaught error as exit 1"); let native_stderr = String::from_utf8_lossy(&native.stderr).into_owned(); assert!( native_stderr.contains("bad: 5"), "the VM's rendered error must reach stderr: {native_stderr}" ); + + // And it reads the same. The VM said `Error: VM execution failed` with the + // real message demoted to anyhow's `Caused by:` block, while lkrt said `lk: + // uncaught error: bad: 5` — one failing program, two reports, and the + // divergence was written off in `lkrt/src/panic.rs` because "the + // differential compares stdout + success only". That says what the gate + // looked at. + // + // Not byte equality: the VM also prints a call-stack traceback, which a + // native binary has no frames for. The contract is the *error line* — the + // traceback is something the VM has to offer on top of it. + let error_line = |stderr: &str| { + stderr + .lines() + .rev() + .find(|line| !line.trim().is_empty()) + .unwrap_or_default() + .to_string() + }; + let vm_stderr = String::from_utf8_lossy(&vm.stderr).into_owned(); + assert_eq!( + error_line(&vm_stderr), + error_line(&native_stderr), + "an uncaught error must read the same on both backends\nvm:\n{vm_stderr}\nnative:\n{native_stderr}" + ); + assert_eq!( + error_line(&native_stderr), + "Error: bad: 5", + "the label is the one the rest of the language reports with: {native_stderr}" + ); + assert!( + vm_stderr.contains("Call stack:"), + "the VM keeps offering its traceback above that line: {vm_stderr}" + ); + let _ = std::fs::remove_dir_all(&dir); +} + +/// A host error is a *language* error: catchable, and fatal only as exit 1. +/// +/// `fs.read_dir` on a missing directory raises in the VM and used to abort the +/// process natively — the same program was recoverable interpreted and fatal +/// compiled, with SIGABRT instead of a status. +#[test] +fn native_host_error_raises_and_exits_one_like_the_vm() { + let dir = std::env::temp_dir().join(format!("lk_native_host_err_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).expect("create tmp dir"); + let missing = dir.join("no-such-dir"); + let program = format!( + "use fs;\nprintln(fs.read_dir(\"{}\"));\n", + missing.to_string_lossy().replace('\\', "\\\\") + ); + std::fs::write(dir.join("dirfail.lk"), program).expect("write program"); + + let vm = Command::new(bin_path()) + .current_dir(&dir) + .arg("dirfail.lk") + .env("LK_FORCE_VM", "1") + .output() + .expect("vm run"); + assert_eq!(vm.status.code(), Some(1), "the VM raises a missing directory"); + + let compile = Command::new(bin_path()) + .current_dir(&dir) + .args(["compile", "dirfail.lk"]) + .output() + .expect("compile"); + assert!( + compile.status.success(), + "compile: {}", + String::from_utf8_lossy(&compile.stderr) + ); + + let native = native_run(&dir, "dirfail"); + assert_eq!( + native.status.code(), + Some(1), + "a host error must exit 1 natively, not abort: {}", + String::from_utf8_lossy(&native.stderr) + ); + + // And it is catchable, which aborting made impossible. + std::fs::write( + dir.join("dircatch.lk"), + format!( + "use fs;\ntry {{\n println(fs.read_dir(\"{}\"));\n}} catch e {{\n println(\"caught\");\n}}\n", + missing.to_string_lossy().replace('\\', "\\\\") + ), + ) + .expect("write program"); + let compile = Command::new(bin_path()) + .current_dir(&dir) + .args(["compile", "dircatch.lk"]) + .output() + .expect("compile"); + assert!( + compile.status.success(), + "compile: {}", + String::from_utf8_lossy(&compile.stderr) + ); + let caught = native_run(&dir, "dircatch"); + assert!(caught.status.success(), "a caught host error must not end the program"); + assert_eq!(String::from_utf8_lossy(&caught.stdout).trim(), "caught"); + let _ = std::fs::remove_dir_all(&dir); } diff --git a/cli/tests/impl_on_builtin_test.rs b/cli/tests/impl_on_builtin_test.rs new file mode 100644 index 00000000..2eb06f17 --- /dev/null +++ b/cli/tests/impl_on_builtin_test.rs @@ -0,0 +1,153 @@ +//! Every built-in type a program can write an `impl` for dispatches to it. +//! +//! Four things have to agree about what a receiver's type *is*: the type +//! parser, the checker's dispatch key, the runtime's dispatch key, and the +//! scope the impl is filed in. They disagreed for five of the thirteen, each in +//! its own way — `Bytes` on the scope; `Slice` on the parse, both dispatch keys +//! and the scope; `Task`, `Channel` and `Stream` on the parse and the runtime +//! key, where a bare `Task` meant a task of *nothing* and matched no receiver. +//! +//! The symptom was identical every time: the impl compiled, and calling its +//! method said the value had no such method. So the list is walked whole here +//! rather than case by case — a type added to the language belongs in it, and +//! the failure it guards against is silent until someone writes that impl. +//! +//! A CLI test rather than a `core` one because `Stream` needs the standard +//! library, which `core`'s executor tests do not link. + +/// The parameter is not part of the identity: an impl target names the +/// *constructor*, so a `Channel` finds what `impl Channel` registered. +#[test] +fn an_impl_on_a_built_in_type_is_reachable_from_a_value_of_it() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("impls.lk"); + std::fs::write( + &source, + r#"use stream; + +impl Nil { fn tag(self) -> String { return "nil"; } } +impl Bool { fn tag(self) -> String { return "bool"; } } +impl Int { fn tag(self) -> String { return "int"; } } +impl Float { fn tag(self) -> String { return "float"; } } +impl String { fn tag(self) -> String { return "string"; } } +impl List { fn tag(self) -> String { return "list"; } } +impl Map { fn tag(self) -> String { return "map"; } } +impl Set { fn tag(self) -> String { return "set"; } } +impl Bytes { fn tag(self) -> String { return "bytes"; } } +impl Slice { fn tag(self) -> String { return "slice"; } } +impl Task { fn tag(self) -> String { return "task"; } } +impl Channel { fn tag(self) -> String { return "channel"; } } +impl Stream { fn tag(self) -> String { return "stream"; } } + +let nothing = nil; +let c = chan(1); +send(c, "x"); +println([ + nothing.tag(), + true.tag(), + (1).tag(), + (1.5).tag(), + "s".tag(), + [1].tag(), + {"k": 1}.tag(), + Set([1]).tag(), + "ab".bytes().tag(), + [1, 2, 3].slice(0, 2).tag(), + spawn(|| 1).tag(), + c.tag(), + stream.range(0, 2).tag(), +].join(",")); +"#, + ) + .expect("write source"); + + let out = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + assert!( + out.status.success(), + "the program did not run: {}", + String::from_utf8_lossy(&out.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&out.stdout).trim(), + "nil,bool,int,float,string,list,map,set,bytes,slice,task,channel,stream" + ); +} + +/// The same list again, reached through a *trait* parameter rather than by +/// naming the method on the value. +/// +/// A different path — the checker has to accept the argument where the trait is +/// declared, which asks the registry whether the type implements it — and it +/// was wrong for three of the thirteen. `List` and the other containers keyed +/// on their element (`List` against a registration of `List`); +/// `Stream` keyed the other way round, a bare name against a written-out +/// registration; and `Nil` never reached the question at all, because "a +/// nullable value does not fit a slot that is not nullable" answered first — +/// true of a slot, and not of a trait somebody wrote `impl D for Nil` for. +#[test] +fn a_trait_implemented_for_a_built_in_type_accepts_a_value_of_it() { + let dir = tempfile::tempdir().expect("temp dir"); + let source = dir.path().join("trait_impls.lk"); + std::fs::write( + &source, + r#"use stream; + +trait Tag { fn tag(self) -> String; } + +impl Tag for Nil { fn tag(self) -> String { return "nil"; } } +impl Tag for Bool { fn tag(self) -> String { return "bool"; } } +impl Tag for Int { fn tag(self) -> String { return "int"; } } +impl Tag for Float { fn tag(self) -> String { return "float"; } } +impl Tag for String { fn tag(self) -> String { return "string"; } } +impl Tag for List { fn tag(self) -> String { return "list"; } } +impl Tag for Map { fn tag(self) -> String { return "map"; } } +impl Tag for Set { fn tag(self) -> String { return "set"; } } +impl Tag for Bytes { fn tag(self) -> String { return "bytes"; } } +impl Tag for Slice { fn tag(self) -> String { return "slice"; } } +impl Tag for Task { fn tag(self) -> String { return "task"; } } +impl Tag for Channel { fn tag(self) -> String { return "channel"; } } +impl Tag for Stream { fn tag(self) -> String { return "stream"; } } + +fn name(v: Tag) -> String { return v.tag(); } + +let nothing = nil; +let c = chan(1); +send(c, "x"); +println([ + name(nothing), + name(true), + name(1), + name(1.5), + name("s"), + name([1]), + name({"k": 1}), + name(Set([1])), + name("ab".bytes()), + name([1, 2, 3].slice(0, 2)), + name(spawn(|| 1)), + name(c), + name(stream.range(0, 2)), +].join(",")); +"#, + ) + .expect("write source"); + + let out = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .env("LK_FORCE_VM", "1") + .output() + .expect("run under the VM"); + assert!( + out.status.success(), + "the program did not run: {}", + String::from_utf8_lossy(&out.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&out.stdout).trim(), + "nil,bool,int,float,string,list,map,set,bytes,slice,task,channel,stream" + ); +} diff --git a/cli/tests/imported_type_construction_test.rs b/cli/tests/imported_type_construction_test.rs new file mode 100644 index 00000000..98e956ba --- /dev/null +++ b/cli/tests/imported_type_construction_test.rs @@ -0,0 +1,305 @@ +//! Constructing a type another module declares. +//! +//! Every `struct S` gets a generated top-level `fn S$new({…}) -> S` beside it +//! (`stmt::struct_ctors`), and `m.S { … }` is parse-time sugar for calling it. +//! The constructor runs in the declaring module, so the object it returns +//! carries that module's `TypeScope` and its methods dispatch. +//! +//! `use { S } from "m"` now binds that constructor under the name, so the bare +//! spellings — `S { … }` and `S(field: …)` — reach the same sugar. What stays +//! refused is a bare literal for a type this file only sees through a namespace +//! import: there the name is not bound to anything, and `NewObject` would stamp +//! the *constructing* module's scope, producing a same-named type with none of +//! the methods. + +use std::process::Command; + +fn lk() -> Command { + Command::new(env!("CARGO_BIN_EXE_lk")) +} + +fn run(dir: &std::path::Path, main: &str) -> (String, String, bool) { + let source = dir.join("main.lk"); + std::fs::write(&source, main).expect("write main"); + let output = lk().arg(&source).output().expect("run lk"); + ( + String::from_utf8_lossy(&output.stdout).into_owned(), + String::from_utf8_lossy(&output.stderr).into_owned(), + output.status.success(), + ) +} + +fn with_geo(dir: &std::path::Path) { + std::fs::write( + dir.join("geo.lk"), + "struct P { x: Int }\n\ + impl P { fn norm(self) -> Int { return self.x * self.x; } }\n\ + trait Shape { fn area(self) -> Int; }\n\ + struct Sq { s: Int }\n\ + impl Shape for Sq { fn area(self) -> Int { return self.s * self.s; } }\n\ + type Pair = List;\n", + ) + .expect("write geo"); +} + +/// Both bare spellings build the declaring module's type: the value renders and +/// answers `typeof` as `P`, and — the part a same-named local type would fail — +/// its methods resolve. +#[test] +fn a_type_imported_by_name_is_constructible_by_its_bare_name() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (stdout, stderr, ok) = run( + dir.path(), + "use { P } from \"geo\";\n\ + let a = P { x: 4 };\n\ + let b = P(x: 3);\n\ + println(a);\n\ + println(typeof(a));\n\ + println(a.norm());\n\ + println(b.norm());\n\ + println(a == P { x: 4 });\n", + ); + assert!(ok, "stderr: {stderr}"); + assert_eq!(stdout, "P{x:4}\nP\n16\n9\ntrue\n"); +} + +/// An alias renames the binding, not the type: `use { P as Q }` makes `Q { … }` +/// build a `P` — same identity, same methods, and `typeof` still answers `P`. +#[test] +fn an_alias_renames_the_binding_not_the_type() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (stdout, stderr, ok) = run( + dir.path(), + "use { P as Q } from \"geo\";\n\ + let a = Q { x: 4 };\n\ + let b = Q(x: 4);\n\ + println(a);\n\ + println(typeof(a));\n\ + println(a.norm());\n\ + println(a == b);\n", + ); + assert!(ok, "stderr: {stderr}"); + assert_eq!(stdout, "P{x:4}\nP\n16\ntrue\n"); + + // And the schema it is checked against is the declaring module's, named + // by the declaring module's spelling. + let (_, stderr, ok) = run(dir.path(), "use { P as Q } from \"geo\";\nlet a = Q { z: 4 };\n"); + assert!(!ok, "an undeclared field is refused"); + assert!(stderr.contains("struct 'P'"), "{stderr}"); +} + +/// The same type reached through a namespace import: `m.P { … }` works, and the +/// value it builds is interchangeable with the one the bare spelling builds — +/// one identity, two spellings. +#[test] +fn the_namespace_spelling_builds_the_same_identity() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (stdout, stderr, ok) = run( + dir.path(), + "use \"geo\";\n\ + use { P } from \"geo\";\n\ + let a = geo.P { x: 4 };\n\ + let b = P { x: 4 };\n\ + println(a == b);\n\ + println(typeof(a) == typeof(b));\n\ + println(a.norm() + b.norm());\n", + ); + assert!(ok, "stderr: {stderr}"); + assert_eq!(stdout, "true\ntrue\n32\n"); +} + +/// Seen only through a namespace, the bare literal is still refused, and the +/// message names both ways out. +#[test] +fn a_type_seen_only_through_a_namespace_is_not_constructible_by_its_bare_name() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (_, stderr, ok) = run(dir.path(), "use \"geo\";\nlet a = P { x: 4 };\nprintln(a);\n"); + assert!(!ok, "a bare literal for a namespace-visible type is refused"); + assert!(stderr.contains("only sees it through its namespace"), "{stderr}"); + assert!(stderr.contains("geo.P") || stderr.contains("m.P"), "{stderr}"); + assert!(stderr.contains("use { P } from"), "{stderr}"); +} + +/// A `trait` has no constructor to bind, so importing one by name is refused — +/// with the reason, not with "not an export". +#[test] +fn a_trait_cannot_be_imported_by_name() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (_, stderr, ok) = run(dir.path(), "use { Shape } from \"geo\";\nprintln(1);\n"); + assert!(!ok, "importing a trait by name is refused"); + assert!(stderr.contains("Shape"), "{stderr}"); + assert!(stderr.contains("no constructor to bind"), "{stderr}"); +} + +/// A `type` alias cannot be imported by name either, and the refusal does not +/// claim the module declares no such thing. +/// +/// The message used to end "and this module declares neither" — a fact it had +/// not checked. `type Pair = List;` *is* declared, and got told it was +/// not. +#[test] +fn a_type_alias_is_refused_without_denying_it_exists() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (_, stderr, ok) = run(dir.path(), "use { Pair } from \"geo\";\nprintln(1);\n"); + assert!(!ok, "importing a type alias by name is refused"); + assert!(stderr.contains("Pair"), "{stderr}"); + assert!( + stderr.contains("compile-time only"), + "the refusal should say why a `type` cannot be a binding: {stderr}" + ); + assert!( + !stderr.contains("declares neither"), + "the refusal must not claim the module declares no such thing: {stderr}" + ); +} + +/// The REPL brings the *type* an import names, not only its value. +/// +/// Every other entry point seeds the checker with what an import declares +/// (`typ::seed_imported_signatures`): the CLI for a file, the native compiler +/// for a compile, `execute_with_ctx_from` for a module loaded as an import. The +/// session did not, so `use { P } from "geo";` bound the constructor and left +/// the type unknown — and `P { x: 1 }` was refused by a message suggesting the +/// import that had just been written. +#[test] +fn the_repl_gets_the_type_an_import_names() { + use std::io::Write; + + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + + let mut child = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .current_dir(dir.path()) + .env("LK_FORCE_VM", "1") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("spawn repl"); + child + .stdin + .as_mut() + .expect("piped stdin") + .write_all(b"use { P } from \"geo\";\nP { x: 3 }\nP { x: 4 }.norm()\n") + .expect("write session"); + let out = child.wait_with_output().expect("repl output"); + let stdout = String::from_utf8_lossy(&out.stdout).to_string(); + let stderr = String::from_utf8_lossy(&out.stderr).to_string(); + + assert!(stdout.contains("P{x:3}"), "stdout: {stdout}\nstderr: {stderr}"); + // The imported type's own method, on a value built by the bare literal on a + // later input. + assert!(stdout.contains("16"), "stdout: {stdout}\nstderr: {stderr}"); +} + +/// The two backends agree on a struct built in another file — through either +/// spelling. +/// +/// The bundler merged an imported module's `impl` blocks but not its `struct` +/// declarations, and a declaration is what earns a type its runtime id: without +/// one `NewObject` skipped `obj_mark`, so native display rendered the carrier +/// (`{"x":4}`) where the VM prints `P{x:4}`. Method dispatch was unaffected — +/// it reads the *static* provenance — so the wrong answer showed up only in +/// output, and only for a type declared one file away. +#[cfg(feature = "aot")] +#[test] +fn the_two_backends_agree_on_a_struct_declared_in_another_file() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + for (name, main) in [ + ( + "item", + "use { P } from \"geo\";\n\ + fn main() -> Int { let a = P { x: 4 }; println(a); println(typeof(a)); println(a.norm()); return 0; }\n\ + main();\n", + ), + ( + "trait_impl", + "use \"geo\";\n\ + fn main() -> Int { let a = geo.Sq { s: 3 }; println(a); println(a.area()); return 0; }\n\ + main();\n", + ), + ( + "namespace", + "use \"geo\";\n\ + fn main() -> Int { let a = geo.P { x: 4 }; println(a); println(typeof(a)); println(a.norm()); return 0; }\n\ + main();\n", + ), + ] { + let source = dir.path().join(format!("{name}.lk")); + std::fs::write(&source, main).expect("write main"); + let vm = lk().arg(&source).output().expect("run vm"); + assert!(vm.status.success(), "[{name}] {}", String::from_utf8_lossy(&vm.stderr)); + + let compiled = lk() + .current_dir(dir.path()) + .args(["compile", &format!("{name}.lk")]) + .env("LK_AOT_HYBRID", "0") + .env("LK_AOT_NO_FALLBACK", "1") + .output() + .expect("compile natively"); + assert!( + compiled.status.success(), + "[{name}] native compile failed: {}", + String::from_utf8_lossy(&compiled.stderr) + ); + let native = std::process::Command::new(dir.path().join(name)) + .env("ASAN_OPTIONS", "detect_leaks=0") + .output() + .expect("run native"); + assert_eq!( + String::from_utf8_lossy(&vm.stdout), + String::from_utf8_lossy(&native.stdout), + "[{name}] stdout diverged" + ); + } +} + +/// A local declaration of the same name wins over the *aliased* import too, +/// and the checker has to agree with the compiler about which one it is. +/// +/// The compiler picks by the local `Q$new`'s existence, so it always built the +/// local type; the registry cleared its "declared elsewhere" mark on a local +/// declaration but not its "imported by name" one, and that one is keyed by the +/// bound name — so `Q { z: 4 }` was checked against `P`'s schema and refused +/// for a field the local `Q` declares. +#[test] +fn a_local_declaration_wins_over_an_aliased_import() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (stdout, stderr, ok) = run( + dir.path(), + "use { P as Q } from \"geo\";\n\ + struct Q { z: Int }\n\ + let q = Q { z: 4 };\n\ + println(q);\n\ + println(typeof(q));\n", + ); + assert!(ok, "stderr: {stderr}"); + assert_eq!(stdout, "Q{z:4}\nQ\n"); +} + +/// A local declaration of the same name wins: the bare literal builds the local +/// type, and the import does not shadow it. +#[test] +fn a_local_declaration_of_the_same_name_wins() { + let dir = tempfile::tempdir().expect("temp dir"); + with_geo(dir.path()); + let (stdout, stderr, ok) = run( + dir.path(), + "use \"geo\";\n\ + struct P { x: Int }\n\ + impl P { fn norm(self) -> Int { return self.x + 1; } }\n\ + let a = P { x: 4 };\n\ + println(a.norm());\n\ + println(geo.P { x: 4 }.norm());\n", + ); + assert!(ok, "stderr: {stderr}"); + assert_eq!(stdout, "5\n16\n"); +} diff --git a/cli/tests/proc_macro_dependency_cli_test.rs b/cli/tests/proc_macro_dependency_cli_test.rs index ebc7a876..e0c53fd5 100644 --- a/cli/tests/proc_macro_dependency_cli_test.rs +++ b/cli/tests/proc_macro_dependency_cli_test.rs @@ -96,13 +96,110 @@ args = ["-c", "cat >/dev/null; printf '%s' '{{\"protocol_version\":1,\"output_to ); let stdout = String::from_utf8(output.stdout).expect("utf8 stdout"); assert!( - stdout.contains("return 42;"), + stdout.contains("return (42);"), "expected trusted provider output: {stdout}" ); let _ = fs::remove_dir_all(&dir); } +/// An **untrusted** dependency's provider is never spawned. +/// +/// `[macros] trusted_dependencies` is the security boundary of the whole macro +/// system: a provider is an external process, run during `lk check` / `lk macro +/// expand`, before any of the program executes. Only the positive half was +/// tested — a listed dependency expands — so a refactor that dropped the +/// `trusted.contains(&module.name)` guard would have left every test green +/// while a dependency gained arbitrary compile-time execution. +/// +/// The assertion is the *spawn*, not the output. Discarding a provider's answer +/// after running it would satisfy "the macro did not expand" and still have run +/// the command, so the provider writes a sentinel file and this checks that the +/// file is absent. +#[test] +fn an_untrusted_dependency_provider_is_never_spawned() { + let Some(shell) = test_shell() else { + return; + }; + let dir = unique_tmp_dir("untrusted_dependency_proc_macro"); + ensure_clean_dir(&dir); + let sentinel = dir.join("provider_ran"); + + // Identical to the trusted case below it, minus the `[macros]` table. + write_file( + &dir, + "Lk.toml", + r#" +[package] +name = "app" + +[dependencies] +helper = { path = "deps/helper" } +"#, + ); + write_file(&dir, "main.lk", "\nreturn helper::answer!();\n"); + write_file( + &dir, + "deps/helper/Lk.toml", + &format!( + r#" +[package] +name = "helper" + +[macros.function_like.answer] +command = "{}" +args = ["-c", "touch '{}'; cat >/dev/null; printf '%s' '{{\"protocol_version\":1,\"output_tokens\":[{{\"kind\":\"Int\",\"lexeme\":\"42\",\"span\":null}}],\"diagnostics\":[],\"dependencies\":[]}}'"] +"#, + shell.display(), + sentinel.display() + ), + ); + write_file(&dir, "deps/helper/src/mod.lk", "fn value() { return 1; }\n"); + + let output = run_cli(&dir, ["macro", "expand", "main.lk"]) + .output() + .expect("spawn macro expand"); + let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + + assert!( + !sentinel.exists(), + "an untrusted dependency's provider was executed — the trust list is the only thing \ + between a dependency and arbitrary compile-time execution" + ); + assert!( + !stdout.contains("return (42);"), + "an untrusted provider's output reached the program: {stdout}" + ); + + // And the same package *with* the dependency trusted runs it — otherwise + // this test would pass on a build where providers never run at all. + write_file( + &dir, + "Lk.toml", + r#" +[package] +name = "app" + +[dependencies] +helper = { path = "deps/helper" } + +[macros] +trusted_dependencies = ["helper"] +"#, + ); + let output = run_cli(&dir, ["macro", "expand", "main.lk"]) + .output() + .expect("spawn macro expand"); + assert!( + output.status.success(), + "macro expand failed once trusted: {}", + String::from_utf8_lossy(&output.stderr) + ); + assert!(sentinel.exists(), "the trusted run must actually spawn the provider"); + + let _ = fs::remove_dir_all(&dir); +} + fn test_shell() -> Option { let shell = PathBuf::from("/bin/sh"); shell.exists().then_some(shell) diff --git a/cli/tests/repl_echo_test.rs b/cli/tests/repl_echo_test.rs new file mode 100644 index 00000000..a33a2de4 --- /dev/null +++ b/cli/tests/repl_echo_test.rs @@ -0,0 +1,153 @@ +//! What the REPL echoes. +//! +//! Its contract is "type a thing, see its value", and that used to be decided +//! by whether the input *also* happened to be a valid statement: the session +//! tried the program parse first and only fell back to wrapping the input in +//! `return (…)` when that failed. Most expressions need a semicolon to be a +//! statement, so most of them fell through and echoed — while everything that +//! stands alone as a statement computed its value and dropped it: +//! +//! ```text +//! > if true { 1 } else { 2 } (nothing) +//! > S { x: 8 } (nothing) +//! > [1, 2, 3] [1,2,3] +//! ``` +//! +//! These cases are the ones the accident got wrong, plus the ones it got right, +//! so a future "simplification" back to statement-first fails here. + +use assert_cmd::prelude::*; +use std::error::Error; +use std::io::Write; +use std::process::{Command, Stdio}; + +/// Everything the session should print, in order, for one scripted session. +fn repl_stdout(input: &str) -> Result> { + let mut child = Command::cargo_bin("lk")? + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn()?; + child.stdin.as_mut().expect("piped stdin").write_all(input.as_bytes())?; + let output = child.wait_with_output()?; + Ok(String::from_utf8(output.stdout)?) +} + +#[test] +fn a_statement_shaped_expression_still_shows_its_value() -> Result<(), Box> { + let out = repl_stdout( + "struct S { x: Int }\n\ + S { x: 8 }\n\ + if true { 1 } else { 2 }\n\ + match 2 { 1 => \"one\", _ => \"other\" }\n\ + { let b = 2; b * 3 }\n", + )?; + assert_eq!(out.lines().collect::>(), vec!["S{x:8}", "1", "other", "6"]); + Ok(()) +} + +#[test] +fn a_declaration_or_a_terminated_statement_stays_quiet() -> Result<(), Box> { + let out = repl_stdout( + "let a = 5;\n\ + fn f() -> Int { return 9; }\n\ + a + 1;\n\ + a + 1\n\ + f()\n", + )?; + // The `let`, the `fn` and the semicolon-terminated `a + 1;` print nothing; + // a trailing `;` is how a session suppresses its own echo. + assert_eq!(out.lines().collect::>(), vec!["6", "9"]); + Ok(()) +} + +/// A call that prints and returns nil prints once. +/// +/// This is why the wrapper returns the value instead of wrapping it in +/// `println`: `println(a)` once ran `println((println(a)))` and echoed the +/// inner call's nil under its output. +#[test] +fn a_printing_call_is_not_echoed_twice() -> Result<(), Box> { + let out = repl_stdout("println(\"side effect\")\n")?; + assert_eq!(out.lines().collect::>(), vec!["side effect"]); + Ok(()) +} + +/// Collecting a session's heaps must not re-walk the module graph once per path +/// into it. +/// +/// A heap holding an imported function reaches another module's heap through +/// it, and the collector followed every such edge without remembering where it +/// had been — so a module reachable by K paths was collected K times, each +/// repeating the walk beneath it. Every REPL input is its own module and holds +/// a callable for every earlier one, so the paths multiply with the session: +/// cross-module collections went 8 closures -> ~1_000, 12 -> ~20_000, +/// 16 -> ~327_000, and 40 closures under `LK_GC_STRESS=1` did not finish in +/// 200 seconds. It is now flat — the whole session below runs in about a tenth +/// of a second. +/// +/// Wall-clock, but not a close call: the bound is over a hundred times the +/// fixed cost, and what it catches is a return to exponential. +#[test] +fn a_session_of_closures_collects_without_re_walking_the_module_graph() -> Result<(), Box> { + let mut input = String::new(); + for index in 0..40 { + input.push_str(&format!("let f{index} = |x| x + {index};\n")); + } + input.push_str("f0(1)\n"); + + let start = std::time::Instant::now(); + let mut child = Command::cargo_bin("lk")? + .env("LK_GC_STRESS", "1") + .env("LK_FORCE_VM", "1") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn()?; + child.stdin.as_mut().expect("piped stdin").write_all(input.as_bytes())?; + let output = child.wait_with_output()?; + let elapsed = start.elapsed(); + + let stdout = String::from_utf8(output.stdout)?; + assert!(stdout.contains('1'), "the session should still answer: {stdout}"); + assert!( + elapsed < std::time::Duration::from_secs(20), + "40 closures under GC stress took {elapsed:?}; the module graph is being re-walked per path" + ); + Ok(()) +} + +/// A statement typed over several lines, where the continuation opens no +/// bracket. +/// +/// Continuation used to be decided on bracket depth alone, and a method chain +/// closes every bracket it opens on each line. So `let out = nums` ran on its +/// own — "Expected Semicolon, found end of input" — and the `.map(…)` beneath +/// it arrived as a line starting with `.`. Three example programs failed in the +/// session for exactly this and no other reason. +#[test] +fn a_method_chain_split_across_lines_is_one_input() -> Result<(), Box> { + let out = repl_stdout( + "let nums = [1, 2, 3];\n\ + let out = nums\n\ + .map(|v| v * 2)\n\ + .filter(|v| v > 2);\n\ + out\n", + )?; + assert!(out.contains("[4,6]"), "{out}"); + Ok(()) +} + +/// An input that is *wrong* rather than unfinished still stops. +/// +/// The continuation test asks the parser whether it ran out of input, and a +/// session that waited on every parse error would hang on a typo with no way +/// out. +#[test] +fn a_wrong_input_is_reported_rather_than_waited_on() -> Result<(), Box> { + let out = repl_stdout("let x = 1 2;\nprintln(7)\n")?; + // The second line still ran, which it could not have if the session were + // still collecting the first. + assert!(out.contains('7'), "{out}"); + Ok(()) +} diff --git a/cli/tests/stdlib_module_native_coverage_test.rs b/cli/tests/stdlib_module_native_coverage_test.rs new file mode 100644 index 00000000..0f55af8f --- /dev/null +++ b/cli/tests/stdlib_module_native_coverage_test.rs @@ -0,0 +1,191 @@ +//! Every declared stdlib module function, asked whether it lowers natively. +//! +//! The module counterpart of `builtin_method_native_coverage_test`, and the +//! same class of gap: a member with no ABI row is not a wrong answer, it is a +//! program that runs on the VM about three times slower with no diagnostic. +//! `MODULE_ABI` is the list of members that *do* lower, so reading it says +//! nothing about what is missing from it — only the stdlib's own declaration +//! can. +//! +//! The probes are checked and compiled, never **run**: this surface is `fs`, +//! `net`, `process` and `os`, and running a generated call to it would touch +//! the machine. So a probe's validity is decided by `lk check` alone, and a +//! member the checker refuses for one spelling of its arguments is reported +//! rather than skipped — there is no second carrier to compare it against the +//! way there is for a method. + +use lk_core::module::ModuleRegistry; +use lk_core::val::Type; +use std::path::Path; + +/// Members that do not lower, and why. +/// +/// Each is a decision. Asserted in both directions: a member here that starts +/// lowering fails the test, so the list cannot quietly become a parking space. +const EXCLUDED: &[(&str, &str)] = &[ + ( + "env.get", + "`m.get(k)` with one argument compiles to a map *read* whatever `m` is, so this arrives as \ + `GetIndex env, \"KEY\"` — the shape a member read of a member named `KEY` also has. \ + Nothing in the bytecode separates them: both carry a constant string key and both record \ + a key fact. Deciding by \"is the key a member name\" would compile a read of any member \ + the lowering table happens to lack into `env.get(\"that name\")`, which is a wrong answer \ + rather than a fallback. The runtime side is not the obstacle.", + ), + ( + "http.get", + "the `http` module has no lkrt implementation: a native binary would need an HTTP client \ + linked into the runtime, and `lkrt` is deliberately small.", + ), + ("http.post", "see `http.get`."), + ("http.request", "see `http.get`."), + ( + "math.random", + "a deterministic xorshift over *process-global* state — the same sequence every run, so \ + both back ends have to produce it in step. A second generator in lkrt would have to match \ + bit for bit, and with the hybrid bridge on it would interleave with the VM's copy of the \ + state and diverge from either. A fallback keeps one generator.", + ), + ( + "stream.iterate", + "an unbounded source. The stream lowering is an eager materialization — sound because a \ + finite pipeline with pure lambdas is observationally the same list — and eagerly \ + materializing an infinite one does not terminate.", + ), + ("stream.repeat", "see `stream.iterate`."), + ( + "task.stats", + "reports the async runtime's internal counters, and a native binary has no async runtime to \ + report on.", + ), +]; + +#[test] +fn every_declared_stdlib_module_function_lowers() { + let mut registry = ModuleRegistry::new(); + lk_stdlib::register_stdlib_modules(&mut registry).expect("stdlib registers"); + let dir = tempfile::tempdir().expect("temp dir"); + + let mut refused = Vec::new(); + let mut stale_exclusions = Vec::new(); + let mut unprobeable = Vec::new(); + let mut checked = 0usize; + + for module in &lk_stdlib::stdlib_catalog().modules { + for export in &module.exports { + let path = format!("{}.{}", module.name, export.name); + // No declared signature: an overloaded export, which the checker + // itself declines to type. Nothing to generate a call from. + let Some(sig) = lk_core::typ::stdlib_signature(&path) else { + continue; + }; + let Some(args) = sig + .params + .iter() + .filter(|p| !p.optional) + .map(|p| literal_for(&p.ty)) + .collect::>>() + else { + // A parameter type with no obvious literal (a callback, a + // handle, a struct). Reported, not skipped: the list of what + // this test cannot reach is part of what it measures. + unprobeable.push(path); + continue; + }; + let call = format!("{path}({})", args.join(", ")); + let stem = path.replace('.', "_"); + let source = dir.path().join(format!("{stem}.lk")); + let root = module.name.split('.').next().expect("a module name"); + std::fs::write(&source, format!("use {root};\nlet probe = {call};\nprintln(probe);\n")) + .expect("write probe"); + + if !std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["check", source.to_str().expect("utf-8 path")]) + .output() + .expect("run lk check") + .status + .success() + { + unprobeable.push(path); + continue; + } + + checked += 1; + let lowers = lowers_natively(&source, &dir.path().join(stem)); + let excluded = EXCLUDED.iter().any(|(name, _)| *name == path); + match (lowers, excluded) { + (false, false) => refused.push(call), + (true, true) => stale_exclusions.push(call), + _ => {} + } + } + } + + assert!( + checked > 100, + "only {checked} module members were probed, which is far below what the stdlib declares — \ + the probes are failing for a reason other than coverage. {} could not be given arguments.", + unprobeable.len() + ); + assert!( + refused.is_empty(), + "{} of {checked} stdlib module functions do not lower natively and are not in EXCLUDED:\n {}\n\ + A program calling any of them drops its whole module to the VM, silently. Add the ABI row, \ + or list it in EXCLUDED with the reason it cannot be lowered.", + refused.len(), + refused.join("\n ") + ); + assert!( + stale_exclusions.is_empty(), + "these are in EXCLUDED but now lower: {stale_exclusions:?}. Remove them — an exclusion that \ + no longer holds is what makes the list stop meaning anything." + ); +} + +/// A literal of the declared parameter type, or `None` when the type is not one +/// a fixed expression can stand in for. +fn literal_for(ty: &Type) -> Option { + Some(match ty { + Type::Int => "1".to_string(), + Type::Float => "1.5".to_string(), + Type::Bool => "true".to_string(), + Type::String => "\"probe\"".to_string(), + Type::Any => "1".to_string(), + Type::List(elem) => format!("[{}]", literal_for(elem)?), + Type::Set(elem) => format!("Set([{}])", literal_for(elem)?), + Type::Map(key, value) => format!("{{{}: {}}}", literal_for(key)?, literal_for(value)?), + // A union takes whichever arm has a literal. + Type::Union(arms) => arms.iter().find_map(literal_for)?, + _ => return None, + }) +} + +/// Whether `source` compiles with the bridge off and fallback forbidden. +fn lowers_natively(source: &Path, exe: &Path) -> bool { + let out = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .args(["compile", source.to_str().expect("utf-8 path")]) + .arg("--output") + .arg(exe.to_str().expect("utf-8 path")) + .env("LK_AOT_NO_FALLBACK", "1") + .env("LK_AOT_HYBRID", "0") + .output() + .expect("run lk compile"); + if out.status.success() { + return true; + } + // A failed compile is only an answer about *coverage* when the compiler + // says so. Everything else — a linker that could not write, a full disk — + // exits non-zero too, and reading that as "does not lower" reports a + // coverage regression for a machine problem. A full `/tmp` did exactly + // that here: the last two receivers in the table failed as a block, which + // is what a resource running out looks like and not what a lowering gap + // looks like. + let message = String::from_utf8_lossy(&out.stderr).to_string() + &String::from_utf8_lossy(&out.stdout); + assert!( + message.contains("native AOT does not support this program yet"), + "`lk compile` failed for a reason that is not a lowering refusal, so this run says nothing \ + about coverage:\n{}", + message.trim() + ); + false +} diff --git a/cli/tests/stdlib_named_params_test.rs b/cli/tests/stdlib_named_params_test.rs new file mode 100644 index 00000000..08503ab0 --- /dev/null +++ b/cli/tests/stdlib_named_params_test.rs @@ -0,0 +1,83 @@ +//! The AOT lowering's `named(...)` lists are copies. This is what checks them. +//! +//! `aot/lower` cannot read the stdlib signature registry — it is populated at +//! run time by whoever links the standard library, and a lowering that quietly +//! stops lowering because registration has not happened yet would be worse than +//! a copy. So the copy stays, and this test, in the one crate that has both +//! sides, compares it against the declaration. + +use lk_core::module::ModuleRegistry; + +/// Every `named` list in the lowering table matches the stdlib export's own +/// `named(...)`, in the same order. +/// +/// Order is the whole point: the list *is* the permutation from caller order +/// into frame order, so a swapped pair would compile +/// `regex.replace(s, pattern: p, replacement: r)` into `replace(s, r, p)` — a +/// wrong answer that still runs. +#[test] +fn lowering_named_parameter_lists_match_the_stdlib_declaration() { + let mut registry = ModuleRegistry::new(); + lk_stdlib::register_stdlib_modules(&mut registry).expect("stdlib registers"); + + let mut checked = 0; + for (module, member, leading, named) in lk_aot_lower::named_parameter_rows() { + let path = format!("{module}.{member}"); + let signature = + lk_core::typ::stdlib_signature(&path).unwrap_or_else(|| panic!("{path} has no declared signature")); + let declared: Vec<&str> = signature + .params + .iter() + .filter(|param| param.named) + .map(|param| param.name.as_str()) + .collect(); + assert_eq!( + declared, named, + "{path}: the lowering's named(...) copy disagrees with the stdlib declaration" + ); + // And where the named block *starts*, which is what turns a name into a + // frame slot. Off by one and `string.slice(s, 1, end: 3)` writes `end` + // past the end of the frame — the mixed spelling, which the VM accepts, + // stops lowering. + let declared_leading = signature.params.iter().take_while(|param| !param.named).count(); + assert_eq!( + declared_leading, leading, + "{path}: the lowering thinks the named block starts at {leading}, the declaration says {declared_leading}" + ); + checked += 1; + } + assert!(checked > 0, "no named rows were checked — the accessor lost its rows"); +} + +/// The other direction: a member the stdlib declares `named(...)` must carry +/// those names in its ABI row. +/// +/// The test above walks the rows that already have names, so it cannot see a +/// row that has none. That mistake is silent — `CallNamed` finds no names to +/// resolve, the member stops lowering by name, and the program falls back to +/// the VM with the right answer and none of the speed. Adding `named(...)` to +/// a stdlib export without touching the table is exactly how it happens. +#[test] +fn every_declared_named_list_reaches_the_lowering_table() { + let mut registry = ModuleRegistry::new(); + lk_stdlib::register_stdlib_modules(&mut registry).expect("stdlib registers"); + + let with_names: std::collections::HashSet<(&str, &str)> = lk_aot_lower::named_parameter_rows() + .map(|(module, member, _, _)| (module, member)) + .collect(); + let mut missing = Vec::new(); + for (module, member) in lk_aot_lower::module_abi_row_paths() { + let path = format!("{module}.{member}"); + let Some(signature) = lk_core::typ::stdlib_signature(&path) else { + continue; + }; + if signature.params.iter().any(|param| param.named) && !with_names.contains(&(module, member)) { + missing.push(path); + } + } + missing.sort(); + assert!( + missing.is_empty(), + "declared `named(...)` but the lowering row has none, so the named spelling falls back: {missing:?}" + ); +} diff --git a/cli/tests/stdlib_surface_test.rs b/cli/tests/stdlib_surface_test.rs new file mode 100644 index 00000000..c2b011c0 --- /dev/null +++ b/cli/tests/stdlib_surface_test.rs @@ -0,0 +1,100 @@ +//! The catalogue and the runtime describe the same standard library. +//! +//! Two lists of members exist: `lk_stdlib::stdlib_catalog()`, which the type +//! checker, the completion engine and the LSP read, and the `ModuleRegistry` +//! the executor actually looks names up in. Nothing keeps them in step, and a +//! disagreement is silent in both directions: +//! +//! - **Catalogued, not registered.** The member type-checks and resolves to +//! `nil` at run time, so the program dies with "nil is not a function" — a +//! sentence naming neither the module nor the member. +//! - **Registered, not catalogued.** The member works, and the checker refuses +//! it (`has no member`), completion never offers it, and the LSP marks it an +//! error. A working feature nobody can find. +//! +//! A module at run time *is* a map, so the runtime list is `module.keys()` — +//! the same lookup the executor does, asked from LK. + +use std::collections::BTreeSet; +use std::process::Command; + +/// Every member the runtime exposes, as `module.member`. +fn runtime_members() -> BTreeSet { + let dir = tempfile::tempdir().expect("temp dir"); + let mut roots: BTreeSet<&str> = BTreeSet::new(); + for module in &lk_stdlib::stdlib_catalog().modules { + roots.insert(module.name.split('.').next().expect("a module name")); + } + + let mut members = BTreeSet::new(); + for root in roots { + let source = dir.path().join(format!("{root}.lk")); + // Bound to a local first: `root.keys()` would look `keys` up *in* the + // module, and the point is to read the map it is. + std::fs::write( + &source, + format!( + "use {root};\n\ + let module = {root};\n\ + let names = module.keys();\n\ + let index = 0;\n\ + while index < names.len() {{\n\ + \x20 println(\"{root}.\" + names[index]);\n\ + \x20 index = index + 1;\n\ + }}\n" + ), + ) + .expect("write probe"); + + let output = Command::new(env!("CARGO_BIN_EXE_lk")) + .arg(source.to_str().expect("utf-8 path")) + .output() + .expect("run lk"); + assert!( + output.status.success(), + "listing `{root}`'s members failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + for line in String::from_utf8_lossy(&output.stdout).lines() { + let line = line.trim(); + if !line.is_empty() { + members.insert(line.to_string()); + } + } + } + members +} + +#[test] +fn the_catalogue_and_the_runtime_list_the_same_members() { + let catalogued: BTreeSet = lk_stdlib::stdlib_catalog() + .modules + .iter() + .flat_map(|module| { + module + .exports + .iter() + .map(move |export| format!("{}.{}", module.name, export.name)) + }) + .collect(); + let live = runtime_members(); + + let missing_at_runtime: Vec<_> = catalogued.difference(&live).collect(); + let missing_from_catalogue: Vec<_> = live.difference(&catalogued).collect(); + + assert!( + missing_at_runtime.is_empty(), + "catalogued but not registered — these type-check and answer nil: {missing_at_runtime:?}" + ); + assert!( + missing_from_catalogue.is_empty(), + "registered but not catalogued — these work and `lk check` refuses them: {missing_from_catalogue:?}" + ); + // A floor, so that a registry that silently stops registering anything at + // all cannot pass by matching an empty catalogue. + assert!( + catalogued.len() > 200, + "the catalogue lost members: {}", + catalogued.len() + ); +} diff --git a/cli/tests/stream_boundary_test.rs b/cli/tests/stream_boundary_test.rs new file mode 100644 index 00000000..ca69e6db --- /dev/null +++ b/cli/tests/stream_boundary_test.rs @@ -0,0 +1,132 @@ +//! A stream cannot cross a module boundary while its pipeline holds heap values. +//! +//! A stream is an id into a process-global registry *plus* handles: its `roots` +//! are heap references, and the pipeline the registry holds for that id keeps +//! its `map`/`filter` callbacks as heap references too. Both belong to the heap +//! that built them, and a copy between heaps rewrote neither — so the other +//! side got an id whose callbacks pointed into a heap it could not read. +//! +//! What that produced depended on when the collector ran. Plainly, the callback +//! handle was out of range and the program died with `heap object 102 out of +//! bounds`. With a collection in between, the slot had been reused and the +//! filter was silently skipped: `[1,2,3,4,5,6]` came back where `[16,25,36]` +//! was asked for. The second is the one worth a test. +//! +//! Refused rather than repaired: repairing means rewriting the *registry's* +//! pipeline into the destination module, and the registry lives in the stdlib, +//! which `core` must not reach into. + +use std::process::{Command, Stdio}; + +fn bin() -> Command { + Command::new(env!("CARGO_BIN_EXE_lk")) +} + +fn run_file(dir: &std::path::Path, name: &str) -> (String, String) { + let out = bin() + .current_dir(dir) + .arg(name) + .env("LK_FORCE_VM", "1") + .output() + .expect("run lk"); + ( + String::from_utf8_lossy(&out.stdout).to_string(), + String::from_utf8_lossy(&out.stderr).to_string(), + ) +} + +/// Building the pipeline in one module and consuming it in another is refused, +/// and the refusal says what to do instead. +#[test] +fn a_stream_with_a_callback_cannot_cross_a_module_boundary() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("slib.lk"), + "use stream;\n\ + fn filtered() -> Any {\n\ + let s = stream.from_list([1, 2, 3, 4, 5, 6]);\n\ + return stream.filter(s, fn(x) => x > 3);\n\ + }\n", + ) + .expect("write slib"); + std::fs::write( + dir.path().join("main.lk"), + "use stream;\n\ + use { filtered } from \"slib\";\n\ + println(stream.collect(stream.map(filtered(), fn(x) => x * x)));\n", + ) + .expect("write main"); + + let (stdout, stderr) = run_file(dir.path(), "main.lk"); + assert!( + !stdout.contains("[1,4,9,16,25,36]"), + "the filter must not be silently skipped: {stdout}" + ); + assert!( + stderr.contains("cannot be") && stderr.contains("stream.collect"), + "the refusal should name the way out: {stderr}" + ); +} + +/// A stream whose pipeline holds no heap value is just an id, and crosses. +/// +/// Refusing every stream would have been the easy rule and the wrong one: it +/// would break `stream.range(…)` and a list of scalars, which are sound. +#[test] +fn a_stream_with_no_heap_roots_still_crosses() { + let dir = tempfile::tempdir().expect("temp dir"); + std::fs::write( + dir.path().join("slib.lk"), + "use stream;\n\ + fn counted() -> Any { return stream.range(0, 5); }\n", + ) + .expect("write slib"); + std::fs::write( + dir.path().join("main.lk"), + "use stream;\n\ + use { counted } from \"slib\";\n\ + println(stream.collect(counted()));\n", + ) + .expect("write main"); + + let (stdout, stderr) = run_file(dir.path(), "main.lk"); + assert_eq!(stdout.trim(), "[0,1,2,3,4]", "stderr: {stderr}"); +} + +/// The same rule on the REPL's path, which is a different copy implementation: +/// every input is its own module, so the pipeline crosses on the next line. +#[test] +fn the_repl_refuses_the_same_crossing() { + use std::io::Write; + + let mut child = bin() + .env("LK_FORCE_VM", "1") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("spawn repl"); + child + .stdin + .as_mut() + .expect("piped stdin") + .write_all( + b"use stream;\n\ + let s = stream.from_list([1, 2, 3, 4, 5, 6]);\n\ + let f = stream.filter(s, fn(x) => x > 3);\n\ + stream.collect(f)\n", + ) + .expect("write session"); + let out = child.wait_with_output().expect("repl output"); + let stdout = String::from_utf8_lossy(&out.stdout).to_string(); + let stderr = String::from_utf8_lossy(&out.stderr).to_string(); + + assert!( + !stdout.contains("[1,2,3,4,5,6]"), + "the filter must not be silently skipped: {stdout}" + ); + assert!( + stderr.contains("cannot be imported"), + "the refusal should reach the session: {stderr}" + ); +} diff --git a/cli/tests/tutorial_examples_test.rs b/cli/tests/tutorial_examples_test.rs new file mode 100644 index 00000000..2d8a1ab9 --- /dev/null +++ b/cli/tests/tutorial_examples_test.rs @@ -0,0 +1,85 @@ +//! Every complete program in the tutorial type-checks. +//! +//! Nothing checked the tutorial before this. Running its blocks by hand turned +//! up four defects at once: `let count := 0;` (both `let` and the short +//! declaration), `struct Point { x: Int, y: y: Int }`, a `select` whose cases +//! were separated by commas where the parser wants semicolons, and +//! `for entry in { "a": 1 } { … }` — where the `{` is the loop body, so a map +//! literal cannot be written in a `for` header at all. +//! +//! It also turned up a defect in the *language*: the expression table claimed +//! `[1, 2, 3] - [2] // [1, 3]`, which both executors answered and the checker +//! refused. The tutorial was right and the implementation was not. +//! +//! A block that is a fragment — an expression table, a snippet naming something +//! an earlier block defined, an example whose point is the error it raises — is +//! fenced ```lk,fragment and skipped. That marker is the whole design: the +//! alternative is a gate that checks nothing because most blocks cannot stand +//! alone, or one that gets disabled the first time a fragment is added. + +use std::io::Write; +use std::process::Command; + +/// The tutorials this covers. Both translations, because they carry the same +/// code and a fix applied to one is the defect staying in the other. +const TUTORIALS: &[&str] = &["../website/src/learn/LEARN.md", "../website/src/learn/LEARN_zh.md"]; + +#[test] +fn every_complete_tutorial_example_type_checks() { + let lk = env!("CARGO_BIN_EXE_lk"); + let dir = std::env::temp_dir().join(format!("lk-tutorial-{}", std::process::id())); + std::fs::create_dir_all(&dir).expect("temp dir"); + let file = dir.join("example.lk"); + let mut checked = 0; + + for tutorial in TUTORIALS { + let source = std::fs::read_to_string(tutorial).unwrap_or_else(|err| panic!("{tutorial}: {err}")); + for (index, block) in complete_blocks(&source).into_iter().enumerate() { + let mut handle = std::fs::File::create(&file).expect("write example"); + handle.write_all(block.as_bytes()).expect("write example"); + drop(handle); + + let output = Command::new(lk) + .arg("check") + .arg(&file) + .output() + .expect("run `lk check`"); + assert!( + output.status.success(), + "{tutorial}: complete example #{index} does not check. Fence it \ + ```lk,fragment if it is deliberately incomplete.\n--- source ---\n{block}\n--- error ---\n{}", + String::from_utf8_lossy(&output.stderr) + ); + checked += 1; + } + } + + let _ = std::fs::remove_dir_all(&dir); + assert!( + checked >= 40, + "expected the tutorials to carry examples, checked {checked}" + ); +} + +/// The ```lk blocks, without the ```lk,fragment ones. +fn complete_blocks(source: &str) -> Vec { + let mut blocks = Vec::new(); + let mut current: Option = None; + for line in source.lines() { + match (&mut current, line.trim_end()) { + (None, "```lk") => current = Some(String::new()), + // Any other info string — `lk,fragment`, `rust`, `bash` — is not a + // complete example, and is skipped fence and all. + (None, _) => {} + (Some(body), "```") => { + blocks.push(std::mem::take(body)); + current = None; + } + (Some(body), _) => { + body.push_str(line); + body.push('\n'); + } + } + } + blocks +} diff --git a/cli/tests/type_system_cli_test.rs b/cli/tests/type_system_cli_test.rs index 224f9849..1fb668c2 100644 --- a/cli/tests/type_system_cli_test.rs +++ b/cli/tests/type_system_cli_test.rs @@ -22,7 +22,7 @@ fn reports_numeric_operand_error() -> Result<(), Box> { cmd.args(["check", script_path.to_str().unwrap()]); cmd.assert() .failure() - .stderr(predicate::str::contains("must by numeric types")); + .stderr(predicate::str::contains("must be numeric types")); Ok(()) } @@ -50,3 +50,373 @@ fn reports_macro_origin_for_macro_generated_type_error() -> Result<(), Box Result<(), Box> { + let dir = tempdir()?; + for (source, module, member) in [ + ("use math;\nlet r = math.nonexistent(1);\n", "math", "nonexistent"), + ("use string;\nlet r = string.bogus(\"a\");\n", "string", "bogus"), + ("use os;\nlet r = os.name();\n", "os", "name"), + ("use hash;\nlet r = hash.md5(\"a\");\n", "hash", "md5"), + ] { + let script_path = dir.path().join(format!("{module}_{member}.lk")); + fs::write(&script_path, source)?; + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + cmd.assert() + .failure() + .stderr(predicate::str::contains(format!("`{module}` has no member `{member}`"))); + } + Ok(()) +} + +/// Every spelling of an import gets the member check, not just `use module;`. +/// +/// A member can itself be a module — `use { json } from encoding;` binds +/// `encoding.json` under the bare name `json` — and that spelling had no check +/// at all: `json.encode(v)` type-checked and died with "nil is not a function", +/// which is the sentence naming neither the module nor the member. It is also +/// the spelling the examples use. +#[test] +fn every_import_spelling_checks_its_members() -> Result<(), Box> { + let dir = tempdir()?; + for (name, source, reported) in [ + ( + "submodule_by_name", + "use { json } from encoding;\nlet r = json.nope(1);\n", + "`encoding.json` has no member `nope`", + ), + ( + "submodule_aliased", + "use { json as j } from encoding;\nlet r = j.nope(1);\n", + "`encoding.json` has no member `nope`", + ), + ( + "namespace_of_a_module", + "use * as m from math;\nlet r = m.nope(1);\n", + "`math` has no member `nope`", + ), + ( + "namespace_of_a_parent", + "use * as e from encoding;\nlet r = e.json.nope(1);\n", + "`encoding.json` has no member `nope`", + ), + ( + "module_aliased", + "use math as m;\nlet r = m.nope(1);\n", + "`math` has no member `nope`", + ), + ] { + let script_path = dir.path().join(format!("{name}.lk")); + fs::write(&script_path, source)?; + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + cmd.assert().failure().stderr(predicate::str::contains(reported)); + } + + // And the members that do exist still resolve through every spelling. + for (name, source) in [ + ( + "ok_submodule", + "use { json } from encoding;\nlet r = json.parse(\"[1]\");\n", + ), + ( + "ok_aliased", + "use { json as j } from encoding;\nlet r = j.parse(\"[1]\");\n", + ), + ("ok_namespace", "use * as m from math;\nlet r = m.floor(1.5);\n"), + ] { + let script_path = dir.path().join(format!("{name}.lk")); + fs::write(&script_path, source)?; + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + cmd.assert().success(); + } + Ok(()) +} + +/// A user module's namespace answers the same way a standard library module's +/// does. +/// +/// `use * as lib from "./lib.lk"; lib.nothere()` type-checked and died with "nil +/// is not a function" — the same hole as a stdlib member, one layer over, and +/// the checker had the namespace's exports the whole time (it already reports +/// the *arity* of a member that does exist). +#[test] +fn a_namespace_member_that_does_not_exist_is_refused_at_check_time() -> Result<(), Box> { + let dir = tempdir()?; + fs::write(dir.path().join("lib.lk"), "fn hi() { return 1; }\n")?; + let script_path = dir.path().join("main.lk"); + fs::write( + &script_path, + "use * as lib from \"./lib.lk\";\nlet r = lib.nothere();\n", + )?; + + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + cmd.assert() + .failure() + .stderr(predicate::str::contains("`lib` has no member `nothere`")); + Ok(()) +} + +/// A *local* named after the namespace takes the name back. +/// +/// The predicate here is `has_local_binding`, deliberately not the +/// `lookup_binding` the standard-library check uses — that one counts a +/// namespace as a binding, which is exactly what disqualifies the library +/// reading of `math.f()` and exactly the opposite of what this check wants. +/// Using the wrong one made this check silently never fire. +#[test] +fn a_local_named_after_a_namespace_is_an_ordinary_value() -> Result<(), Box> { + let dir = tempdir()?; + fs::write(dir.path().join("lib.lk"), "fn hi() { return 1; }\n")?; + let script_path = dir.path().join("main.lk"); + fs::write( + &script_path, + "use * as lib from \"./lib.lk\";\n\ + fn main() {\n let lib = {\"anything\": 1};\n println(\"${lib.anything}\");\n}\n\ + main();\nprintln(\"${lib.hi()}\");\n", + )?; + + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + cmd.assert().success(); + Ok(()) +} + +/// …and everything that is not that keeps working. +/// +/// A dotted call is `a.b.c()` for *any* `a`, so the check has to know when it is +/// not looking at the standard library at all: a struct field chain, a map +/// member, a local that happens to be named after a module, a namespace bound by +/// `use * as`, and — the case that would be silently wrong — a variadic export +/// like `path.join`, which has no single-arity signature and would look +/// undeclared to anything that asked `stdlib_signature` instead of asking +/// whether the member exists. +#[test] +fn the_member_check_leaves_everything_else_alone() -> Result<(), Box> { + let dir = tempdir()?; + let script_path = dir.path().join("not_the_stdlib.lk"); + fs::write( + &script_path, + r#" + use math; + use path; + struct P { v: Int } + struct Q { p: P } + fn main() { + let q = Q { p: P { v: 1 } }; + println("${q.p.v}"); + let m = {"k": 1}; + println("${m.k}"); + let math = {"foo": 2}; + println("${math.foo}"); + println("${path.join("a", "b")}"); + } + main(); + "#, + )?; + + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + cmd.assert().success(); + Ok(()) +} + +/// `lk check` refuses named arguments to a builtin global, and lets a program's +/// own function of the same name keep its own rules. +/// +/// The rule holds for every builtin — none declares a named parameter — so the +/// checker only needs to know which names the standard library registers, not +/// their signatures. Saying it here rather than at run time is the difference +/// between `lk check` passing a program that cannot run and catching it with a +/// span. +#[test] +fn named_arguments_to_a_builtin_are_a_check_time_error() { + let dir = tempfile::tempdir().expect("temp dir"); + + let refused = dir.path().join("refused.lk"); + std::fs::write(&refused, "assert(cond: true);\n").expect("write"); + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg("check") + .arg(&refused) + .output() + .expect("run lk check"); + assert!(!output.status.success(), "the call cannot run, so it must not check"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("assert() does not accept named arguments"), + "the checker should use the same sentence the native does: {stderr}" + ); + + // A program that declares its own `assert` owns the name, and its named + // parameters are its own business. + let shadowed = dir.path().join("shadowed.lk"); + std::fs::write( + &shadowed, + "fn assert({cond: Bool}) -> Nil { return nil; }\nassert(cond: true);\n", + ) + .expect("write"); + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg("check") + .arg(&shadowed) + .output() + .expect("run lk check"); + assert!( + output.status.success(), + "a user function of the same name keeps its own rules: {}", + String::from_utf8_lossy(&output.stderr) + ); +} + +/// `lk check` catches a builtin called with the wrong number of arguments — +/// including the ones whose range only its own body used to know. +/// +/// The count is stated once, where the native enforces it, and handed to the +/// checker at registration. Before that it lived in three places (the +/// registry's single `arity`, the body's own check, a hand-written arm in the +/// checker) and only three globals had the third — so `assert(true, "a", "b")` +/// type-checked and then failed. +#[test] +fn builtin_arity_is_a_check_time_error() { + let dir = tempfile::tempdir().expect("temp dir"); + for (source, expected) in [ + ("assert(true, \"a\", \"b\");\n", "assert() expects 1 or 2 arguments"), + ( + "assert_eq(1, 1, \"a\", \"b\");\n", + "assert_eq() expects 2 or 3 arguments", + ), + ("spawn(|| 1, 2);\n", "spawn() expects exactly 1 argument"), + ("recv();\n", "recv() expects exactly 1 argument"), + ] { + let path = dir.path().join("case.lk"); + std::fs::write(&path, source).expect("write"); + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg("check") + .arg(&path) + .output() + .expect("run lk check"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(!output.status.success(), "`{source}` must not check"); + assert!(stderr.contains(expected), "`{source}` reported `{stderr}`"); + } + + // A genuinely variadic builtin takes what it is given. + let ok = dir.path().join("ok.lk"); + std::fs::write(&ok, "println(1, 2, 3);\nprint();\n").expect("write"); + let output = std::process::Command::new(env!("CARGO_BIN_EXE_lk")) + .arg("check") + .arg(&ok) + .output() + .expect("run lk check"); + assert!( + output.status.success(), + "println is variadic: {}", + String::from_utf8_lossy(&output.stderr) + ); +} + +/// `lk check` answers the same question the executors answer. +/// +/// It used to run a *stricter* checker than either backend: an unannotated +/// parameter or return type was `Function 'f' infers implicit Any …`, and the +/// same file ran fine and compiled to a native executable. Four of the +/// language's own examples were rejected by the command whose whole job is to +/// be run before running. +/// +/// The strict pass is still there behind `--strict`, where it is what it +/// always was: a lint about under-specified signatures. +#[test] +fn check_accepts_what_the_executors_accept_and_strict_is_opt_in() -> Result<(), Box> { + let dir = tempdir()?; + let script_path = dir.path().join("unannotated.lk"); + fs::write( + &script_path, + r#" + fn process(xs) { + return xs.map(|x| x * x).reduce(0, |a, b| a + b); + } + assert(process([1, 2, 3]) == 14); + "#, + )?; + + Command::cargo_bin("lk")? + .args(["check", script_path.to_str().unwrap()]) + .assert() + .success(); + + // …and it really does run, which is what makes the old answer wrong rather + // than merely strict. + Command::cargo_bin("lk")? + .arg(script_path.to_str().unwrap()) + .assert() + .success(); + + Command::cargo_bin("lk")? + .args(["check", "--strict", script_path.to_str().unwrap()]) + .assert() + .failure() + .stderr(predicate::str::contains("infers implicit Any")); + + Ok(()) +} + +/// A syntax error is reported **once**. +/// +/// `lk check` printed it twice — once through `diagnostic::parse_error` with +/// its caret snippet, and once more because the same error was then *returned* +/// for the caller to print. `lk FILE` prints and exits, which is the shape the +/// other two sites in that function already had. +#[test] +fn a_syntax_error_is_reported_once() -> Result<(), Box> { + let dir = tempdir()?; + let script_path = dir.path().join("bad_syntax.lk"); + fs::write(&script_path, "let x = ;\n")?; + + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + let output = cmd.output()?; + let stderr = String::from_utf8_lossy(&output.stderr); + let occurrences = stderr.matches("Expected expression").count(); + assert_eq!(occurrences, 1, "reported {occurrences} times:\n{stderr}"); + + Ok(()) +} + +/// `export fn` is named rather than left to "Unexpected tokens at end". +/// +/// `export` means two things in this language — `export macro_rules!` and the +/// `#[export]` attribute — and neither is what somebody who read the macro +/// documentation writes when they want a function out of a module. A function +/// needs no export at all. The old message pointed at `export` and said +/// "(found Fn)", naming neither. +#[test] +fn export_before_a_declaration_is_named() -> Result<(), Box> { + for declaration in ["fn f() { return 1; }", "struct S { x: Int }", "const C = 1;"] { + let dir = tempdir()?; + let script_path = dir.path().join("exported.lk"); + fs::write(&script_path, format!("export {declaration}\n"))?; + + let mut cmd = Command::cargo_bin("lk")?; + cmd.args(["check", script_path.to_str().unwrap()]); + cmd.assert() + .failure() + .stderr(predicate::str::contains("`export` applies to `macro_rules!` only")); + } + + Ok(()) +} diff --git a/completion/Cargo.toml b/completion/Cargo.toml index ed2ca36d..a2bc2ddc 100644 --- a/completion/Cargo.toml +++ b/completion/Cargo.toml @@ -16,4 +16,7 @@ lk-stdlib = { path = "../stdlib", optional = true } anyhow = { workspace = true } [dev-dependencies] +# `RuntimeVal`'s `PartialEq` exists for test code only — see the `testing` +# feature in lk-core. +lk-core = { path = "../core", features = ["testing"] } tempfile = { workspace = true } diff --git a/completion/src/lib.rs b/completion/src/lib.rs index 17c3918a..8ee1896a 100644 --- a/completion/src/lib.rs +++ b/completion/src/lib.rs @@ -1,4 +1,5 @@ use lk_core::token::{Token, Tokenizer}; +use lk_core::val::Type; #[cfg(feature = "stdlib")] use lk_stdlib::{StdlibExportKind, StdlibExportSpec, stdlib_catalog}; use std::{ @@ -95,6 +96,15 @@ pub struct CompletionRequest<'a> { pub trigger: CompletionTrigger, pub session_source: Option<&'a str>, pub base_dir: Option<&'a Path>, + /// Types the caller already knows for names in scope, if it knows any. + /// + /// Passed in rather than inferred here: a caller with a type checker and a + /// cache (the LSP) can hand over the result of a check it already ran, + /// while one without (the REPL, or a half-typed line that will not parse) + /// leaves it `None` and falls back to reading the token shapes. Deciding a + /// receiver's methods from "the token after `=` was a `[`" is a guess; + /// this is not. + pub known_types: Option<&'a HashMap>, } #[derive(Debug)] @@ -119,7 +129,7 @@ impl CompletionEngine { let cursor = request.cursor.min(request.source.len()); let ctx = CompletionContext::new(request.source, cursor); let symbol_source = merged_symbol_source(request.source, request.session_source); - let symbols = SymbolIndex::from_source(&symbol_source); + let symbols = SymbolIndex::from_source(&symbol_source, request.known_types); let mut out = Vec::new(); if request.mode == CompletionMode::Repl && ctx.line_prefix.trim_start().starts_with(':') { @@ -625,7 +635,21 @@ struct SymbolIndex { } impl SymbolIndex { - fn from_source(source: &str) -> Self { + fn from_source(source: &str, known_types: Option<&HashMap>) -> Self { + let mut index = Self::scan_source(source); + // Known types win: the scan above guessed from token shapes, and a + // guess should not outrank a check. + if let Some(known_types) = known_types { + for (name, ty) in known_types { + if let Some(receiver) = receiver_type_from_type(ty) { + index.types.insert(name.clone(), receiver); + } + } + } + index + } + + fn scan_source(source: &str) -> Self { let Ok((tokens, _spans)) = Tokenizer::tokenize_enhanced_with_spans(source) else { return Self::scan_lines(source); }; @@ -902,16 +926,34 @@ fn infer_receiver_type_from_tokens(token: Option<&Token>) -> Option Option { - match name { - "String" | "Str" => Some(ReceiverType::String), - "List" => Some(ReceiverType::List), - "Map" => Some(ReceiverType::Map), - "Set" => Some(ReceiverType::Set), +/// The receiver a checked type completes as. +/// +/// `Optional` unwraps: `x?.` offers the methods of what is inside. Anything +/// else — `Any`, a type variable, a struct — has no method set here, and +/// returning `None` leaves the token-shape guess in place rather than +/// replacing it with nothing. +fn receiver_type_from_type(ty: &Type) -> Option { + match ty { + Type::String => Some(ReceiverType::String), + Type::List(_) => Some(ReceiverType::List), + Type::Map(_, _) => Some(ReceiverType::Map), + Type::Set(_) => Some(ReceiverType::Set), + Type::Optional(inner) | Type::Boxed(inner) => receiver_type_from_type(inner), _ => None, } } +/// The receiver an annotation names, read through the language's own parser. +/// +/// This used to be a fourth hand-written list of type names, and it had drifted: +/// it accepted `Str`, which the language has never had, and it had no idea what +/// `List` was because it only ever saw the bare word. Asking `Type::parse` +/// costs a string parse on a path that already tokenized the whole document, and +/// it cannot disagree with the language about what a type is called. +fn receiver_type_from_name(name: &str) -> Option { + receiver_type_from_type(&Type::parse(name)?) +} + fn merged_symbol_source(source: &str, session_source: Option<&str>) -> String { match session_source { Some(session) if !session.trim().is_empty() => { @@ -1120,66 +1162,53 @@ fn parse_quoted_value(source: &str, mut cursor: usize, quote: u8) -> Option<(Str None } +/// The methods offered for a receiver, and the detail line each carries. +/// +/// Derived from `lk_core::typ::BUILTIN_METHODS` rather than listed here. The +/// list that used to live at this spot had drifted: it offered no `slice`, +/// `sort`, `pop`, `insert` or `remove_at` on a list, so the completion menu +/// quietly asserted those did not exist. fn method_candidates(receiver_type: Option) -> Vec<(&'static str, &'static str)> { - const LIST: &[&str] = &[ - "len", - "push", - "concat", - "join", - "get", - "first", - "last", - "map", - "filter", - "reduce", - "take", - "skip", - "chain", - "flatten", - "unique", - "chunk", - "enumerate", - "zip", - "contains", - ]; - const MAP: &[&str] = &[ - "len", "keys", "values", "has", "contains", "get", "set", "delete", "clear", - ]; - const SET: &[&str] = &["len", "has", "contains", "insert", "delete", "clear"]; - const STRING: &[&str] = &[ - "len", - "lower", - "upper", - "trim", - "starts_with", - "ends_with", - "contains", - "replace", - "substring", - "split", - "join", - "to_int", - "to_float", - ]; - let mut out = Vec::new(); - let groups: &[(&[&str], &str)] = match receiver_type { - Some(ReceiverType::List) => &[(LIST, "List")], - Some(ReceiverType::Map) => &[(MAP, "Map")], - Some(ReceiverType::Set) => &[(SET, "Set")], - Some(ReceiverType::String) => &[(STRING, "String")], - None => &[(LIST, "List"), (MAP, "Map"), (SET, "Set"), (STRING, "String")], + use lk_core::typ::BuiltinReceiverKind; + let kinds: &[BuiltinReceiverKind] = match receiver_type { + Some(ReceiverType::List) => &[BuiltinReceiverKind::List, BuiltinReceiverKind::Slice], + Some(ReceiverType::Map) => &[BuiltinReceiverKind::Map], + Some(ReceiverType::Set) => &[BuiltinReceiverKind::Set], + Some(ReceiverType::String) => &[BuiltinReceiverKind::Str], + // No receiver type inferred: offer everything, first owner wins, so + // the ordering below decides who `len` is attributed to. + None => &[ + BuiltinReceiverKind::List, + BuiltinReceiverKind::Map, + BuiltinReceiverKind::Set, + BuiltinReceiverKind::Str, + BuiltinReceiverKind::Slice, + ], }; + let mut out = Vec::new(); let mut seen = BTreeSet::new(); - for (items, owner) in groups { - for item in *items { - if seen.insert(*item) { - out.push((*item, *owner)); + for kind in kinds { + for sig in lk_core::typ::builtin_methods_for(*kind) { + if seen.insert(sig.name) { + out.push((sig.name, receiver_kind_label(*kind))); } } } out } +fn receiver_kind_label(kind: lk_core::typ::BuiltinReceiverKind) -> &'static str { + use lk_core::typ::BuiltinReceiverKind::*; + match kind { + List => "List", + Bytes => "Bytes", + Slice => "Slice", + Map => "Map", + Set => "Set", + Str => "String", + } +} + const KEYWORDS: &[&str] = &[ "if", "else", "while", "for", "let", "const", "fn", "return", "break", "continue", "use", "from", "as", "match", "case", "default", "true", "false", "nil", "select", "struct", "trait", "impl", "type", "go", "try", "catch", @@ -1190,6 +1219,11 @@ const OPERATORS: &[&str] = &["==", "!=", "<=", ">=", "&&", "||", "in", "<-", "?? const TYPES: &[&str] = &[ "Int", "Float", + // `Int | Float`, and the second spellings of each. A completion list is a + // claim about what can be written down. + "Number", + "i64", + "f64", "Bool", "String", "Str", @@ -1214,6 +1248,40 @@ mod tests { items.into_iter().map(|item| item.label).collect() } + #[test] + fn a_known_type_narrows_the_member_list() { + let engine = CompletionEngine::fallback(); + let source = "let parts = string.split(\"a,b\", \",\");\nparts."; + let request = CompletionRequest { + source, + cursor: source.len(), + mode: CompletionMode::Lsp, + trigger: CompletionTrigger::TriggerCharacter('.'), + session_source: None, + base_dir: None, + known_types: None, + }; + + // Reading token shapes, the value after `=` is a call, which says + // nothing — so every receiver's methods are offered at once. + let guessed = labels(engine.complete(request)); + assert!( + guessed.contains(&"keys".to_string()) && guessed.contains(&"push".to_string()), + "without a type the engine cannot tell a list from a map: {guessed:?}" + ); + + let known = HashMap::from([("parts".to_string(), Type::List(Box::new(Type::String)))]); + let checked = labels(engine.complete(CompletionRequest { + known_types: Some(&known), + ..request + })); + assert!(checked.contains(&"push".to_string()), "list methods: {checked:?}"); + assert!( + !checked.contains(&"keys".to_string()), + "a list has no `keys`: {checked:?}" + ); + } + #[cfg(feature = "stdlib")] #[test] fn completes_stdlib_globals_from_registry() { @@ -1225,6 +1293,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, })); assert!(got.contains(&"assert".to_string())); assert!(got.contains(&"assert_eq".to_string())); @@ -1242,6 +1311,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, })); assert!(got.contains(&"read_to_string".to_string()), "{got:?}"); } @@ -1256,6 +1326,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: Some("let user_name = 1;\nfn user_score() { return 1; }"), base_dir: None, + known_types: None, })); assert!(got.contains(&"user_name".to_string())); assert!(got.contains(&"user_score".to_string())); @@ -1273,6 +1344,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: Some(session_source), base_dir: None, + known_types: None, })); assert!(drawable.contains(&"Drawable".to_string())); @@ -1283,6 +1355,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: Some(session_source), base_dir: None, + known_types: None, })); assert!(point.contains(&"Point".to_string())); @@ -1293,6 +1366,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: Some(session_source), base_dir: None, + known_types: None, })); assert!(user_id.contains(&"UserId".to_string())); } @@ -1308,6 +1382,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, }); assert!( got.iter() @@ -1326,6 +1401,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, }); assert!(got.iter().any(|item| item.label == "Int")); assert!( @@ -1345,6 +1421,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, })); assert!(got.contains(&"starts_with".to_string())); assert!(!got.contains(&"set".to_string())); @@ -1362,6 +1439,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: Some(dir.path()), + known_types: None, })); assert!(got.contains(&"main.lk".to_string())); } @@ -1379,6 +1457,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, }); assert!(got.iter().any(|item| item.label == "prime_trial_division")); assert!(!got.iter().any(|item| item.label == "gcd_batch")); @@ -1399,6 +1478,7 @@ mod tests { trigger: CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, })); assert!(got.contains(&"should_run".to_string())); } @@ -1414,6 +1494,7 @@ mod tests { trigger: CompletionTrigger::Incomplete, session_source: None, base_dir: None, + known_types: None, })); assert!(got.contains(&"should_run".to_string())); } @@ -1429,6 +1510,7 @@ mod tests { trigger: CompletionTrigger::TriggerCharacter('{'), session_source: None, base_dir: None, + known_types: None, }); assert!(got.candidates.is_empty()); assert!(got.is_incomplete); @@ -1446,6 +1528,7 @@ mod tests { trigger: CompletionTrigger::TriggerCharacter('\''), session_source: None, base_dir: None, + known_types: None, })); assert_eq!(got, vec!["gcd_batch".to_string()]); } @@ -1461,6 +1544,7 @@ mod tests { trigger: CompletionTrigger::TriggerCharacter('{'), session_source: None, base_dir: None, + known_types: None, }); assert!(got.is_empty()); } @@ -1477,7 +1561,76 @@ mod tests { trigger: CompletionTrigger::TriggerCharacter('{'), session_source: None, base_dir: None, + known_types: None, })); assert!(!got.is_empty()); } + + /// Completion offers exactly the methods the checker knows, because both + /// read the one table. + /// + /// The list this replaced had drifted: no `slice`, `sort`, `pop`, + /// `insert`, `remove_at` or `index_of` on a list. A completion menu is a + /// claim about what exists, and that one was wrong in six places. + #[test] + fn every_offered_method_is_one_the_checker_has_a_signature_for() { + for (name, _) in method_candidates(Some(ReceiverType::List)) { + let list = lk_core::val::Type::List(Box::new(lk_core::val::Type::Int)); + let window = lk_core::typ::slice_of(lk_core::val::Type::Int); + assert!( + lk_core::typ::builtin_method_signature(&list, name).is_some() + || lk_core::typ::builtin_method_signature(&window, name).is_some(), + "completion offers `{name}` on a list, which the checker has no signature for" + ); + } + let offered: Vec<&str> = method_candidates(Some(ReceiverType::List)) + .into_iter() + .map(|(name, _)| name) + .collect(); + for expected in ["slice", "sort", "pop", "insert", "remove_at", "index_of"] { + assert!( + offered.contains(&expected), + "a list can `{expected}`, so completion must offer it" + ); + } + } + + /// The published string-method table names exactly the methods that exist. + /// + /// It named three that do not: `substring` (it became `slice`, with an + /// *end* rather than a length), `find` (it became `index_of`) and + /// `char_at` (it became `get`) — and `join`, which is a module function + /// taking the list first, not a method on the separator. It also omitted + /// fifteen that do. A reference page is a claim about what exists, and + /// nothing until now could disagree with it. + #[test] + fn the_published_string_method_table_names_the_methods_that_exist() { + let doc = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/../website/src/stdlib/STDLIB.md")) + .expect("the published stdlib reference"); + let section = doc + .split("\n## ") + .find(|section| section.starts_with("string\n")) + .expect("a `string` section"); + let documented: Vec<&str> = section + .lines() + .filter_map(|line| line.strip_prefix("| `")) + .filter_map(|line| line.split(['(', '`']).next()) + .collect(); + assert!(documented.len() > 20, "the table did not parse: {documented:?}"); + let declared: Vec<&str> = lk_core::typ::builtin_methods_for(lk_core::typ::BuiltinReceiverKind::Str) + .map(|sig| sig.name) + .collect(); + for name in &documented { + assert!( + declared.contains(name), + "the reference documents `{name}` on a string, which is not a method" + ); + } + for name in &declared { + assert!( + documented.contains(name), + "a string can `{name}`, and the reference does not say so" + ); + } + } } diff --git a/core/Cargo.toml b/core/Cargo.toml index 38bd1585..db7f995f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,6 +15,12 @@ tempfile = { workspace = true } [features] default = ["std", "async-runtime"] +# `RuntimeVal`'s `PartialEq`, for test code only. The type cannot answer `==` +# correctly — equality of a heap value needs the heap — so production code has +# no `==` at all and asks `Executor::runtime_values_equal` or +# `lk_stdlib_common::runtime_native::runtime_values_equal` instead. A test +# compares against values it wrote down, which is a different question. +testing = [] # `std` gates the std-heavy, VM-core-independent modules (currently the # `package` manager: Lk.toml/lock, git, filesystem). Disabling it is a step # toward the no_std `lk-vm-core` split (plan M0.7/8): the VM core (token/ast/ @@ -51,6 +57,10 @@ stacker = { version = "0.1", optional = true } # Pure-Rust float math for no_std (the inherent f64 methods live in std). libm = { version = "0.2", default-features = false } hashbrown = { version = "0.15", features = ["serde"] } +# Insertion-ordered value maps (`ValueMap`): a program's `Map` is one of these, +# so `println(m)` and `m.keys()` are functions of the value rather than of the +# hash layout. `default-features = false` keeps the no_std build available. +indexmap = { version = "2", default-features = false, features = ["serde"] } spin = { version = "0.9", default-features = false, features = ["mutex", "spin_mutex", "once"] } itoa = { version = "1" } ryu = { version = "1" } diff --git a/core/src/ast.rs b/core/src/ast.rs index 1e3c0a72..d3a41a01 100644 --- a/core/src/ast.rs +++ b/core/src/ast.rs @@ -1,5 +1,5 @@ #[cfg(test)] mod ast_test; -mod parser; +pub(crate) mod parser; pub use parser::*; diff --git a/core/src/ast/ast_test.rs b/core/src/ast/ast_test.rs index 6ba02365..4837f562 100644 --- a/core/src/ast/ast_test.rs +++ b/core/src/ast/ast_test.rs @@ -49,6 +49,49 @@ mod test { assert_eq!(parsed, expr); } + /// `{` opens a block where a map cannot be, and a map everywhere it was. + /// + /// `Expr::Block` is what every `if` arm and every function body is, and it + /// could not be *written* in value position: `let x = { let a = 1; a + 1 };` + /// was "Invalid map key start: Let", because `{` committed to a map + /// literal. A macro whose template needs a temporary has no other spelling. + /// + /// The map cases are the point of the test: this rule must not take one + /// away. + #[test] + fn a_brace_opens_a_block_only_where_a_map_cannot_be() { + let block = |source: &str| { + let tokens = Tokenizer::tokenize(source).expect("tokenize"); + match Parser::new(&tokens).parse().expect(source) { + Expr::Block(_) => true, + Expr::Map(_) => false, + other => panic!("{source} parsed as neither a block nor a map: {other:?}"), + } + }; + + // A statement keyword after the brace: a block, whatever punctuation + // follows. `let a: Int = …` puts a colon at depth 0, which the scan + // below would read as a map key — hence the keyword rule comes first. + assert!(block("{ let a = 1; a + 1 }")); + assert!(block("{ let a: Int = 1; a }")); + assert!(block("{ return 1; }")); + // No keyword: whichever of `:` / `;` / `}` comes first at depth 0. + assert!(block("{ f(); 2 }")); + assert!(block("{ 7 }")); + assert!(block("{ xs[0] }")); + // Maps, all of which still parse as maps. + assert!(!block("{}")); + assert!(!block("{\"a\": 1}")); + assert!(!block("{\"a\": {\"b\": 2}}")); + assert!(!block("{f(x): 1}")); + assert!(!block("{xs[0]: 1}")); + // A statement keyword cannot be a map key in the first place — + // `{let: 1}` was a syntax error before this rule and still is — so the + // keyword check has nothing to disambiguate against. + let tokens = Tokenizer::tokenize("{let: 1}").expect("tokenize"); + assert!(Parser::new(&tokens).parse().is_err()); + } + #[test] fn paren() { let r = r#" @@ -637,6 +680,59 @@ mod test { assert!(err.to_string().contains("too deep"), "{err}"); } + /// Only a bare **name** can start a macro invocation. + /// + /// The test used to be the open delimiter alone, so `m["a"]![0]` — unwrap a + /// map read, then index it — was "a macro invocation reached the parser", + /// for a spelling no macro could ever have. The workaround was to + /// parenthesise or split the line, for an expression with no ambiguity in + /// it. + #[test] + fn postfix_unwrap_is_not_a_macro_invocation() { + let parses = |src: &str| { + let tokens = Tokenizer::tokenize(src).expect("tokenize"); + Parser::new(&tokens).parse().is_ok() + }; + assert!(parses(r#"m["a"]![0]"#), "unwrap a map read, then index it"); + assert!(parses("xs[0]![0]"), "unwrap a list read, then index it"); + assert!(parses("m.field![0]"), "unwrap a field read, then index it"); + assert!( + parses("(m!)[0]"), + "the parenthesised spelling for unwrapping a bare name" + ); + assert!( + parses("m[\"a\"]! + 1"), + "a `!` not followed by a delimiter was always fine" + ); + + // A bare name *is* ambiguous, and the name goes to the macro — with the + // message that says so, since expansion runs before the parser. + let tokens = Tokenizer::tokenize("nope!()").expect("tokenize"); + let error = Parser::new(&tokens).parse().expect_err("no such macro"); + let text = alloc::format!("{error:#}"); + assert!(text.contains("no macro named `nope`"), "{text}"); + assert!(text.contains("(nope!)(…)"), "{text}"); + } + + /// `Expr` is parsed recursively, so its *size* is part of how deep the + /// parser can go before the stack runs out — and the depth guard is only + /// useful if it trips first. + /// + /// Adding a `Type` field to `Expr::Closure` by value (a large enum, inline) + /// grew every parse frame enough that + /// `deeply_nested_match_arms_error_instead_of_overflowing_the_stack` started + /// aborting instead of erroring. Boxing fixed it; this says so out loud, so + /// the next field either stays small or is a deliberate decision about the + /// depth bound rather than a surprise crash. + #[test] + fn the_expression_node_stays_small_enough_to_recurse_over() { + let size = core::mem::size_of::(); + assert!( + size <= 80, + "Expr grew to {size} bytes; box the new field or re-tune the parser's depth guard" + ); + } + /// A `match` value is parsed by its own `Parser`; without inheriting the /// budget, nesting there would get a fresh allowance each level. #[test] @@ -674,7 +770,7 @@ mod test { panic!("unsafe should wrap a block"); }; assert!( - matches!(statements.last().map(|s| s.as_ref()), Some(Stmt::Expr(_))), + matches!(statements.last().map(|s| s.as_ref()), Some(Stmt::Expr { .. })), "the tail must stay an expression, not become a return: {statements:?}" ); } @@ -760,4 +856,71 @@ mod test { "`1 < < 3` must not parse as a shift" ); } + + /// Macro expansion runs before parsing, so a `name!(…)` that reaches the + /// parser is one no macro answered. It used to leave the `!` unconsumed and + /// report "Unexpected tokens at end (found Not)" — a token the program does + /// not contain, and no mention of macros at all. + #[test] + fn an_undefined_macro_says_so() { + for source in ["nope!();", "let x = nope!();", "println(nope!());", "let y = nope![1];"] { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let error = crate::stmt::StmtParser::new(&tokens) + .parse_program() + .expect_err("no macro named `nope`"); + let text = format!("{error:#}"); + assert!(text.contains("no macro named `nope`"), "{source} → {text}"); + } + } + + /// A parenthesised comma says the language has no tuple literal, and what + /// to write instead. + /// + /// `(1, 2)` is what somebody coming from Python or Rust writes first. The + /// message was `Expecting ')', found Comma` — true, and no help at all: + /// there is a `Tuple` *type* in this language, so "no tuples" is not + /// the answer either. The value it describes is a list. + #[test] + fn a_parenthesised_comma_names_the_missing_tuple_literal() { + for source in ["let t = (1, 2);", "f((1, 2));", "return (a, b);"] { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let error = crate::stmt::StmtParser::new(&tokens) + .parse_program() + .expect_err("there is no tuple literal"); + let text = format!("{error:#}"); + assert!(text.contains("there is no tuple literal"), "{source} → {text}"); + assert!(text.contains("`[a, b]`"), "{source} → {text}"); + } + + // An unbalanced parenthesis that is *not* a comma keeps the plain + // report — the hint is about one mistake, not about every `)`. + let tokens = crate::token::Tokenizer::tokenize("let t = (1;").expect("tokenize"); + let error = crate::stmt::StmtParser::new(&tokens) + .parse_program() + .expect_err("unbalanced"); + let text = format!("{error:#}"); + assert!(text.contains("Expecting ')'"), "{text}"); + } + + /// `try` may start a container element, like the other block expressions. + /// + /// It became an expression in 2026-07, and `let x = try …`, `f(try …)` and + /// `return try …` all took it — but a list element and a map value are + /// decided by their own start-token list, and `Token::Try` was not on it. + /// So the one place a fallible value is most often *collected* was the one + /// place it could not be written. + #[test] + fn try_may_start_a_container_element() { + for source in [ + "let xs = [try { 1 } catch e { 0 }];", + "let m = {\"k\": try { 1 } catch e { 0 }};", + "let s = Set([try { 1 } catch e { 0 }]);", + "let xs = [1, try { 2 } catch e { 0 }, 3];", + ] { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + crate::stmt::StmtParser::new(&tokens) + .parse_program() + .unwrap_or_else(|error| panic!("{source} → {error:#}")); + } + } } diff --git a/core/src/ast/parser.rs b/core/src/ast/parser.rs index 1111be5c..8b67e209 100644 --- a/core/src/ast/parser.rs +++ b/core/src/ast/parser.rs @@ -3,7 +3,10 @@ use crate::compat::prelude::*; use crate::{ expr::{Expr, MatchArm, Pattern, TemplateStringPart}, operator::{BinOp, UnaryOp}, - token::{ParseError, Span, Token, Tokenizer, offset_to_position}, + token::{ + ParseError, Span, TemplateScanError, TemplateSegment, Token, Tokenizer, offset_to_position, + split_template_string, + }, val::{LiteralVal, Type}, }; use anyhow::{Result, anyhow}; @@ -12,16 +15,23 @@ mod literals; mod patterns; mod support; +use support::BlockTail; + pub struct Parser<'a> { tokens: &'a [Token], - pos: usize, - len: usize, + pub(crate) pos: usize, + pub(crate) len: usize, token_spans: Option<&'a [Span]>, prefix_mode: bool, /// Monotonic id for parse-time desugars (`select`, postfix `!`), so /// nested instances don't shadow each other's synthesized locals. pub(super) desugar_counter: usize, - /// Live nesting depth of `parse_expr`, bounded by [`MAX_EXPR_DEPTH`]. + /// Live *left*-nesting charged by [`Parser::nest_left`] — chain links, + /// which cost tree depth but no parser stack. Kept apart from `depth` + /// because the two are bounded by different things, and summed because a + /// later walk recurses over both. + pub(crate) left: usize, + /// Live nesting depth of `parse_expr`, bounded by [`MAX_PARSE_DEPTH`]. /// /// Expression parsing is recursive descent, so nesting depth in the source /// is Rust stack depth. Without a bound, `((((…1…))))` overflows the stack @@ -29,26 +39,111 @@ pub struct Parser<'a> { /// host that abort is at least clean (the guard page traps); on bare metal /// there is no guard page, so the same input silently walks off the stack /// into whatever is below it. - pub(super) depth: usize, + pub(crate) depth: usize, } -/// Cap on expression nesting depth (see [`Parser::depth`]). +/// Cap on source nesting depth, shared by the expression and statement +/// parsers (see [`Parser::depth`] and `StmtParser::depth`). /// /// Hand-written code does not approach this — the bound exists to turn a /// pathological or hostile input into a syntax error instead of an abort. /// -/// The value is set from measurement, not taste. One level of *source* nesting -/// costs about 18KiB of debug stack, because it unwinds the whole precedence -/// chain (`conditional` → `nullish` → `or` → … → `postfix` → `primary` → -/// `paren`) rather than one frame. A debug `lk check` (8MiB main stack) aborts -/// somewhere between 400 and 500 levels; a libtest thread only gets 2MiB, so -/// its ceiling is nearer 110. 64 sits under that with room to spare and is -/// still far past anything real code nests to. +/// One budget, not two. `if c { if c { … } }` alternates between the two +/// parsers, so a per-parser budget bounds neither: each crossing would hand +/// the next level a fresh allowance and the combined nesting would be +/// unbounded. Both parsers count into the same budget and seed it across +/// every crossing. +/// +/// The value is set from measurement, not taste. One level of *source* +/// nesting costs about 18KiB of debug stack in a plain expression, because it +/// unwinds the whole precedence chain (`conditional` → `nullish` → `or` → … +/// → `postfix` → `primary` → `paren`) rather than one frame; a level that +/// crosses into the statement parser and back (`if`, `match`, a block) costs +/// several times that. The smallest stack this has to survive is a libtest +/// thread's 2MiB, which is where the cap is measured — `deeply_nested_*` in +/// `stmt_test.rs` and `ast_test.rs` are that measurement, and they abort the +/// whole test process rather than fail if the cap is ever raised past it. #[cfg(feature = "std")] -pub(super) const MAX_EXPR_DEPTH: usize = 64; +pub(crate) const MAX_PARSE_DEPTH: usize = 64; + +/// How deep the *tree* may get, counting chain links as well as recursion. +/// +/// Recursion is bounded lower ([`MAX_PARSE_DEPTH`]) because each level is a +/// parser stack frame. A chain link is not — it costs only tree depth — so it +/// gets the larger allowance, and the two are summed because the walks that run +/// afterwards recurse over the tree without caring which built it. +/// +/// Measured: a left-nested tree overflows a debug build's stack between 700 and +/// 900 levels, and the walks that run after the parser are what overflow — the +/// parser itself only loops. `a_tree_at_the_bound_is_checked_not_aborted` runs +/// a tree of exactly this depth through the whole front end on a libtest +/// thread, which is the smallest stack any of this has to survive, so the value +/// is pinned by measurement rather than by this comment. +/// +/// The floor is real code: `one_expression_reuses_its_scratch_registers` sums +/// 300 terms on purpose, so anything under about 320 refuses a program the +/// repository itself contains. 400 clears that and keeps a two-fold margin +/// against the measured overflow. +/// +/// The stack it is measured against is a *main thread's* 8MiB, which is what +/// the CLI, the LSP and the playground run the front end on. A 2MiB libtest +/// thread takes fewer levels, so the test that pins this spawns a thread of the +/// real size rather than pretending the default is the requirement. +#[cfg(feature = "std")] +pub(crate) const MAX_TREE_DEPTH: usize = 400; + /// An MCU stack is kilobytes, not megabytes, so bare metal gets a tighter cap. #[cfg(not(feature = "std"))] -pub(super) const MAX_EXPR_DEPTH: usize = 16; +pub(crate) const MAX_PARSE_DEPTH: usize = 16; + +/// The bare-metal twin of [`MAX_TREE_DEPTH`], scaled to that stack. +#[cfg(not(feature = "std"))] +pub(crate) const MAX_TREE_DEPTH: usize = 64; + +/// Nesting-budget exhaustion, kept distinguishable from an ordinary syntax +/// error. +/// +/// A speculative parse treats a syntax error as "not this shape" and lets the +/// next candidate retry the same tokens — `try { … } catch e { }` is refused +/// by the expression parser and accepted by the statement parser, so that +/// retry is load-bearing. Budget exhaustion is not shape information: every +/// candidate fails it, and retrying each of them at every level doubles the +/// work per level. 256 nested `if`s did not finish in five minutes while the +/// two were indistinguishable. +#[derive(Debug)] +pub(crate) struct NestingTooDeep; + +impl core::fmt::Display for NestingTooDeep { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "nesting too deep") + } +} + +impl core::error::Error for NestingTooDeep {} + +/// What an expression's unconsumed tail means. +/// +/// The generic wording says only that something is left over. One token gets +/// its own sentence: `=` is the `==` slip, and it is the reason this check +/// matters — a header that dropped its tail parsed `if a = 2 { … }` as +/// `if a { … }`, type-checked, and ran with the assignment gone. +fn leftover_token_message(token: &Token) -> alloc::string::String { + if matches!(token, Token::Assign) { + return alloc::string::String::from( + "`=` assigns, and an assignment in LK is a statement rather than an expression — a comparison is `==`", + ); + } + // The other arrival from another language: `=>` heads a lambda in JS and + // an arm in Rust's `match`; here it is neither spelling. + if matches!(token, Token::Arrow) { + return alloc::string::String::from( + "`=>` is not an operator in LK — a lambda is `|x| x + 1`, and a `match` arm is `pattern => { … }`", + ); + } + // Plain wording: `err` appends the token itself, so naming it here would + // print it twice. + alloc::string::String::from("Unexpected tokens at end") +} struct StructLiteralParts { fields: Vec<(String, Box)>, @@ -68,11 +163,64 @@ struct ParsedSelectCase { body: Expr, } +/// The name a parse-time desugar binds its temporary under. +/// +/// `$` cannot appear in a source identifier — the lexer will not produce one — +/// which is why the internal builtins (`try$call`, `select$block`) already use +/// it. Minting every desugar local through here keeps the two halves from +/// drifting: the name nothing can collide with, and the name tools recognise +/// as *not the writer's*. They used to be `__unwrap0` / `__optcall0`, which a +/// program may legitimately spell, so no filter could tell them apart — and +/// the editor's outline listed them beside the real variables. +pub(crate) fn desugar_local(kind: &str, id: usize) -> String { + format!("{kind}${id}") +} + +/// Is `name` a local a desugar minted, rather than one the writer bound? +pub fn is_desugar_local(name: &str) -> bool { + name.contains('$') +} + +/// Build the desugared AST for `a?.m(args)`. +/// +/// `Vec>` is the AST's own argument type, not a boxing choice made +/// here — see `Expr::CallExpr`. +/// +/// `{ let t = a; t == nil ? nil : t.m(args) }` — the receiver is evaluated +/// once, and the call does not happen at all when it is nil. +#[allow(clippy::vec_box, reason = "the AST stores arguments as `Vec>`")] +fn desugar_optional_call(id: usize, receiver: Expr, field: Expr, args: Vec>) -> Expr { + use crate::stmt::Stmt; + + let name = desugar_local("optcall", id); + let binding = Box::new(Stmt::Let { + pattern: Pattern::Variable(name.clone()), + type_annotation: None, + value: Box::new(receiver), + span: None, + is_const: false, + }); + let call = Expr::CallExpr( + Box::new(Expr::Access(Box::new(Expr::Var(name.clone())), Box::new(field))), + args, + ); + let check = Expr::Conditional( + Box::new(Expr::Bin( + Box::new(Expr::Var(name)), + BinOp::Eq, + Box::new(Expr::Literal(LiteralVal::Nil)), + )), + Box::new(Expr::Literal(LiteralVal::Nil)), + Box::new(call), + ); + Expr::Block(vec![binding, Box::new(Stmt::expr(Box::new(check)))]) +} + /// Build the desugared AST for a postfix `!` unwrap (see `parse_postfix`). fn desugar_unwrap(id: usize, operand: Expr) -> Expr { use crate::stmt::Stmt; - let name = format!("__unwrap{id}"); + let name = desugar_local("unwrap", id); let binding = Box::new(Stmt::Let { pattern: Pattern::Variable(name.clone()), type_annotation: None, @@ -92,7 +240,7 @@ fn desugar_unwrap(id: usize, operand: Expr) -> Expr { )), Box::new(Expr::Var(name)), ); - Expr::Block(vec![binding, Box::new(Stmt::Expr(Box::new(check)))]) + Expr::Block(vec![binding, Box::new(Stmt::expr(Box::new(check)))]) } /// Build the desugared AST for a parsed `select` (see `parse_select` for the @@ -133,7 +281,7 @@ fn desugar_select(id: usize, cases: Vec, default_case: Option< // Channel operands, send values, and guards evaluate eagerly, in source // order (Go's rule), into synthesized locals. for (i, case) in cases.into_iter().enumerate() { - let channel_name = format!("__select{id}_ch_{i}"); + let channel_name = format!("{}_ch_{i}", desugar_local("select", id)); let (kind, binding) = match case.arm { ParsedSelectArm::Recv { binding, channel } => { statements.push(let_stmt(channel_name.clone(), channel)); @@ -142,13 +290,13 @@ fn desugar_select(id: usize, cases: Vec, default_case: Option< } ParsedSelectArm::Send { channel, value } => { statements.push(let_stmt(channel_name.clone(), channel)); - let value_name = format!("__select{id}_v_{i}"); + let value_name = format!("{}_v_{i}", desugar_local("select", id)); statements.push(let_stmt(value_name.clone(), value)); values.push(Box::new(Expr::Var(value_name))); (1, None) } }; - let guard_name = format!("__select{id}_g_{i}"); + let guard_name = format!("{}_g_{i}", desugar_local("select", id)); // Normalize any truthy guard to a real Bool — `select$block` treats // non-Bool guard entries as disabled. let guard_value = match case.guard { @@ -162,7 +310,7 @@ fn desugar_select(id: usize, cases: Vec, default_case: Option< arms.push((binding, case.body)); } - let result_name = format!("__select{id}_r"); + let result_name = format!("{}_r", desugar_local("select", id)); statements.push(let_stmt( result_name.clone(), Expr::Call( @@ -185,7 +333,7 @@ fn desugar_select(id: usize, cases: Vec, default_case: Option< let arm_body = match binding { Some(name) => Expr::Block(vec![ let_stmt(name, index(index(Expr::Var(result_name.clone()), 2), 1)), - Box::new(Stmt::Expr(Box::new(body))), + Box::new(Stmt::expr(Box::new(body))), ]), None => body, }; @@ -204,7 +352,7 @@ fn desugar_select(id: usize, cases: Vec, default_case: Option< Box::new(default_case.unwrap_or_else(nil_lit)), Box::new(dispatch), ); - statements.push(Box::new(Stmt::Expr(Box::new(top)))); + statements.push(Box::new(Stmt::expr(Box::new(top)))); Expr::Block(statements) } @@ -217,7 +365,8 @@ impl<'a> Parser<'a> { let exp = self.parse_expr()?; if !self.eof() { - return Err(anyhow!(self.err("Unexpected tokens at end"))); + let msg = leftover_token_message(&self.tokens[self.pos]); + return Err(anyhow!(self.err(&msg))); } // All sub-expressions parsed, apply constant folding optimization @@ -290,24 +439,54 @@ impl<'a> Parser<'a> { Ok(exp.fold_constants()) } - /// Runs `parse` one level deeper, refusing to go past [`MAX_EXPR_DEPTH`]. + /// Runs `parse` one level deeper, refusing to go past [`MAX_PARSE_DEPTH`]. /// /// Every recursive descent that can nest without bound has to go through /// here, not just `parse_expr`: prefix operators recurse into themselves /// (`!!!…x`) and `match` arms recurse into `parse_conditional` directly, /// so bounding only `parse_expr` left both able to overflow the stack. fn deeper(&mut self, parse: impl FnOnce(&mut Self) -> Result) -> Result { - if self.depth >= MAX_EXPR_DEPTH { - return Err(anyhow!(self.err("Expression nesting too deep"))); + if self.depth >= MAX_PARSE_DEPTH { + return Err(anyhow::Error::new(NestingTooDeep).context(self.err("Expression nesting too deep"))); } + let entry = (self.depth, self.left); self.depth += 1; - // Decremented on the error path too — a bounded parse that fails must - // not leave the counter raised for whatever the caller tries next. + // Restored on the error path too — a bounded parse that fails must not + // leave the counter raised for whatever the caller tries next. And + // restored *absolutely* rather than by one, because `nest_left` charges + // this same counter without a matching decrement of its own: a chain's + // levels belong to the expression that contains it, and this is where + // that expression ends. let parsed = parse(self); - self.depth -= 1; + (self.depth, self.left) = entry; parsed } + /// Charges one more level of *left* nesting against the same budget. + /// + /// `deeper` bounds recursive descent. These loops are the other half: + /// `a.b().c()…`, `a + b + c…` and every other precedence level are parsed + /// by iteration and build a tree exactly as deep as the chain is long, so + /// they spent nothing and were unbounded. A 1700-link method chain and a + /// 1200-term sum both parsed clean and then overflowed the stack in a later + /// walk — `SIGABRT`, not a diagnostic, on input the parser had accepted. + /// The interpreter, the LSP and the browser playground all parse text they + /// did not write. + /// + /// The budget is the *tree's* depth, so this shares `self.depth` rather + /// than keeping its own count: two chains at different precedence levels on + /// one path add up, and separate counters would each see only their half. + /// The language already says expression nesting is bounded at + /// [`MAX_PARSE_DEPTH`] — a chain is nesting, and this is what makes it + /// count. + fn nest_left(&mut self) -> Result<()> { + if self.depth + self.left >= MAX_TREE_DEPTH { + return Err(anyhow::Error::new(NestingTooDeep).context(self.err("Expression nesting too deep"))); + } + self.left += 1; + Ok(()) + } + /// A parser over a token sub-slice that continues *this* parser's depth /// budget. A nested parse is still nesting even when it gets its own /// `Parser`, so starting the sub-parser back at zero would hand a @@ -315,6 +494,7 @@ impl<'a> Parser<'a> { fn sub_parser<'b>(&self, tokens: &'b [Token]) -> Parser<'b> { let mut parser = Parser::new(tokens); parser.depth = self.depth; + parser.left = self.left; parser } @@ -332,6 +512,7 @@ impl<'a> Parser<'a> { /// - `cond ? then : else` (ternary conditional) /// Right-associative; precedence lower than nullish coalescing/or/and. fn parse_conditional(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_nullish_coalescing()?; if !self.eof() && self.tokens[self.pos] == Token::Question { // consume '?' @@ -349,70 +530,97 @@ impl<'a> Parser<'a> { // parse else branch (allow nesting: right-associative) let else_expr = self.parse_expr()?; + self.nest_left()?; expr = Expr::Conditional(Box::new(expr), Box::new(then_expr), Box::new(else_expr)); } + self.left = entry_left; Ok(expr) } /// - `expr ?? expr` (nullish coalescing) fn parse_nullish_coalescing(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_or()?; while !self.eof() { match self.tokens[self.pos] { Token::NullishCoalescing => { self.pos += 1; let right = self.parse_or()?; + self.nest_left()?; expr = Expr::NullishCoalescing(Box::new(expr), Box::new(right)); } _ => break, } } + self.left = entry_left; Ok(expr) } /// - `expr || expr` fn parse_or(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_and()?; while !self.eof() { match self.tokens[self.pos] { Token::Or => { self.pos += 1; let right = self.parse_and()?; + self.nest_left()?; expr = Expr::Or(Box::new(expr), Box::new(right)); } _ => break, } } + self.left = entry_left; Ok(expr) } /// `expr && expr` fn parse_and(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_bit_or()?; while !self.eof() { match self.tokens[self.pos] { Token::And => { self.pos += 1; let right = self.parse_bit_or()?; + self.nest_left()?; expr = Expr::And(Box::new(expr), Box::new(right)); } _ => break, } } + self.left = entry_left; Ok(expr) } /// `expr | expr` fn parse_bit_or(&mut self) -> Result { - let mut expr = self.parse_bit_and()?; + let mut expr = self.parse_bit_xor()?; while !self.eof() && self.tokens[self.pos] == Token::Pipe { self.pos += 1; - let right = self.parse_bit_and()?; + let right = self.parse_bit_xor()?; expr = Self::builtin_call("__lk_bit_or", vec![expr, right]); } Ok(expr) } + /// `expr ^ expr` + /// + /// Between `|` and `&`, as in C and Rust. It was the one bitwise operator + /// with no spelling: `&`, `|`, `<<`, `>>` and `~` were all there, and + /// `__lk_bit_xor` was already named in the type checker's arity table and + /// the VM compiler's builtin list — a name nothing could produce. + fn parse_bit_xor(&mut self) -> Result { + let mut expr = self.parse_bit_and()?; + while !self.eof() && self.tokens[self.pos] == Token::BitXor { + self.pos += 1; + let right = self.parse_bit_and()?; + expr = Self::builtin_call("__lk_bit_xor", vec![expr, right]); + } + Ok(expr) + } + /// `expr & expr` fn parse_bit_and(&mut self) -> Result { let mut expr = self.parse_cmp()?; @@ -469,6 +677,7 @@ impl<'a> Parser<'a> { /// - `expr != expr` /// ... fn parse_cmp(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_range()?; while !self.eof() { let op = match self.tokens[self.pos] { @@ -483,8 +692,10 @@ impl<'a> Parser<'a> { }; self.pos += 1; let right = self.parse_range()?; + self.nest_left()?; expr = Expr::Bin(Box::new(expr), op, Box::new(right)); } + self.left = entry_left; Ok(expr) } @@ -542,6 +753,7 @@ impl<'a> Parser<'a> { /// - `expr + expr` /// - `expr - expr` fn parse_add_sub(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_mul_div()?; while !self.eof() { let op = match self.tokens[self.pos] { @@ -551,14 +763,17 @@ impl<'a> Parser<'a> { }; self.pos += 1; let right = self.parse_mul_div()?; + self.nest_left()?; expr = Expr::Bin(Box::new(expr), op, Box::new(right)); } + self.left = entry_left; Ok(expr) } /// - `expr * expr` /// - `expr / expr` fn parse_mul_div(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_cast()?; while !self.eof() { let op = match self.tokens[self.pos] { @@ -569,8 +784,10 @@ impl<'a> Parser<'a> { }; self.pos += 1; let right = self.parse_cast()?; + self.nest_left()?; expr = Expr::Bin(Box::new(expr), op, Box::new(right)); } + self.left = entry_left; Ok(expr) } @@ -628,16 +845,20 @@ impl<'a> Parser<'a> { /// the same precedence Rust gives it. Left-associative: `x as u8 as u32` /// is `(x as u8) as u32`, which is how a double conversion is written. fn parse_cast(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_unary()?; while !self.eof() && self.tokens[self.pos] == Token::As { self.pos += 1; let ty = self.parse_cast_target()?; + self.nest_left()?; expr = Expr::Cast(Box::new(expr), ty); } + self.left = entry_left; Ok(expr) } /// - `!expr` + /// - `-expr` /// - `expr` fn parse_unary(&mut self) -> Result { if self.eof() { @@ -645,6 +866,25 @@ impl<'a> Parser<'a> { } let token = &self.tokens[self.pos]; match token { + // `-expr`. + // + // The lexer already folds a *literal* `-5` into `Int(-5)` where it + // can tell an operand is expected, which is why the language got + // this far without a negation operator at all: `-5` worked and + // `-x` was a syntax error everywhere, with `0 - x` as the + // workaround. That lexer path stays — it is the only thing that can + // spell `-9223372036854775808`, whose magnitude does not fit in an + // `i64` — so this arm folds literals the same way to keep the two + // routes producing identical code. + Token::Sub => { + self.pos += 1; + let expr = self.deeper(Self::parse_unary)?; + Ok(match expr { + Expr::Literal(LiteralVal::Int(value)) => Expr::Literal(LiteralVal::Int(-value)), + Expr::Literal(LiteralVal::Float(value)) => Expr::Literal(LiteralVal::Float(-value)), + other => Expr::Unary(UnaryOp::Neg, Box::new(other)), + }) + } Token::Not => { self.pos += 1; let expr = self.deeper(Self::parse_unary)?; @@ -666,6 +906,7 @@ impl<'a> Parser<'a> { /// - `func_name(args)` /// - `TypeName { field: expr, ... }` (struct literal) fn parse_postfix(&mut self) -> Result { + let entry_left = self.left; let mut expr = self.parse_primary()?; loop { @@ -709,9 +950,29 @@ impl<'a> Parser<'a> { } self.pos += 1; // skip ')' - if saw_named { + // `a?.m(args)` is a *call*, and `OptionalAccess` is a read: + // the compiler lowers it as an index, so `s?.len()` indexed the + // string with the string `"len"` and failed at runtime with + // "String index must be Int" — on the one operator that exists + // for values which may be nil. + // + // Rewritten here into the conditional it means, the way postfix + // `!` is. The checker and the compiler then see ordinary + // constructs, and the result is `T?` because one branch is nil + // — the rule every other maybe-missing branch follows. + let optional_receiver = matches!((&expr, saw_named), (Expr::OptionalAccess(_, _), false)); + if optional_receiver { + let Expr::OptionalAccess(receiver, field) = expr else { + unreachable!("checked just above"); + }; + let id = self.desugar_counter; + self.desugar_counter += 1; + expr = desugar_optional_call(id, *receiver, *field, pos_args); + } else if saw_named { + self.nest_left()?; expr = Expr::CallNamed(Box::new(expr), pos_args, named_args); } else { + self.nest_left()?; expr = Expr::CallExpr(Box::new(expr), pos_args); } } else if !self.eof() && self.tokens[self.pos] == Token::LBrace { @@ -720,6 +981,8 @@ impl<'a> Parser<'a> { expr = self.parse_struct_literal_after_name(name.clone())?; } else if self.prefix_mode { break; + } else if let Some(literal) = self.parse_qualified_struct_literal(&expr)? { + expr = literal; } else { // If not a simple Var before '{', treat as error to avoid ambiguity with blocks return Err(anyhow!(self.err( @@ -735,6 +998,7 @@ impl<'a> Parser<'a> { } let field = self.parse_field_name()?; + self.nest_left()?; expr = Expr::Access(Box::new(expr), Box::new(field)); } else if !self.eof() && self.tokens[self.pos] == Token::OptionalDot { // Optional dot access (?.) @@ -743,7 +1007,18 @@ impl<'a> Parser<'a> { return Err(anyhow!(self.err("Expecting field after '?.'"))); } let field = self.parse_field_name()?; + // `a?.m(args)` is a *call*, and `OptionalAccess` is a read: the + // compiler lowers it as an index, so `s?.len()` indexed the + // string with the string `"len"` and failed at runtime with + // "String index must be Int" — on the one operator that exists + // for values which may be nil. + // + // Desugared here, the way postfix `!` is, into the conditional + // it means. Both the checker and the compiler then see ordinary + // constructs: the result is `T?` because one branch is nil, + // which is the rule every other maybe-missing branch follows. // Optional access is only supported on regular expressions, not @ expressions + self.nest_left()?; expr = Expr::OptionalAccess(Box::new(expr), Box::new(field)); } else if !self.eof() && self.tokens[self.pos] == Token::Question @@ -782,6 +1057,7 @@ impl<'a> Parser<'a> { } self.pos += 1; // skip ']' + self.nest_left()?; expr = Expr::OptionalAccess(Box::new(expr), index_expr); } else if !self.eof() && self.tokens[self.pos] == Token::LBracket { // Bracket indexing: expr[expr] @@ -817,35 +1093,67 @@ impl<'a> Parser<'a> { self.pos += 1; // skip ']' // Build bracket Access + self.nest_left()?; expr = Expr::Access(Box::new(expr), index_expr); - } else if !self.eof() - && self.tokens[self.pos] == Token::Not - && !matches!( - self.tokens.get(self.pos + 1), - Some(Token::LParen | Token::LBracket | Token::LBrace) - ) - { + } else if !self.eof() && self.tokens[self.pos] == Token::Not && !self.macro_invocation_follows(&expr) { // Postfix `!` — Swift-style force unwrap, parse-time sugar: // `expr!` ⇒ `{ let __unwrap{n} = expr; // __unwrap{n} == nil ? error("unwrap of nil value") // : __unwrap{n} }` // Raises a catchable error on nil, evaluates to the value - // otherwise. Two boundaries: `!` immediately followed by an - // open delimiter stays a *macro invocation* (`name!(...)` / - // `name![...]` / `name!{...}` — parenthesize as `(x!)(...)` - // to call an unwrapped value), and the lexer greedily takes - // `!=` as Ne, so `x!== 1` is a parse error — write `x! == 1`. + // otherwise. Two boundaries: a `!` that continues a *macro + // name* is a macro invocation, not an unwrap (see + // [`Parser::macro_invocation_follows`]), and the lexer greedily + // takes `!=` as Ne, so `x!== 1` is a parse error — write + // `x! == 1`. self.pos += 1; self.desugar_counter += 1; expr = desugar_unwrap(self.desugar_counter, expr); + } else if !self.eof() && self.tokens[self.pos] == Token::Not && self.macro_invocation_follows(&expr) { + // A macro invocation that reached the parser is one macro + // expansion left alone, and expansion runs first — so no macro + // of this name is defined. Saying that beats what the fall + // through said: `nope!()` left the `!` unconsumed and reported + // "Unexpected tokens at end (found Not)", which names a token + // the program does not contain and no macro at all. + let Expr::Var(name) = &expr else { + unreachable!("macro_invocation_follows only answers true for a bare name"); + }; + let msg = alloc::format!( + "no macro named `{name}` is defined — `{name}!(…)` is a macro invocation, \ + and to call an unwrapped value write `({name}!)(…)`" + ); + return Err(anyhow!(self.err(&msg))); } else { break; // No more postfix operations } } + self.left = entry_left; Ok(expr) } + /// Whether the `!` at the cursor continues a **macro invocation** rather + /// than being a postfix unwrap. + /// + /// A macro name is an identifier, so only a bare name can be one: + /// `name!(…)`, `name![…]`, `name!{…}`. The test used to be the open + /// delimiter alone, which made `m["a"]![0]` — unwrap a map read, then index + /// it — a "macro invocation reached the parser" error, for a spelling no + /// macro could ever have. `xs[0]![0]` likewise. The workaround was to + /// parenthesise or split the line, for an expression with no ambiguity in + /// it at all. + /// + /// To *call* an unwrapped value bound to a bare name, parenthesise: + /// `(f!)(…)`. That one really is ambiguous, and the name goes to the macro. + fn macro_invocation_follows(&self, expr: &Expr) -> bool { + matches!(expr, Expr::Var(_)) + && matches!( + self.tokens.get(self.pos + 1), + Some(Token::LParen | Token::LBracket | Token::LBrace) + ) + } + /// Parse struct fields: '{ id: expr, ... }' fn parse_struct_literal_after_name(&mut self, name: String) -> Result { let parts = self.parse_struct_fields()?; @@ -870,6 +1178,53 @@ impl<'a> Parser<'a> { } } + /// `module.Type { field: value, … }` — the constructor call it desugars to. + /// + /// The object has to be built *by the module that declares the type*: a + /// type's identity carries its defining module (`val::TypeScope`), and one + /// built here would not be the type `impl … for Type` was registered + /// against. So this becomes `module.Type$new(field: value, …)`, the + /// constructor `stmt::struct_ctors` puts beside every `struct` — an + /// ordinary cross-module call, which runs *there*. + /// + /// Named arguments, so the call site needs to know nothing about the + /// declaration: the same `field: value` pairs the literal is written with, + /// and a missing or misspelled one is the constructor's own arity error. + /// + /// `None` when the receiver is not a qualified name (`m.f() { … }`, a map + /// field followed by a block) — the caller then reports its own error. + fn parse_qualified_struct_literal(&mut self, expr: &Expr) -> Result> { + let Expr::Access(module, field) = expr else { + return Ok(None); + }; + let Expr::Var(_) = module.as_ref() else { + return Ok(None); + }; + let Expr::Literal(name) = field.as_ref() else { + return Ok(None); + }; + let Some(type_name) = name.as_str() else { + return Ok(None); + }; + // A type, by the same spelling rule the rest of the language uses. + if !type_name.starts_with(char::is_uppercase) { + return Ok(None); + } + let parts = self.parse_struct_fields()?; + if parts.update_base.is_some() { + return Err(anyhow!(self.err( + "`..base` update syntax needs the type's own module: build the value there and update it here" + ))); + } + let callee = Expr::Access( + module.clone(), + Box::new(Expr::Literal(LiteralVal::from_str( + &crate::stmt::struct_ctors::constructor_name(type_name), + ))), + ); + Ok(Some(Expr::CallNamed(Box::new(callee), Vec::new(), parts.fields))) + } + fn parse_struct_fields(&mut self) -> Result { if self.eof() || self.tokens[self.pos] != Token::LBrace { return Err(anyhow!(self.err("Expecting '{' to start struct literal"))); @@ -886,6 +1241,13 @@ impl<'a> Parser<'a> { } loop { + // The token stream can end here — a struct literal inside a string + // interpolation is cut at the first `}`, so `"${R {}}"` arrives as + // `R {` and nothing more. Reading past the end panicked the parser; + // an unterminated literal is a syntax error like any other. + if self.eof() { + return Err(anyhow!(self.err("Unexpected end in struct literal fields"))); + } if self.tokens[self.pos] == Token::Range { if update_base.is_some() { return Err(anyhow!(self.err("Duplicate struct update base"))); @@ -918,11 +1280,15 @@ impl<'a> Parser<'a> { } } - // Field name must be identifier + // Field name. A keyword names one unambiguously here — a struct + // literal's `{ … }` holds `name: value` pairs and nothing else. let key = if let Token::Id(id) = &self.tokens[self.pos] { let k = id.clone(); self.pos += 1; k + } else if let Some(word) = crate::token::keyword_as_name(&self.tokens[self.pos]) { + self.pos += 1; + word.to_string() } else { return Err(anyhow!(self.err("Expected identifier as struct field name"))); }; @@ -992,6 +1358,22 @@ impl<'a> Parser<'a> { self.pos += 1; Ok(Expr::Literal(LiteralVal::Int(*i))) } + // A radix literal that needs all 64 bits *is* a `u64`, so it is + // parsed as one — the same expression `0x8000_0000_0000_0000 as u64` + // builds, which already worked and is what the error used to tell + // people to write. + // + // Saying it here rather than relaxing the range check is what keeps + // `let y: u8 = -1` refused: the carrier cannot tell those two apart, + // and by this point the token still can. + Token::UInt { value, .. } => { + let value = *value; + self.pos += 1; + Ok(Expr::Cast( + Box::new(Expr::Literal(LiteralVal::Int(value as i64))), + crate::val::Type::MachineInt(crate::val::IntKind::U64), + )) + } Token::Float(f) => { self.pos += 1; Ok(Expr::Literal(LiteralVal::Float(*f))) @@ -1005,10 +1387,14 @@ impl<'a> Parser<'a> { self.parse_template_string_content(content) } Token::LBracket => self.parse_list(), + // `{` opens a map *or* a block — see `brace_opens_a_block`. + Token::LBrace if self.brace_opens_a_block() => self.parse_brace_block(BlockTail::Value), Token::LBrace => self.parse_map(), Token::Select => self.parse_select(), Token::Unsafe => self.parse_unsafe_block(), Token::Match => self.parse_match(), + Token::If => self.parse_if_expr(), + Token::Try => self.parse_try_expr(), Token::LParen => self.parse_paren(), Token::Fn => self.parse_fn_closure(), Token::Pipe => self.parse_closure(), @@ -1117,15 +1503,16 @@ impl<'a> Parser<'a> { } /// Parse match expression: match value { pattern => expr, ... } - fn parse_match(&mut self) -> Result { - if self.tokens[self.pos] != Token::Match { - let msg = format!("Expecting 'match', found {:?}", self.tokens[self.pos]); - return Err(anyhow!(self.err(&msg))); - } - self.pos += 1; - - // Parse the value to match against, stopping before the opening '{' - // to avoid consuming it as a struct literal in postfix parsing. + /// The expression between a keyword and its `{ … }` — a `match` scrutinee + /// or an `if` condition. + /// + /// Parsed from a token slice that stops at the first *top-level* `{`, + /// because postfix parsing would otherwise read `match x {` as the struct + /// literal `x { … }`. Leaves `self.pos` on that brace; the caller consumes + /// it. The cost is that a struct literal cannot be written bare in this + /// position — `if Point { x: 1 } == p` needs parentheses — which is the + /// same trade Rust makes, for the same reason. + fn parse_header_expr_before_brace(&mut self, keyword: &str) -> Result> { let start_pos = self.pos; let mut i = self.pos; let mut paren: i32 = 0; @@ -1152,15 +1539,20 @@ impl<'a> Parser<'a> { } i += 1; } - Token::LBrace if paren == 0 && bracket == 0 => { - break; // stop before '{' that begins match arms - } + Token::LBrace if paren == 0 && bracket == 0 => break, _ => i += 1, } } if i == start_pos { - return Err(anyhow!(self.err("Expected value before '{' in match expression"))); + // The only expression that can start with `{` is a map literal, and + // here `{` is the body's — so say which way out there is rather + // than only that this is wrong. + let msg = alloc::format!( + "Expected an expression before '{{' in {keyword}: a `{{` here opens the body, \ + so a map literal must be parenthesised — `{keyword} ({{…}}) {{ … }}`" + ); + return Err(anyhow!(self.err(&msg))); } let value_tokens = &self.tokens[start_pos..i]; @@ -1171,12 +1563,116 @@ impl<'a> Parser<'a> { self.sub_parser(value_tokens) }; let value = Box::new(sub.parse_expr()?); + // Everything up to the `{` has to *be* the expression. The sub-parser + // stops at the first token it cannot continue with, and its leftovers + // used to be dropped without a word: `if a = 2 { … }` parsed as + // `if a { … }`, type-checked, and ran with the assignment gone — + // exactly the `=`-for-`==` slip, turned into a silent wrong answer. + // + // Statement position never had this: it parses the condition through + // the ordinary path, which does check what it did not consume. Only a + // *tail* `if`/`match` reaches here, so the shape was accepted at the + // end of a file and rejected one line earlier. + if sub.pos < sub.len { + self.pos = start_pos + sub.pos; + let msg = leftover_token_message(&sub.tokens[sub.pos]); + return Err(anyhow!(self.err(&msg))); + } self.pos = i; if self.eof() || self.tokens[self.pos] != Token::LBrace { - return Err(anyhow!(self.err("Expecting '{' after match value"))); + let msg = alloc::format!("Expecting '{{' after the {keyword} expression"); + return Err(anyhow!(self.err(&msg))); + } + Ok(value) + } + + /// `try { … } catch e { … }`, which is an expression like `if` and `match`. + /// + /// The value is the body's trailing expression, or the handler's when the + /// body raised — the same rule `if` uses for its two branches, including + /// "a branch that ends in a statement yields nil". `let r = try { … } + /// catch e { … };` used to be a syntax error, so the way to get a value out + /// was to declare a `nil` first and assign into it from both halves, or to + /// wrap the whole thing in a function and `return` twice. + /// + /// Statement position parses through here too (`StmtParser::parse_try_stmt` + /// wraps the result in `Stmt::Expr`), so there is one node, one type rule + /// and one lowering — as with `if`, whose statement form is not a second + /// implementation either. + fn parse_try_expr(&mut self) -> Result { + self.pos += 1; // 'try' + if self.eof() || self.tokens[self.pos] != Token::LBrace { + return Err(anyhow!(self.err("Expected '{' after `try`"))); + } + let Expr::Block(body) = self.parse_brace_block(BlockTail::Value)? else { + return Err(anyhow!(self.err("`try` body must be a block"))); + }; + if self.eof() || self.tokens[self.pos] != Token::Catch { + return Err(anyhow!(self.err("Expected `catch` after the `try` block"))); } self.pos += 1; + let catch_var = match self.tokens.get(self.pos) { + Some(Token::Id(name)) => { + let name = name.clone(); + self.pos += 1; + name + } + _ => return Err(anyhow!(self.err("Expected an identifier after `catch`"))), + }; + if self.eof() || self.tokens[self.pos] != Token::LBrace { + return Err(anyhow!(self.err("Expected '{' after the `catch` binding"))); + } + let Expr::Block(handler) = self.parse_brace_block(BlockTail::Value)? else { + return Err(anyhow!(self.err("`catch` body must be a block"))); + }; + Ok(Expr::Try { + body, + catch_var, + handler, + }) + } + + /// `if cond { … } else { … }` in *expression* position. + /// + /// `match` has always been an expression here; `if` was not, so + /// `let a = match c { … };` worked and `let a = if c { … } else { … };` + /// was a syntax error, with the C-style ternary as the only way to choose + /// a value — the very operator a language whose `if` is an expression does + /// not need. Both now lower through the same node: `Expr::Conditional` + /// over two `Expr::Block`s, each evaluating to its last expression. + /// + /// A missing `else` yields `nil`, as does a branch whose block ends in a + /// statement rather than an expression. + fn parse_if_expr(&mut self) -> Result { + self.pos += 1; // 'if' + let condition = self.parse_header_expr_before_brace("if")?; + let then_block = self.parse_brace_block(BlockTail::Value)?; + let else_expr = if !self.eof() && self.tokens[self.pos] == Token::Else { + self.pos += 1; + if self.eof() { + return Err(anyhow!(self.err("Expected a block or 'if' after 'else'"))); + } + match self.tokens[self.pos] { + Token::If => self.deeper(Self::parse_if_expr)?, + Token::LBrace => self.parse_brace_block(BlockTail::Value)?, + _ => return Err(anyhow!(self.err("Expected a block or 'if' after 'else'"))), + } + } else { + Expr::Literal(LiteralVal::Nil) + }; + Ok(Expr::Conditional(condition, Box::new(then_block), Box::new(else_expr))) + } + + fn parse_match(&mut self) -> Result { + if self.tokens[self.pos] != Token::Match { + let msg = format!("Expecting 'match', found {:?}", self.tokens[self.pos]); + return Err(anyhow!(self.err(&msg))); + } + self.pos += 1; + + let value = self.parse_header_expr_before_brace("match")?; + self.pos += 1; let mut arms = Vec::new(); @@ -1192,7 +1688,18 @@ impl<'a> Parser<'a> { // Parse body expression. Through `parse_expr`, not // `parse_conditional`: the arm body is where `match` nests into // itself, so it has to be counted. - let body = Box::new(self.parse_expr()?); + // + // A `{` here opens a *block*, as it does after a closure's + // parameters — `1 => { work(); }` was a syntax error before, + // because postfix parsing read the brace as a map literal and then + // found statements inside it. The cost is that an arm whose value + // really is a map needs parentheses (`_ => ({"k": 1})`), which is + // the trade Rust makes for the same reason. + let body = Box::new(if !self.eof() && self.tokens[self.pos] == Token::LBrace { + self.parse_brace_block(BlockTail::Value)? + } else { + self.parse_expr()? + }); arms.push(MatchArm { pattern, body }); @@ -1215,75 +1722,35 @@ impl<'a> Parser<'a> { } /// Parse template string content from a TemplateString token + /// + /// Where the `${…}` boundaries are is [`split_template_string`]'s answer, not + /// this function's: the scan used to live here as a second copy of the + /// lexer's, and the two disagreed about nested braces (`"${R {}}"` cut at the + /// first `}` and the struct-literal parser then read past the end of its + /// stream). The macro expander now needs the same answer, which makes a + /// shared scanner the only way to keep three readers of one syntax honest. fn parse_template_string_content(&mut self, content: &str) -> Result { + let segments = split_template_string(content) + .map_err(|TemplateScanError::Unclosed| anyhow!(self.err("Unclosed template expression")))?; let mut parts = Vec::new(); - let mut current_literal = String::new(); - let mut in_expr = false; - let mut expr_start = 0usize; // byte offset into `content` - - // Use char_indices so `byte_pos` is always a valid byte boundary for slicing. - let chars: Vec<(usize, char)> = content.char_indices().collect(); - let mut i = 0; - - while i < chars.len() { - let (byte_pos, c) = chars[i]; - - if in_expr { - if c == '}' { - // End of ${...} expression — byte_pos is the correct slice bound. - let expr_content = &content[expr_start..byte_pos]; - if !expr_content.is_empty() { - let expr_tokens = match Tokenizer::tokenize_enhanced(expr_content) { - Ok(tokens) => tokens, - Err(e) => { - return Err(anyhow!( - self.err(&format!("Failed to parse template expression: {}", e)) - )); - } - }; - - if !expr_tokens.is_empty() { - let mut expr_parser = self.sub_parser(&expr_tokens); - match expr_parser.parse_expr() { - Ok(expr) => parts.push(TemplateStringPart::Expr(Box::new(expr))), - Err(e) => { - return Err(anyhow!( - self.err(&format!("Failed to parse template expression: {}", e)) - )); - } - } - } + for segment in segments { + match segment { + TemplateSegment::Literal(text) => parts.push(TemplateStringPart::Literal(text.to_string())), + TemplateSegment::Expr("") => {} + TemplateSegment::Expr(text) => { + let expr_tokens = Tokenizer::tokenize_enhanced(text) + .map_err(|e| anyhow!(self.err(&format!("Failed to parse template expression: {e}"))))?; + if expr_tokens.is_empty() { + continue; } - in_expr = false; + let mut expr_parser = self.sub_parser(&expr_tokens); + let expr = expr_parser + .parse_expr() + .map_err(|e| anyhow!(self.err(&format!("Failed to parse template expression: {e}"))))?; + parts.push(TemplateStringPart::Expr(Box::new(expr))); } - i += 1; - } else if c == '$' && i + 1 < chars.len() && chars[i + 1].1 == '{' { - // Start of ${expr} syntax — skip both '$' and '{'. - i += 2; - - if !current_literal.is_empty() { - parts.push(TemplateStringPart::Literal(core::mem::take(&mut current_literal))); - } - - in_expr = true; - // expr_start is the byte offset of the first char inside the braces. - expr_start = if i < chars.len() { chars[i].0 } else { content.len() }; - } else { - current_literal.push(c); - i += 1; } } - - // Push any remaining literal content - if !current_literal.is_empty() { - parts.push(TemplateStringPart::Literal(current_literal)); - } - - // If we're still in an expression, it's an error - if in_expr { - return Err(anyhow!(self.err("Unclosed template expression"))); - } - Ok(Expr::TemplateString(parts)) } @@ -1294,14 +1761,24 @@ impl<'a> Parser<'a> { self.pos += 1; let expr = self.parse_expr()?; if self.eof() || self.tokens[self.pos] != Token::RParen { - let msg = format!( - "Expecting ')', found {:?}", - if self.eof() { - &Token::Nil - } else { - &self.tokens[self.pos] - } - ); + // A comma here is almost always somebody writing a tuple. The + // language has none — `Tuple` is a *type*, and the value + // it describes is a list — so "Expecting ')'" left the reader + // to guess what to write instead. + let msg = if !self.eof() && self.tokens[self.pos] == Token::Comma { + "there is no tuple literal — a value with several elements is a list, written `[a, b]`. \ + (`Tuple` is a type for exactly that, not a second kind of value)" + .to_string() + } else { + format!( + "Expecting ')', found {:?}", + if self.eof() { + &Token::Nil + } else { + &self.tokens[self.pos] + } + ) + }; return Err(anyhow!(self.err(&msg))); } self.pos += 1; diff --git a/core/src/ast/parser/literals.rs b/core/src/ast/parser/literals.rs index 12be36e2..20d42e44 100644 --- a/core/src/ast/parser/literals.rs +++ b/core/src/ast/parser/literals.rs @@ -8,7 +8,10 @@ impl<'a> Parser<'a> { /// Parse map literal: `{key: value, key: value, ...}` pub(super) fn parse_map(&mut self) -> Result { if self.tokens[self.pos] != Token::LBrace { - let msg = format!("Expecting '{{', found {:?}", self.tokens[self.pos]); + let msg = format!( + "Expecting '{{', found `{}`", + crate::token::token_lexeme(&self.tokens[self.pos]) + ); return Err(anyhow!(self.err(&msg))); } self.pos += 1; @@ -54,7 +57,10 @@ impl<'a> Parser<'a> { } Token::RBrace => break, _ => { - let msg = format!("Expecting ',' or '}}', found {:?}", self.tokens[self.pos]); + let msg = format!( + "Expecting ',' or '}}', found `{}`", + crate::token::token_lexeme(&self.tokens[self.pos]) + ); return Err(anyhow!(self.err(&msg))); } } @@ -114,8 +120,16 @@ impl<'a> Parser<'a> { self.pos += 1; Ok(expr) } - _ => { - let msg = format!("Invalid field name: {:?}", self.tokens[self.pos]); + // A keyword is a fine member name: this position follows a `.`, + // where nothing can start a statement (see `keyword_as_name`). + token if crate::token::keyword_as_name(token).is_some() => { + let word = crate::token::keyword_as_name(token).expect("checked"); + let expr = Expr::Literal(LiteralVal::from_str(word)); + self.pos += 1; + Ok(expr) + } + other => { + let msg = alloc::format!("Invalid field name: {}", crate::token::token_lexeme(other)); Err(anyhow!(self.err(&msg))) } } diff --git a/core/src/ast/parser/patterns.rs b/core/src/ast/parser/patterns.rs index 09558e66..264a7b01 100644 --- a/core/src/ast/parser/patterns.rs +++ b/core/src/ast/parser/patterns.rs @@ -14,6 +14,22 @@ impl<'a> Parser<'a> { Ok((pattern, self.pos)) } + /// Parse `tokens` as one whole pattern — every token must belong to it. + /// + /// The binding parsers slice out "everything before the top-level `=`" and + /// hand the slice here. Parsing a *prefix* and dropping the rest is what + /// let `let mut s = 0;` through: it bound `mut`, discarded `s`, and the + /// program only failed later at the *use* of `s`, pointing away from the + /// actual mistake. Anything left over is a syntax error, reported here. + pub fn parse_whole_pattern(tokens: &[Token]) -> Result { + let mut parser = Parser::new(tokens); + let (pattern, consumed) = parser.parse_pattern_prefix()?; + if consumed < tokens.len() { + return Err(anyhow!(unconsumed_pattern_error(tokens, consumed))); + } + Ok(pattern) + } + /// Parse OR pattern: pattern1 | pattern2 pub(super) fn parse_or_pattern(&mut self) -> Result { let mut patterns = vec![self.parse_guard_pattern()?]; @@ -54,8 +70,18 @@ impl<'a> Parser<'a> { match &self.tokens[self.pos] { // Literal patterns - Token::Int(i) => { - let start_val = *i; + // + // `UInt` joins `Int` here rather than becoming a cast the way it does + // in expression position: a pattern compares carriers, and the + // carrier of `0xFFFF_FFFF_FFFF_FFFF` is the one the scrutinee will + // be holding. Leaving it out would turn a pattern that used to mean + // *something* into a parse error. + Token::Int(_) | Token::UInt { .. } => { + let start_val = match &self.tokens[self.pos] { + Token::Int(i) => *i, + Token::UInt { value, .. } => *value as i64, + _ => unreachable!("matched just above"), + }; self.pos += 1; // Check if this is a range pattern @@ -318,3 +344,17 @@ impl<'a> Parser<'a> { Ok(ParsedSelectCase { arm, guard, body }) } } + +fn unconsumed_pattern_error(tokens: &[Token], consumed: usize) -> String { + let tail: Vec = tokens[consumed..].iter().map(crate::token::token_lexeme).collect(); + let tail = tail.join(" "); + // `mut` is a plain identifier to the lexer, so `let mut x` parses as the + // pattern `mut` with `x` left over. LK has no binding modifier: every + // variable is rebindable, so the fix is always to drop the word. + if matches!(tokens.first(), Some(Token::Id(name)) if name == "mut") { + return alloc::format!( + "Syntax error: `mut` is not a binding modifier in LK (variables are rebindable already) — write `{tail}` instead of `mut {tail}`" + ); + } + alloc::format!("Syntax error: unexpected `{tail}` after the pattern — a binding takes one pattern before '='") +} diff --git a/core/src/ast/parser/support.rs b/core/src/ast/parser/support.rs index 3b155f49..18ff046b 100644 --- a/core/src/ast/parser/support.rs +++ b/core/src/ast/parser/support.rs @@ -7,6 +7,8 @@ use crate::{ expr::Expr, stmt::{Stmt, StmtParser}, token::{ParseError, Span, Token}, + type_syntax::StopAt, + val::Type, }; /// What a `{ … }` block's final expression means. @@ -30,6 +32,7 @@ impl<'a> Parser<'a> { prefix_mode: false, desugar_counter: 0, depth: 0, + left: 0, } } @@ -44,6 +47,7 @@ impl<'a> Parser<'a> { prefix_mode: false, desugar_counter: 0, depth: 0, + left: 0, } } @@ -109,21 +113,36 @@ impl<'a> Parser<'a> { } let body = self.parse_expr()?; + let param_types = vec![None; params.len()]; Ok(Expr::Closure { params, + param_types, + return_type: None, body: Box::new(body), }) } /// Parse closure expression: `|param1, param2| expr`. + /// + /// Each parameter may carry a type, and the whole closure a return type: + /// `|x: Int, y: Int| -> Int { … }`. Both are optional and independent — a + /// lambda was the one callable in the language whose types could not be + /// written down at all, so its parameter types could only be guessed from a + /// call site. + /// + /// A union cannot be written directly in a parameter, because `|` there + /// closes the list; it goes through a `type` alias (see + /// [`crate::type_syntax`]). pub(super) fn parse_closure(&mut self) -> Result { self.pos += 1; let mut params = Vec::new(); + let mut param_types: Vec> = Vec::new(); if !self.eof() && self.tokens[self.pos] != Token::Pipe { if let Token::Id(param_name) = &self.tokens[self.pos] { params.push(param_name.clone()); self.pos += 1; + param_types.push(self.parse_closure_param_type()?); } else { return Err(anyhow!( self.err("Expected parameter name or '|' after opening '|' in closure") @@ -135,6 +154,7 @@ impl<'a> Parser<'a> { if let Token::Id(param_name) = &self.tokens[self.pos] { params.push(param_name.clone()); self.pos += 1; + param_types.push(self.parse_closure_param_type()?); } else { return Err(anyhow!(self.err("Expected parameter name after comma in closure"))); } @@ -146,6 +166,20 @@ impl<'a> Parser<'a> { } self.pos += 1; + // `-> T` before the body. The body is what follows either way, so this + // is the only place the arrow can appear. + let return_type = if !self.eof() && self.tokens[self.pos] == Token::FnArrow { + self.pos += 1; + let Some((ty, end)) = crate::type_syntax::parse_type_at(self.tokens, self.pos, StopAt::ClosureReturn) + else { + return Err(anyhow!(self.err("Expected a return type after '->' in closure"))); + }; + self.pos = end; + Some(Box::new(ty)) + } else { + None + }; + if self.eof() || !self.is_valid_expr_start() { return Err(anyhow!(self.err("Expected expression after closure parameters"))); } @@ -157,10 +191,25 @@ impl<'a> Parser<'a> { }; Ok(Expr::Closure { params, + param_types, + return_type, body: Box::new(body), }) } + /// The `: T` after a closure parameter name, when written. + fn parse_closure_param_type(&mut self) -> Result> { + if self.eof() || self.tokens[self.pos] != Token::Colon { + return Ok(None); + } + self.pos += 1; + let Some((ty, end)) = crate::type_syntax::parse_type_at(self.tokens, self.pos, StopAt::ClosureParam) else { + return Err(anyhow!(self.err("Expected a type after ':' in closure parameter"))); + }; + self.pos = end; + Ok(Some(ty)) + } + pub(super) fn parse_closure_block_expr(&mut self) -> Result { self.parse_brace_block(BlockTail::Return) } @@ -233,12 +282,15 @@ impl<'a> Parser<'a> { if !matches!(inner.last(), Some(Token::Semicolon)) { inner.push(Token::Semicolon); } + // Continues this parser's nesting budget: a block body is still + // nesting even though the statement parser gets its own counter. let mut stmt_parser = StmtParser::new(&inner); + stmt_parser.depth = self.depth; let program = stmt_parser.parse_program()?; let mut statements = program.statements; if tail == BlockTail::Return && let Some(last) = statements.last_mut() - && let Stmt::Expr(expr) = last.as_ref() + && let Stmt::Expr { value: expr, .. } = last.as_ref() { let value = expr.clone(); **last = Stmt::Return { value: Some(value) }; @@ -321,7 +373,7 @@ impl<'a> Parser<'a> { pub(super) fn err(&self, msg: &str) -> String { let ctx = if let Some(token) = self.tokens.get(self.pos) { - format!("found {:?}", token) + format!("found `{}`", crate::token::token_lexeme(token)) } else { "found end of input".to_string() }; @@ -430,6 +482,83 @@ impl<'a> Parser<'a> { } /// Check if the current token can start a valid expression. + /// Can an expression begin at the current token? + /// + /// This is the *predicate* form of the grammar `parse_primary` and + /// `parse_unary` implement, used wherever an expression is optional — a + /// range with no end, a trailing list element, a closure body. It has to + /// list every form those two accept, and it is checked by hand, so it + /// drifts: `Unsafe` and `Match` were missing, which is why + /// `|x| match x { … }` was a syntax error while `let a = match x { … };` + /// parsed fine. Adding a primary form means adding it here too. + /// Whether the `{` at the cursor opens a **block**, not a map literal. + /// + /// `{` is the one token that starts two different things, and the parser + /// used to commit to "map" — so `Expr::Block`, which every `if` arm and + /// every function body is, could not be *written* where a value was + /// expected: `let x = { let a = 1; a + 1 };` was "Invalid map key start: + /// Let". A macro whose template needs a temporary has no other spelling, + /// which is where this surfaced. + /// + /// Two rules, in order: + /// + /// 1. A statement keyword right after the brace is a block. Every keyword + /// in that list is statement-*only* — none of them can start an + /// expression, so none can be a map key, and there is nothing to + /// disambiguate against. The rule comes first because a `let`'s own type + /// annotation puts a colon at depth 0 (`{ let a: Int = 1; a }`), which + /// rule 2 would read as a map key. + /// 2. Otherwise: whichever of `:` / `;` / `}` appears first at depth 0. A + /// map *must* have `key: value`, and cannot contain a `;` at all, so a + /// `;` or a closing brace first means block. `{ }` is decided before + /// either rule — it is the empty map, as it always was. + pub(super) fn brace_opens_a_block(&self) -> bool { + let statement_keyword = |token: &Token| { + matches!( + token, + Token::Let + | Token::Const + | Token::Return + | Token::While + | Token::For + | Token::Break + | Token::Continue + | Token::Use + | Token::Struct + | Token::Trait + | Token::Impl + | Token::Go + ) + }; + let first = self.pos + 1; + // `{}` is the empty map, as it always was — and it has to be decided + // here, because rule 2 sees the closing brace first and would call it a + // block (whose value is nil). + if self.tokens.get(first) == Some(&Token::RBrace) { + return false; + } + if self.tokens.get(first).is_some_and(statement_keyword) { + return true; + } + let (mut paren, mut bracket, mut brace) = (0usize, 0usize, 0usize); + for token in &self.tokens[first..] { + match token { + Token::LParen => paren += 1, + Token::RParen => paren = paren.saturating_sub(1), + Token::LBracket => bracket += 1, + Token::RBracket => bracket = bracket.saturating_sub(1), + Token::LBrace => brace += 1, + Token::RBrace if brace > 0 => brace -= 1, + _ if paren + bracket + brace > 0 => {} + Token::Colon => return false, + Token::Semicolon | Token::RBrace => return true, + _ => {} + } + } + // Unterminated: let the map parser report it, as it did before. + false + } + pub(super) fn is_valid_expr_start(&self) -> bool { if self.eof() { return false; @@ -440,17 +569,34 @@ impl<'a> Parser<'a> { Token::Nil | Token::Bool(_) | Token::Int(_) + | Token::UInt { .. } | Token::Float(_) | Token::Str(_) + // A template string is a string. Leaving it out made `|x| + // "n=${x}"` a syntax error while `|x| "n"` parsed — the + // interpolation, not the closure, was what the parser objected + // to, and it is the more common of the two by far. + | Token::TemplateString(_) | Token::Id(_) | Token::LBracket | Token::LBrace | Token::LParen | Token::Not + | Token::Sub | Token::BitNot | Token::Select | Token::Pipe | Token::Fn + | Token::Match + | Token::If + // `try` is an expression like the two above it — that is what + // the 2026-07 decision made it — and this list is what decides + // whether one may start a *container element*. Missing here, it + // was an expression everywhere else and a syntax error inside + // `[…]` and `{k: …}`, which is precisely where a fallible value + // gets collected. + | Token::Try + | Token::Unsafe ) } diff --git a/core/src/compat.rs b/core/src/compat.rs index e4ca95c5..4c93d87e 100644 --- a/core/src/compat.rs +++ b/core/src/compat.rs @@ -54,6 +54,13 @@ pub mod sync { pub fn lock(&self) -> std::sync::LockResult> { self.0.lock() } + + /// The guard, or `None` if the lock is held — *including by this very + /// thread*, which neither backing type is re-entrant about. That case + /// is the reason this exists: see `RuntimeCallable::collect_garbage`. + pub fn try_lock(&self) -> Option> { + self.0.try_lock().ok() + } } #[cfg(not(feature = "std"))] impl Mutex { @@ -61,6 +68,11 @@ pub mod sync { pub fn lock(&self) -> Result, core::convert::Infallible> { Ok(self.0.lock()) } + + /// See the std impl above. + pub fn try_lock(&self) -> Option> { + self.0.try_lock() + } } } @@ -133,14 +145,14 @@ pub mod prelude { pub(crate) mod float { #[cfg(feature = "std")] #[inline] - pub(crate) fn fract(x: f64) -> f64 { - x.fract() + pub(crate) fn floor(x: f64) -> f64 { + x.floor() } #[cfg(not(feature = "std"))] #[inline] - pub(crate) fn fract(x: f64) -> f64 { - libm::modf(x).0 + pub(crate) fn floor(x: f64) -> f64 { + libm::floor(x) } #[cfg(feature = "std")] diff --git a/core/src/expr/expr_impl.rs b/core/src/expr/expr_impl.rs index 41e99b72..433eaa86 100644 --- a/core/src/expr/expr_impl.rs +++ b/core/src/expr/expr_impl.rs @@ -68,6 +68,79 @@ pub enum Pattern { inclusive: bool, }, } + +/// The first name a binder declares twice, if any. +/// +/// A construct that binds one name twice can never read the first binding: the +/// second shadows it before anything runs, so `fn f(a: Int, a: Int)` ignores +/// its first argument and `[a, a]` matches *any* two elements rather than two +/// equal ones — the reading somebody arrives with from a language whose +/// patterns are non-linear. +/// +/// `Or` alternatives are walked one at a time on purpose: `A(x) | B(x)` binds +/// the same name in every arm deliberately, and that is the only way an `Or` +/// binds anything at all. +pub(crate) fn duplicate_binding(pattern: &Pattern) -> Option { + fn note(name: &str, seen: &mut Vec) -> Option { + if seen.iter().any(|s| s == name) { + return Some(name.to_string()); + } + seen.push(name.to_string()); + None + } + + fn walk(pattern: &Pattern, seen: &mut Vec) -> Option { + match pattern { + Pattern::Variable(name) => note(name, seen), + Pattern::Wildcard | Pattern::Literal(_) | Pattern::Range { .. } => None, + Pattern::List { patterns, rest } => { + for p in patterns { + if let Some(dup) = walk(p, seen) { + return Some(dup); + } + } + rest.as_ref().and_then(|r| note(r, seen)) + } + Pattern::Map { patterns, rest } => { + for (_key, p) in patterns { + if let Some(dup) = walk(p, seen) { + return Some(dup); + } + } + rest.as_ref().and_then(|r| note(r, seen)) + } + Pattern::Or(alts) => alts.iter().find_map(|alt| walk(alt, &mut seen.clone())), + Pattern::Guard { pattern, .. } => walk(pattern, seen), + } + } + + walk(pattern, &mut Vec::new()) +} + +impl Pattern { + /// Whether this pattern matches every value, with no guard to make it + /// conditional. + /// + /// Two questions the type checker asks are the same question, so they + /// share one answer: whether a `match` can fall through all its arms + /// (which is why its type is `T?` and not `T`), and whether a later arm is + /// dead code. Answering them separately lets a pattern be total for one + /// and partial for the other. + /// + /// The VM compiler recognizes a *narrower* set — `Compiler::bind_catch_all` + /// takes only `Wildcard` and `Variable`, leaving an or-pattern to its + /// ordinary test — so it is conservative exactly where this is permissive, + /// which is the safe direction: it emits a fallthrough the checker has + /// proven unreachable, rather than dropping one that is not. + pub fn is_unguarded_catch_all(&self) -> bool { + match self { + Pattern::Wildcard | Pattern::Variable(_) => true, + // An or-pattern is total when any alternative is. + Pattern::Or(alternatives) => alternatives.iter().any(Pattern::is_unguarded_catch_all), + _ => false, + } + } +} /// Match arm: pattern => expression #[derive(Debug, Clone, PartialEq)] pub struct MatchArm { @@ -159,9 +232,25 @@ pub enum Expr { }, /// Template string: `Hello ${name}!` TemplateString(Vec), - /// Closure: |param1, param2| expr + /// Closure: `|param1, param2| expr`, optionally annotated — + /// `|x: Int, y: Int| -> Int { … }`. Closure { params: Vec, + /// Declared parameter types, positionally; `None` where unannotated. + /// Always the same length as `params`. + /// + /// A lambda used to be the one callable in the language whose types + /// could not be written down, even though `Type::Function` has always + /// had both halves — so a lambda's parameter type could only ever be + /// *guessed* from a call site. + param_types: Vec>, + /// Declared return type, when written. + /// + /// Boxed: `Type` is a large enum, and `Expr` is parsed recursively — + /// inlining it here grew every parse frame enough to overflow the stack + /// at a nesting depth the parser's own guard used to catch first + /// (`deeply_nested_match_arms_error_instead_of_overflowing_the_stack`). + return_type: Option>, body: Box, }, /// Expression-level block, primarily for multi-statement closure bodies. @@ -171,6 +260,26 @@ pub enum Expr { value: Box, arms: Vec, }, + /// `try { body } catch name { handler }` — a protected region, and a value. + /// + /// One node for both positions. It used to be a statement, so + /// `let r = try { … } catch e { … };` was a syntax error while `if` and + /// `match` were both expressions. In statement position it is a + /// `Stmt::Expr` of this and the value is discarded — which is how `if` and + /// `match` sit there too, so it needs no second node. + /// + /// A real node rather than parse-time sugar, for the reason it stopped + /// being sugar in the first place: rewritten as + /// `let [ok, e] = try$call(|| { body })`, every later stage saw a closure + /// and a destructuring `let` instead of a protected region, and an + /// annotated local assigned inside the body came back out as a fresh type + /// variable. + Try { + body: Vec>, + /// The name the handler binds the caught error to. + catch_var: String, + handler: Vec>, + }, Literal(LiteralVal), } impl Expr { @@ -270,10 +379,10 @@ impl Expr { } } } - Expr::Closure { params: _, body } => { + Expr::Closure { params: _, body, .. } => { body.collect_ctx_names(names); } - Expr::Block(_) => {} + Expr::Block(_) | Expr::Try { .. } => {} Expr::Match { value, arms } => { value.collect_ctx_names(names); for arm in arms { @@ -293,7 +402,26 @@ impl Expr { Expr::Literal(_) => {} // Receive operator: collect from inner expression } } - /// Constant folding: calculate pure constant sub-expressions as LiteralVal constants + /// Constant folding: calculate pure constant sub-expressions as LiteralVal constants. + /// + /// This runs in the **parser**, before name resolution and before the type + /// checker. So it may *compute*, but it may not *delete*: a fold that + /// selects one of two operands throws the other one away, and whatever was + /// in there is then never checked by anybody. `let x = if false { + /// undefined_fn() } else { 1 };` and `let x = false && undefined_fn();` + /// both passed `lk check` for exactly that reason — the call was gone + /// before the checker ran. + /// + /// The rule is therefore: **a selecting fold is allowed only when the + /// discarded side is already a literal**, since a literal has nothing left + /// to check. Eliminating a branch on a constant condition is an + /// optimization, and optimizations belong after the front end — the VM + /// compiler sees the same constant and the AOT backend folds it again. + /// + /// The other half of the same mistake is folding without looking at the + /// operator: `-true` used to fold to `false` and print it, while `-b` on a + /// `Bool` variable is rejected. That is a wrong answer, not a missing + /// diagnostic. pub(crate) fn fold_constants(self) -> Expr { match self { Expr::Literal(_) => self, // Constant value, return directly @@ -322,15 +450,34 @@ impl Expr { let t = (*t_box).fold_constants(); let e = (*e_box).fold_constants(); if let Expr::Literal(LiteralVal::Bool(b)) = c { - return if b { t } else { e }; + // Only when the arm being dropped is itself a literal — + // see the note on this method. + let discarded = if b { &e } else { &t }; + if matches!(discarded, Expr::Literal(_)) { + return if b { t } else { e }; + } } Expr::Conditional(Box::new(c), Box::new(t), Box::new(e)) } Expr::Unary(op, expr_box) => { let inner = (*expr_box).fold_constants(); - // Constant folding: !expr, if expr is boolean constant then calculate result - if let Expr::Literal(LiteralVal::Bool(b)) = &inner { - return Expr::Literal(LiteralVal::Bool(!*b)); + // The operator decides what folds. `!` on a `Bool` and `-` on a + // number; every other pairing is a type error the checker owns. + match (&op, &inner) { + (UnaryOp::Not, Expr::Literal(LiteralVal::Bool(b))) => { + return Expr::Literal(LiteralVal::Bool(!*b)); + } + // `checked_neg`, because `-i64::MIN` has no answer and the + // executor raises there rather than wrapping. + (UnaryOp::Neg, Expr::Literal(LiteralVal::Int(i))) => { + if let Some(negated) = i.checked_neg() { + return Expr::Literal(LiteralVal::Int(negated)); + } + } + (UnaryOp::Neg, Expr::Literal(LiteralVal::Float(f))) => { + return Expr::Literal(LiteralVal::Float(-*f)); + } + _ => {} } Expr::Unary(op, Box::new(inner)) } @@ -340,18 +487,13 @@ impl Expr { Expr::Cast(expr_box, ty) => Expr::Cast(Box::new((*expr_box).fold_constants()), ty), // The marker survives folding; it is what the checker reads. Expr::Unsafe(expr_box) => Expr::Unsafe(Box::new((*expr_box).fold_constants())), + // `&&` and `||` do not short-circuit *here*. Dropping the right + // operand because the left is a constant hides it from the type + // checker; the executor still short-circuits at run time, which is + // the only place short-circuiting is observable. Expr::And(e1_box, e2_box) => { let e1 = (*e1_box).fold_constants(); - // Short-circuit constant false: left side constant false, then entire AND is constant false - if let Expr::Literal(LiteralVal::Bool(false)) = e1 { - return Expr::Literal(LiteralVal::Bool(false)); - } let e2 = (*e2_box).fold_constants(); - // Short-circuit constant true: left side constant true, then return right side expression result - if let Expr::Literal(LiteralVal::Bool(true)) = e1 { - return e2; - } - // Both folded, if both are boolean constants then can further fold if let (Expr::Literal(LiteralVal::Bool(b1)), Expr::Literal(LiteralVal::Bool(b2))) = (&e1, &e2) { return Expr::Literal(LiteralVal::Bool(*b1 && *b2)); } @@ -359,15 +501,7 @@ impl Expr { } Expr::Or(e1_box, e2_box) => { let e1 = (*e1_box).fold_constants(); - if let Expr::Literal(LiteralVal::Bool(true)) = e1 { - // Left side constant true, OR expression is constant true - return Expr::Literal(LiteralVal::Bool(true)); - } let e2 = (*e2_box).fold_constants(); - if let Expr::Literal(LiteralVal::Bool(false)) = e1 { - // Left side constant false, OR result depends on right side - return e2; - } if let (Expr::Literal(LiteralVal::Bool(b1)), Expr::Literal(LiteralVal::Bool(b2))) = (&e1, &e2) { return Expr::Literal(LiteralVal::Bool(*b1 || *b2)); } @@ -375,15 +509,25 @@ impl Expr { } Expr::NullishCoalescing(e1_box, e2_box) => { let e1 = (*e1_box).fold_constants(); - // If left side is constant not nil, return it - if let Expr::Literal(v) = &e1 - && *v != LiteralVal::Nil - { - return e1; - } let e2 = (*e2_box).fold_constants(); - // If left side is constant nil, return right side - if let Expr::Literal(LiteralVal::Nil) = e1 { + // `nil ?? e` discards only the literal `nil`, so it folds. + // + // The other direction does **not**, even with two literals. It + // discards `e` — and `??` requires its two sides to unify, so + // discarding one hides a type error that only the checker can + // see: + // + // ```lk + // let a = 7 ?? "ab"; // folded to 7 + // let b = maybe_int() ?? "ab"; // Cannot unify Int with String + // ``` + // + // That was fourteen of the operator/type pairs in the fold-vs-run + // differential, and every one of them the same shape as the + // string-repeat fold: the folder deciding a typing question it + // has no business deciding. Folding `7 ?? 0` bought one branch + // at run time in code nobody writes. + if let Expr::Literal(LiteralVal::Nil) = &e1 { return e2; } Expr::NullishCoalescing(Box::new(e1), Box::new(e2)) @@ -524,14 +668,30 @@ impl Expr { } Expr::TemplateString(folded_parts) } - Expr::Closure { params, body } => { + Expr::Closure { + params, + param_types, + return_type, + body, + } => { // Closures cannot be folded at compile time due to environment capture Expr::Closure { params: params.clone(), + param_types: param_types.clone(), + return_type: return_type.clone(), body: Box::new(body.fold_constants()), } } Expr::Block(statements) => Expr::Block(statements), + Expr::Try { + body, + catch_var, + handler, + } => Expr::Try { + body, + catch_var, + handler, + }, Expr::Match { value, arms } => { // Match expressions cannot be fully folded without runtime evaluation // but we can fold the value and arm bodies @@ -660,11 +820,12 @@ impl Display for Expr { } write!(f, "\"") } - Expr::Closure { params, body } => { + Expr::Closure { params, body, .. } => { let params_str = params.join(", "); write!(f, "|{}| {}", params_str, body) } Expr::Block(_) => write!(f, "{{ ... }}"), + Expr::Try { catch_var, .. } => write!(f, "try {{ ... }} catch {catch_var} {{ ... }}"), Expr::Match { value, arms } => { write!(f, "match {} {{", value)?; for (i, arm) in arms.iter().enumerate() { @@ -690,10 +851,23 @@ impl Display for Expr { } } +/// Folding is a *shortcut*, so every one of these must answer exactly what the +/// executors answer — the fold happens before the type checker even runs, so a +/// rule that only exists here is a rule no diagnostic can reach. +/// +/// Two ways that went wrong, both fixed below: +/// +/// - **Overflow.** These used the bare operators. Int arithmetic wraps in both +/// executors (`i64::MAX + 1` is `i64::MIN`, `i64::MIN % -1` is `0`), but +/// `a + b` in Rust *panics* in a debug build and wraps in a release one — so +/// `9223372036854775807 + 1` in a source file crashed the parser with +/// `attempt to add with overflow`, or folded correctly, depending on which +/// profile `lk` itself was built with. `wrapping_*` states the rule. +/// - **Operations the language does not have.** See `fold_literal_mul`. fn fold_literal_arith(lhs: &LiteralVal, op: &BinOp, rhs: &LiteralVal) -> Option { match op { BinOp::Add => fold_literal_add(lhs, rhs), - BinOp::Sub => fold_literal_numeric(lhs, rhs, |a, b| a - b, |a, b| a - b), + BinOp::Sub => fold_literal_numeric(lhs, rhs, i64::wrapping_sub, |a, b| a - b), BinOp::Mul => fold_literal_mul(lhs, rhs), BinOp::Div => fold_literal_div(lhs, rhs), BinOp::Mod => fold_literal_mod(lhs, rhs), @@ -703,7 +877,7 @@ fn fold_literal_arith(lhs: &LiteralVal, op: &BinOp, rhs: &LiteralVal) -> Option< fn fold_literal_add(lhs: &LiteralVal, rhs: &LiteralVal) -> Option { match (lhs, rhs) { - (LiteralVal::Int(a), LiteralVal::Int(b)) => Some(LiteralVal::Int(a + b)), + (LiteralVal::Int(a), LiteralVal::Int(b)) => Some(LiteralVal::Int(a.wrapping_add(*b))), (LiteralVal::Float(a), LiteralVal::Float(b)) => Some(LiteralVal::Float(a + b)), (LiteralVal::Float(a), LiteralVal::Int(b)) => Some(LiteralVal::Float(a + *b as f64)), (LiteralVal::Int(a), LiteralVal::Float(b)) => Some(LiteralVal::Float(*a as f64 + b)), @@ -718,13 +892,17 @@ fn fold_literal_add(lhs: &LiteralVal, rhs: &LiteralVal) -> Option { buf.format(*value), )) } - (lhs, LiteralVal::Float(value)) if lhs.as_str().is_some() => { - let mut buf = ryu::Buffer::new(); - Some(LiteralVal::concat_strings( - lhs.as_str().expect("checked string"), - buf.format(*value), - )) - } + // `to_string`, not `ryu`. A float's rendering is Rust's `Display` + // (`docs/semantics.md`), which lkrt is aligned to byte for byte — + // `ryu` is a *shortest round-trip* formatter and answers differently: + // `3.0` where `Display` says `3`, `1e300` where it says three hundred + // digits. Folding used it, so `"" + 1.0e300` and + // `let x = 1.0e300; "" + x` were the same expression with two answers, + // decided by whether the operand happened to be a literal. + (lhs, LiteralVal::Float(value)) if lhs.as_str().is_some() => Some(LiteralVal::concat_strings( + lhs.as_str().expect("checked string"), + &value.to_string(), + )), (LiteralVal::Int(value), rhs) if rhs.as_str().is_some() => { let mut buf = itoa::Buffer::new(); Some(LiteralVal::concat_strings( @@ -732,35 +910,32 @@ fn fold_literal_add(lhs: &LiteralVal, rhs: &LiteralVal) -> Option { rhs.as_str().expect("checked string"), )) } - (LiteralVal::Float(value), rhs) if rhs.as_str().is_some() => { - let mut buf = ryu::Buffer::new(); - Some(LiteralVal::concat_strings( - buf.format(*value), - rhs.as_str().expect("checked string"), - )) - } + (LiteralVal::Float(value), rhs) if rhs.as_str().is_some() => Some(LiteralVal::concat_strings( + &value.to_string(), + rhs.as_str().expect("checked string"), + )), _ => None, } } +/// Numeric only — `*` **does not repeat strings** in this language. +/// +/// This used to fold `"ha" * 3` to `"hahaha"`, while the type checker rejects +/// `*` on a string with a message naming the operation that does exist +/// (`text.repeat(count)`). Folding runs before the checker, so which of the two +/// rules a program met depended on whether the count was a literal: +/// +/// ```lk +/// let a = "ha" * 3; // folded → "hahaha" +/// let n = 3; +/// let b = "ha" * n; // Type Error: `*` does not repeat a string +/// ``` +/// +/// The checker's rule is the language's rule; this one was a leftover of the +/// feature the checker removed, and it is what kept three documents claiming +/// the feature still worked. fn fold_literal_mul(lhs: &LiteralVal, rhs: &LiteralVal) -> Option { - match (lhs, rhs) { - (left, LiteralVal::Int(count)) if left.as_str().is_some() => { - Some(repeat_literal_string(left.as_str()?, *count)) - } - (LiteralVal::Int(count), right) if right.as_str().is_some() => { - Some(repeat_literal_string(right.as_str()?, *count)) - } - _ => fold_literal_numeric(lhs, rhs, |a, b| a * b, |a, b| a * b), - } -} - -fn repeat_literal_string(value: &str, count: i64) -> LiteralVal { - if count <= 0 { - LiteralVal::from_str("") - } else { - LiteralVal::from_str(&value.repeat(count as usize)) - } + fold_literal_numeric(lhs, rhs, i64::wrapping_mul, |a, b| a * b) } fn fold_literal_div(lhs: &LiteralVal, rhs: &LiteralVal) -> Option { @@ -769,14 +944,10 @@ fn fold_literal_div(lhs: &LiteralVal, rhs: &LiteralVal) -> Option { } match (lhs, rhs) { - (LiteralVal::Int(a), LiteralVal::Int(b)) => { - let result = (*a as f64) / (*b as f64); - if crate::compat::float::fract(result) == 0.0 { - Some(LiteralVal::Int(result as i64)) - } else { - Some(LiteralVal::Float(result)) - } - } + // `Int / Int` is a `Float`, always. This used to keep the quotient as + // an `Int` when it came out whole, so the *values* picked the *type*: + // `20 / 4` folded to `Int` and `7 / 2` to `Float`. + (LiteralVal::Int(a), LiteralVal::Int(b)) => Some(LiteralVal::Float(*a as f64 / *b as f64)), _ => fold_literal_numeric(lhs, rhs, |a, b| a / b, |a, b| a / b), } } @@ -785,7 +956,8 @@ fn fold_literal_mod(lhs: &LiteralVal, rhs: &LiteralVal) -> Option { if literal_is_zero(rhs) { return None; } - fold_literal_numeric(lhs, rhs, |a, b| a % b, |a, b| a % b) + // `i64::MIN % -1` is 0 in both executors; `%` on those operands panics. + fold_literal_numeric(lhs, rhs, i64::wrapping_rem, |a, b| a % b) } fn literal_is_zero(value: &LiteralVal) -> bool { @@ -812,7 +984,7 @@ fn fold_literal_numeric( } impl Expr { - /// 静态类型检查表达式 + /// Type-checks the expression. pub fn type_check(&self, type_checker: &mut TypeChecker) -> Result { type_checker.check_expr(self) } diff --git a/core/src/expr/expr_test.rs b/core/src/expr/expr_test.rs index f936f443..0763b84f 100644 --- a/core/src/expr/expr_test.rs +++ b/core/src/expr/expr_test.rs @@ -12,8 +12,8 @@ mod test { expect_env("user.name + 'pt'", "lkpt"); expect_env("user.age + list.0 == 19", "true"); expect_env("user.name + user.age", "lk18"); - expect("[1, 2, 3] + [2]", "[1, 2, 3, 2]"); - expect("[1, 2, 3] - [2]", "[1, 3]"); + expect("[1, 2, 3] + [2]", "[1,2,3,2]"); + expect("[1, 2, 3] - [2]", "[1,3]"); expect_env("list.2 / 2.0", "1.5"); panic_env("user.name / list"); } @@ -52,9 +52,18 @@ mod test { expect_env("pub && (user.age > 17 || user.name == 'john')", "true"); expect_env("pub || (user.age < 17 && user.name == 'john')", "true"); - // Short-circuit evaluation (RHS not evaluated) - expect("false && nonexistent.field", "false"); - expect("true || nonexistent.field", "true"); + // Short-circuit evaluation (RHS not evaluated). + // + // Probed with `% 0`, which raises, rather than with an undefined name: + // an undefined name is caught before execution, and these two lines + // used to pass only because parse-time folding deleted the RHS outright + // — which tested the folder, not the executor. + // + // Not `/ 0`: `/` is float division and `1 / 0` is `inf` (see + // docs/semantics.md), so it never raises and the probe would pass + // whether the RHS ran or not. + expect_source("let z = 0;\nreturn false && (1 % z == 1);", "false"); + expect_source("let z = 0;\nreturn true || (1 % z == 1);", "true"); } #[test] @@ -66,8 +75,9 @@ mod test { // With bound variables expect_env("pub ? user.name : 'guest'", "lk"); - // Short-circuit: only selected branch should evaluate - expect("false ? (nonexistent.field) : 42", "42"); + // Short-circuit: only the selected branch evaluates. Same reason as in + // `logical_operators` for probing with `% 0` rather than a name or `/`. + expect_source("let z = 0;\nreturn false ? (1 % z) : 42;", "42"); // Precedence with arithmetic on else branch expect("true ? 1 : 2 + 3", "1"); @@ -148,16 +158,16 @@ mod test { expect("[]", "[]"); // Simple list - expect("[1, 2, 3]", "[1, 2, 3]"); + expect("[1,2,3]", "[1,2,3]"); // Mixed types - expect(r#"[1, "hello", true]"#, "[1, hello, true]"); + expect(r#"[1, "hello", true]"#, "[1,\"hello\",true]"); // Nested lists - expect("[[1, 2], [3, 4]]", "[[1, 2], [3, 4]]"); + expect("[[1,2],[3,4]]", "[[1,2],[3,4]]"); // List with expressions - expect("[1 + 2, 3 * 4]", "[3, 12]"); + expect("[1 + 2,3 * 4]", "[3,12]"); // List with variable access expect_source( @@ -166,7 +176,7 @@ mod test { let list = [1, 2, 3]; return [user.age, list.0]; "#, - "[18, 1]", + "[18,1]", ); } @@ -253,7 +263,7 @@ mod test { #[test] fn trailing_commas() { // List with trailing comma - expect("[1, 2, 3,]", "[1, 2, 3]"); + expect("[1,2,3,]", "[1,2,3]"); // Map with trailing comma expect(r#"{"a": 1, "b": 2,}.a"#, "1"); @@ -276,6 +286,45 @@ mod test { panic("1.0 % 0.0"); } + /// Folding is a shortcut, not a second language. It runs before the type + /// checker, so anything it answers differently is unreachable by any + /// diagnostic. + #[test] + fn constant_folding_answers_what_the_executors_answer() { + // Int arithmetic wraps in both executors. The folder used the bare + // operators, which panic in a debug build — so this crashed the parser + // with `attempt to add with overflow`. + expect("9223372036854775807 + 1", "-9223372036854775808"); + expect("-9223372036854775807 - 2", "9223372036854775807"); + expect("9223372036854775807 * 2", "-2"); + // `i64::MIN % -1` is 0, not a panic — the executors were fixed for this + // and the folder was not. + expect("(-9223372036854775807 - 1) % -1", "0"); + + // `*` does not repeat a string: the checker rejects it and names + // `text.repeat(count)`. The folder implemented it anyway, so the rule a + // program met depended on whether the count was a literal. + let folded = Expr::try_from(r#""ha" * 3"#).expect("parses"); + assert!( + !matches!(&folded, Expr::Literal(_)), + "`\"ha\" * 3` must reach the type checker, not fold to a string: {folded:?}" + ); + + // `??` requires its two sides to unify, so folding `a ?? b` to `a` + // deletes the side the checker needs. `7 ?? "ab"` answered 7 while + // `maybe_int() ?? "ab"` is `Cannot unify Int with String`. + let folded = Expr::try_from(r#"7 ?? "ab""#).expect("parses"); + assert!( + !matches!(&folded, Expr::Literal(_)), + "`7 ?? \"ab\"` must reach the type checker: {folded:?}" + ); + // `nil ?? e` still folds — it discards only the literal `nil`. + assert!(matches!( + Expr::try_from(r#"nil ?? "ab""#).expect("parses"), + Expr::Literal(_) + )); + } + #[test] fn test_nil_handling() { expect("nil == nil", "true"); @@ -283,7 +332,7 @@ mod test { expect("nil", "nil"); } - // 缺失 Optional Chanining 测试 + // TODO(coverage): optional chaining has no case here. #[test] fn optional_chaining_access_and_index() { @@ -360,10 +409,10 @@ mod test { #[test] fn range_expressions() { // Exclusive range - expect("1..5", "[1, 2, 3, 4]"); + expect("1..5", "[1,2,3,4]"); // Inclusive range - expect("1..=5", "[1, 2, 3, 4, 5]"); + expect("1..=5", "[1,2,3,4,5]"); // Single element inclusive range expect("1..=1", "[1]"); @@ -372,10 +421,10 @@ mod test { expect("5..5", "[]"); // Negative ranges - expect("-3..=3", "[-3, -2, -1, 0, 1, 2, 3]"); + expect("-3..=3", "[-3,-2,-1,0,1,2,3]"); } - // 缺失 Closure 测试 + // TODO(coverage): closures have no case here. #[test] fn template_strings() { diff --git a/core/src/expr/select_guard_parsing_test.rs b/core/src/expr/select_guard_parsing_test.rs index 06210af7..711a4cac 100644 --- a/core/src/expr/select_guard_parsing_test.rs +++ b/core/src/expr/select_guard_parsing_test.rs @@ -6,6 +6,8 @@ #[cfg(test)] mod tests { + #[cfg(not(feature = "std"))] + use crate::compat::prelude::*; use crate::{ast::Parser, expr::Expr, stmt::Stmt, token::Tokenizer, val::LiteralVal}; fn parse(code: &str) -> Expr { @@ -28,7 +30,7 @@ mod tests { call_args = Some(args); } } - let Some(Stmt::Expr(dispatch)) = statements.last().map(|s| s.as_ref()) else { + let Some(Stmt::Expr { value: dispatch, .. }) = statements.last().map(|s| s.as_ref()) else { panic!("desugared select must end in a dispatch expression"); }; ( @@ -74,7 +76,7 @@ mod tests { panic!("third arg must be the values list"); }; assert!( - matches!(values[0].as_ref(), Expr::Var(name) if name.starts_with("__select")), + matches!(values[0].as_ref(), Expr::Var(name) if crate::ast::is_desugar_local(name) && name.starts_with("select$")), "send value must be hoisted: {:?}", values[0] ); @@ -118,11 +120,11 @@ mod tests { let rendered = format!("{expr:?}"); // Two desugar instances → two distinct counters in synthesized names. assert!( - rendered.contains("__select1_r"), + rendered.contains("select$1_r"), "outer or inner select id 1: {rendered}" ); assert!( - rendered.contains("__select2_r"), + rendered.contains("select$2_r"), "outer or inner select id 2: {rendered}" ); } diff --git a/core/src/fmt.rs b/core/src/fmt.rs index 5f003885..6a864f7a 100644 --- a/core/src/fmt.rs +++ b/core/src/fmt.rs @@ -59,16 +59,26 @@ struct LineFacts { continuation: Vec, } -/// Does a line starting with this token continue the previous line? +/// Does a line starting with these tokens continue the previous line? /// -/// Only tokens that can never be a *prefix* operator qualify: `-x` and `*p` at -/// the start of a line are just as likely to be a fresh list element as a -/// continuation, and guessing wrong misaligns the element. (A leading `-2` is -/// not even a `Sub` token — the lexer folds the sign into the number.) -fn is_continuation_lead(token: &Token) -> bool { - matches!( - token, - Token::Dot +/// The rule is "a token that can never *start* an expression", and it is +/// checked rather than assumed: of the infix operators only `-` can, because +/// `-x` is negation. `+x`, `*x`, `/x`, `%x` and `&x` are all syntax errors, so +/// a line beginning with one is a continuation and nothing else. (A leading +/// `-2` is not even a `Sub` token — the lexer folds the sign into the number.) +/// +/// The doc comment here used to also exclude `*` on the grounds that `*p` was +/// "just as likely to be a fresh list element". There is no such element: `*` +/// has no prefix form. Left alone, every wrapped `a * b`, `a + b` and `a & b` +/// was dedented to statement level. +/// +/// `|` is the one genuinely ambiguous lead, because it opens a lambda. That is +/// decided by looking at the rest of the line: `|x|` and `|x, y|` are a +/// parameter list, anything else is a bitwise or. +fn is_continuation_lead(line: &[Token]) -> bool { + match line.first() { + Some( + Token::Dot | Token::OptionalDot | Token::And | Token::Or @@ -79,7 +89,31 @@ fn is_continuation_lead(token: &Token) -> bool { | Token::Gt | Token::Le | Token::Ge - ) + | Token::Add + | Token::Mul + | Token::Div + | Token::Mod + | Token::BitAnd + | Token::BitXor, + ) => true, + Some(Token::Pipe) => !opens_lambda_params(&line[1..]), + _ => false, + } +} + +/// `x|`, `x, y|`, or `|` — the parameter list of a lambda whose `|` has already +/// been consumed. Anything else means the leading `|` was a bitwise or. +fn opens_lambda_params(rest: &[Token]) -> bool { + let mut expect_name = true; + for token in rest { + match token { + Token::Pipe => return true, + Token::Id(_) if expect_name => expect_name = false, + Token::Comma if !expect_name => expect_name = true, + _ => return false, + } + } + false } fn render(input: &str, tokens: &[Token], spans: &[Span], options: FormatOptions) -> String { @@ -117,7 +151,10 @@ fn collect_line_facts( // Still counting leading closers on this line (cleared by the first token // that is not a closing bracket). let mut counting = vec![true; line_count]; - let mut seen_token = vec![false; line_count]; + /// Enough to see `|a, b, c|` — a longer parameter list on a wrapped line is + /// not worth a heap allocation per line to catch. + const LEAD_TOKENS: usize = 8; + let mut line_lead: Vec> = vec![Vec::new(); line_count]; for (token, span) in tokens.iter().zip(spans.iter()) { let start = (span.start.line as usize).saturating_sub(1); @@ -135,9 +172,12 @@ fn collect_line_facts( *flag = true; } - if !seen_token[start] { - seen_token[start] = true; - facts.continuation[start] = is_continuation_lead(token); + // The first few tokens of each line, so the continuation test can look + // past the lead — `|` needs the rest of a lambda's parameter list to + // tell itself from a bitwise or. + let lead = &mut line_lead[start]; + if lead.len() < LEAD_TOKENS { + lead.push(token.clone()); } let delta = match token { @@ -155,6 +195,10 @@ fn collect_line_facts( } } + for (idx, lead) in line_lead.iter().enumerate() { + facts.continuation[idx] = is_continuation_lead(lead); + } + // Everything between two tokens is whitespace or a comment: block comments // are the only thing there that can span lines. let mut cursor = 0usize; @@ -249,9 +293,25 @@ fn emit(input: &str, facts: &LineFacts, options: FormatOptions) -> String { .map(|idx| if lines[idx].ends_with('\r') { "\r\n" } else { "\n" }) .unwrap_or("\n"); - // Level of the last line actually indented, so a line can never jump more - // than one level past the one before it (see below). - let mut prev_level = 0usize; + // The emitted level chosen for each bracket depth. Two lines at the *same* + // depth must land in the same column, which is the one thing a re-indenter + // cannot get wrong and still be called one. + // + // This used to be `depth.min(prev_level + 1)` — a per-line clamp meant to + // stop a line that opens several brackets from jumping two levels. It also + // made the column depend on how many lines had been emitted since, so a + // list wrapped after a two-bracket open climbed a staircase: + // + // ```lk + // uart_write([110, 97, 116, // depth 0 -> level 0, opens `(` and `[` + // 32, 116, 104, // depth 2, prev 0 -> level 1 + // 102, 105, 98]); // depth 2, prev 1 -> level 2 <- same depth! + // ``` + // + // Recording the level per depth keeps the "one level at a time" rule (a + // multi-bracket open assigns *one* new level to every depth it skipped) + // without letting position in the file decide the column. + let mut depth_levels: Vec = vec![0]; for (idx, raw_line) in lines.iter().enumerate() { let raw = raw_line.strip_suffix('\r').unwrap_or(raw_line); @@ -260,19 +320,20 @@ fn emit(input: &str, facts: &LineFacts, options: FormatOptions) -> String { } else { let trimmed = raw.trim(); if !trimmed.is_empty() { - let depth = - (indent - facts.leading_closers[idx] as i32).max(0) as usize + usize::from(facts.continuation[idx]); - // One line can open several brackets — `unless!(cond {`, or - // `foo(bar(`. Bracket depth says the next line is two levels in, - // but nothing was ever written at the level in between, so that - // is just an indent nobody asked for. Advance one level at a - // time; the closing line dedents by its own closers, so the - // running depth (`indent`) stays exact and files still return - // to column 0. - let level = depth.min(prev_level + 1); + let depth = (indent - facts.leading_closers[idx] as i32).max(0) as usize; + // A depth first seen on this line takes one level past the + // deepest one already assigned — `unless!(cond {` or `foo(bar(` + // skips a depth nobody wrote anything at, so both new depths + // share that one level. Coming back out drops the levels that + // are no longer open. + depth_levels.truncate(depth + 1); + if depth_levels.len() <= depth { + let level = depth_levels.last().copied().unwrap_or(0) + 1; + depth_levels.resize(depth + 1, level); + } + let level = depth_levels[depth] + usize::from(facts.continuation[idx]); push_indent(&mut out, level, options); out.push_str(trimmed); - prev_level = level; } } out.push_str(eol); diff --git a/core/src/fmt/fmt_test.rs b/core/src/fmt/fmt_test.rs index de1946b2..5a545063 100644 --- a/core/src/fmt/fmt_test.rs +++ b/core/src/fmt/fmt_test.rs @@ -1,4 +1,6 @@ use super::{FormatOptions, format_source}; +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; fn fmt(src: &str) -> String { format_source(src, FormatOptions::default()).expect("source should tokenize") @@ -207,3 +209,40 @@ fn line_endings_come_from_the_lines_not_the_content() { let lf_with_crlf_inside_a_string = "let s = \"a\\r\\nb\";\nlet y = 2;\n"; assert_eq!(fmt(lf_with_crlf_inside_a_string), "let s = \"a\\r\\nb\";\nlet y = 2;\n"); } + +/// Two lines at the same bracket depth land in the same column. +/// +/// The indent used to be `depth.min(prev_level + 1)` — a clamp meant to stop a +/// multi-bracket open from jumping two levels, which also made the column +/// depend on how many lines had already been emitted. A wrapped argument list +/// after a two-bracket open therefore climbed a staircase, one level per line, +/// which is the one thing a re-indenter cannot get wrong and still be one. +#[test] +fn lines_at_the_same_depth_get_the_same_column() { + let src = "fn f() {\nwrite([110, 97,\n32, 116,\n102, 105]);\n}\n"; + assert_eq!( + fmt(src), + "fn f() {\n write([110, 97,\n 32, 116,\n 102, 105]);\n}\n" + ); +} + +/// A line led by an infix operator continues the previous one. +/// +/// The rule is "a token that can never *start* an expression". Of the infix +/// operators only `-` can (`-x` is negation), so `+`, `*`, `/`, `%` and `&` +/// were being dedented to statement level for no reason. `|` is decided by +/// looking further: `|x|` is a lambda's parameter list, `| expr` is a bitwise +/// or. +#[test] +fn an_infix_lead_is_a_continuation_but_a_lambda_is_not() { + let src = "fn f() {\nlet d = (a & 1)\n| (b << 16)\n| c;\nlet e = a\n+ b\n% 3;\n}\n"; + assert_eq!( + fmt(src), + "fn f() {\n let d = (a & 1)\n | (b << 16)\n | c;\n let e = a\n + b\n % 3;\n}\n" + ); + + // The `|y|` here opens a lambda, so the line is a list element at the + // bracket's own level — not one column further in. + let src = "fn f() {\nlet fs = [|x| x + 1,\n|y| y * 2];\n}\n"; + assert_eq!(fmt(src), "fn f() {\n let fs = [|x| x + 1,\n |y| y * 2];\n}\n"); +} diff --git a/core/src/lib.rs b/core/src/lib.rs index d8ab563d..83f4d011 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -36,6 +36,7 @@ pub mod stmt; pub mod syntax; pub mod token; pub mod typ; +pub(crate) mod type_syntax; pub mod util; pub mod val; diff --git a/core/src/macro_system.rs b/core/src/macro_system.rs index 0fa45a57..d3af8be9 100644 --- a/core/src/macro_system.rs +++ b/core/src/macro_system.rs @@ -4,7 +4,7 @@ use crate::compat::path::{Path, PathBuf}; use crate::compat::prelude::*; use crate::token::token_lexeme; -use crate::token::{ParseError, Span, Token}; +use crate::token::{ParseError, Span, TemplateSegment, Token, Tokenizer, split_template_string}; mod expansion; mod follow; @@ -27,6 +27,9 @@ mod validation; #[cfg(test)] mod hygiene_tests; +#[cfg(test)] +mod template_tests; + // The validation corpus drives macro *file* imports and proc-macro // providers, both std-only leaves. #[cfg(all(test, feature = "std"))] @@ -55,14 +58,33 @@ pub use procedural::run_proc_macro_process; const DEFAULT_RECURSION_LIMIT: usize = 128; +/// Where `use ;` finds a package module's root, when the name is a +/// package rather than a builtin macro module. +/// +/// A function the caller supplies, not a call into `package`: macro expansion +/// is part of *parsing*, and the package manager is built on top of it — it +/// hands `ProcMacroProviders` down to expansion. `imports.rs` used to reach up +/// and call `PackageGraph::discover` itself, which made the two mutually +/// dependent and, at the crate level, unsplittable. `syntax::ParseOptions` +/// installs the real one; `None` simply means package macro imports do not +/// resolve, which is already the answer without a filesystem. +pub type PackageMacroModuleResolver = fn(&Path, &str) -> Result, String>; + #[derive(Debug, Clone)] pub struct MacroExpandOptions { pub recursion_limit: usize, pub trace: bool, pub base_dir: Option, + pub package_macro_resolver: Option, pub proc_macro_providers: ProcMacroProviders, pub proc_macro_features: Vec, pub proc_macro_dependency_recorder: ProcMacroDependencyRecorder, + /// Definitions from an earlier expansion to treat as already in scope. + /// + /// A carried definition loses to one this source declares, so re-entering + /// `macro_rules! m` in a REPL replaces it rather than colliding with it — + /// the same rule `let` and `fn` follow there. + pub carried_definitions: MacroDefinitions, } impl Default for MacroExpandOptions { @@ -71,9 +93,11 @@ impl Default for MacroExpandOptions { recursion_limit: DEFAULT_RECURSION_LIMIT, trace: false, base_dir: None, + package_macro_resolver: None, proc_macro_providers: ProcMacroProviders::default(), proc_macro_features: Vec::new(), proc_macro_dependency_recorder: ProcMacroDependencyRecorder::default(), + carried_definitions: MacroDefinitions::default(), } } } @@ -117,6 +141,44 @@ pub struct MacroExpandResult { pub origins: Vec, pub trace: Vec, pub proc_macro_dependencies: Vec, + /// The `macro_rules!` definitions this expansion collected. + /// + /// A caller that compiles one source text has no use for these — the + /// definitions are consumed by the same expansion that found them. A REPL + /// does: each input is its own source text, so without carrying them a + /// macro defined on one line is gone by the next, and the definition is + /// dropped in silence. Feed this back through + /// [`MacroExpandOptions::carried_definitions`]. + pub definitions: MacroDefinitions, +} + +/// `macro_rules!` definitions collected by one expansion, to be carried into +/// the next. Opaque: what a definition *is* stays inside this module. +#[derive(Debug, Clone, Default)] +pub struct MacroDefinitions { + registry: MacroRegistry, +} + +impl MacroDefinitions { + /// Whether anything is carried — an empty set is the ordinary case for a + /// single-shot compile. + pub fn is_empty(&self) -> bool { + self.registry.macros.is_empty() && self.registry.runtime_anchors.is_empty() + } + + /// The names carried, for a REPL that wants to complete or list them. + /// + /// A macro is registered twice — once under what the source wrote and once + /// under a `__lk_macro_crate_::` anchored alias that makes hygiene + /// work across files. Only the first is a name anyone typed, so the alias + /// is not offered. + pub fn names(&self) -> impl Iterator { + self.registry + .macros + .keys() + .map(String::as_str) + .filter(|name| !name.starts_with("__lk_macro_crate_")) + } } #[derive(Debug, Clone)] @@ -217,7 +279,7 @@ struct ExpandedToken { origin_kind: MacroOriginKind, } -#[derive(Default)] +#[derive(Default, Debug, Clone)] struct MacroRegistry { macros: HashMap, pub(in crate::macro_system) runtime_anchors: HashMap, @@ -283,11 +345,56 @@ pub fn expand_macros( } }) .collect::>(); - let (without_defs, registry) = collect_macro_defs(&source_tokens, options.base_dir.as_deref())?; + // Collect-then-expand, repeated: a `macro_rules!` that an *expansion* + // produces is not in the input the collection pass read, so a macro + // defining a macro used to leave the inner definition sitting in the token + // stream as ordinary tokens — the parser then failed on `macro_rules` + // itself, with the origin stack the only hint that a macro put it there. + // + // Each round expands what the previous one produced. It stops as soon as a + // round finds no definitions to collect, which is the first round for every + // program that does not do this; the cap is a backstop against a macro that + // defines a macro that defines a macro… + const MAX_DEFINITION_ROUNDS: usize = 8; let mut trace = Vec::new(); let mut stack = Vec::new(); - let expanded = expand_stream(&without_defs, ®istry, &options, 0, &mut trace, &mut stack)?; - let expanded = runtime_anchor::rewrite_anchor_runtime_refs(expanded, ®istry); + let mut current = source_tokens; + let mut expanded; + let mut rounds = 0usize; + // Assigned every round before any exit; the definitions handed back are the + // last round's, which is the set that was actually in scope. + let mut collected; + loop { + let (without_defs, mut registry) = + collect_macro_defs(¤t, options.base_dir.as_deref(), options.package_macro_resolver)?; + // Carried definitions fill in behind this source's own, so a redefinition + // wins instead of raising "already defined in this macro scope" — which + // is the collision a REPL would otherwise hit on its second `macro_rules! + // m`, having been told the first one did not exist. + for (name, definition) in options.carried_definitions.registry.macros.clone() { + registry.insert_macro_if_absent(name, definition); + } + for (anchor, source) in options.carried_definitions.registry.runtime_anchors.clone() { + registry.insert_runtime_anchor(anchor, source); + } + expanded = expand_stream(&without_defs, ®istry, &options, 0, &mut trace, &mut stack)?; + expanded = runtime_anchor::rewrite_anchor_runtime_refs(expanded, ®istry); + collected = registry; + // Another round only when this one *both* took definitions out and put + // new ones back: otherwise there is nothing left to collect. + let produced_definitions = (0..expanded.len()).any(|index| macro_rules_start_at(&expanded, index).is_some()); + if !produced_definitions { + break; + } + rounds += 1; + if rounds >= MAX_DEFINITION_ROUNDS { + return Err(ParseError::new(alloc::format!( + "macro definitions nested more than {MAX_DEFINITION_ROUNDS} deep; \ + a macro that defines a macro that defines a macro… does not terminate here" + ))); + } + current = expanded; + } let (tokens, spans, origins) = split_source_tokens(expanded); Ok(MacroExpandResult { tokens, @@ -295,6 +402,7 @@ pub fn expand_macros( origins, trace, proc_macro_dependencies: options.proc_macro_dependency_recorder.dependencies(), + definitions: MacroDefinitions { registry: collected }, }) } @@ -305,10 +413,11 @@ pub fn is_builtin_macro_module(name: &str) -> bool { fn collect_macro_defs( tokens: &[SourceToken], base_dir: Option<&Path>, + package_resolver: Option, ) -> Result<(Vec, MacroRegistry), ParseError> { let mut registry = MacroRegistry::default(); let mut loading = Vec::new(); - imports::collect_imported_macro_defs(tokens, base_dir, &mut registry, &mut loading)?; + imports::collect_imported_macro_defs(tokens, base_dir, package_resolver, &mut registry, &mut loading)?; let mut skipped_ranges = Vec::new(); let mut export_items = Vec::new(); let mut local_names = Vec::new(); @@ -695,6 +804,65 @@ fn parse_fragment_kind(name: &str) -> Option { } } +/// Parenthesises an expansion that lands where an *expression* is expected. +/// +/// A declarative macro's output is spliced as tokens, so its pieces used to +/// bind to whatever surrounded the call rather than to each other: +/// +/// ```text +/// macro_rules! twice { ($e:expr) => { ($e) + ($e) }; } +/// let n = 3; +/// twice!(n) → 6 (nothing to bind to) +/// twice!(n) * 2 → 9 (`n + n * 2`), and `2 * twice!(n)` the same +/// "v=" + twice!(n) → "v=33" +/// ``` +/// +/// Two conditions, and both are needed. The **site** must want an expression — +/// statement position is `;`, `{`, `}` or the start of the stream, and nothing +/// else is — and the **expansion** must be a single expression, which a +/// top-level `;` in it says it is not (`swap_names!` expands to three +/// statements and must stay three statements). +fn group_expression_expansion(before: &[SourceToken], expanded: Vec) -> Vec { + if expanded.is_empty() { + return expanded; + } + let statement_position = match before.last() { + None => true, + Some(token) => matches!(token.token, Token::Semicolon | Token::LBrace | Token::RBrace), + }; + if statement_position { + return expanded; + } + let mut depth = 0i32; + for token in &expanded { + match token.token { + Token::LParen | Token::LBracket | Token::LBrace => depth += 1, + Token::RParen | Token::RBracket | Token::RBrace => depth -= 1, + Token::Semicolon if depth == 0 => return expanded, + _ => {} + } + } + // The parentheses take the call's span and origins, so a diagnostic inside + // still points at the macro rather than at punctuation nobody wrote. + let open = SourceToken { + token: Token::LParen, + span: expanded[0].span.clone(), + lexeme: "(".to_string(), + origins: expanded[0].origins.clone(), + }; + let close = SourceToken { + token: Token::RParen, + span: expanded[expanded.len() - 1].span.clone(), + lexeme: ")".to_string(), + origins: expanded[expanded.len() - 1].origins.clone(), + }; + let mut grouped = Vec::with_capacity(expanded.len() + 2); + grouped.push(open); + grouped.extend(expanded); + grouped.push(close); + grouped +} + fn expand_stream( tokens: &[SourceToken], registry: &MacroRegistry, @@ -714,7 +882,14 @@ fn expand_stream( let mut index = 0usize; while index < tokens.len() { let Some((name, group_start)) = macro_invocation_at(tokens, index, registry, options) else { - output.push(tokens[index].clone()); + output.push(expand_template_interiors( + &tokens[index], + registry, + options, + depth, + trace, + stack, + )?); index += 1; continue; }; @@ -761,12 +936,116 @@ fn expand_stream( } }; stack.pop(); + let expanded = group_expression_expansion(&output, expanded); output.extend(expanded); index = inner_end + 1; } Ok(output) } +/// Expand macro invocations that sit inside a template string's `${…}` holes. +/// +/// A template is one token here — its interior is only tokenized much later, by +/// the parser — so `"${twice!(3)}"` used to reach the parser with the invocation +/// intact, and the parser answered "no macro named `twice` is defined". That +/// message rests on "expansion runs first, so anything left is undefined", which +/// is true of every position *except* this one; `twice!` worked in +/// `let a = twice!(3)` and in `println("{}", twice!(4))` in the same file. +/// +/// A segment whose token stream comes back unchanged keeps its original text +/// byte for byte. That matters: rendering tokens back to source is by lexeme, so +/// `a.b` would return as `a . b`, and a program with no macros in its templates +/// must not be rewritten at all. A segment that does not tokenize is also left +/// alone — the parser reports that, with a position, and this pass has none. +fn expand_template_interiors( + token: &SourceToken, + registry: &MacroRegistry, + options: &MacroExpandOptions, + depth: usize, + trace: &mut Vec, + stack: &mut Vec, +) -> Result { + let Token::TemplateString(content) = &token.token else { + return Ok(token.clone()); + }; + let Ok(segments) = split_template_string(content) else { + return Ok(token.clone()); + }; + if !segments + .iter() + .any(|segment| matches!(segment, TemplateSegment::Expr(_))) + { + return Ok(token.clone()); + } + + let mut rebuilt = String::with_capacity(content.len()); + let mut changed = false; + for segment in &segments { + match segment { + TemplateSegment::Literal(text) => rebuilt.push_str(text), + TemplateSegment::Expr(text) => { + rebuilt.push_str("${"); + match expand_template_expr(text, token, registry, options, depth, trace, stack)? { + Some(expanded) => { + changed = true; + rebuilt.push_str(&expanded); + } + None => rebuilt.push_str(text), + } + rebuilt.push('}'); + } + } + } + if !changed { + return Ok(token.clone()); + } + let mut rewritten = token.clone(); + rewritten.token = Token::TemplateString(rebuilt); + rewritten.lexeme = token_lexeme(&rewritten.token); + Ok(rewritten) +} + +/// Expand one `${…}` interior; `None` when it holds no macro to expand. +fn expand_template_expr( + text: &str, + token: &SourceToken, + registry: &MacroRegistry, + options: &MacroExpandOptions, + depth: usize, + trace: &mut Vec, + stack: &mut Vec, +) -> Result, ParseError> { + let Ok(inner) = Tokenizer::tokenize_enhanced(text) else { + return Ok(None); + }; + let inner: Vec = inner + .into_iter() + .map(|inner_token| SourceToken { + lexeme: token_lexeme(&inner_token), + token: inner_token, + // Every interior token answers with the template's own position: the + // template is one token to the lexer, so there is nothing finer. + span: token.span.clone(), + origins: token.origins.clone(), + }) + .collect(); + if !inner + .iter() + .enumerate() + .any(|(index, _)| macro_invocation_at(&inner, index, registry, options).is_some()) + { + return Ok(None); + } + let expanded = expand_stream(&inner, registry, options, depth + 1, trace, stack)?; + Ok(Some( + expanded + .iter() + .map(|expanded_token| expanded_token.lexeme.as_str()) + .collect::>() + .join(" "), + )) +} + fn macro_error_with_stack(error: ParseError, stack: &[MacroCallFrame]) -> ParseError { const HEADER: &str = "Macro expansion stack:"; if stack.is_empty() || error.message.contains(HEADER) { @@ -858,6 +1137,7 @@ fn token_matches(expected: &Token, actual: &Token) -> bool { (Token::Str(a), Token::Str(b)) => a == b, (Token::TemplateString(a), Token::TemplateString(b)) => a == b, (Token::Int(a), Token::Int(b)) => a == b, + (Token::UInt { value: a, .. }, Token::UInt { value: b, .. }) => a == b, (Token::Float(a), Token::Float(b)) => a == b, (Token::Bool(a), Token::Bool(b)) => a == b, _ => core::mem::discriminant(expected) == core::mem::discriminant(actual), @@ -911,6 +1191,48 @@ mod tests { vm::execute_source, }; + /// A definition survives into a *later* source text when carried. + /// + /// This is what a REPL needs and what it did not have: macros are expanded + /// during parsing, so a `macro_rules!` entered on one line was gone by the + /// next — accepted in silence, then reported as "no macro named `m` is + /// defined". `fn`, `struct`, `impl` and `let` all persisted. + #[test] + fn carried_definitions_outlive_the_source_that_declared_them() { + let first = expand_source("macro_rules! two { () => { 2 }; }", ParseOptions::default()) + .expect("the definition expands"); + assert_eq!(first.macro_definitions.names().collect::>(), ["two"]); + + // Without carrying, the second source does not know the name. + assert!(parse_program_source("return two!();", ParseOptions::default()).is_err()); + + let carried = ParseOptions { + carried_macro_definitions: first.macro_definitions.clone(), + ..ParseOptions::default() + }; + let second = expand_source("return two!();", carried).expect("the carried macro resolves"); + assert!(render_tokens(&second.tokens).contains('2')); + } + + /// A source's own definition beats a carried one, so re-entering + /// `macro_rules! m` replaces it. Inserting the carried set first would + /// instead raise "already defined in this macro scope" — a collision on a + /// name the session had just been told did not exist. + #[test] + fn a_redefinition_beats_the_carried_definition() { + let first = + expand_source("macro_rules! v { () => { 1 }; }", ParseOptions::default()).expect("the first definition"); + let carried = ParseOptions { + carried_macro_definitions: first.macro_definitions, + ..ParseOptions::default() + }; + let second = expand_source("macro_rules! v { () => { 9 }; } return v!();", carried) + .expect("the redefinition replaces rather than collides"); + let rendered = render_tokens(&second.tokens); + assert!(rendered.contains('9'), "expected the new body, got {rendered}"); + assert!(!rendered.contains('1'), "expected the old body gone, got {rendered}"); + } + #[test] fn expands_vec_like_repetition() { let result = execute_source( @@ -925,6 +1247,26 @@ mod tests { assert_eq!(result.display_first_return(), "5"); } + /// A macro call is an expression, so it may be an `expr` fragment. + /// + /// Composing macros is most of what macros are for, and this did not work: + /// expansion is token-level, so at capture time the inner call is still + /// `Id ! ( … )` — a shape the expression parser does not know — and the + /// matcher answered "expected `expr` fragment `$e`". Captured as tokens it + /// expands on a later round, like any other macro output. + #[test] + fn an_expr_fragment_may_be_a_macro_call() { + let result = execute_source( + r#" + macro_rules! twice { ($e:expr) => { ($e) + ($e) }; } + macro_rules! deep { ($e:expr) => { twice!(twice!($e)) }; } + return [twice!(twice!(1)), deep!(1), twice!(3)]; + "#, + ) + .expect("macro program should execute"); + assert_eq!(result.display_first_return(), "[4,4,6]"); + } + #[test] fn expands_block_fragment() { let result = execute_source( @@ -1005,7 +1347,7 @@ mod tests { .expect("macro expansion succeeds"); assert_eq!(expanded.trace.len(), 1); assert_eq!(expanded.trace[0].macro_name, "id"); - assert!(render_tokens(&expanded.tokens).contains("return 7;")); + assert!(render_tokens(&expanded.tokens).contains("return (7);")); } #[test] @@ -1290,7 +1632,7 @@ mod tests { }, ) .expect("aliased macro import should expand"); - assert!(render_tokens(&expanded.tokens).contains("return 42;")); + assert!(render_tokens(&expanded.tokens).contains("return (42);")); } #[test] @@ -1316,7 +1658,7 @@ mod tests { }, ) .expect("namespaced file macro import should expand"); - assert!(render_tokens(&expanded.tokens).contains("return 42;")); + assert!(render_tokens(&expanded.tokens).contains("return (42);")); } #[test] @@ -1342,7 +1684,7 @@ mod tests { }, ) .expect("aliased namespace macro import should expand"); - assert!(render_tokens(&expanded.tokens).contains("return 42;")); + assert!(render_tokens(&expanded.tokens).contains("return (42);")); } #[test] diff --git a/core/src/macro_system/expansion.rs b/core/src/macro_system/expansion.rs index 349fd504..d1e203c0 100644 --- a/core/src/macro_system/expansion.rs +++ b/core/src/macro_system/expansion.rs @@ -1,7 +1,7 @@ use crate::compat::collections::HashMap; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::token::token_lexeme; +use crate::token::{TemplateSegment, split_template_string, token_lexeme}; use crate::{ ast::Parser as ExprParser, @@ -207,6 +207,7 @@ fn capture_fragment( Token::Str(_) | Token::TemplateString(_) | Token::Int(_) + | Token::UInt { .. } | Token::Float(_) | Token::Bool(_) | Token::Nil @@ -249,14 +250,75 @@ fn capture_expr_fragment( pos: usize, next_literal: Option<&Token>, ) -> Option<(usize, Vec)> { - let tokens = source_tokens_to_tokens(&input[pos..]); + // A macro call is an expression, and composing macros is most of what + // macros are for — `twice!(twice!(1))`. Expansion is token-level, so at + // this point the inner call is still `Id ! ( … )`, which the expression + // parser does not know. + // + // Collapsing each call to a single name lets the *real* parser decide where + // the expression ends, so a call composes like any other operand. Taking the + // call itself as the whole fragment — which is what this did — stopped at + // the call: `twice!(n)` matched and `twice!(n) * 2` "matched a prefix but + // left unexpected `*`". The captured tokens are the originals, expanded on a + // later round like any other output. + let (tokens, widths) = collapse_macro_calls(input, pos); let mut parser = ExprParser::new(&tokens); let Ok((_, consumed)) = parser.parse_prefix() else { return None; }; + let consumed = widths.get(..consumed)?.iter().sum(); capture_parser_prefix(input, pos, consumed, next_literal) } +/// The tokens of `input[pos..]` with every macro invocation collapsed to one +/// identifier, plus how many original tokens each rewritten token stands for. +/// +/// Summing the widths of the tokens a parser consumed gives the length in the +/// original stream. +fn collapse_macro_calls(input: &[SourceToken], pos: usize) -> (Vec, Vec) { + let mut tokens = Vec::with_capacity(input.len() - pos); + let mut widths = Vec::with_capacity(input.len() - pos); + let mut index = pos; + while index < input.len() { + match macro_invocation_prefix_len(input, index) { + Some(len) => { + tokens.push(Token::Id("__lk_macro_operand".into())); + widths.push(len); + index += len; + } + None => { + tokens.push(input[index].token.clone()); + widths.push(1); + index += 1; + } + } + } + (tokens, widths) +} + +/// The token length of a macro invocation starting at `pos`, if there is one. +/// +/// The shape is the language's own rule for telling a macro call from a force +/// unwrap: `name!` immediately followed by `(`, `[` or `{` (see +/// `docs/semantics.md`). The delimiter group is balanced, so the whole call is +/// taken as one fragment. +fn macro_invocation_prefix_len(input: &[SourceToken], pos: usize) -> Option { + if !matches!(input.get(pos).map(|t| &t.token), Some(Token::Id(_))) { + return None; + } + if !matches!(input.get(pos + 1).map(|t| &t.token), Some(Token::Not)) { + return None; + } + if !matches!( + input.get(pos + 2).map(|t| &t.token), + Some(Token::LParen | Token::LBracket | Token::LBrace) + ) { + return None; + } + let (_, close) = super::find_group(input, pos + 2).ok()?; + Some(close + 1 - pos) +} + fn capture_stmt_fragment( input: &[SourceToken], pos: usize, @@ -566,6 +628,105 @@ fn collect_repeated_capture_names(elems: &[PatternElem], names: &mut Vec } } +/// Substitute `$name` metavariables that sit inside a template string's `${…}`. +/// +/// A `TemplateString` in a macro body is one token, and the body is a list of +/// tokens, so `$e` inside `"value = ${$e}"` was never a `TemplateElem::MetaVar` +/// to substitute — it stayed in the text and the parser, tokenizing the interior +/// much later, reported `Unexpected token: Dollar`. Formatting an argument into a +/// message is close to the whole reason to write a macro in this language, so +/// that hole covered the common case. +/// +/// Only `${…}` interiors are rewritten. A `$e` in the literal part of a template +/// is the two characters `$` and `e`, exactly as `"$e"` is outside a macro. +fn substitute_inside_template_string( + token: &SourceToken, + captures: &HashMap, + repeat_path: &[usize], + call_span: &Span, +) -> Result { + let Token::TemplateString(content) = &token.token else { + return Ok(token.clone()); + }; + if !content.contains('$') { + return Ok(token.clone()); + } + let Ok(segments) = split_template_string(content) else { + return Ok(token.clone()); + }; + + let mut rebuilt = String::with_capacity(content.len()); + let mut changed = false; + for segment in &segments { + match segment { + TemplateSegment::Literal(text) => rebuilt.push_str(text), + TemplateSegment::Expr(text) => { + rebuilt.push_str("${"); + let (rewritten, hit) = substitute_metavars_in_text(text, captures, repeat_path, call_span)?; + changed |= hit; + rebuilt.push_str(&rewritten); + rebuilt.push('}'); + } + } + } + if !changed { + return Ok(token.clone()); + } + let mut rewritten = token.clone(); + rewritten.token = Token::TemplateString(rebuilt); + rewritten.lexeme = token_lexeme(&rewritten.token); + Ok(rewritten) +} + +/// Replace every `$name` in one interpolation's text with its captured tokens. +/// +/// An unknown `$name` is an error rather than a pass-through: the same name +/// outside the template would be, and a template that silently kept `$typo` +/// would fail later as a parse error naming a `$` the program did not write. +fn substitute_metavars_in_text( + text: &str, + captures: &HashMap, + repeat_path: &[usize], + call_span: &Span, +) -> Result<(String, bool), ParseError> { + let bytes = text.as_bytes(); + let mut out = String::with_capacity(text.len()); + let mut changed = false; + let mut index = 0usize; + while index < text.len() { + if bytes[index] != b'$' { + let ch = text[index..].chars().next().expect("index is a char boundary"); + out.push(ch); + index += ch.len_utf8(); + continue; + } + let name_start = index + 1; + let name_end = name_start + + text[name_start..] + .find(|ch: char| !ch.is_alphanumeric() && ch != '_') + .unwrap_or(text.len() - name_start); + if name_end == name_start { + out.push('$'); + index += 1; + continue; + } + let name = &text[name_start..name_end]; + let capture = captures + .get(name) + .ok_or_else(|| ParseError::with_span(format!("Unknown macro metavariable `${name}`"), call_span.clone()))?; + let replacement = capture_tokens_at_path(capture, repeat_path, name, call_span)?; + let rendered = replacement + .iter() + .map(|token| token.lexeme.as_str()) + .collect::>() + .join(" "); + out.push_str(&rendered); + changed = true; + index = name_end; + } + Ok((out, changed)) +} + fn substitute_template( template: &[TemplateElem], captures: &HashMap, @@ -586,7 +747,7 @@ fn substitute_template_at( for elem in template { match elem { TemplateElem::Token(token) => output.push(ExpandedToken { - token: token.clone(), + token: substitute_inside_template_string(token, captures, repeat_path, call_span)?, from_capture: false, origin_kind: MacroOriginKind::Definition, }), diff --git a/core/src/macro_system/hygiene_tests.rs b/core/src/macro_system/hygiene_tests.rs index 71011b85..2c3cb7c5 100644 --- a/core/src/macro_system/hygiene_tests.rs +++ b/core/src/macro_system/hygiene_tests.rs @@ -1,4 +1,5 @@ mod bindings; mod control_flow; +mod internal_rules; mod params; mod semantic_names; diff --git a/core/src/macro_system/hygiene_tests/internal_rules.rs b/core/src/macro_system/hygiene_tests/internal_rules.rs new file mode 100644 index 00000000..757e0c1a --- /dev/null +++ b/core/src/macro_system/hygiene_tests/internal_rules.rs @@ -0,0 +1,89 @@ +//! `@` as an internal-rule marker, the way Rust's `macro_rules!` use it. +//! +//! A declarative macro has no accumulator: adding a column of numbers up means a +//! rule that calls itself carrying the running total. That rule must not be +//! reachable from a caller, and the way that is arranged is a token which is +//! legal in a token stream and illegal in every position a person would write +//! one. `@` had no meaning in this language, which is exactly what makes it the +//! right one — LK's macros are Rust-shaped, and a macro ported from Rust reaches +//! for it immediately. + +use crate::{ + syntax::{expand_source, render_tokens}, + vm::execute_source, +}; + +/// The case the token was added for: offsets from widths. +/// +/// Each constant is the running total *before* its field, and the size is the +/// total after the last one — so the size cannot disagree with the layout above +/// it, which is the number a hand-written column always gets wrong. +#[test] +fn an_internal_rule_can_accumulate_across_a_recursion() { + let result = execute_source( + r#" + macro_rules! layout { + (@from $prev:expr, => $size:ident) => { + const $size = $prev; + }; + (@from $prev:expr, $name:ident : $width:expr, $($rest:tt)*) => { + const $name = $prev; + layout!(@from ($prev) + ($width), $($rest)*); + }; + ($($body:tt)*) => { + layout!(@from 0, $($body)*); + }; + } + layout! { + DEST: 6, + SOURCE: 6, + KIND: 2, + => HEADER + } + return DEST * 1000000 + SOURCE * 10000 + KIND * 100 + HEADER; + "#, + ) + .expect("macro program should execute"); + + // 0, 6, 12, 14 — the widths added up, not machine words. + assert_eq!(result.display_first_return(), "61214"); +} + +/// The marker is what separates the rules. +/// +/// Without it the internal rule and the public one would both be "some tokens", +/// and the first would shadow the second. Checked by *expanding*: a caller's +/// invocation has to reach the entry rule and come back with the internal ones +/// already resolved. +#[test] +fn a_caller_reaches_the_entry_rule_and_not_the_internal_one() { + let expanded = expand_source( + r#" + macro_rules! pick { + (@internal $x:expr) => { ($x) + 100 }; + ($x:expr) => { pick!(@internal $x) }; + } + let a = pick!(1); + "#, + Default::default(), + ) + .expect("macro program should expand"); + let text = render_tokens(&expanded.tokens); + assert!( + text.contains("100"), + "the entry rule should have gone through the internal one, got: {text}" + ); + assert!(!text.contains('@'), "no `@` should survive expansion, got: {text}"); +} + +/// Outside a macro, `@` is still an error — a parse error rather than a lexer +/// one, which is the same answer with a better message. +#[test] +fn an_at_outside_a_macro_is_rejected() { + let error = execute_source("let x = 1 @ 2;\nreturn x;\n").expect_err("`@` is not an operator"); + let text = format!("{error:#}"); + assert!( + text.contains("At") || text.contains('@'), + "the diagnostic should name the token, got: {text}" + ); +} diff --git a/core/src/macro_system/imports.rs b/core/src/macro_system/imports.rs index 16a0a0c4..a0961867 100644 --- a/core/src/macro_system/imports.rs +++ b/core/src/macro_system/imports.rs @@ -1,8 +1,6 @@ use crate::compat::path::{Path, PathBuf}; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -#[cfg(feature = "std")] -use crate::package::PackageGraph; use crate::token::token_lexeme; use crate::{ token::{ParseError, Token, Tokenizer}, @@ -128,11 +126,12 @@ struct LoadedMacroModule { pub(super) fn collect_imported_macro_defs( tokens: &[SourceToken], base_dir: Option<&Path>, + package_resolver: Option, registry: &mut MacroRegistry, loading: &mut Vec, ) -> Result<(), ParseError> { for spec in macro_import_specs(tokens)? { - let loaded = load_imported_macros(base_dir, &spec, tokens, loading)?; + let loaded = load_imported_macros(base_dir, package_resolver, &spec, tokens, loading)?; register_anchor_macros(registry, &loaded.anchors); for (anchor, source) in loaded.runtime_anchors.iter().cloned() { registry.insert_runtime_anchor(anchor, source); @@ -230,6 +229,7 @@ fn register_file_macros( fn load_imported_macros( base_dir: Option<&Path>, + package_resolver: Option, spec: &MacroImportSpec, tokens: &[SourceToken], loading: &mut Vec, @@ -245,7 +245,7 @@ fn load_imported_macros( }; let resolved = resolve_macro_import_path(base_dir, path) .map_err(|message| error_at(tokens, spec.span_index, &message))?; - load_macro_file(&resolved, loading) + load_macro_file(&resolved, package_resolver, loading) } #[cfg(not(feature = "std"))] { @@ -254,7 +254,7 @@ fn load_imported_macros( } } MacroImportSource::Module(name) => { - if let Some(macros) = load_builtin_macro_module(name, loading)? { + if let Some(macros) = load_builtin_macro_module(name, package_resolver, loading)? { return Ok(macros); } // Package-based macro imports need the `package` manager and the @@ -264,14 +264,16 @@ fn load_imported_macros( let Some(base_dir) = base_dir else { return Ok(LoadedMacroModule::default()); }; - let Some(resolved) = resolve_package_macro_module(base_dir, name, tokens, spec.span_index)? else { + let Some(resolved) = + resolve_package_macro_module(package_resolver, base_dir, name, tokens, spec.span_index)? + else { return Ok(LoadedMacroModule::default()); }; - load_macro_file(&resolved, loading) + load_macro_file(&resolved, package_resolver, loading) } #[cfg(not(feature = "std"))] { - let _ = (base_dir, name, tokens, &mut *loading); + let _ = (base_dir, package_resolver, name, tokens, &mut *loading); Ok(LoadedMacroModule::default()) } } @@ -279,7 +281,11 @@ fn load_imported_macros( } #[cfg(feature = "std")] -fn load_macro_file(path: &Path, loading: &mut Vec) -> Result { +fn load_macro_file( + path: &Path, + package_resolver: Option, + loading: &mut Vec, +) -> Result { let canonical = path.canonicalize(); let path = match &canonical { Ok(p) => p.clone(), @@ -297,21 +303,29 @@ fn load_macro_file(path: &Path, loading: &mut Vec) -> Result) -> Result { +fn load_macro_file_inner( + path: &Path, + package_resolver: Option, + loading: &mut Vec, +) -> Result { let source = std::fs::read_to_string(path) .map_err(|error| ParseError::new(format!("Failed to read macro import '{}': {error}", path.display())))?; let base_dir = path.parent().unwrap_or_else(|| Path::new(".")); let crate_anchor = macro_crate_anchor_for_label(&path.display().to_string()); - load_macro_source(&source, base_dir, loading, crate_anchor) + load_macro_source(&source, base_dir, package_resolver, loading, crate_anchor) } -fn load_builtin_macro_module(name: &str, loading: &mut Vec) -> Result, ParseError> { +fn load_builtin_macro_module( + name: &str, + package_resolver: Option, + loading: &mut Vec, +) -> Result, ParseError> { if !is_builtin_macro_module(name) { return Ok(None); } @@ -324,6 +338,7 @@ fn load_builtin_macro_module(name: &str, loading: &mut Vec) -> Result) -> Result, loading: &mut Vec, crate_anchor: String, ) -> Result { @@ -354,7 +370,7 @@ fn load_macro_source( ..Default::default() }; for spec in macro_import_specs(&source_tokens)? { - let imported = load_imported_macros(Some(base_dir), &spec, &source_tokens, loading)?; + let imported = load_imported_macros(Some(base_dir), package_resolver, &spec, &source_tokens, loading)?; register_file_macro_map(&mut module.macros, &imported.public, &spec, &source_tokens)?; merge_anchor_macros(&mut module.anchors, &imported.anchors); module.imported_runtime_anchors.extend(imported.runtime_anchors); @@ -667,26 +683,25 @@ fn default_namespace_alias(raw: &str) -> Option { #[cfg(feature = "std")] fn resolve_package_macro_module( + resolver: Option, base_dir: &Path, name: &str, tokens: &[SourceToken], index: usize, ) -> Result, ParseError> { - let graph = PackageGraph::discover(base_dir).map_err(|error| { + // No resolver installed means nothing here knows what a package is — the + // same answer as a program with no manifest. See + // [`super::PackageMacroModuleResolver`] for why this is not a direct call. + let Some(resolver) = resolver else { + return Ok(None); + }; + resolver(base_dir, name).map_err(|error| { error_at( tokens, index, &format!("Failed to discover macro package graph: {error}"), ) - })?; - let Some(graph) = graph else { - return Ok(None); - }; - Ok(graph - .modules - .into_iter() - .find(|module| module.name == name) - .map(|module| module.root)) + }) } fn use_statement_end(tokens: &[SourceToken], mut index: usize) -> usize { @@ -726,8 +741,12 @@ fn resolve_macro_import_path(base_dir: &Path, raw: &str) -> Result Stmt { Stmt::Trait { + default_methods: Vec::new(), name: BUILTIN_SHOW_TRAIT.to_string(), methods: vec![( "show".to_string(), @@ -212,7 +213,7 @@ fn builtin_show_trait() -> Stmt { fn derive_show_impl(name: &str, fields: &[(String, Option)]) -> Stmt { Stmt::Impl { - trait_name: BUILTIN_SHOW_TRAIT.to_string(), + trait_name: Some(BUILTIN_SHOW_TRAIT.to_string()), target_type: Type::Named(name.to_string()), methods: vec![Stmt::Function { name: "show".to_string(), diff --git a/core/src/macro_system/procedural/origins.rs b/core/src/macro_system/procedural/origins.rs index 44b8364e..7f6da62e 100644 --- a/core/src/macro_system/procedural/origins.rs +++ b/core/src/macro_system/procedural/origins.rs @@ -92,7 +92,7 @@ fn generated_member_origins_for_stmt(stmt: &Stmt, span: Option) -> Vec { + Stmt::Trait { name, methods, .. } => { let mut origins = vec![AstGeneratedMemberOrigin { label: format!("trait {name}"), span: span.clone(), @@ -113,11 +113,18 @@ fn generated_member_origins_for_stmt(stmt: &Stmt, span: Option) -> Vec { - let mut origins = vec![AstGeneratedMemberOrigin { - label: format!("type_ref {trait_name}"), - span: span.clone(), - }]; - push_generated_statement_origin("stmt impl_trait", span.clone(), &mut origins); + // An inherent `impl Type { … }` names no trait, so there is no + // trait reference to record. + let mut origins = match trait_name { + Some(trait_name) => vec![AstGeneratedMemberOrigin { + label: format!("type_ref {trait_name}"), + span: span.clone(), + }], + None => Vec::new(), + }; + if trait_name.is_some() { + push_generated_statement_origin("stmt impl_trait", span.clone(), &mut origins); + } push_generated_statement_origin("stmt impl_target", span.clone(), &mut origins); collect_generated_type_origins(target_type, span.clone(), &mut origins); origins.extend(methods.iter().flat_map(|method| { @@ -173,6 +180,8 @@ fn generated_member_origins_for_stmt(stmt: &Stmt, span: Option) -> Vec, origins: &mut Vec) { match ty { + // `_` names nothing, so there is no type reference to record. + Type::Unknown => {} Type::Named(name) => { push_generated_statement_origin("type_expr named", span.clone(), origins); origins.push(AstGeneratedMemberOrigin { @@ -295,6 +304,7 @@ fn collect_generated_expr_origins_from_stmt( origins.extend(generated_attribute_origins(attributes, span.clone())); collect_generated_expr_origins_from_stmt(item, span, origins); } + Stmt::Defer { body, .. } => collect_generated_expr_origins_from_stmt(body, span, origins), Stmt::If { condition, then_stmt, @@ -387,7 +397,7 @@ fn collect_generated_expr_origins_from_stmt( push_generated_statement_origin("stmt compound_assign_value", span.clone(), origins); collect_generated_expr_origins(value, span, origins); } - Stmt::Define { name, value } => { + Stmt::Define { name, value, .. } => { push_generated_statement_origin("stmt define", span.clone(), origins); push_generated_reference_origin("binding", name, span.clone(), origins); push_generated_statement_origin("stmt initializer", span.clone(), origins); @@ -438,11 +448,13 @@ fn collect_generated_expr_origins_from_stmt( target_type, methods, } => { - origins.push(AstGeneratedMemberOrigin { - label: format!("type_ref {trait_name}"), - span: span.clone(), - }); - push_generated_statement_origin("stmt impl_trait", span.clone(), origins); + if let Some(trait_name) = trait_name { + origins.push(AstGeneratedMemberOrigin { + label: format!("type_ref {trait_name}"), + span: span.clone(), + }); + push_generated_statement_origin("stmt impl_trait", span.clone(), origins); + } push_generated_statement_origin("stmt impl_target", span.clone(), origins); collect_generated_type_origins(target_type, span.clone(), origins); for method in methods { @@ -450,7 +462,7 @@ fn collect_generated_expr_origins_from_stmt( collect_generated_expr_origins_from_stmt(method, span.clone(), origins); } } - Stmt::Expr(expr) => { + Stmt::Expr { value: expr, .. } => { push_generated_statement_origin("stmt expr", span.clone(), origins); push_generated_statement_origin("stmt expr_value", span.clone(), origins); collect_generated_expr_origins(expr, span, origins); @@ -469,24 +481,6 @@ fn collect_generated_expr_origins_from_stmt( collect_generated_expr_origins_from_stmt(statement, span.clone(), origins); } } - Stmt::Try { - body, - catch_var, - handler, - } => { - push_generated_statement_origin("stmt try", span.clone(), origins); - for statement in body { - collect_generated_expr_origins_from_stmt(statement, span.clone(), origins); - } - push_generated_statement_origin("stmt try_catch", span.clone(), origins); - // The caught name is a binding this statement introduces, like a - // parameter or a `let` — recorded so a macro-generated `catch e` - // resolves to its origin. - push_generated_reference_origin("binding", catch_var, span.clone(), origins); - for statement in handler { - collect_generated_expr_origins_from_stmt(statement, span.clone(), origins); - } - } Stmt::Struct { name, fields } => { origins.push(AstGeneratedMemberOrigin { label: format!("struct {name}"), @@ -508,7 +502,7 @@ fn collect_generated_expr_origins_from_stmt( push_generated_statement_origin("stmt type_alias_target", span.clone(), origins); collect_generated_type_origins(target, span, origins); } - Stmt::Trait { name, methods } => { + Stmt::Trait { name, methods, .. } => { origins.push(AstGeneratedMemberOrigin { label: format!("trait {name}"), span: span.clone(), @@ -850,7 +844,7 @@ fn collect_generated_expr_origins(expr: &Expr, span: Option, origins: &mut } } } - Expr::Closure { params, body } => { + Expr::Closure { params, body, .. } => { push_generated_statement_origin("expr closure", span.clone(), origins); for param in params { push_generated_statement_origin("expr closure_param", span.clone(), origins); @@ -866,6 +860,24 @@ fn collect_generated_expr_origins(expr: &Expr, span: Option, origins: &mut collect_generated_expr_origins_from_stmt(statement, span.clone(), origins); } } + Expr::Try { + body, + catch_var, + handler, + } => { + push_generated_statement_origin("expr try", span.clone(), origins); + for statement in body { + collect_generated_expr_origins_from_stmt(statement, span.clone(), origins); + } + push_generated_statement_origin("expr try_catch", span.clone(), origins); + // The caught name is a binding this expression introduces, like a + // parameter or a `let` — recorded so a macro-generated `catch e` + // resolves to its origin. + push_generated_reference_origin("binding", catch_var, span.clone(), origins); + for statement in handler { + collect_generated_expr_origins_from_stmt(statement, span.clone(), origins); + } + } Expr::Match { value, arms } => { push_generated_statement_origin("expr match", span.clone(), origins); push_generated_statement_origin("expr match_value", span.clone(), origins); @@ -952,6 +964,7 @@ fn generated_compound_assign_origin_label(op: &BinOp) -> &'static str { fn generated_unary_origin_label(op: &UnaryOp) -> &'static str { match op { UnaryOp::Not => "unary not", + UnaryOp::Neg => "unary neg", } } @@ -1199,7 +1212,10 @@ pub(super) fn stmt_label(stmt: &Stmt) -> String { trait_name, target_type, .. - } => format!("impl {trait_name} for {}", target_type.display()), + } => match trait_name { + Some(trait_name) => format!("impl {trait_name} for {}", target_type.display()), + None => format!("impl {}", target_type.display()), + }, Stmt::TypeAlias { name, .. } => format!("type {name}"), Stmt::Attributed { item, .. } => stmt_label(item), Stmt::Block { .. } => "block".to_string(), diff --git a/core/src/macro_system/procedural/origins/tests.rs b/core/src/macro_system/procedural/origins/tests.rs index 4bc21a15..d5dddcc4 100644 --- a/core/src/macro_system/procedural/origins/tests.rs +++ b/core/src/macro_system/procedural/origins/tests.rs @@ -224,6 +224,7 @@ fn generated_statement_shape_origins_are_recorded() { body, }; let trait_stmt = Stmt::Trait { + default_methods: Vec::new(), name: "Reader".to_string(), methods: vec![( "read".to_string(), @@ -235,7 +236,7 @@ fn generated_statement_shape_origins_are_recorded() { )], }; let impl_stmt = Stmt::Impl { - trait_name: "Reader".to_string(), + trait_name: Some("Reader".to_string()), target_type: Type::Named("File".to_string()), methods: vec![Stmt::Function { name: "read".to_string(), @@ -304,7 +305,7 @@ fn generated_top_level_declaration_shape_origins_are_recorded() { }, }), Box::new(Stmt::Impl { - trait_name: "Show".to_string(), + trait_name: Some("Show".to_string()), target_type: Type::Named("User".to_string()), methods: vec![Stmt::Function { name: "show".to_string(), @@ -388,30 +389,32 @@ fn generated_remaining_expression_child_role_origins_are_recorded() { )); let literal = || Box::new(Expr::Literal(LiteralVal::Int(1))); let expr = Expr::Block(vec![ - Box::new(Stmt::Expr(Box::new(Expr::Paren(literal())))), - Box::new(Stmt::Expr(Box::new(Expr::StructLiteral { + Box::new(Stmt::expr(Box::new(Expr::Paren(literal())))), + Box::new(Stmt::expr(Box::new(Expr::StructLiteral { name: "User".to_string(), fields: vec![("id".to_string(), literal())], }))), - Box::new(Stmt::Expr(Box::new(Expr::Access( + Box::new(Stmt::expr(Box::new(Expr::Access( Box::new(Expr::Var("items".to_string())), Box::new(Expr::Var("current".to_string())), )))), - Box::new(Stmt::Expr(Box::new(Expr::OptionalAccess( + Box::new(Stmt::expr(Box::new(Expr::OptionalAccess( Box::new(Expr::Var("maybe_items".to_string())), Box::new(Expr::Var("fallback".to_string())), )))), - Box::new(Stmt::Expr(Box::new(Expr::Call("make".to_string(), vec![literal()])))), - Box::new(Stmt::Expr(Box::new(Expr::CallNamed( + Box::new(Stmt::expr(Box::new(Expr::Call("make".to_string(), vec![literal()])))), + Box::new(Stmt::expr(Box::new(Expr::CallNamed( Box::new(Expr::Var("make".to_string())), vec![literal()], vec![("id".to_string(), literal())], )))), - Box::new(Stmt::Expr(Box::new(Expr::Closure { + Box::new(Stmt::expr(Box::new(Expr::Closure { params: vec!["current".to_string()], + param_types: vec![None], + return_type: None, body: Box::new(Expr::Var("current".to_string())), }))), - Box::new(Stmt::Expr(Box::new(Expr::Match { + Box::new(Stmt::expr(Box::new(Expr::Match { value: Box::new(Expr::Var("current".to_string())), arms: vec![ MatchArm { diff --git a/core/src/macro_system/template_tests.rs b/core/src/macro_system/template_tests.rs new file mode 100644 index 00000000..bdf3136c --- /dev/null +++ b/core/src/macro_system/template_tests.rs @@ -0,0 +1,134 @@ +//! Macros and template strings, which used to be blind to each other. +//! +//! A template is a *single token* to the expander — the lexer hands over its +//! whole content as `Token::TemplateString`, and only the parser, much later, +//! tokenizes what sits inside `${…}`. So neither direction worked: a macro +//! invocation written in a hole was never expanded, and a metavariable written +//! in a hole was never substituted. Both failed with messages that named +//! something the program did not contain. + +use crate::vm::execute_source; + +#[test] +fn a_macro_invocation_inside_an_interpolation_expands() { + // The parser reported "no macro named `twice` is defined", resting on + // "expansion runs first, so anything left over is undefined" — true in every + // position except this one. The same macro worked two lines away. + let result = execute_source( + r#" + macro_rules! twice { + ($e:expr) => { ($e) + ($e) }; + } + return "${twice!(3)}"; + "#, + ) + .expect("a macro in a template hole should expand"); + + assert_eq!(result.display_first_return(), "6"); +} + +#[test] +fn a_metavariable_inside_an_interpolation_is_substituted() { + // `Unexpected token: Dollar` before this — formatting an argument into a + // message is most of the reason to write a macro here, and it was the one + // shape that did not work. + let result = execute_source( + r#" + macro_rules! show { + ($e:expr) => { "value = ${$e}" }; + } + return show!(1 + 2); + "#, + ) + .expect("a metavariable in a template hole should substitute"); + + assert_eq!(result.display_first_return(), "value = 3"); +} + +#[test] +fn a_metavariable_and_an_invocation_nest_in_one_template() { + let result = execute_source( + r#" + macro_rules! twice { ($e:expr) => { ($e) + ($e) }; } + macro_rules! show { + ($label:expr, $e:expr) => { "${$label} = ${twice!($e)}" }; + } + return show!("total", 21); + "#, + ) + .expect("both rewrites apply to one template"); + + assert_eq!(result.display_first_return(), "total = 42"); +} + +#[test] +fn a_dollar_in_literal_text_is_not_a_metavariable() { + // Only `${…}` interiors are rewritten. `$e` in the literal part is the two + // characters, exactly as it is outside a macro — substituting there would + // silently edit message text. + let result = execute_source( + r#" + macro_rules! show { + ($e:expr) => { "cost: $e is ${$e}" }; + } + return show!(5); + "#, + ) + .expect("literal text is left alone"); + + assert_eq!(result.display_first_return(), "cost: $e is 5"); +} + +#[test] +fn an_unknown_metavariable_in_an_interpolation_is_an_error() { + // Passing it through would fail later as `Unexpected token: Dollar`, which + // names a `$` the program did write but blames the wrong layer. + let error = execute_source( + r#" + macro_rules! show { + ($e:expr) => { "${$typo}" }; + } + return show!(1); + "#, + ) + .expect_err("an undefined metavariable must be reported as one"); + + assert!( + format!("{error}").contains("Unknown macro metavariable `$typo`"), + "{error}" + ); +} + +#[test] +fn a_template_with_no_macros_keeps_its_text_byte_for_byte() { + // Rewriting is by lexeme, so an interior that round-trips through the token + // stream would come back as `a . b` and `{ "k" : 1 }`. A segment whose tokens + // are unchanged has to keep its original text, or every program in the tree + // would be quietly reformatted inside its strings. + let result = execute_source( + r#" + struct P { a: Int } + let p = P { a: 7 }; + return "${p.a} ${ {"k": 1}.len() } ${[1, 2].len()}"; + "#, + ) + .expect("a template with no macro in it still parses"); + + assert_eq!(result.display_first_return(), "7 1 2"); +} + +#[test] +fn nested_braces_still_bound_the_interpolation() { + // The scan lives in `token::split_template_string` now because there were + // two of them and they disagreed here: the parser's copy cut at the first + // `}`, so `"${R {}}"` arrived as `R {`. The expander is the third reader. + let result = execute_source( + r#" + macro_rules! id { ($e:expr) => { $e }; } + return "${ id!({"k": 1}.len()) }"; + "#, + ) + .expect("braces nest inside a hole"); + + assert_eq!(result.display_first_return(), "1"); +} diff --git a/core/src/macro_system/validation_tests.rs b/core/src/macro_system/validation_tests.rs index 2dcde8c4..32a05c9b 100644 --- a/core/src/macro_system/validation_tests.rs +++ b/core/src/macro_system/validation_tests.rs @@ -214,3 +214,36 @@ fn rejects_nested_matcher_repetition_with_missing_template_depth() { "{message}" ); } + +/// Expansion is collect-then-expand, and a `macro_rules!` an *expansion* +/// produces is not in the input the collection pass read. So a macro that +/// defines a macro left the inner definition in the token stream as ordinary +/// tokens, and the parser failed on `macro_rules` itself — the origin stack the +/// only hint that a macro had put it there. Rounds now repeat until a pass +/// produces no new definitions. +#[test] +fn a_macro_can_define_a_macro() { + let result = execute_source( + r#" + macro_rules! define_answer { + () => { macro_rules! answer { () => { 42 }; } }; + } + define_answer!(); + return answer!(); + "#, + ) + .expect("nested macro definition"); + assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(42)]); + + // …and it composes, because each round expands what the last produced. + let deep = execute_source( + r#" + macro_rules! a { () => { macro_rules! b { () => { macro_rules! c { () => { 5 }; } }; } }; } + a!(); + b!(); + return c!(); + "#, + ) + .expect("three levels of definition"); + assert_eq!(deep.returns, vec![crate::val::RuntimeVal::Int(5)]); +} diff --git a/core/src/module.rs b/core/src/module.rs index e5f7a683..3ad6eac2 100644 --- a/core/src/module.rs +++ b/core/src/module.rs @@ -2,7 +2,7 @@ use crate::compat::collections::HashMap; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; use crate::compat::sync::Mutex; -use crate::util::fast_map::fast_hash_map_new; +use crate::util::value_map::value_map_new; use crate::{ val::{CallableValue, HeapStore, HeapValue, RuntimeVal, TypedMap}, vm::{ContextNativeFunction, Module, NativeFunction, PlainNativeFunction, RuntimeExport, RuntimeModuleState}, @@ -195,7 +195,7 @@ pub fn runtime_export_from_plain_native_entries( values: &[RuntimeValueExport], ) -> RuntimeExport { let mut heap = HeapStore::new(); - let mut entries = fast_hash_map_new(); + let mut entries = value_map_new(); for native in natives { let value = RuntimeVal::Obj(heap.alloc(HeapValue::Callable(CallableValue::RuntimeNative { name: Arc::::from(native.name), @@ -208,11 +208,11 @@ pub fn runtime_export_from_plain_native_entries( entries.insert(Arc::::from(value.name), value.value); } let value = RuntimeVal::Obj(heap.alloc(HeapValue::Map(TypedMap::StringMixed(entries)))); - RuntimeExport::new( - value, - Arc::new(Mutex::new(RuntimeModuleState::new(heap, Vec::new()))), - Arc::new(Module::default()), - ) + // Rooted in its own heap, same as a user module's export — see + // `RuntimeModuleState::export_root`. + let mut state = RuntimeModuleState::new(heap, Vec::new()); + state.set_export_root(value); + RuntimeExport::new(value, Arc::new(Mutex::new(state)), Arc::new(Module::default())) } pub fn runtime_export_from_runtime_native(name: &str, function: NativeFunction, arity: u16) -> RuntimeExport { @@ -231,7 +231,7 @@ pub fn runtime_export_from_runtime_native(name: &str, function: NativeFunction, /// it takes owned names and any [`NativeFunction`], including capturing closures. pub fn runtime_module_export(entries: &[(Arc, u16, NativeFunction)]) -> RuntimeExport { let mut heap = HeapStore::new(); - let mut map = fast_hash_map_new(); + let mut map = value_map_new(); for (name, arity, function) in entries { let callable = RuntimeVal::Obj(heap.alloc(HeapValue::Callable(CallableValue::RuntimeNative { name: Arc::clone(name), diff --git a/core/src/operator/operator_test.rs b/core/src/operator/operator_test.rs index 62f3d62d..a93b31e5 100644 --- a/core/src/operator/operator_test.rs +++ b/core/src/operator/operator_test.rs @@ -12,9 +12,9 @@ mod tests { // Tests with literal expressions #[test] fn literal_list_operations() { - expect_expr("([1, 2, 3]) + ([4, 5])", "[1, 2, 3, 4, 5]"); - expect_expr("([1, 2, 3]) - ([2])", "[1, 3]"); - expect_expr("([1, 2, 3]) - 2", "[1, 3]"); + expect_expr("([1, 2, 3]) + ([4, 5])", "[1,2,3,4,5]"); + expect_expr("([1, 2, 3]) - ([2])", "[1,3]"); + expect_expr("([1, 2, 3]) - 2", "[1,3]"); } #[test] diff --git a/core/src/operator/syntax.rs b/core/src/operator/syntax.rs index f5d7e7b1..f10c62db 100644 --- a/core/src/operator/syntax.rs +++ b/core/src/operator/syntax.rs @@ -8,12 +8,19 @@ use crate::val::LiteralVal; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum UnaryOp { Not, + /// `-expr`. + /// + /// Not a desugaring of `0 - expr`: those differ on floats, where `-0.0` is + /// a value distinct from `0.0 - 0.0`, and the point of writing `-x` is to + /// get the negation the hardware has. + Neg, } impl Display for UnaryOp { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { UnaryOp::Not => write!(f, "!"), + UnaryOp::Neg => write!(f, "-"), } } } @@ -48,8 +55,8 @@ impl BinOp { pub(crate) fn cmp_literals(&self, l: &LiteralVal, r: &LiteralVal) -> Option { match self { - BinOp::Eq => Some(l == r), - BinOp::Ne => Some(l != r), + BinOp::Eq => Some(literals_equal(l, r)), + BinOp::Ne => Some(!literals_equal(l, r)), BinOp::In => match (l, r) { (l, r) if l.as_str().is_some() && r.as_str().is_some() => { Some(r.as_str().unwrap().contains(l.as_str().unwrap())) @@ -71,6 +78,26 @@ impl BinOp { } } +/// Are two literals equal, by the rule the *runtime* uses? +/// +/// `==` used to fold through `LiteralVal`'s derived `PartialEq`, which is +/// structural: `Int(1)` and `Float(1.0)` are different variants, so +/// `println(1 == 1.0)` answered `false` while +/// `let a = 1; let b = 1.0; println(a == b)` answered `true` — the same +/// question, decided by whether the operands were literals. It also +/// contradicted the folder's *own* ordering rule, which promotes across the +/// two: `1 <= 1.0 && 1 >= 1.0` folded to `true`. +/// +/// Ordering is the rule, so equality is "ordering says equal". Values it +/// cannot order — `Bool`, `Nil`, a `NaN` — fall back to the structural +/// comparison, which is what the runtime does for them too. +fn literals_equal(l: &LiteralVal, r: &LiteralVal) -> bool { + match cmp_literal_ordering(l, r) { + Some(ordering) => ordering == Ordering::Equal, + None => l == r, + } +} + fn cmp_literal_ordering(l: &LiteralVal, r: &LiteralVal) -> Option { match (l, r) { (LiteralVal::Int(a), LiteralVal::Int(b)) => a.partial_cmp(b), diff --git a/core/src/package.rs b/core/src/package.rs index cc12b512..9791e7f1 100644 --- a/core/src/package.rs +++ b/core/src/package.rs @@ -6,7 +6,26 @@ use std::{ }; use crate::macro_system::{ProcMacroProcessConfig, ProcMacroProviders}; -use anyhow::{Context, Result, anyhow}; + +/// Where a `use ;` macro import finds package `name`'s module root. +/// +/// Installed into `syntax::ParseOptions` as a +/// [`crate::macro_system::PackageMacroModuleResolver`]. `imports.rs` used to +/// call `PackageGraph::discover` itself; that edge ran *upward* — the package +/// manager already hands proc-macro providers down to expansion — and the two +/// modules could not be separated because of it. +#[cfg(feature = "std")] +pub fn macro_module_root(base_dir: &std::path::Path, name: &str) -> Result, String> { + let graph = PackageGraph::discover(base_dir).map_err(|error| error.to_string())?; + Ok(graph.and_then(|graph| { + graph + .modules + .into_iter() + .find(|module| module.name == name) + .map(|module| module.root) + })) +} +use anyhow::{Context, Result, anyhow, bail}; use serde::{Deserialize, Serialize}; // The centralized signing registry (server / publish / keyring / signed @@ -84,7 +103,10 @@ pub struct DetailedDependency { pub branch: Option, pub tag: Option, pub rev: Option, - #[serde(default)] + // Written only when true: `Lk.toml` is a file people read and edit, and + // `workspace = false` on every dependency `lk pkg add` writes is noise that + // says nothing. + #[serde(default, skip_serializing_if = "core::ops::Not::not")] pub workspace: bool, } @@ -111,13 +133,74 @@ pub struct PackageModule { pub root: PathBuf, } +/// A dependency the graph could not turn into a module, and **why**. +/// +/// The reason is the whole point. Both cases used to print +/// "``", and for a `path` dependency that advice is +/// unactionable: the directory is right there, already on disk. What is absent +/// is the package's *library entry* — `lk pkg init` scaffolds `src/main.lk`, +/// which is an application entry, and a package used as a dependency needs +/// `src/mod.lk` or `src/.lk`. Telling someone to fetch a directory they +/// can see is how a five-second fix becomes an afternoon. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MissingDependency { + pub name: String, + pub reason: MissingReason, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MissingReason { + /// No local checkout: a git/GitHub dependency that has not been fetched. + NotFetched, + /// A `path` dependency pointing at a directory that is not there. Fetching + /// cannot create it, so saying "run `lk pkg fetch`" is a wrong instruction + /// rather than an unhelpful one. + PathNotFound, + /// The directory exists; it has no `src/mod.lk` or `src/.lk`. + NoLibraryEntry, +} + +impl MissingDependency { + /// The one-line explanation a CLI prints after the dependency's name. + pub fn advice(&self) -> &'static str { + match self.reason { + MissingReason::NotFetched => "not fetched; run `lk pkg fetch`", + MissingReason::PathNotFound => "the `path` points at a directory that does not exist", + MissingReason::NoLibraryEntry => { + "found, but the package has no library entry; add `src/mod.lk` (or `src/.lk`)" + } + } + } +} + #[derive(Debug, Clone)] pub struct PackageGraph { pub root: PathBuf, pub manifest_path: PathBuf, pub manifest: Manifest, pub modules: Vec, - pub missing: Vec, + pub missing: Vec, + /// The enclosing workspace, when this graph's subject is a *member*. + /// + /// Kept beside `manifest` rather than replacing it. `discover` used to walk + /// the ancestors and adopt the workspace manifest as the graph's subject, + /// so inside a member `lk pkg check` described the *workspace* and never + /// read the member's own `[dependencies]` — a member depending on something + /// outside the workspace got "package check ok" while the program failed + /// with `Module 'outside' not found`, which is the one thing `check` exists + /// to prevent. + /// + /// The workspace is still needed: it supplies the sibling members as + /// modules, and the table `workspace = true` inherits from. + pub workspace: Option, +} + +/// An enclosing `[workspace]` and the directory its member globs resolve +/// against. +#[derive(Debug, Clone)] +pub struct WorkspaceContext { + pub root: PathBuf, + pub section: WorkspaceSection, } impl Manifest { @@ -252,27 +335,48 @@ impl PackageGraph { if manifests.is_empty() { return Ok(None); }; - let mut manifest_path = manifests[0].clone(); + // The *nearest* manifest is the subject; an ancestor's `[workspace]` is + // context, not a replacement. See `PackageGraph::workspace`. + let manifest_path = manifests[0].clone(); + let mut workspace = None; for candidate in &manifests { - if Manifest::read(candidate)?.workspace.is_some() { - manifest_path = candidate.clone(); + let manifest = Manifest::read(candidate)?; + if let Some(section) = manifest.workspace { + let root = candidate + .parent() + .ok_or_else(|| anyhow!("manifest has no parent: {}", candidate.display()))? + .to_path_buf(); + workspace = Some(WorkspaceContext { root, section }); } } - Self::from_manifest_path(&manifest_path).map(Some) + Self::from_manifest_path_in(&manifest_path, workspace).map(Some) } pub fn from_manifest_path(manifest_path: &Path) -> Result { + Self::from_manifest_path_in(manifest_path, None) + } + + fn from_manifest_path_in(manifest_path: &Path, workspace: Option) -> Result { let manifest = Manifest::read(manifest_path)?; let root = manifest_path .parent() .ok_or_else(|| anyhow!("manifest has no parent: {}", manifest_path.display()))? .to_path_buf(); + let manifest_workspace = manifest.workspace.clone(); let mut graph = Self { root: root.clone(), manifest_path: manifest_path.to_path_buf(), manifest, modules: Vec::new(), missing: Vec::new(), + // A manifest that *is* the workspace is its own context, so running + // at the root behaves exactly as before. + workspace: workspace.or_else(|| { + manifest_workspace.map(|section| WorkspaceContext { + root: root.clone(), + section, + }) + }), }; graph.collect_workspace_modules()?; graph.collect_dependency_modules()?; @@ -347,10 +451,12 @@ impl PackageGraph { self.modules.push(package_module(&self.root, &package.name, root)); } - let Some(workspace) = self.manifest.workspace.as_ref() else { + let Some(workspace) = self.workspace.clone() else { return Ok(()); }; - for member in expand_members(&self.root, &workspace.members)? { + // Members resolve against the *workspace* directory, which is not this + // graph's root when the subject is a member. + for member in expand_members(&workspace.root, &workspace.section.members)? { let manifest_path = member.join(MANIFEST_FILE); if !manifest_path.exists() { continue; @@ -376,22 +482,35 @@ impl PackageGraph { if self.modules.iter().any(|module| module.name == name) { continue; } + let mut from_path = false; let dep_dir = if spec.is_workspace() { continue; } else if let Some(path) = spec.path() { + from_path = true; self.root.join(path) } else if let Some(locked) = locked.get(&name) { - cache_dir_for_source(&locked.source) + cache_dir_for_source(&locked.source)? } else if let Some(url) = spec.git_url() { - cache_dir_for_source(&url) + cache_dir_for_source(&url)? } else { - self.missing.push(name); + self.missing.push(MissingDependency { + name, + reason: MissingReason::NotFetched, + }); continue; }; if let Some(root) = package_entry(&dep_dir, &name) { self.modules.push(package_module(&dep_dir, &name, root)); } else { - self.missing.push(name); + // The directory is on disk (a `path` dependency, or a fetched + // checkout) and has no library entry — a different problem from + // not having fetched it, and `lk pkg fetch` cannot fix it. + let reason = match (dep_dir.exists(), from_path) { + (true, _) => MissingReason::NoLibraryEntry, + (false, true) => MissingReason::PathNotFound, + (false, false) => MissingReason::NotFetched, + }; + self.missing.push(MissingDependency { name, reason }); } } Ok(()) @@ -401,10 +520,9 @@ impl PackageGraph { let mut deps = BTreeMap::new(); for (name, spec) in &self.manifest.dependencies { let resolved = if spec.is_workspace() { - self.manifest - .workspace + self.workspace .as_ref() - .and_then(|workspace| workspace.dependencies.get(name).cloned()) + .and_then(|workspace| workspace.section.dependencies.get(name).cloned()) } else { Some(spec.clone()) }; @@ -471,7 +589,83 @@ pub fn github_url(repo: &str) -> String { } } -pub fn cache_dir_for_source(source: &str) -> PathBuf { +/// Where a git dependency is cloned: `~/.lk/git/` plus the source URL's own +/// shape, so two dependencies from one host share a prefix and a reader can +/// find a clone by eye. +/// +/// **A `..` component is refused, not skipped.** The path is built from a +/// string in `Lk.toml` (or, worse, in `Lk.lock`, which a dependency can +/// contribute to), and `PathBuf::push("..")` walks *up* — so +/// `git = "https://example.com/../../../../../../tmp/x"` had `git clone` +/// writing to `/tmp/x`, outside the cache root entirely. Measured, with git's +/// own message naming the escaped path. +/// +/// Refusing rather than dropping the component: two different sources must not +/// collapse onto one cache directory, and a source nobody meant to write is +/// worth saying out loud. `.` and empty segments are dropped, because those +/// *are* the same path. +/// The one edition this language has. +/// +/// A list rather than a constant because the *shape* of the check is what +/// matters: when a second edition exists, the manifest field starts meaning +/// something and this is where it is decided. +const KNOWN_EDITIONS: &[&str] = &["2026"]; + +/// Checks the three `[package]` fields that were written and never read. +/// +/// `edition` is emitted by `lk pkg init` and read by **nothing** — `"1999"`, +/// `"banana"` and a missing field were all "package check ok". `version` had no +/// reader either, so `version = "not-a-version"` passed. And `name` was +/// unconstrained: `name = "../evil"` and `name = ""` both passed, while the +/// name is what `use ;` has to spell and what a workspace member is +/// looked up by. +/// +/// Checked here rather than at load: this is the command whose job is to answer +/// "is this package well-formed", and a decorative field being wrong should not +/// stop a program that does not read it from running. +pub fn validate_package_section(package: &PackageSection) -> Result<()> { + let name = package.name.as_str(); + if name.is_empty() { + bail!("`[package] name` is empty — it is the name `use ;` spells"); + } + let head_ok = name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_'); + let rest_ok = name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'); + if !head_ok || !rest_ok { + bail!( + "`[package] name = \"{name}\"` is not a name this language can spell — a package name \ + is an identifier (letters, digits, `_`, `-`, not starting with a digit), because \ + `use ;` has to lex" + ); + } + if let Some(version) = &package.version + && !is_semver(version) + { + bail!("`[package] version = \"{version}\"` is not a version — write `major.minor.patch`"); + } + if let Some(edition) = &package.edition + && !KNOWN_EDITIONS.contains(&edition.as_str()) + { + bail!( + "`[package] edition = \"{edition}\"` is not an edition this build knows — {}", + KNOWN_EDITIONS.join(", ") + ); + } + Ok(()) +} + +/// `major.minor.patch`, with the optional `-pre` and `+build` tails. +/// +/// Deliberately not a semver crate: the whole question here is whether someone +/// typed a version or a sentence, and a dependency for that is not worth it. +fn is_semver(version: &str) -> bool { + let core = version.split(['-', '+']).next().unwrap_or(""); + let mut parts = core.split('.'); + let numeric = + |part: Option<&str>| part.is_some_and(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit())); + numeric(parts.next()) && numeric(parts.next()) && numeric(parts.next()) && parts.next().is_none() +} + +pub fn cache_dir_for_source(source: &str) -> Result { let mut root = lk_home().join("git"); let normalized = source .trim_end_matches(".git") @@ -479,10 +673,19 @@ pub fn cache_dir_for_source(source: &str) -> PathBuf { .trim_start_matches("http://") .trim_start_matches("git@") .replace(':', "/"); - for part in normalized.split('/').filter(|part| !part.is_empty()) { + for part in normalized.split('/') { + if part.is_empty() || part == "." { + continue; + } + if part == ".." { + bail!( + "dependency source `{source}` has a `..` path segment — the clone directory is \ + built from the source, and that would put it outside the package cache" + ); + } root.push(part); } - root + Ok(root) } fn resolve_proc_macro_command(manifest_dir: &Path, command: &str) -> PathBuf { @@ -746,6 +949,63 @@ mod tests { Ok(()) } + /// The clone directory is built from the source string, so a `..` in it + /// walks out of the cache. + /// + /// Measured before the guard: `git = "https://example.com/../../../../../../tmp/x"` + /// had git report `Cloning into '/home/…/.lk/git/example.com/../../../../../../tmp/x'` + /// — outside `~/.lk/git` entirely. A source with a clonable remote and a + /// `..` (a local path or `file://` remote) lands the checkout there. + /// + /// Refused rather than dropped: dropping would collapse two different + /// sources onto one directory. + #[test] + fn a_source_with_a_dotdot_segment_cannot_escape_the_cache() { + let error = cache_dir_for_source("https://example.com/../../../tmp/x") + .expect_err("`..` walks out of the cache root") + .to_string(); + assert!(error.contains("`..` path segment"), "{error}"); + + // The shapes that must keep working, including the empty segments a + // scheme leaves behind and a `.` that means nothing. + let ok = cache_dir_for_source("https://github.com/owner/repo.git").expect("an ordinary source"); + assert!(ok.ends_with("git/github.com/owner/repo"), "{}", ok.display()); + let ssh = cache_dir_for_source("git@github.com:owner/repo.git").expect("an ssh source"); + assert_eq!(ok, ssh, "the two spellings of one repository share a cache directory"); + let dotted = cache_dir_for_source("https://example.com/./a").expect("a `.` segment is the same path"); + assert!(dotted.ends_with("git/example.com/a"), "{}", dotted.display()); + } + + /// The three `[package]` fields that were written and never read. + #[test] + fn the_package_section_is_checked() { + let section = |name: &str, version: Option<&str>, edition: Option<&str>| PackageSection { + name: name.to_string(), + version: version.map(str::to_string), + edition: edition.map(str::to_string), + ..PackageSection::default() + }; + + validate_package_section(§ion("pk", Some("0.1.0"), Some("2026"))).expect("an ordinary package"); + validate_package_section(§ion("pk", Some("1.2.3-rc.1+build5"), None)).expect("a pre-release version"); + validate_package_section(§ion("pk", None, None)).expect("both fields are optional"); + + for (name, version, edition, needle) in [ + ("../evil", Some("0.1.0"), None, "is not a name"), + ("", Some("0.1.0"), None, "is empty"), + ("9pk", Some("0.1.0"), None, "is not a name"), + ("pk", Some("not-a-version"), None, "is not a version"), + ("pk", Some("1.2"), None, "is not a version"), + ("pk", Some("0.1.0"), Some("1999"), "is not an edition"), + ("pk", Some("0.1.0"), Some("banana"), "is not an edition"), + ] { + let error = validate_package_section(§ion(name, version, edition)) + .expect_err("refused") + .to_string(); + assert!(error.contains(needle), "{name}/{version:?}/{edition:?}: {error}"); + } + } + #[test] fn macro_distribution_check_reports_bad_provider_metadata() -> Result<()> { let temp = tempfile::tempdir()?; @@ -842,4 +1102,65 @@ mod tests { assert!(modules.contains_key("helper")); Ok(()) } + + /// A workspace member's own dependencies are part of its graph. + /// + /// `discover` walked the ancestors and adopted the *workspace* manifest as + /// the subject, so inside a member `lk pkg check` described the workspace + /// and never read the member's `[dependencies]`. A member depending on + /// something outside the workspace got "package check ok" while the program + /// failed with `Module 'outside' not found` — the one question `check` + /// exists to answer, answered wrong. + #[test] + fn a_workspace_member_graph_is_rooted_at_the_member() { + let temp = tempfile::tempdir().expect("temp dir"); + let root = temp.path(); + std::fs::write(root.join(MANIFEST_FILE), "[workspace]\nmembers = [\"crates/*\"]\n").expect("workspace"); + + let sibling = root.join("crates/sibling"); + std::fs::create_dir_all(sibling.join("src")).expect("sibling dirs"); + std::fs::write( + sibling.join(MANIFEST_FILE), + "[package]\nname = \"sibling\"\nversion = \"0.1.0\"\n", + ) + .expect("sibling manifest"); + std::fs::write(sibling.join("src/mod.lk"), "fn s() -> Int { return 1; }\n").expect("sibling entry"); + + let outside = root.join("outside"); + std::fs::create_dir_all(outside.join("src")).expect("outside dirs"); + std::fs::write( + outside.join(MANIFEST_FILE), + "[package]\nname = \"outside\"\nversion = \"0.1.0\"\n", + ) + .expect("outside manifest"); + std::fs::write(outside.join("src/mod.lk"), "fn o() -> Int { return 2; }\n").expect("outside entry"); + + let member = root.join("crates/member"); + std::fs::create_dir_all(member.join("src")).expect("member dirs"); + std::fs::write( + member.join(MANIFEST_FILE), + "[package]\nname = \"member\"\nversion = \"0.1.0\"\n\n[dependencies.outside]\npath = \"../../outside\"\n", + ) + .expect("member manifest"); + std::fs::write(member.join("src/mod.lk"), "fn m() -> Int { return 3; }\n").expect("member entry"); + + let graph = PackageGraph::discover(&member).expect("discover").expect("a graph"); + assert_eq!( + graph.manifest.package.as_ref().map(|package| package.name.as_str()), + Some("member"), + "the member is the subject, not the workspace" + ); + let names: Vec<&str> = graph.modules.iter().map(|module| module.name.as_str()).collect(); + assert!(names.contains(&"outside"), "the member's own dependency: {names:?}"); + assert!(names.contains(&"sibling"), "and its workspace siblings: {names:?}"); + + // Remove the dependency: the graph must now say so rather than report ok. + std::fs::remove_dir_all(&outside).expect("remove outside"); + let graph = PackageGraph::discover(&member).expect("discover").expect("a graph"); + assert_eq!( + graph.missing.iter().map(|m| m.name.as_str()).collect::>(), + vec!["outside"] + ); + assert_eq!(graph.missing[0].reason, MissingReason::PathNotFound); + } } diff --git a/core/src/resolve/slots.rs b/core/src/resolve/slots.rs index 50dc69bf..a0375bca 100644 --- a/core/src/resolve/slots.rs +++ b/core/src/resolve/slots.rs @@ -143,13 +143,19 @@ impl FnCtx { if let Some(top) = self.scopes.last_mut() { top.insert(name.clone(), idx); } - self.decls.push(Decl { - name, - index: idx, - is_param, - block_depth: self.block_depth(), - span: None, - }); + // A desugar's temporary gets a slot like any other local — the code + // has to run — but it is not something the writer declared, so it does + // not go in the list tools read. The editor's outline used to list + // `__optcall0` and `__unwrap1` beside the real variables. + if !crate::ast::is_desugar_local(&name) { + self.decls.push(Decl { + name, + index: idx, + is_param, + block_depth: self.block_depth(), + span: None, + }); + } idx } @@ -238,7 +244,7 @@ impl ResolverCore { fn resolve_stmt(&mut self, stmt: &Stmt, children_out: &mut Vec) { match stmt { - Stmt::Attributed { item, .. } => { + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => { self.resolve_stmt(item, children_out); } Stmt::Import(_) => { @@ -312,7 +318,7 @@ impl ResolverCore { Stmt::CompoundAssign { value, .. } => { self.resolve_expr(value); } - Stmt::Define { name, value } => { + Stmt::Define { name, value, .. } => { self.resolve_expr(value); let name = name.clone(); self.current_fn().define(name, false); @@ -339,7 +345,7 @@ impl ResolverCore { children_out.push(child_layout); } - Stmt::Expr(expr) => { + Stmt::Expr { value: expr, .. } => { self.resolve_expr(expr); } Stmt::Struct { .. } => { @@ -374,26 +380,6 @@ impl ResolverCore { } self.current_fn().pop_block(); } - Stmt::Try { - body, - catch_var, - handler, - } => { - // Two sibling scopes. The caught name is bound only in the - // second one: the body cannot see it, and the handler's binding - // must not outlive its block. - self.current_fn().push_block(); - for s in body { - self.resolve_stmt(s, children_out); - } - self.current_fn().pop_block(); - self.current_fn().push_block(); - self.current_fn().define(catch_var.clone(), false); - for s in handler { - self.resolve_stmt(s, children_out); - } - self.current_fn().pop_block(); - } Stmt::Empty => {} } } @@ -491,7 +477,7 @@ impl ResolverCore { } } } - Expr::Closure { params, body } => { + Expr::Closure { params, body, .. } => { // Nested anonymous function let child = self.with_new_function(|this| { for p in params { @@ -506,7 +492,7 @@ impl ResolverCore { &mut Vec::new(), ); } - _ => this.resolve_stmt(&Stmt::Expr(body.clone()), &mut Vec::new()), + _ => this.resolve_stmt(&Stmt::expr(body.clone()), &mut Vec::new()), } }); // Attach as an anonymous child of the current function @@ -523,10 +509,32 @@ impl ResolverCore { self.current_fn().pop_block(); } } - // Block expressions in general expression position (today only - // synthesized — e.g. the `select` desugar; closure bodies take - // the dedicated path above): resolve their statements in a block - // scope of their own, same as a statement-level block. + // Block expressions in general expression position — both branches + // of an `if` used for its value, and the synthesized bodies of the + // `select` / `?.` desugars; closure bodies take the dedicated path + // above. Resolve their statements in a block scope of their own, + // same as a statement-level block, so a `let` inside a branch does + // not leak past it. + // Two scopes, and the handler's is the one that binds the caught + // name — same shape as the compiler's lowering. + Expr::Try { + body, + catch_var, + handler, + } => { + self.resolve_stmt( + &Stmt::Block { + statements: body.clone(), + }, + &mut Vec::new(), + ); + self.current_fn().push_block(); + self.current_fn().define(catch_var.clone(), /*is_param=*/ false); + for stmt in handler { + self.resolve_stmt(stmt, &mut Vec::new()); + } + self.current_fn().pop_block(); + } Expr::Block(statements) => { self.resolve_stmt( &Stmt::Block { diff --git a/core/src/rt.rs b/core/src/rt.rs index c636e152..a858b689 100644 --- a/core/src/rt.rs +++ b/core/src/rt.rs @@ -1,3 +1,7 @@ +use alloc::sync::Arc; + +use crate::val::HeapStore; + #[cfg(feature = "async-runtime")] mod runtime; #[cfg(not(feature = "async-runtime"))] @@ -7,3 +11,64 @@ mod unsupported; pub use runtime::*; #[cfg(not(feature = "async-runtime"))] pub use unsupported::*; + +/// A raise carrying a value out of the heap it was raised in. +/// +/// A task runs against a `HeapStore` of its own, and its *result* leaves as a +/// [`RuntimePayload`] — value plus the heap it lives in — precisely so the +/// awaiting side can copy it into its own. A raise carries a value the same +/// way and had no such carrier: the error propagated with a bare handle, and +/// by the time anyone read it the task's heap was gone. `error([1, 2, 3])` +/// inside `spawn` came back as whatever object now sat at that index — +/// `` — with no error reported. +/// +/// [`detach`](Self::detach) is called on the task's side while its heap is +/// still alive, [`reattach`](Self::reattach) on the awaiting side. +#[derive(Debug)] +pub struct RaisedPayload { + pub payload: RuntimePayload, + pub rendered: Arc, +} + +impl core::fmt::Display for RaisedPayload { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.rendered.as_ref()) + } +} + +impl core::error::Error for RaisedPayload {} + +impl RaisedPayload { + /// Take a raise out of `heap`, so it can outlive it. + /// + /// Anything that is not a first-class raise, and any raise whose payload is + /// stored inline (an Int, a short string), is returned untouched — those + /// carry no handle and were never at risk. + pub fn detach(error: anyhow::Error, heap: &HeapStore) -> anyhow::Error { + let Some(raised) = error.root_cause().downcast_ref::() else { + return error; + }; + if !matches!(raised.value, crate::val::RuntimeVal::Obj(_)) { + return error; + } + let rendered = Arc::clone(&raised.rendered); + match RuntimePayload::copy_from_value(&raised.value, heap) { + Ok(payload) => anyhow::anyhow!(RaisedPayload { payload, rendered }), + // A payload that cannot be copied at all (a bare closure) keeps the + // message it already rendered, rather than a handle that faults. + Err(_) => anyhow::anyhow!("{rendered}"), + } + } + + /// Put a detached raise back into `heap`, as the raise it was. + pub fn reattach(error: anyhow::Error, heap: &mut HeapStore) -> anyhow::Error { + let Some(detached) = error.root_cause().downcast_ref::() else { + return error; + }; + let rendered = Arc::clone(&detached.rendered); + match detached.payload.clone_value_into(heap) { + Ok(value) => anyhow::anyhow!(crate::vm::LkRaisedValue { value, rendered }), + Err(_) => anyhow::anyhow!("{rendered}"), + } + } +} diff --git a/core/src/rt/runtime.rs b/core/src/rt/runtime.rs index e585f063..d1c7aabf 100644 --- a/core/src/rt/runtime.rs +++ b/core/src/rt/runtime.rs @@ -233,6 +233,14 @@ impl Runtime { } /// Attempt to send a value without blocking. + /// + /// A closed channel is the *language's* error, worded as the language words + /// it (`send on closed channel`, which is also what `blocking_send_value` + /// raises and what `lkrt::chan` raises natively). Callers propagate it with + /// `?` rather than wrapping: `chan.try_send` used to decorate it into + /// "Failed to send to channel: Channel is closed", so the one operation had + /// three spellings — one per caller — and a caught message is printed + /// output. pub fn try_send(&self, channel_id: u64, value: RuntimePayload) -> Result { let (sender, closed_flag) = { let channels = self.channels.lock().unwrap(); @@ -246,17 +254,17 @@ impl Runtime { Err(mpsc::error::TrySendError::Full(_)) => Ok(false), Err(mpsc::error::TrySendError::Closed(_)) => { closed_flag.store(true, Ordering::SeqCst); - Err(anyhow!("Channel is closed")) + Err(anyhow!("send on closed channel")) } }, ChannelSender::Unbounded(sender) => match sender.send(value) { Ok(()) => Ok(true), Err(_) => { closed_flag.store(true, Ordering::SeqCst); - Err(anyhow!("Channel is closed")) + Err(anyhow!("send on closed channel")) } }, - ChannelSender::Closed => Err(anyhow!("Channel is closed")), + ChannelSender::Closed => Err(anyhow!("send on closed channel")), } } @@ -403,7 +411,12 @@ impl Runtime { pub async fn join_task(&self, task_id: u64) -> Result { let mut task = { let mut tasks = self.tasks.lock().unwrap(); - tasks.remove(&task_id).ok_or_else(|| anyhow!("Task not found"))? + tasks.remove(&task_id).ok_or_else(|| { + // Awaiting takes the task out of the table, so a second await + // finds nothing. "Task not found" described the table; this + // describes the program. + anyhow!("this task has already been awaited — its result was handed to the first `await`") + })? }; // If result is already available, return it diff --git a/core/src/stmt.rs b/core/src/stmt.rs index dc458028..d9842e94 100644 --- a/core/src/stmt.rs +++ b/core/src/stmt.rs @@ -1,9 +1,13 @@ // The file-import resolver (fs/path) is std-gated; under no_std its cache field // and `Path` import are legitimately unused (M0.7/8). #[cfg_attr(not(feature = "std"), allow(dead_code, unused_imports))] +pub mod defer; pub mod import; +pub(crate) mod init_order; mod stmt_impl; pub mod stmt_parser; +pub mod struct_ctors; +pub mod trait_defaults; #[cfg(test)] mod attribute_test; diff --git a/core/src/stmt/defer.rs b/core/src/stmt/defer.rs new file mode 100644 index 00000000..e45f75cb --- /dev/null +++ b/core/src/stmt/defer.rs @@ -0,0 +1,385 @@ +//! `defer` — a release that happens on every path, written once. +//! +//! ```lk +//! fn drive(card: Int) -> Bool { +//! let pages = take_pages(); +//! defer give_pages_back(pages); +//! if (!ready(card)) { return false; } // released +//! if (!answered(card)) { return false; } // released +//! return true; // released +//! } +//! ``` +//! +//! Why it exists here rather than being left to discipline: a kernel is where +//! the discipline fails, and it fails quietly. Writing this language's bare-metal +//! demonstration produced, in one sitting, an allocator that leaked the pages it +//! had already taken when a run turned out not to be contiguous, a task-stack +//! allocator with the same bug, and one function that had to be *split in two* +//! so that five pages could be released on the seven paths that gave up. None of +//! those was a wrong answer. Each was a machine that ran out of memory later, on +//! a path nothing exercised. +//! +//! # It is a shape, not a mechanism +//! +//! The rewrite happens once, on the AST, before the resolver and the type +//! checker and both compilers. Nothing downstream ever sees a `Stmt::Defer`. +//! +//! That is deliberate and it is the whole design. A release that must happen on +//! every path is a property of the *code*, and code is what a compiler already +//! has. Making it a runtime mechanism would mean a stack of pending actions, a +//! way to run them, and — the part that decides it — an implementation in each +//! backend, which is how the interpreter and the compiled build come to disagree +//! about a program. There is no version of that which is worth more than a +//! rewrite. +//! +//! # What it does not do +//! +//! **It does not run when a raise unwinds past it.** A `raise` leaves through +//! `longjmp` on the native path, which is not a `return` and is not visible to +//! this rewrite. A `defer` releasing something a handler then needs would be +//! worse than no `defer`, so this is said plainly rather than approximated. +//! +//! There *is* a third option, and it was **built and measured** (2026-07-30) +//! rather than argued about, so the question does not have to be re-opened from +//! scratch: wrap the body in a `try`, run the releases in the `catch`, and +//! re-raise. It stays a pure AST rewrite — `try`/`catch` and re-raising from a +//! handler both work in both backends — so it is not the runtime mechanism this +//! section argues against, and it does make `defer` run on the raise path. +//! +//! It was reverted, for a cost that only showed up once it existed. Wrapping a +//! whole function body means every register the body assigns becomes a try +//! region *output cell*, and register reuse makes that most of them. A cell +//! round-trip is defined for scalars and deliberately **not** for container +//! handles (`unbox_from_dyn` — reading one back as the wrong typed handle is a +//! wrong answer, not a rejection). So `examples/syntax/defer.lk` stopped +//! lowering natively the moment the wrap went in. +//! +//! That trade is the wrong way round: a documented semantic gap became a +//! *silent* three-times slowdown in exactly the code this feature exists for — a +//! kernel, which is compiled. The prerequisite is therefore not +//! `docs/aot/aot-gaps-and-lkrt.md` §17 (that one is done, and a `return` inside +//! a `try` body lowers now); it is a cell round-trip for container handles. +//! +//! That prerequisite is now met (2026-07-30): a boxed container round-trips by +//! pointer, and a *typed* one parks as a raw handle under its own tag, so every +//! container crosses a region. +//! +//! The wrap was then tried a second time, and reverted again — this time for the +//! *return* plumbing rather than the cells. A function whose returns are all +//! inside the wrapped body has no `Exit::Ret` of its own, so its return type +//! goes missing; carrying the parked type out to the caller fixes that shape and +//! breaks the mixed one (a real return *and* a parked one), and the fall-off +//! path then returns void against a typed signature. Each of those is +//! answerable; together they are a piece of work of their own, and a +//! half-finished version of it is how a function silently returns the wrong +//! thing. The measurements are in `docs/aot/aot-gaps-and-lkrt.md` §17. +//! +//! **It may only appear at the top level of a function body.** Not inside an +//! `if`, a loop, or a nested block. That is what makes the rewrite sound: at the +//! top level, textual order is execution order, so "every `defer` written above +//! this `return`" is exactly "every `defer` that has run". A `defer` inside a +//! branch would have to be tracked at run time, which is the mechanism this is +//! not. +//! +//! The restriction also matches what the feature is for. A resource taken in the +//! middle of a loop is released at the end of that iteration, which is an +//! ordinary pair of statements; what needs `defer` is the resource a whole +//! function holds. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; +use crate::expr::{Expr, Pattern}; +use crate::stmt::Stmt; + +/// The name a deferred `return`'s value is parked under. +/// +/// Written so it cannot collide with anything a person types: `return f(page)` +/// with `defer free(page)` above it has to evaluate `f(page)` *before* the +/// release, or the value being returned is computed out of memory that has just +/// been given back. So the value is bound first, the releases run, and the +/// binding is what is returned. +const RETURN_SLOT: &str = "__lk_defer_return"; + +/// Rewrites a program so every `defer` runs on the way out of its function. +pub fn desugar_defers(statements: &mut Vec>) -> Result<(), String> { + rewrite_sequence(statements)?; + for stmt in statements.iter_mut() { + descend(stmt)?; + } + Ok(()) +} + +/// Walks into every nested function and rewrites its body too. +/// +/// The walk is separate from the rewrite because a `defer` belongs to the +/// function it is written in: a function nested inside one that defers something +/// has its own way out, and running the outer function's releases when the inner +/// one returns would release things the outer one is still using. +fn descend(stmt: &mut Stmt) -> Result<(), String> { + match stmt { + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => descend(item), + Stmt::Function { body, .. } => { + if let Stmt::Block { statements } = body.as_mut() { + rewrite_sequence(statements)?; + } + descend(body) + } + Stmt::Block { statements } => { + for inner in statements.iter_mut() { + reject_stray(inner)?; + descend(inner)?; + } + Ok(()) + } + Stmt::If { + then_stmt, else_stmt, .. + } => { + descend(then_stmt)?; + match else_stmt { + Some(other) => descend(other), + None => Ok(()), + } + } + Stmt::While { body, .. } | Stmt::For { body, .. } => descend(body), + Stmt::Impl { methods, .. } => { + for method in methods.iter_mut() { + descend(method)?; + } + Ok(()) + } + // `try { … } catch e { … }` — an expression now, so it arrives wrapped. + Stmt::Expr { value: expr, .. } => descend_expr(expr), + Stmt::Let { value, .. } | Stmt::Return { value: Some(value), .. } => descend_expr(value), + _ => Ok(()), + } +} + +/// Walks an expression for the two things that carry statements: a closure body +/// and a `try` region. +/// +/// A closure is where this was missing. `descend` handled `Stmt::Function`, but +/// a lambda is an *expression*, so `let f = || { defer …; return …; };` reached +/// neither the rewrite nor the stray check — the `defer` was compiled as an +/// ordinary statement and ran *in place*. Written in a named function the same +/// three lines returned `0`; written in a closure they returned `1`, because the +/// release had already happened when the return expression was evaluated. +/// +/// The match has no wildcard on purpose. What went wrong here is a shape nobody +/// listed, and a `_` arm is how the next one gets in. +fn descend_expr(expr: &mut Expr) -> Result<(), String> { + match expr { + Expr::Closure { body, .. } => { + if let Expr::Block(statements) = body.as_mut() { + rewrite_sequence(statements)?; + for inner in statements.iter_mut() { + reject_stray(inner)?; + descend(inner)?; + } + return Ok(()); + } + descend_expr(body) + } + Expr::Try { body, handler, .. } => { + for inner in body.iter_mut().chain(handler.iter_mut()) { + reject_stray(inner)?; + descend(inner)?; + } + Ok(()) + } + // An expression-level block is a statement sequence that is *not* a + // function body, so a `defer` in it has no way out of its own: rejected + // like any other stray. + Expr::Block(statements) => { + for inner in statements.iter_mut() { + reject_stray(inner)?; + descend(inner)?; + } + Ok(()) + } + Expr::Bin(left, _, right) + | Expr::And(left, right) + | Expr::Or(left, right) + | Expr::NullishCoalescing(left, right) + | Expr::Access(left, right) + | Expr::OptionalAccess(left, right) => { + descend_expr(left)?; + descend_expr(right) + } + Expr::Unary(_, inner) | Expr::Paren(inner) | Expr::Unsafe(inner) | Expr::Cast(inner, _) => descend_expr(inner), + Expr::Conditional(cond, then_expr, else_expr) => { + descend_expr(cond)?; + descend_expr(then_expr)?; + descend_expr(else_expr) + } + Expr::List(items) => items.iter_mut().try_for_each(|item| descend_expr(item)), + Expr::Map(entries) => entries.iter_mut().try_for_each(|(key, value)| { + descend_expr(key)?; + descend_expr(value) + }), + Expr::StructLiteral { fields, .. } => fields.iter_mut().try_for_each(|(_, value)| descend_expr(value)), + Expr::Call(_, args) => args.iter_mut().try_for_each(|arg| descend_expr(arg)), + Expr::CallExpr(callee, args) => { + descend_expr(callee)?; + args.iter_mut().try_for_each(|arg| descend_expr(arg)) + } + Expr::CallNamed(callee, positional, named) => { + descend_expr(callee)?; + positional.iter_mut().try_for_each(|arg| descend_expr(arg))?; + named.iter_mut().try_for_each(|(_, arg)| descend_expr(arg)) + } + Expr::Range { start, end, step, .. } => { + for part in [start, end, step].into_iter().flatten() { + descend_expr(part)?; + } + Ok(()) + } + Expr::TemplateString(parts) => parts.iter_mut().try_for_each(|part| match part { + crate::expr::TemplateStringPart::Expr(inner) => descend_expr(inner), + crate::expr::TemplateStringPart::Literal(_) => Ok(()), + }), + Expr::Match { value, arms } => { + descend_expr(value)?; + arms.iter_mut().try_for_each(|arm| descend_expr(&mut arm.body)) + } + Expr::Var(_) | Expr::Literal(_) => Ok(()), + } +} + +/// A `defer` that survived the rewrite is one the rewrite could not reason +/// about, which is one in a branch, a loop or a nested block. +fn reject_stray(stmt: &Stmt) -> Result<(), String> { + match stmt { + Stmt::Defer { span, .. } => Err(defer_placement_error(span.as_ref())), + _ => Ok(()), + } +} + +fn defer_placement_error(span: Option<&crate::token::Span>) -> String { + let where_at = match span { + Some(span) => format!(" at line {}", span.start.line), + None => String::new(), + }; + format!( + "`defer`{where_at} must be at the top level of a function body — not inside an `if`, a \ + loop, or a nested block. It is a rewrite of the code's shape rather than a runtime \ + mechanism, and what makes that sound is that at the top level textual order is execution \ + order: every `defer` written above a `return` is exactly every `defer` that has run. A \ + resource taken inside a loop is released at the end of that iteration, which is an \ + ordinary pair of statements." + ) +} + +/// The rewrite, on one function body. +/// +/// Each `defer` is removed and its statement remembered. Every `return` gets the +/// statements written above it, in reverse; so does the end of the body. Reverse +/// because releases nest: the second thing taken is the first thing given back, +/// and a lock released before the thing it protects is a window. +#[allow(clippy::vec_box, reason = "the AST stores a block's statements as `Vec>`")] +fn rewrite_sequence(body: &mut Vec>) -> Result<(), String> { + if !body.iter().any(|stmt| matches!(stmt.as_ref(), Stmt::Defer { .. })) { + return Ok(()); + } + + let mut out: Vec> = Vec::with_capacity(body.len()); + let mut pending: Vec> = Vec::new(); + for stmt in body.drain(..) { + match *stmt { + Stmt::Defer { body, .. } => pending.push(body), + other => out.push(Box::new(with_releases(other, &pending))), + } + } + // The fall-off. A body whose last statement is already a `return` gets these + // too — unreachable, harmless, and cheaper than proving it is. + for release in pending.iter().rev() { + out.push(release.clone()); + } + *body = out; + Ok(()) +} + +/// Puts `pending`'s releases in front of every `return` inside `stmt`. +/// +/// Inside, not before: a `return` nested in an `if` or a loop is still a way out +/// of the function, and it is the one a leak hides behind. +fn with_releases(stmt: Stmt, pending: &[Box]) -> Stmt { + if pending.is_empty() { + return stmt; + } + match stmt { + Stmt::Return { value } => { + let mut block: Vec> = Vec::with_capacity(pending.len() + 2); + let returned = match value { + // The value first, under a name, because it may read the very + // thing about to be released. + Some(expr) => { + block.push(Box::new(Stmt::Let { + pattern: Pattern::Variable(RETURN_SLOT.to_string()), + type_annotation: None, + value: expr, + span: None, + is_const: false, + })); + Some(Box::new(Expr::Var(RETURN_SLOT.to_string()))) + } + None => None, + }; + for release in pending.iter().rev() { + block.push(release.clone()); + } + block.push(Box::new(Stmt::Return { value: returned })); + Stmt::Block { statements: block } + } + Stmt::Attributed { attributes, item } => Stmt::Attributed { + attributes, + item: Box::new(with_releases(*item, pending)), + }, + Stmt::Block { statements } => Stmt::Block { + statements: map_releases(statements, pending), + }, + Stmt::If { + condition, + then_stmt, + else_stmt, + } => Stmt::If { + condition, + then_stmt: Box::new(with_releases(*then_stmt, pending)), + else_stmt: else_stmt.map(|other| Box::new(with_releases(*other, pending))), + }, + Stmt::While { condition, body } => Stmt::While { + condition, + body: Box::new(with_releases(*body, pending)), + }, + Stmt::For { + pattern, + iterable, + body, + } => Stmt::For { + pattern, + iterable, + body: Box::new(with_releases(*body, pending)), + }, + Stmt::Expr { value: expr, .. } => match *expr { + Expr::Try { + body, + catch_var, + handler, + } => Stmt::expr(Box::new(Expr::Try { + body: map_releases(body, pending), + catch_var, + handler: map_releases(handler, pending), + })), + other => Stmt::expr(Box::new(other)), + }, + // A nested function's `return` leaves *it*, not the enclosing function. + other => other, + } +} + +#[allow(clippy::vec_box, reason = "the AST stores a block's statements as `Vec>`")] +fn map_releases(stmts: Vec>, pending: &[Box]) -> Vec> { + stmts + .into_iter() + .map(|stmt| Box::new(with_releases(*stmt, pending))) + .collect() +} diff --git a/core/src/stmt/destructuring_test.rs b/core/src/stmt/destructuring_test.rs index b23bf34d..43c1e77e 100644 --- a/core/src/stmt/destructuring_test.rs +++ b/core/src/stmt/destructuring_test.rs @@ -276,4 +276,48 @@ mod tests { let display_str = format!("{}", stmt); assert!(display_str.contains("let {\"name\": name, \"age\": 0..=120} = {};")); } + + /// A binding takes *one* pattern. The parser used to slice out everything + /// before `=`, parse a prefix of it, and drop the rest without a word — so + /// `let mut s = 0;` bound `mut`, threw `s` away, and only failed later at + /// the use of `s`. Every leftover token must be an error at the binding. + #[test] + fn a_binding_pattern_cannot_leave_tokens_behind() { + fn parse_error(source: &str) -> String { + let tokens = Tokenizer::tokenize(source).expect("tokenize"); + let err = StmtParser::new(&tokens) + .parse_program() + .expect_err("trailing tokens after the pattern must not parse"); + err.to_string() + } + + for source in [ + "let a b = 1;", + "let x [1] = 5;", + "const k junk = 7;", + "let [x] y = [1];", + ] { + let message = parse_error(source); + assert!( + message.contains("after the pattern"), + "{source} should report the leftover tokens, said: {message}" + ); + } + + // `mut` lexes as an ordinary identifier, so this is the shape a Rust + // habit produces. It gets its own message instead of the generic one. + let message = parse_error("let mut s = 0;"); + assert!( + message.contains("`mut` is not a binding modifier"), + "`let mut` should name the actual problem, said: {message}" + ); + + // Valid patterns are untouched. + for source in ["let a = 1;", "let [x, ..r] = [1, 2];", "let {\"k\": v} = {};"] { + let tokens = Tokenizer::tokenize(source).expect("tokenize"); + StmtParser::new(&tokens) + .parse_program() + .unwrap_or_else(|e| panic!("{source} should still parse, said: {e}")); + } + } } diff --git a/core/src/stmt/function_test.rs b/core/src/stmt/function_test.rs index 590e436e..f1a888e6 100644 --- a/core/src/stmt/function_test.rs +++ b/core/src/stmt/function_test.rs @@ -628,4 +628,46 @@ mod tests { assert!(stmt.type_check(&mut checker).is_ok()); Ok(()) } + + /// A Rust-shaped function type is rejected with the rule, from every + /// position a type can appear in. + /// + /// `fn(Int) -> Int` is what somebody writes first in a language where `fn` + /// introduces every declaration, and before this it produced two different + /// unhelpful reports depending on which of the five type collectors ran: + /// `Invalid type: Fn ( Int) -> Int` (a spelling the program does not + /// contain) for a parameter, and `Expected type annotation` for a `let`. + /// Neither said `(Int) -> Int`. + /// + /// The position matters as much as the text: the collector stops at + /// whatever *ended* the annotation, so the report used to point at the `,` + /// or the `)`. + #[test] + fn a_rust_shaped_function_type_is_rejected_with_the_rule() { + for source in [ + "fn a(f: fn(Int) -> Int) -> Int { return f(1); }", + "fn a(f: fn(Int) -> Int, x: Int) -> Int { return f(x); }", + "let g: fn(Int) -> Int = |x| x;", + "struct S { f: fn(Int) -> Int }", + "fn mk() -> fn(Int) -> Int { return |x| x; }", + "type T = fn(Int) -> Int;", + ] { + let tokens = Tokenizer::tokenize(source).expect("tokens"); + let mut parser = StmtParser::new(&tokens); + let error = parser + .parse_statement() + .expect_err(&alloc::format!("`{source}` must not parse")) + .to_string(); + assert!( + error.contains("`(Int) -> Int`, not `fn(Int) -> Int`"), + "`{source}` reported `{error}` instead of the spelling rule" + ); + // The token is named the way it was typed — `fn`, not the variant + // `Fn`, which is what every parser message used to print. + assert!( + error.contains("found `fn`"), + "`{source}` reported `{error}`, pointing at something other than the `fn`" + ); + } + } } diff --git a/core/src/stmt/init_order.rs b/core/src/stmt/init_order.rs new file mode 100644 index 00000000..eb86b0b9 --- /dev/null +++ b/core/src/stmt/init_order.rs @@ -0,0 +1,569 @@ +//! Reading a top-level binding before its initializer has run. +//! +//! The top level executes in order, so this answers `nil`: +//! +//! ```lk +//! fn f() -> Int { return LATER; } +//! println(f()); // nil — `f` runs before line 3 does +//! const LATER = 7; +//! ``` +//! +//! `typeof(f())` said `Nil` for a function declared `-> Int`, and whatever +//! touched the nil next reported *its* own complaint ("Add expected numbers, +//! got Nil and Int", "`len()` works on a String, List, …") — never the ordering. +//! Python raises `NameError` here and JavaScript raises out of the temporal dead +//! zone; answering nil is the worst of the three, and this language has a +//! checker to say so before anything runs. +//! +//! The direct case was already refused (`const B = A + 4;` above `const A = 1;` +//! — see `TypeChecker::pending_top_level`) and reading a later binding from +//! inside a function body is *ordinary*, because bodies run after the whole top +//! level. This is the third case: a top-level statement **calls** a function +//! that reaches one. +//! +//! # What it will not catch +//! +//! Deliberately one-sided — it reports only what it can prove, so every +//! approximation here loses cases rather than inventing them: +//! +//! - **Shadowing is subtracted wholesale.** A body's reads are every +//! `Expr::Var` in it minus every name it binds *anywhere*, at any depth. A +//! function with a local `LATER` therefore reports no read of the global one +//! — including in the parts where the local is not in scope. +//! - **Indirect calls are invisible.** Only a syntactic `f(…)` naming a +//! top-level `fn` joins the call graph; a function reached through a value +//! does not. +//! - **Closure and nested-`fn` bodies do not count as executed** at the point +//! they are written, only where they are called — which is the invisible case +//! above. +//! - **A branch that never runs still counts.** `if never { return LATER; }` +//! inside a called function is reported. Moving the binding up is always +//! available and always correct, so a false report costs one line. +//! +//! Both walks match their AST enums **exhaustively**, with no catch-all arm: a +//! new `Stmt` or `Expr` variant breaks the build here rather than silently +//! falling out of the analysis. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; + +use crate::compat::collections::{HashMap, HashSet}; +use crate::expr::{Expr, Pattern, TemplateStringPart}; +use crate::stmt::{ForPattern, Program, Stmt}; + +/// What a subtree reads, binds and calls. +#[derive(Debug, Default)] +struct Facts { + /// Every `Expr::Var` name, whether it names a local, a global or a function. + reads: HashSet, + /// Every name bound anywhere inside, at any depth (see the module doc: + /// subtracted wholesale). + binds: HashSet, + /// Every syntactic `name(…)` callee. + calls: HashSet, + /// Every method name a call spells, in walk order. + /// + /// A `Vec`, not a set: the compiler seeds a function's constant pool with + /// these before lowering the body, and the pool's order is part of the + /// artifact. Walk order is deterministic; a hash set's is not. + methods: Vec, +} + +impl Facts { + /// The reads that survive this subtree's own bindings. + fn free_reads(&self) -> HashSet { + self.reads.difference(&self.binds).cloned().collect() + } +} + +/// Whether the walk is inside something that runs *now*. +/// +/// A `fn` declaration and a closure literal are values at the point they are +/// written; their bodies run where they are called, which the caller side of +/// this analysis is what covers. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Depth { + /// Descend into everything, including declarations — used for a function + /// body, where all of it runs when the function is called. + Everything, + /// Skip the bodies of `fn` declarations and closure literals. + ExecutedNow, +} + +fn walk_stmt(stmt: &Stmt, depth: Depth, out: &mut Facts) { + match stmt { + Stmt::Attributed { attributes: _, item } => walk_stmt(item, depth, out), + // An import binds names, and none of them is a top-level `let`. + Stmt::Import(_) => {} + Stmt::If { + condition, + then_stmt, + else_stmt, + } => { + walk_expr(condition, depth, out); + walk_stmt(then_stmt, depth, out); + if let Some(else_stmt) = else_stmt { + walk_stmt(else_stmt, depth, out); + } + } + Stmt::IfLet { + pattern, + value, + then_stmt, + else_stmt, + } => { + collect_pattern(pattern, out); + walk_expr(value, depth, out); + walk_stmt(then_stmt, depth, out); + if let Some(else_stmt) = else_stmt { + walk_stmt(else_stmt, depth, out); + } + } + Stmt::While { condition, body } => { + walk_expr(condition, depth, out); + walk_stmt(body, depth, out); + } + Stmt::WhileLet { pattern, value, body } => { + collect_pattern(pattern, out); + walk_expr(value, depth, out); + walk_stmt(body, depth, out); + } + Stmt::For { + pattern, + iterable, + body, + } => { + collect_for_pattern(pattern, out); + walk_expr(iterable, depth, out); + walk_stmt(body, depth, out); + } + Stmt::Let { pattern, value, .. } => { + collect_pattern(pattern, out); + walk_expr(value, depth, out); + } + // The target of an assignment is not a *read* of it, but it is also not + // a binding: `x = 1` at the top level writes a binding declared + // elsewhere. Neither set gets it. + Stmt::Assign { name: _, value, .. } => walk_expr(value, depth, out), + Stmt::CompoundAssign { name, value, .. } => { + // `x += 1` reads `x` first. + out.reads.insert(name.clone()); + walk_expr(value, depth, out); + } + Stmt::Define { name, value, .. } => { + out.binds.insert(name.clone()); + walk_expr(value, depth, out); + } + Stmt::Defer { body, .. } => walk_stmt(body, depth, out), + Stmt::Break | Stmt::Continue | Stmt::Empty => {} + Stmt::Return { value } => { + if let Some(value) = value { + walk_expr(value, depth, out); + } + } + // Declarations bind a *type* name, not a value binding, and hold no + // expression that runs here. + Stmt::Struct { .. } | Stmt::TypeAlias { .. } | Stmt::Trait { .. } => {} + Stmt::Function { + name, + params, + named_params, + body, + .. + } => { + out.binds.insert(name.clone()); + for param in params { + out.binds.insert(param.clone()); + } + for param in named_params { + out.binds.insert(param.name.clone()); + if let Some(default) = ¶m.default { + walk_expr(default, depth, out); + } + } + if depth == Depth::Everything { + walk_stmt(body, depth, out); + } + } + // An impl block's methods run when dispatched, like a `fn`. + Stmt::Impl { methods, .. } => { + if depth == Depth::Everything { + for method in methods { + walk_stmt(method, depth, out); + } + } + } + Stmt::Expr { value, .. } => walk_expr(value, depth, out), + Stmt::Block { statements } => { + for stmt in statements { + walk_stmt(stmt, depth, out); + } + } + } +} + +fn walk_expr(expr: &Expr, depth: Depth, out: &mut Facts) { + match expr { + Expr::Var(name) => { + out.reads.insert(name.clone()); + } + Expr::Literal(_) => {} + Expr::Call(name, args) => { + out.calls.insert(name.clone()); + for arg in args { + walk_expr(arg, depth, out); + } + } + Expr::CallExpr(callee, args) => { + walk_callee(callee, depth, out); + for arg in args { + walk_expr(arg, depth, out); + } + } + Expr::CallNamed(callee, positional, named) => { + walk_callee(callee, depth, out); + for arg in positional { + walk_expr(arg, depth, out); + } + for (_, arg) in named { + walk_expr(arg, depth, out); + } + } + Expr::Bin(left, _, right) + | Expr::And(left, right) + | Expr::Or(left, right) + | Expr::NullishCoalescing(left, right) => { + walk_expr(left, depth, out); + walk_expr(right, depth, out); + } + // A field name is an expression node, and in `a.b` the `b` is a `Var` + // that names nothing — walking it would report a read of a top-level + // binding that happens to share the field's name. + Expr::Access(target, field) | Expr::OptionalAccess(target, field) => { + walk_expr(target, depth, out); + if !matches!(**field, Expr::Var(_)) { + walk_expr(field, depth, out); + } + } + Expr::Unary(_, inner) | Expr::Paren(inner) | Expr::Unsafe(inner) | Expr::Cast(inner, _) => { + walk_expr(inner, depth, out) + } + Expr::Conditional(cond, then_expr, else_expr) => { + walk_expr(cond, depth, out); + walk_expr(then_expr, depth, out); + walk_expr(else_expr, depth, out); + } + Expr::List(items) => { + for item in items { + walk_expr(item, depth, out); + } + } + Expr::Map(pairs) => { + for (key, value) in pairs { + walk_expr(key, depth, out); + walk_expr(value, depth, out); + } + } + Expr::StructLiteral { name: _, fields } => { + for (_, value) in fields { + walk_expr(value, depth, out); + } + } + Expr::Range { start, end, step, .. } => { + for part in [start, end, step].iter().copied().flatten() { + walk_expr(part, depth, out); + } + } + Expr::TemplateString(parts) => { + for part in parts { + match part { + TemplateStringPart::Literal(_) => {} + TemplateStringPart::Expr(expr) => walk_expr(expr, depth, out), + } + } + } + Expr::Closure { params, body, .. } => { + for param in params { + out.binds.insert(param.clone()); + } + if depth == Depth::Everything { + walk_expr(body, depth, out); + } + } + Expr::Match { value, arms } => { + walk_expr(value, depth, out); + for arm in arms { + collect_pattern(&arm.pattern, out); + walk_expr(&arm.body, depth, out); + } + } + Expr::Block(statements) => { + for stmt in statements { + walk_stmt(stmt, depth, out); + } + } + Expr::Try { + body, + catch_var, + handler, + } => { + out.binds.insert(catch_var.clone()); + for stmt in body { + walk_stmt(stmt, depth, out); + } + for stmt in handler { + walk_stmt(stmt, depth, out); + } + } + } +} + +/// The callee position of a call. +/// +/// `f(x)` reaches the parser as `CallExpr(Var("f"), …)`, not as `Call("f", …)` +/// — that spelling is for a call the parser could resolve to a name directly. +/// Walking the callee as an ordinary expression made every call a *read* of its +/// own name and recorded no call at all, which is why the whole analysis +/// answered nothing on `println(f())`. +fn walk_callee(callee: &Expr, depth: Depth, out: &mut Facts) { + if let Expr::Var(name) = callee { + out.calls.insert(name.clone()); + return; + } + // `a.m(…)` is `CallExpr(Access(a, m), …)`, and `m` is a string literal — + // that is what a member is. A bare `Var` is a bracket index (`a[m](…)`), + // which calls whatever the element holds and names no method. + if let Expr::Access(target, field) | Expr::OptionalAccess(target, field) = callee { + let name = match &**field { + Expr::Literal(value) => value.as_str().map(alloc::string::ToString::to_string), + _ => None, + }; + if let Some(name) = name { + if !out.methods.contains(&name) { + out.methods.push(name); + } + walk_expr(target, depth, out); + return; + } + } + walk_expr(callee, depth, out); +} + +/// Every method name a body's calls spell, in source order and deduplicated. +/// +/// The compiler seeds a function's constant pool with these before lowering it. +/// `CallMethodK` carries the name's constant index in **8 bits** (the `abc` +/// form is full: 7 opcode + 8 A + 1 K + 8 B + 8 C), so a name landing past 255 +/// falls back to a `__lk_call_method` helper call — which the native backend +/// cannot lower, taking the whole program with it. +/// +/// Measured before the seeding: 130 structs each with one method, called once +/// each from `main`, stopped lowering at the 129th — the struct names and field +/// names of the literals share the same per-function pool and pushed the method +/// names past the byte. Seeding first makes the bound what it reads like: 256 +/// distinct method names called from one function. +pub(crate) fn method_names_called(body: &Stmt) -> Vec { + let mut facts = Facts::default(); + walk_stmt(body, Depth::Everything, &mut facts); + facts.methods +} + +/// The same, for the top level — whose statements are the entry function's +/// body and are not wrapped in a `Stmt`. +/// +/// `Depth::ExecutedNow` so a `fn`'s own body is left to its own seeding: those +/// names belong in *that* function's pool, and crowding the entry's pool with +/// them is what this whole seeding is avoiding. +pub(crate) fn method_names_called_at_top_level(program: &Program) -> Vec { + let mut facts = Facts::default(); + for stmt in &program.statements { + walk_stmt(stmt, Depth::ExecutedNow, &mut facts); + } + facts.methods +} + +fn collect_for_pattern(pattern: &ForPattern, out: &mut Facts) { + match pattern { + ForPattern::Variable(name) => { + out.binds.insert(name.clone()); + } + ForPattern::Ignore => {} + ForPattern::Tuple(patterns) => { + for pattern in patterns { + collect_for_pattern(pattern, out); + } + } + ForPattern::Array { patterns, rest } => { + for pattern in patterns { + collect_for_pattern(pattern, out); + } + if let Some(rest) = rest { + out.binds.insert(rest.clone()); + } + } + ForPattern::Object(entries) => { + for (_, pattern) in entries { + collect_for_pattern(pattern, out); + } + } + } +} + +fn collect_pattern(pattern: &Pattern, out: &mut Facts) { + match pattern { + Pattern::Variable(name) => { + out.binds.insert(name.clone()); + } + Pattern::Wildcard | Pattern::Literal(_) => {} + Pattern::List { patterns, rest } => { + for pattern in patterns { + collect_pattern(pattern, out); + } + if let Some(rest) = rest { + out.binds.insert(rest.clone()); + } + } + Pattern::Map { patterns, rest } => { + for (_, pattern) in patterns { + collect_pattern(pattern, out); + } + if let Some(rest) = rest { + out.binds.insert(rest.clone()); + } + } + Pattern::Or(patterns) => { + for pattern in patterns { + collect_pattern(pattern, out); + } + } + Pattern::Guard { pattern, guard } => { + collect_pattern(pattern, out); + walk_expr(guard, Depth::ExecutedNow, out); + } + Pattern::Range { start, end, .. } => { + walk_expr(start, Depth::ExecutedNow, out); + walk_expr(end, Depth::ExecutedNow, out); + } + } +} + +/// Per top-level `fn` name: the names its body reads, closed over the functions +/// it calls. +pub(crate) struct InitOrder { + reads_of: HashMap>, +} + +fn unwrap_attributes(stmt: &Stmt) -> &Stmt { + match stmt { + Stmt::Attributed { item, .. } => unwrap_attributes(item), + other => other, + } +} + +impl InitOrder { + pub(crate) fn of(program: &Program) -> Self { + let mut direct: HashMap, HashSet)> = HashMap::new(); + for stmt in &program.statements { + let Stmt::Function { name, body, .. } = unwrap_attributes(stmt) else { + continue; + }; + let mut facts = Facts::default(); + walk_stmt(body, Depth::Everything, &mut facts); + // The function's own parameters are bound by the declaration, not + // by the body, so they are collected here rather than by the walk. + if let Stmt::Function { + params, named_params, .. + } = unwrap_attributes(stmt) + { + for param in params { + facts.binds.insert(param.clone()); + } + for param in named_params { + facts.binds.insert(param.name.clone()); + } + } + direct.insert(name.clone(), (facts.free_reads(), facts.calls)); + } + + // Transitive closure over the call graph: an explicit work stack, not + // recursion. O(V + E) amortized — the fixpoint loop this replaced was + // O(rounds × functions), where "rounds" is the depth of the call chain, + // so a 4000-`fn` program with a deep chain ran it 4000 times. + // + // Iterative for the reason `HeapStore::collect` is: the depth here is + // the program's call depth, which a generated file can make as large as + // it likes, and putting it on the Rust stack turns that into a process + // abort with no line to blame. + // + // A cycle contributes nothing on its back edge (`in_progress` below), + // so mutually recursive functions can lose a read the other one has. + // That is the same one-sided trade as the rest of this module — see the + // header — and it is the only place the answer depends on where the + // walk entered. + let mut reads_of: HashMap> = HashMap::new(); + let mut roots: Vec<&String> = direct.keys().collect(); + roots.sort(); + let mut in_progress: HashSet = HashSet::new(); + for root in roots { + let mut work: Vec<(String, bool)> = alloc::vec![(root.clone(), false)]; + while let Some((name, expanded)) = work.pop() { + if reads_of.contains_key(&name) { + continue; + } + let Some((own_reads, calls)) = direct.get(&name) else { + continue; + }; + if expanded { + let mut reads = own_reads.clone(); + for callee in calls { + if let Some(callee_reads) = reads_of.get(callee) { + reads.extend(callee_reads.iter().cloned()); + } + } + in_progress.remove(&name); + reads_of.insert(name, reads); + continue; + } + if !in_progress.insert(name.clone()) { + continue; + } + work.push((name, true)); + for callee in calls { + if !reads_of.contains_key(callee) && !in_progress.contains(callee) { + work.push((callee.clone(), false)); + } + } + } + in_progress.clear(); + } + + Self { reads_of } + } + + /// The binding a top-level statement would read before it is initialized, + /// and the function that reaches it — or `None` when nothing does. + /// + /// `pending` is the set of top-level bindings whose `let`/`const` has not + /// been reached yet. + pub(crate) fn premature_read(&self, stmt: &Stmt, pending: &HashSet) -> Option<(String, String)> { + if pending.is_empty() { + return None; + } + let mut facts = Facts::default(); + walk_stmt(stmt, Depth::ExecutedNow, &mut facts); + // Sorted so the message does not depend on hash order. + let mut callees: Vec<&String> = facts.calls.iter().collect(); + callees.sort(); + for callee in callees { + let Some(reads) = self.reads_of.get(callee) else { + continue; + }; + let mut hits: Vec<&String> = reads.intersection(pending).collect(); + hits.sort(); + if let Some(name) = hits.first() { + return Some(((*name).clone(), callee.clone())); + } + } + None + } +} diff --git a/core/src/stmt/stmt_impl/ast.rs b/core/src/stmt/stmt_impl/ast.rs index 968684c5..2dab9b37 100644 --- a/core/src/stmt/stmt_impl/ast.rs +++ b/core/src/stmt/stmt_impl/ast.rs @@ -16,38 +16,67 @@ pub struct Attribute { pub span: Option, } -/// For 循环的模式匹配 (类似 Rust 的 Pattern) +/// A `for` loop's binding pattern. #[derive(Debug, Clone, PartialEq)] pub enum ForPattern { - /// 简单变量绑定:for x in iter + /// `for x in iter` Variable(String), - /// 忽略模式:for _ in iter + /// `for _ in iter` Ignore, - /// 元组解构:for (a, b, c) in iter + /// `for (a, b, c) in iter` Tuple(Vec), - /// 数组解构:for [a, b] in iter + /// `for [a, b] in iter` Array { patterns: Vec, rest: Option, // for [a, b, ..rest] or [a, b, ..] }, - /// 对象解构:for {"k1": v1, "k2": v2} in iter - /// 仅支持字符串字面量作为键,值位置可以是变量或更深的模式(递归支持) + /// `for {"k1": v1, "k2": v2} in iter` — string-literal keys only; a value + /// position takes a name or a deeper pattern. Object(Vec<(String, ForPattern)>), } -/// 具名参数声明(用于函数定义) +/// The first name a `for` pattern binds twice, if any. +/// +/// The `for` twin of [`crate::expr::duplicate_binding`] — a separate walk only +/// because the loop header has its own pattern type. +pub(crate) fn duplicate_for_binding(pattern: &ForPattern) -> Option { + fn note(name: &str, seen: &mut Vec) -> Option { + if seen.iter().any(|s| s == name) { + return Some(name.to_string()); + } + seen.push(name.to_string()); + None + } + + fn walk(pattern: &ForPattern, seen: &mut Vec) -> Option { + match pattern { + ForPattern::Variable(name) => note(name, seen), + ForPattern::Ignore => None, + ForPattern::Tuple(patterns) => patterns.iter().find_map(|p| walk(p, seen)), + ForPattern::Array { patterns, rest } => patterns + .iter() + .find_map(|p| walk(p, seen)) + .or_else(|| rest.as_ref().and_then(|r| note(r, seen))), + ForPattern::Object(entries) => entries.iter().find_map(|(_k, p)| walk(p, seen)), + } + } + + walk(pattern, &mut Vec::new()) +} + +/// A named parameter, as a function declares it. #[derive(Debug, Clone, PartialEq)] pub struct NamedParamDecl { pub name: String, - /// 可选类型注解(None 表示未注解,按 Any 处理) + /// `None` means unannotated, which is `Any`. pub type_annotation: Option, - /// 可选默认值表达式(仅在调用省略该具名参数时使用) + /// Used only when the call omits this parameter. pub default: Option, } -/// Statement AST 节点类型定义 +/// The statement AST. /// -/// 语法设计: +/// The syntax: /// program ::= statement* /// statement ::= import_stmt | if_stmt | while_stmt | let_stmt | assign_stmt | break_stmt | continue_stmt | return_stmt | fn_stmt | expr_stmt | block_stmt /// import_stmt ::= 'use' import_spec ';' @@ -84,7 +113,10 @@ pub enum Stmt { else_stmt: Option>, }, /// while (condition) body - While { condition: Box, body: Box }, + While { + condition: Box, + body: Box, + }, /// while let pattern = expression { body } WhileLet { pattern: Pattern, @@ -105,34 +137,68 @@ pub enum Stmt { span: Option, is_const: bool, }, - /// name = value; (赋值语句) + /// `name = value;` Assign { name: String, value: Box, span: Option, }, - /// name op= value; (复合赋值语句, 如 x += 5) + /// `name op= value;` — `x += 5` and the rest. CompoundAssign { name: String, op: BinOp, value: Box, span: Option, }, - /// name = value; (变量定义,类似 Go 的短声明) - Define { name: String, value: Box }, + /// `name := value;` — Go's short declaration. + /// + /// The same binding `let name = value` makes — both lower through + /// `lower_define` — so it carries a span for the same reasons `Let` does: + /// to place its type error, and to place the type hint an editor writes + /// where the annotation would have gone. + Define { + name: String, + value: Box, + span: Option, + }, /// break; + /// `defer ` — run it when the function *returns*, on every + /// return path, in reverse order. + /// + /// **Not** when a raise unwinds past it. This comment used to say "whichever + /// way", which the rewrite below cannot deliver and which + /// [`crate::stmt::defer`] contradicts in the same words two files away — see + /// there for the two measured attempts at the raise path and why each was + /// reverted. + /// + /// Gone by the time anything but the parser sees it: a pass rewrites each + /// function's body so the deferred statements appear before every `return` + /// and at the end, in reverse order. That keeps it out of the type checker, + /// the resolver, both compilers and both backends — a release that has to + /// happen on every path is a *shape*, not a runtime mechanism, and the one + /// thing worse than not having it would be having it in one backend. + Defer { + body: Box, + span: Option, + }, + Break, /// continue; Continue, /// return [expression]; - Return { value: Option> }, + Return { + value: Option>, + }, /// struct Name { field: Type, ... } Struct { name: String, fields: Vec<(String, Option)>, }, /// type Alias = ExistingType; - TypeAlias { name: String, target: Type }, + TypeAlias { + name: String, + target: Type, + }, /// fn name(param1[: type], ...) [-> type] { body } Function { name: String, @@ -150,39 +216,60 @@ pub enum Stmt { name: String, /// Method signatures indexed by method name methods: Vec<(String, Type)>, + /// Methods the trait wrote a *body* for, as the `Stmt::Function` an + /// `impl` block would have held. + /// + /// A type that implements the trait and does not write the method gets + /// this one, copied in by `stmt::trait_defaults` — so dispatch, the + /// type checker and the AOT lowering never learn that defaults exist. + /// Storing the whole function is what makes the copy exact: a signature + /// alone loses the parameter *names* the body reads. + default_methods: Vec, }, /// impl Trait for Type { fn method(...) { body } } Impl { - trait_name: String, + /// `None` for an inherent `impl Type { … }` — methods that belong to + /// the type itself rather than to a trait it satisfies. + trait_name: Option, target_type: Type, /// Methods implemented in this block (as function statements) methods: Vec, }, /// expression; - Expr(Box), - /// `try { body } catch name { handler }` /// - /// A real statement rather than parse-time sugar. It used to be rewritten in - /// the parser into `let [ok, e] = try$call(|| { body }); if !ok { handler }`, - /// so every later stage — name resolution, the type checker, both back ends — - /// saw a closure and a destructuring `let` instead of a protected region. - /// Three separate wrong answers came out of that shape: a `return` inside - /// the body returned from the *closure*, an assignment to an annotated local - /// inside the body lost its type, and a top-level body writing an outer - /// local failed at runtime in the cell-capture machinery. - Try { - body: Vec>, - /// The name the handler binds the caught error to. - catch_var: String, - handler: Vec>, + /// Carries a span for the same reason `Let` does, and for one more: this is + /// where a bare call statement lives, so it is where argument type errors + /// are raised. It was the one statement variant with no position at all, and + /// `TypeError::span` is filled by the enclosing statement on the way out — + /// so `f("x");` reported "Argument 1 has the wrong type (expected Int, got + /// String) at `x`" and nothing else. In a four-thousand-line program that is + /// not a diagnostic; the same mistake in a `let` said `1:1-6`. + Expr { + value: Box, + span: Option, }, /// { statements } - Block { statements: Vec> }, - /// 空语句 (用于处理解析时的占位) + Block { + statements: Vec>, + }, + /// A placeholder the parser emits where a statement was expected. Empty, } -/// 程序结构 - 包含语句列表 +impl Stmt { + /// A bare expression statement whose position is not known yet. + /// + /// Most construction sites are desugarings and tests, which have no source + /// text to point at; the parser fills the span in where the statement really + /// was written. Having the constructor keeps those sites from each having to + /// spell `span: None`, and keeps the field from drifting back to "there is + /// no position here" by default in the one place that does have one. + pub fn expr(value: Box) -> Self { + Self::Expr { value, span: None } + } +} + +/// A program: its statements. #[derive(Debug, Clone, PartialEq)] pub struct Program { pub statements: Vec>, diff --git a/core/src/stmt/stmt_impl/display.rs b/core/src/stmt/stmt_impl/display.rs index 76baae72..07a57766 100644 --- a/core/src/stmt/stmt_impl/display.rs +++ b/core/src/stmt/stmt_impl/display.rs @@ -12,6 +12,7 @@ use core::fmt::{self, Display}; impl Display for Stmt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Stmt::Defer { body, .. } => write!(f, "defer {body}"), Stmt::Attributed { attributes, item } => { for attr in attributes { writeln!(f, "#[{}]", format_attribute_tokens(&attr.tokens))?; @@ -90,7 +91,7 @@ impl Display for Stmt { }; write!(f, "{} {} {};", name, op_str, value) } - Stmt::Define { name, value } => { + Stmt::Define { name, value, .. } => { write!(f, "{} = {};", name, value) } Stmt::Break => { @@ -124,7 +125,7 @@ impl Display for Stmt { Stmt::TypeAlias { name, target } => { write!(f, "type {} = {};", name, target.display()) } - Stmt::Trait { name, methods } => { + Stmt::Trait { name, methods, .. } => { write!(f, "trait {} {{", name)?; for (i, (m, ty)) in methods.iter().enumerate() { if i > 0 { @@ -139,7 +140,10 @@ impl Display for Stmt { target_type, methods, } => { - write!(f, "impl {} for {} {{", trait_name, target_type.display())?; + match trait_name { + Some(trait_name) => write!(f, "impl {} for {} {{", trait_name, target_type.display())?, + None => write!(f, "impl {} {{", target_type.display())?, + } for m in methods { if let Stmt::Function { name, @@ -206,7 +210,7 @@ impl Display for Stmt { write!(f, "fn {}({}) {{ {} }}", name, parts.join(", "), body_summary) } } - Stmt::Expr(expr) => { + Stmt::Expr { value: expr, .. } => { write!(f, "{};", expr) } Stmt::Block { statements } => { @@ -216,21 +220,6 @@ impl Display for Stmt { } write!(f, "}}") } - Stmt::Try { - body, - catch_var, - handler, - } => { - writeln!(f, "try {{")?; - for stmt in body { - writeln!(f, " {}", stmt)?; - } - writeln!(f, "}} catch {catch_var} {{")?; - for stmt in handler { - writeln!(f, " {}", stmt)?; - } - write!(f, "}}") - } Stmt::Empty => { write!(f, ";") } diff --git a/core/src/stmt/stmt_impl/flow.rs b/core/src/stmt/stmt_impl/flow.rs new file mode 100644 index 00000000..96b4ff24 --- /dev/null +++ b/core/src/stmt/stmt_impl/flow.rs @@ -0,0 +1,143 @@ +//! "Can control reach the end of this body?" +//! +//! A function that declares `-> Int` and has a path with no `return` on it +//! answers `nil` for that path — a value of a type the declaration ruled out, +//! with no diagnostic anywhere: +//! +//! ```lk +//! fn g(c: Bool) -> Int { if c { return 1; } } +//! g(false) + 1 // Add expected numbers or strings, got Nil and Int +//! ``` +//! +//! The runtime error names the operator, three call frames from the function +//! that promised an `Int`. This module is what lets the checker say it at the +//! declaration instead. +//! +//! **Only provable divergence counts.** The answer is used to *reject*, so a +//! construct this cannot analyse must answer `false` for "diverges" — the +//! program then needs an explicit `return`, which is a false alarm — or `true` +//! — and the hole stays. Between those, the second is the conservative choice +//! for a language with existing programs, so anything not listed here is +//! treated as "may fall through" only when that cannot produce a false alarm; +//! see the `Expr` arms, which are deliberately generous. + +use crate::expr::{Expr, MatchArm, Pattern}; +use crate::stmt::Stmt; + +/// Does every path through `stmt` leave the function (via `return`, or a raise +/// that cannot be caught here)? +pub(crate) fn always_diverges(stmt: &Stmt) -> bool { + match stmt { + Stmt::Return { .. } => true, + Stmt::Attributed { item, .. } => always_diverges(item), + Stmt::Block { statements, .. } => statements.iter().any(|stmt| always_diverges(stmt)), + // Both arms, or nothing: an `if` with no `else` always has the path + // where the condition was false. + Stmt::If { + then_stmt, + else_stmt: Some(else_stmt), + .. + } + | Stmt::IfLet { + then_stmt, + else_stmt: Some(else_stmt), + .. + } => always_diverges(then_stmt) && always_diverges(else_stmt), + // `while true { … }` with no `break` never falls out of the loop. The + // condition has already been constant-folded by the parser, so this is + // the literal `true` a reader wrote. + Stmt::While { condition, body } => is_true_literal(condition) && !contains_break(body), + Stmt::Expr { value, .. } => expr_always_diverges(value), + _ => false, + } +} + +fn is_true_literal(expr: &Expr) -> bool { + matches!(expr, Expr::Literal(crate::val::LiteralVal::Bool(true))) +} + +/// Does `expr` — as a *statement* — leave the function on every path? +/// +/// Generous on purpose: an expression this does not recognise answers `false`, +/// which only ever means "the enclosing body needs an explicit `return`". +fn expr_always_diverges(expr: &Expr) -> bool { + match expr { + // `error(v)` / `panic(v)` raise, and a raise leaves the function unless + // a `try` in *this* body catches it — which the `Try` arm below + // accounts for. + // + // Both spellings: the parser produces `CallExpr(Var("error"), …)` for a + // bare name, and `Call` for the desugarings that build one directly. + // Matching only `Call` made `fn f() -> Int { error("x"); }` a false + // alarm — which is how a new check earns its reputation. + Expr::Call(name, _) => is_raising_builtin(name), + Expr::CallExpr(callee, _) => matches!(callee.as_ref(), Expr::Var(name) if is_raising_builtin(name)), + Expr::Paren(inner) | Expr::Unsafe(inner) => expr_always_diverges(inner), + Expr::Block(statements) => statements.iter().any(|stmt| always_diverges(stmt)), + Expr::Conditional(_, then_expr, else_expr) => { + expr_always_diverges(then_expr) && expr_always_diverges(else_expr) + } + // Every arm diverges *and* the arms cover everything. Without a + // catch-all the value may match none of them, and that path falls + // through — `match`'s own exhaustiveness is checked elsewhere and does + // not extend to "and therefore every path returned". + Expr::Match { arms, .. } => arms.iter().any(|arm| is_catch_all(&arm.pattern)) && arms.iter().all(arm_diverges), + // A `try` whose *handler* diverges: the body may or may not raise, so + // the handler is the path that has to leave, and so does the body. + Expr::Try { body, handler, .. } => { + body.iter().any(|stmt| always_diverges(stmt)) && handler.iter().any(|stmt| always_diverges(stmt)) + } + _ => false, + } +} + +fn is_raising_builtin(name: &str) -> bool { + matches!(name, "error" | "panic") +} + +fn arm_diverges(arm: &MatchArm) -> bool { + expr_always_diverges(&arm.body) +} + +fn is_catch_all(pattern: &Pattern) -> bool { + match pattern { + Pattern::Wildcard => true, + // A bare binding matches anything; a guarded one does not. + Pattern::Variable(_) => true, + _ => false, + } +} + +/// A `break` that would leave *this* loop — nested loops swallow their own. +fn contains_break(stmt: &Stmt) -> bool { + match stmt { + Stmt::Break => true, + Stmt::Attributed { item, .. } => contains_break(item), + Stmt::Block { statements, .. } => statements.iter().any(|stmt| contains_break(stmt)), + Stmt::If { + then_stmt, else_stmt, .. + } + | Stmt::IfLet { + then_stmt, else_stmt, .. + } => contains_break(then_stmt) || else_stmt.as_deref().is_some_and(contains_break), + // A `break` inside a nested loop belongs to that loop. + Stmt::While { .. } | Stmt::WhileLet { .. } | Stmt::For { .. } => false, + // An expression can hold a block (`match` arms, `if` values), and a + // `break` in one of those does leave this loop. + Stmt::Expr { value, .. } => expr_contains_break(value), + _ => false, + } +} + +fn expr_contains_break(expr: &Expr) -> bool { + match expr { + Expr::Paren(inner) | Expr::Unsafe(inner) => expr_contains_break(inner), + Expr::Block(statements) => statements.iter().any(|stmt| contains_break(stmt)), + Expr::Conditional(_, then_expr, else_expr) => expr_contains_break(then_expr) || expr_contains_break(else_expr), + Expr::Match { arms, .. } => arms.iter().any(|arm| expr_contains_break(&arm.body)), + Expr::Try { body, handler, .. } => { + body.iter().any(|stmt| contains_break(stmt)) || handler.iter().any(|stmt| contains_break(stmt)) + } + _ => false, + } +} diff --git a/core/src/stmt/stmt_impl/mod.rs b/core/src/stmt/stmt_impl/mod.rs index 0b2b0d4e..e42dacc4 100644 --- a/core/src/stmt/stmt_impl/mod.rs +++ b/core/src/stmt/stmt_impl/mod.rs @@ -7,6 +7,7 @@ mod ast; mod display; +mod flow; mod type_check; pub use ast::{Attribute, ForPattern, NamedParamDecl, Program, Stmt}; diff --git a/core/src/stmt/stmt_impl/type_check.rs b/core/src/stmt/stmt_impl/type_check.rs index ac199fa1..1adea963 100644 --- a/core/src/stmt/stmt_impl/type_check.rs +++ b/core/src/stmt/stmt_impl/type_check.rs @@ -5,8 +5,8 @@ use crate::{ expr::Pattern, token::ParseError, typ::{ - FunctionSig, NamedParamSig, PendingStrictFunction, PendingStrictParam, StructDef, TraitDef, - TypeAlias as AliasDef, TypeChecker, + FunctionSig, NamedParamSig, PendingStrictFunction, PendingStrictParam, StructDef, TraitDef, TraitImpl, + TypeAlias as AliasDef, TypeChecker, union_of, }, val::{FunctionNamedParamType, Type}, }; @@ -14,10 +14,48 @@ use anyhow::{Result, anyhow}; use hashbrown::HashMap; impl Stmt { - /// 静态类型检查语句 + /// Type-checks a statement. pub fn type_check(&self, type_checker: &mut TypeChecker) -> Result<()> { + let result = self.type_check_inner(type_checker); + let Err(error) = result else { + return Ok(()); + }; + // Give the error this statement's position, if it does not have one. + // An expression has no position of its own, so without this the only + // way to place the error is to hunt the token stream for a token that + // looks like the offending expression — which finds the first such + // token in the file rather than this one. + let Some(span) = self.span() else { + return Err(error); + }; + Err(match error.downcast::() { + Ok(mut type_error) => { + type_error.attach_span(&span); + anyhow!(type_error) + } + Err(error) => error, + }) + } + + /// This statement's own position, for the statements that carry one. + fn span(&self) -> Option { + match self { + Stmt::Let { span, .. } + | Stmt::Assign { span, .. } + | Stmt::CompoundAssign { span, .. } + | Stmt::Define { span, .. } + // The variant a bare call statement is, and therefore the one every + // argument type error is raised under. It carried no span, so those + // errors carried no position — the same mistake written as a `let` + // said `1:1-6`, and written as a call said nothing. + | Stmt::Expr { span, .. } => span.clone(), + _ => None, + } + } + + fn type_check_inner(&self, type_checker: &mut TypeChecker) -> Result<()> { match self { - Stmt::Attributed { item, .. } => item.type_check(type_checker), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => item.type_check(type_checker), Stmt::TypeAlias { name, target } => { type_checker.registry_mut().register_type_alias(AliasDef { name: name.clone(), @@ -26,12 +64,20 @@ impl Stmt { Ok(()) } Stmt::Struct { name, fields } => { + // The declaration's own binder: two fields of one name leave a + // field nothing can ever address, and the literal that fills + // "both" satisfies the requirement once. + if let Some(dup) = first_repeat(fields.iter().map(|(k, _)| k.as_str())) { + return Err(anyhow!(format!("struct '{name}' declares the field '{dup}' twice"))); + } // Register struct in registry for subsequent checks let mut fm = HashMap::new(); let mut missing: Vec = Vec::new(); for (k, ty_opt) in fields.iter() { match ty_opt { Some(ty) => { + type_checker + .check_type_annotation(ty, &alloc::format!("field '{k}' of struct '{name}'"))?; fm.insert(k.clone(), ty.clone()); } None => { @@ -56,10 +102,15 @@ impl Stmt { type_checker.registry_mut().register_struct(sd); Ok(()) } - Stmt::Trait { name, methods } => { + Stmt::Trait { name, methods, .. } => { // Register trait with method signatures let mut map = HashMap::with_capacity(methods.len()); for (m, ty) in methods.iter() { + // A trait's method signatures are annotations like any + // other, and were the one kind nothing checked: a trait + // could promise a type that does not exist, and every impl + // of it would then be measured against nothing. + type_checker.check_type_annotation(ty, &alloc::format!("method '{m}' of trait '{name}'"))?; map.insert(m.clone(), ty.clone()); } let def = TraitDef { @@ -70,10 +121,97 @@ impl Stmt { Ok(()) } Stmt::Impl { - trait_name: _, + trait_name, target_type, methods, } => { + // The *target* was unchecked while the trait name was checked + // and the method bodies were checked: `impl Show for + // Nonexistent { … }` registered methods on a type nothing + // declares, so they could never be reached and nothing said so. + type_checker.check_type_annotation(target_type, "the impl target")?; + // A builtin container dispatches with its element type erased — + // a `TypedList::Mixed` has nothing else to report — so + // `impl T for List` names something the runtime cannot + // tell from `List`. It used to register under a key + // nothing looks up, and the call failed later with "List has no + // method", which is true and unhelpful. + let resolved_target = type_checker.resolve_aliases(target_type); + let erased = crate::typ::TypeChecker::dispatch_type(&resolved_target); + if erased != resolved_target { + let bare = match erased { + crate::val::Type::List(_) => "List", + crate::val::Type::Map(_, _) => "Map", + crate::val::Type::Set(_) => "Set", + _ => "the bare type", + }; + return Err(anyhow::anyhow!( + "Type Error: an impl target cannot name an element type: `{}` is not \ + distinguishable from another element type at run time — write `{bare}`", + resolved_target.display() + )); + } + // Every method the trait declares has to be here. The check + // existed (`TypeRegistry::validate_trait_impl`) and only ran at + // *run* time, when the VM registers impls — so `lk check`, the + // pre-flight command, passed a program that could not run and + // said nothing. Trait defaults are already copied in by + // `stmt::trait_defaults`, so "present" is the whole question. + if let Some(trait_name) = trait_name { + // An impl of a trait that does not exist. The target type + // half is checked just above; this half used to slip + // through a `let Some(…)` that simply skipped the whole + // conformance check, so the program failed at run time + // with "Trait 'X' not found" and `lk check` said nothing. + let Some(trait_def) = type_checker.registry().get_trait(trait_name).cloned() else { + return Err(anyhow!(format!( + "Trait '{trait_name}' not found — an impl names the trait it implements, and \ + a type's own methods are written as `impl {} {{ … }}` with no trait", + target_type.display() + ))); + }; + let declared: Vec<(String, crate::val::Type)> = trait_def + .methods + .iter() + .map(|(name, ty)| (name.clone(), ty.clone())) + .collect(); + for (required, expected) in declared { + let implemented = methods + .iter() + .map(item_of) + .find(|item| matches!(item, Stmt::Function { name, .. } if *name == required)); + let Some(implemented) = implemented else { + return Err(anyhow!(format!( + "Method '{required}' required by trait '{trait_name}' not implemented for type '{}'", + target_type.display() + ))); + }; + // Present is not the whole question: the same rule the + // VM applies when it registers the impl decides whether + // the signature can stand in for the trait's, and it + // used to run only there. + if let Some((_, actual)) = crate::typ::declared_signature::signature_of_stmt(implemented) { + crate::typ::trait_method_conformance(&required, trait_name, &expected, &actual)?; + } + } + // And nothing the trait did *not* declare. The rule existed + // (`TypeRegistry::validate_trait_impl`) and ran only when + // the VM registered the impl — so `lk check`, which is + // supposed to be the same check the executors run, passed a + // program that stopped on its first line. + for method in methods.iter().map(item_of) { + let Stmt::Function { name, .. } = method else { + continue; + }; + if !trait_def.methods.contains_key(name) { + let target = target_type.display(); + return Err(anyhow!(format!( + "Method '{name}' is not declared by trait '{trait_name}' — put it in \ + `impl {target} {{ … }}`, which is where a type's own methods go" + ))); + } + } + } let prev = type_checker.set_impl_self_type(Some(type_checker.resolve_aliases(target_type))); let result: Result<()> = methods.iter().try_for_each(|method| method.type_check(type_checker)); type_checker.set_impl_self_type(prev); @@ -86,15 +224,55 @@ impl Stmt { span, is_const, } => { - // 检查表达式的类型 - let expr_type = value.type_check(type_checker)?; + type_checker.reject_duplicate_bindings(pattern)?; + if let Some(annotation) = type_annotation { + type_checker.check_type_annotation(annotation, "this binding")?; + } + // + // A function-type annotation flows *into* a lambda instead of + // being compared against it afterwards. Checked in isolation a + // lambda types as `('T0) -> Any`, which does not unify with the + // annotation written for it — so a lambda could not be + // annotated at all, while a named `fn` assigned to the same + // binding was accepted. Same narrow bidirectionality as the + // machine-int literal rule below, for the same reason: the + // alternative is a feature nobody can use. + let expr_type = type_checker.check_expr_against(value, type_annotation.as_ref())?; + // A `let` at the top level may not take a name a *declaration* + // already binds. A `fn` or a type declaration is hoisted, so + // source order does not apply to it and "the `let` shadows it" + // has no coherent meaning — it showed as `fn pick() {…}` then + // `let pick = …;` resolving to the `let` in *either* order, + // silently. Two `fn`s of one name were already refused; this is + // the same collision, and it is the mistake that put a dead + // `fn apply` next to a live `let apply` in `closure.lk`. + // + // Inside a callable body it *is* ordinary shadowing: the local + // is order-sensitive within its scope and the declaration is + // outside it. + if !type_checker.inside_callable_body() { + for name in pattern_names(pattern) { + if let Some(kind) = type_checker.top_level_declaration_kind(&name) { + let error_msg = format!( + "`{name}` is already declared as a {kind} in this module: a {kind} is visible \ + before the line it is written on, so a `let` of the same name cannot shadow it — \ + rename one of them" + ); + return if let Some(span) = span { + Err(anyhow!(ParseError::with_span(error_msg, span.clone()))) + } else { + Err(anyhow!(error_msg)) + }; + } + } + } // Reached: statements below this one may read it. Done after // the value, so `const A = A + 1;` still reports the read. for name in pattern_names(pattern) { type_checker.define_top_level(&name); } - // 如果有类型注解,验证类型匹配 + // An annotation is a claim about the value; check it. // // A machine-int annotation *retypes* an integer literal rather // than rejecting it — `let x: u8 = 5` is the common case, and @@ -118,7 +296,7 @@ impl Stmt { }; } } else if let Some(expected_type) = type_annotation - && !type_checker.is_assignable(&expr_type, expected_type) + && !type_checker.value_fits(value, &expr_type, expected_type) { let error_msg = format!( "Type mismatch in let statement: pattern expected type {}, but expression has type {}", @@ -132,6 +310,19 @@ impl Stmt { }; } + // A `let` pattern is a *requirement*, unlike a `match` arm, + // which asks a question. Two of its ways to be impossible are + // decidable here, and both used to reach the runtime and raise + // `Pattern does not match value` — with `lk check` clean, though + // it is documented as the same check the executors run. + if let Some(reason) = impossible_let_pattern(pattern, &expr_type, type_checker) { + return if let Some(span) = span { + Err(anyhow!(ParseError::with_span(reason, span.clone()))) + } else { + Err(anyhow!(reason)) + }; + } + // The pattern is distributed over the value's type, so each name // gets *its own* element type. Binding the whole right-hand side // to every name (what this used to do) types `v` in @@ -139,13 +330,16 @@ impl Stmt { let bound_type = type_annotation.clone().unwrap_or(expr_type); bind_pattern_types(pattern, &bound_type, *is_const, type_checker); + if let Some(span) = span { + let names = pattern_names(pattern); + type_checker.record_bindings(span, type_annotation.is_some(), names.iter().map(String::as_str)); + } + Ok(()) } Stmt::Assign { name, value, span } => { - // 检查表达式的类型 let expr_type = value.type_check(type_checker)?; - // 获取变量的已声明类型 if let Some(var_type) = type_checker.get_local_type(name) { if type_checker.is_const_local(name) { let error_msg = format!("Cannot assign to const variable '{}'", name); @@ -158,10 +352,62 @@ impl Stmt { if matches!(var_type, Type::Variable(_)) { // Refine previously unknown binding with the inferred expression type. type_checker.add_local_type(name.clone(), expr_type.clone()); + } else if *var_type == Type::Nil && expr_type != Type::Nil && !expr_type.contains_variables() { + // A binding that started as `nil` and now holds something + // is *that*, made optional — it is not `Nil` any more. + // + // Leaving it `Nil` is what made `let caught = nil; …; + // caught == "kaboom"` compare a string against nil. The + // comparison did not fail only because the solver ends in + // a rule that accepts any two disagreeing concrete types; + // the type was wrong either way, and everything reading + // it downstream — a hint, a hover, a completion — read + // the wrong one. + let widened = Type::Optional(Box::new(expr_type.clone())); + type_checker.add_local_type(name.clone(), widened); + } else if expr_type == Type::Nil && !matches!(var_type, Type::Optional(_) | Type::Any) { + // The other direction: a typed binding assigned `nil` + // becomes optional rather than staying what it was. + let widened = Type::Optional(Box::new(var_type.clone())); + type_checker.add_local_type(name.clone(), widened); } else if expr_type.contains_variables() { // Expression has unresolved type variables; add constraint instead of failing. type_checker.add_constraint(expr_type, var_type.clone()); - } else if !type_checker.is_assignable(&expr_type, var_type) { + } else if var_type.contains_variables() { + // The same thing said the other way round, which was + // missing: a *binding* whose type is not yet resolved + // cannot reject an assignment either, because there is + // nothing settled to reject it against. + // + // Nothing hit it while `map.get` was typed `Any`. Once + // it started saying `Val?`, `let v = m.get(k); if v == + // nil { v = 0; }` — a map read followed by a default — + // reported a mismatch between `'T?` and `Int`. + type_checker.add_constraint(var_type.clone(), expr_type); + } else if let Type::MachineInt(kind) = var_type + && !matches!(expr_type, Type::MachineInt(_)) + && let Some(literal) = int_literal_value(value) + { + // A literal takes the variable's machine width, the same + // as `let x: u8 = 5` does one line earlier and as + // `x + 1` and `x > 1` do. + // + // This was the last of the four and it was found by + // converting a driver: `mask = 0xfffffffc;` on a `u32` + // was refused, which is the shape a register-mask + // variable has every time. + if !kind.accepts_literal(literal) { + let error_msg = alloc::format!( + "literal {literal} is out of range for {}", + Type::MachineInt(*kind).display() + ); + return if let Some(span) = span { + Err(anyhow!(ParseError::with_span(error_msg, span.clone()))) + } else { + Err(anyhow!(error_msg)) + }; + } + } else if !type_checker.value_fits(value, &expr_type, var_type) { let error_msg = format!( "Type mismatch in assignment: variable '{}' has type {}, but right-hand side has type {}", name, @@ -194,7 +440,8 @@ impl Stmt { Err(anyhow!(error_msg)) }; } - // 检查操作类型兼容性 (var_type op expr_type -> var_type). + // The compound form promises the variable keeps its type: + // `var_type op expr_type` has to answer `var_type`. // If either side is still inferred, keep the relationship as a constraint // so function-body compound assignments can refine unannotated params. if var_type.contains_variables() { @@ -233,6 +480,31 @@ impl Stmt { body, named_params, } => { + // A `fn` inside another callable is *parsed*, and then the + // compiler cannot find it: function indices are collected from + // top-level statements only, so `fn outer() { fn helper() {…} + // return helper(1); }` failed with "Compiler undefined function + // `helper`" — a construct the grammar accepts and the backend + // does not, reported in the backend's words. + // + // Refused here, in the language's words, with both ways to say + // it instead. Supporting it is a feature (a nested `fn` cannot + // capture, so it is a hoist plus a scoped name), not this. + if type_checker.inside_callable_body() { + return Err(anyhow!(format!( + "a function cannot be declared inside another: move `{name}` to the top level, \ + or bind a closure with `let {name} = |…| …;` if it needs the enclosing scope" + ))); + } + // A parameter list is a binder like any other: a repeated name + // means the argument passed for the first one can never be + // read, and every call site still has to pass it. + if let Some(dup) = first_repeat(params.iter().map(String::as_str)) { + return Err(anyhow!(format!( + "`{dup}` is declared twice in the parameters of `{name}` — the second one shadows \ + the first, so nothing can read the argument passed for it" + ))); + } type_checker.push_scope(); // A body runs after the whole top level, so it may read a // binding declared below it. @@ -248,6 +520,9 @@ impl Stmt { let impl_self_ty = type_checker.current_impl_self_type().cloned(); for (i, param) in params.iter().enumerate() { let annotated = param_types.get(i).cloned().flatten(); + if let Some(ref ann) = annotated { + type_checker.check_type_annotation(ann, &alloc::format!("parameter '{param}'"))?; + } let mut origin_flag = annotated.is_some(); let mut ty = if let Some(ref ann) = annotated { ann.clone() @@ -324,6 +599,9 @@ impl Stmt { }); } + if let Some(ret) = return_type { + type_checker.check_type_annotation(ret, &alloc::format!("the return type of '{name}'"))?; + } let (return_placeholder, return_was_annotated) = if let Some(ret) = return_type.clone() { (ret, true) } else { @@ -339,6 +617,7 @@ impl Stmt { type_checker.add_function_sig( name.clone(), FunctionSig { + origin: Default::default(), positional: positional_tys.clone(), named: named_sigs.clone(), return_type: Some(return_placeholder.clone()), @@ -354,7 +633,7 @@ impl Stmt { // Popped on both paths, like the closure case: propagating the // body's error through `?` before popping would leave a dead frame // on the stack for an enclosing function's returns to land in. - type_checker.push_return_frame(); + type_checker.push_return_frame(return_was_annotated.then(|| return_placeholder.clone())); let body_checked = body.type_check(type_checker); let collected_returns = type_checker.pop_return_frame(); body_checked?; @@ -392,8 +671,38 @@ impl Stmt { } } } + // Resolved before the solver sees them: a `type` alias is a + // second spelling, not a second type, and the solver has no + // registry to look it up in. `fn f(v: Int) -> U` with + // `type U = Int` failed with "Cannot unify U with Int" — + // aliases worked in a binding and in a parameter, and broke in + // exactly one position. + let declared_return = type_checker.resolve_aliases(&return_placeholder); for ty in &collected_returns { - type_checker.add_constraint(return_placeholder.clone(), ty.clone()); + let returned = type_checker.resolve_aliases(ty); + type_checker.add_constraint(declared_return.clone(), returned); + } + + // A declared return type is a promise about *every* path. A + // body that can reach its closing brace answers `nil` on that + // path, and the failure surfaces at the caller: `g(false) + 1` + // reported "Add expected numbers or strings, got Nil and Int", + // naming the operator rather than the function that promised an + // `Int`. + // + // Only annotations that exclude nil are checked — `-> Nil`, + // `-> Any` and `-> Int?` all admit the fall-through value, and + // an unannotated function's return type is *inferred* from what + // it returns, so there is no promise to break. + if return_was_annotated { + let declared = type_checker.resolve_aliases(&return_placeholder); + if !declared_admits_nil(&declared) && !super::flow::always_diverges(body) { + return Err(anyhow!(format!( + "function '{name}' can reach its end without returning, but declares `-> {}`: the path that falls through answers nil. Add a `return`, or declare `-> {}?`", + declared.display(), + declared.display() + ))); + } } type_checker.pop_scope(); @@ -437,6 +746,7 @@ impl Stmt { type_checker.add_function_sig( name.clone(), FunctionSig { + origin: Default::default(), positional: positional_tys, named: named_sigs, return_type: Some(inferred_return.clone()), @@ -523,6 +833,7 @@ impl Stmt { type_checker.add_function_sig( name.clone(), FunctionSig { + origin: Default::default(), positional: resolved_positional, named: resolved_named_sigs, return_type: Some(resolved_return), @@ -537,14 +848,12 @@ impl Stmt { then_stmt, else_stmt, } => { - condition.type_check(type_checker)?; + type_checker.check_condition(condition)?; - // then 分支 type_checker.push_scope(); then_stmt.type_check(type_checker)?; type_checker.pop_scope(); - // else 分支 if let Some(else_stmt) = else_stmt { type_checker.push_scope(); else_stmt.type_check(type_checker)?; @@ -559,22 +868,21 @@ impl Stmt { then_stmt, else_stmt, } => { - // 检查值表达式的类型 let value_type = value.type_check(type_checker)?; - // 为 then 分支创建新作用域,以便模式变量绑定 + // The pattern binds into the `then` arm only. type_checker.push_scope(); - // 根据模式与被匹配值类型,添加类型绑定,并校验模式兼容性 + // A repeated name is refused; a pattern the matched type + // cannot produce is not, because testing that is what this + // construct is for. + type_checker.reject_duplicate_bindings(pattern)?; type_checker.add_bindings_for_pattern(pattern, &value_type).ok(); - // 现在检查 then 分支 then_stmt.type_check(type_checker)?; - // 弹出作用域 type_checker.pop_scope(); - // 检查 else 分支(如果有) if let Some(else_stmt) = else_stmt { else_stmt.type_check(type_checker)?; } @@ -584,25 +892,24 @@ impl Stmt { Stmt::While { condition, body } => { condition.type_check(type_checker)?; - // 检查循环体 body.type_check(type_checker)?; Ok(()) } Stmt::WhileLet { pattern, value, body } => { - // 检查值表达式的类型 let value_type = value.type_check(type_checker)?; - // 为循环体创建新作用域,以便模式变量绑定 + // The pattern binds into the body only. type_checker.push_scope(); - // 根据模式与被匹配值类型,添加类型绑定,并校验模式兼容性 + // A repeated name is refused; a pattern the matched type + // cannot produce is not, because testing that is what this + // construct is for. + type_checker.reject_duplicate_bindings(pattern)?; type_checker.add_bindings_for_pattern(pattern, &value_type).ok(); - // 现在简化为检查循环体 body.type_check(type_checker)?; - // 弹出作用域 type_checker.pop_scope(); Ok(()) @@ -612,85 +919,100 @@ impl Stmt { iterable, body, } => { - // 检查可迭代表达式的类型 let iter_type = iterable.type_check(type_checker)?; - // 验证可迭代类型 - match iter_type { + match &iter_type { Type::List(_) | Type::String | Type::Map(_, _) | Type::Set(_) | Type::Any | Type::Variable(_) => { - // 这些类型都是可迭代的(Any和类型变量在运行时确定) + // `Any` and a type variable are decided at run time. } + // A window iterates by its own length and indices, on both + // back ends: `to_iter` hands the slice handle back as-is + // rather than materializing it. + Type::Generic { name, .. } if name == "Slice" => {} + // `Bytes` likewise — a sequence whose elements are `Int`. + Type::Named(name) if name == "Bytes" => {} + // A tuple is a list; `is_assignable_to` already says so. + Type::Tuple(_) => {} _ => { return Err(anyhow!(format!( - "For loop iterable must be List, String, Map, or Set, but got {}", + "For loop iterable must be List, String, Map, Set, Bytes, Slice or Tuple, but got {}", iter_type.display() ))); } } - // 为模式匹配创建新的作用域 type_checker.push_scope(); - // 根据模式添加变量类型 Self::add_pattern_types(pattern, &iter_type, type_checker)?; - // 检查循环体 body.type_check(type_checker)?; - // 弹出作用域 type_checker.pop_scope(); Ok(()) } - Stmt::Expr(expr) => { - // 表达式语句,只检查类型,不使用结果 + Stmt::Expr { value: expr, .. } => { + // An expression statement: checked, answer discarded. expr.type_check(type_checker)?; Ok(()) } Stmt::Block { statements } => { - // 为块语句创建新的作用域 type_checker.push_scope(); - // 检查块中的所有语句 for stmt in statements { stmt.type_check(type_checker)?; } - // 弹出作用域 type_checker.pop_scope(); Ok(()) } - Stmt::Try { - body, - catch_var, - handler, - } => { - // Straight-line scopes, which is the point of keeping this a - // statement: as `let [ok, e] = try$call(|| { body })` the checker - // saw a closure and a destructuring `let`, so an annotated local - // assigned inside the body came back out as a fresh type - // variable — `let r: Int = 0; try { r = x; } catch e {}` failed - // with "expected Int, got 'T2". - type_checker.push_scope(); - for stmt in body { - stmt.type_check(type_checker)?; - } - type_checker.pop_scope(); - - type_checker.push_scope(); - // The caught value is the message string for a plain raise and - // the raised value itself for `error(v)`, so the binding is as - // wide as the top type (see `vm::exec::handler`). - type_checker.add_local_type(catch_var.clone(), Type::Any); - for stmt in handler { - stmt.type_check(type_checker)?; + Stmt::Import(import) => { + // The one thing an import contributes to type checking: the + // *name* it binds. A standard library module bound to a name + // shadows whatever that name meant, and `chan` is also a + // callable global — see `is_imported_stdlib_module`. + match import { + crate::stmt::ImportStmt::Module { module } => { + type_checker.add_imported_stdlib_module(module.clone(), module.clone()); + } + crate::stmt::ImportStmt::ModuleAlias { alias, module } => { + type_checker.add_imported_stdlib_module(alias.clone(), module.clone()); + } + // `use { a, b } from m;` binds the *members* — but a + // member can itself be a module, and that one binds a + // module under a bare name. `use { json } from encoding;` + // then `json.encode(v)` type-checked and died at run time + // with "nil is not a function": `json` alone is not a + // declared module, so the member check skipped it, while + // `use encoding;` + `encoding.json.encode(v)` and + // `use math;` + `math.nope()` were both caught. The + // spelling the examples use was the one without the check. + crate::stmt::ImportStmt::Items { + items, + source: crate::stmt::ImportSource::Module(source), + } => { + for item in items { + let path = alloc::format!("{source}.{}", item.name); + if crate::typ::stdlib_module_is_declared(&path) { + let bound = item.alias.clone().unwrap_or_else(|| item.name.clone()); + type_checker.add_imported_stdlib_module(bound, path); + } + } + } + // `use * as e from encoding;` binds the whole module under + // one name, which is `use module as alias` written the other + // way round — and it had no member check either. + crate::stmt::ImportStmt::Namespace { + alias, + source: crate::stmt::ImportSource::Module(source), + } => { + type_checker.add_imported_stdlib_module(alias.clone(), source.clone()); + } + // A file import binds a namespace that `imported_members` + // already covers. + _ => {} } - type_checker.pop_scope(); - Ok(()) - } - Stmt::Import(_) => { - // Use 语句暂时不需要类型检查 Ok(()) } Stmt::Return { value } => { @@ -698,33 +1020,66 @@ impl Stmt { // the only point at which the returned expression's scope is still // live (see `TypeChecker::push_return_frame`). let ty = match value { - Some(expr) => expr.type_check(type_checker)?, + // Against the declaration, not merely compared with it + // afterwards: a lambda typed in isolation does not match the + // function type written for it, so + // `fn make() -> (Int) -> Int { return |x| … }` was rejected. + Some(expr) => { + let declared = type_checker.declared_return(); + let inferred = type_checker.check_expr_against(expr, declared.as_ref())?; + // `return [1];` for a declared `List` is the same + // written value as `let x: List = [1];`, and the + // literal rule is what makes both fine. The declaration + // is adopted here rather than widened at the join, so + // `pop_return_frame` stays a list of plain types. + match &declared { + Some(want) if type_checker.value_fits(expr, &inferred, want) => want.clone(), + _ => inferred, + } + } None => Type::Nil, }; type_checker.record_return(ty); Ok(()) } Stmt::Break | Stmt::Continue => { - // 控制流语句暂时不需要类型检查 + // `break` / `continue` carry no value to check. Ok(()) } - Stmt::Define { .. } | Stmt::Empty => { - // Define 语句和空语句暂时不需要类型检查 + Stmt::Define { name, value, span } => { + // `x := v` binds exactly what `let x = v` binds, and lowers + // through the same `lower_define`. Skipping it here meant the + // name had no type at all: every later read of it went through + // `check_identifier`'s last line and got a fresh type variable, + // so nothing downstream of a `:=` could be checked either. + let value_type = value.type_check(type_checker)?; + type_checker.define_top_level(name); + type_checker.add_local_type(name.clone(), value_type); + if let Some(span) = span { + type_checker.record_bindings(span, false, core::iter::once(name.as_str())); + } Ok(()) } + Stmt::Empty => Ok(()), } } - /// 为 for 循环模式添加类型信息 + /// Binds a `for` pattern's variables to the element type. fn add_pattern_types(pattern: &ForPattern, iter_type: &Type, type_checker: &mut TypeChecker) -> Result<()> { + if let Some(name) = crate::stmt::stmt_impl::ast::duplicate_for_binding(pattern) { + return Err(anyhow!(format!( + "`{name}` is bound twice by one loop pattern — the second binding shadows the first, \ + so a repeated name matches any element rather than an equal one" + ))); + } match pattern { ForPattern::Variable(name) => { - // 根据可迭代类型确定变量类型 let var_type = match iter_type { Type::List(inner) => (**inner).clone(), Type::String => Type::String, Type::Map(k, v) => { - // Map 迭代返回 [key, value] 对,使用 Tuple 表示 + // Iterating a map yields `[key, value]` pairs, spelled + // as a tuple here. Type::Tuple(vec![(**k).clone(), (**v).clone()]) } Type::Set(inner) => (**inner).clone(), @@ -733,7 +1088,7 @@ impl Stmt { type_checker.add_local_type(name.clone(), var_type); } ForPattern::Ignore => { - // 忽略模式,不需要添加类型 + // A wildcard binds nothing. } ForPattern::Tuple(patterns) => match iter_type { Type::List(inner_types) => { @@ -742,7 +1097,7 @@ impl Stmt { } } Type::Map(k, v) => { - // 直接迭代 Map:元素为 [key, value] + // Iterating a map directly: the element is `[key, value]`. for (i, pattern) in patterns.iter().enumerate() { let elem_ty = if i == 0 { (**k).clone() } else { (**v).clone() }; Self::add_pattern_types(pattern, &elem_ty, type_checker)?; @@ -752,7 +1107,6 @@ impl Stmt { }, ForPattern::Array { patterns, rest } => match iter_type { Type::List(inner_types) => { - // 为固定模式的每个部分添加类型 for pattern in patterns { Self::add_pattern_types(pattern, inner_types, type_checker)?; } @@ -761,24 +1115,25 @@ impl Stmt { } } Type::Map(k, v) => { - // 为 [k, v] 模式提供类型 for (i, pattern) in patterns.iter().enumerate() { let elem_ty = if i == 0 { (**k).clone() } else { (**v).clone() }; Self::add_pattern_types(pattern, &elem_ty, type_checker)?; } - // 数组解构下的 rest 在 Map 迭代语义中不太适用,忽略处理 + // A rest binding has no meaning against a two-element + // pair, so it binds nothing. } _ => {} }, ForPattern::Object(entries) => { - // 目前仅支持元素为 Map 的列表:List> - // 将每个绑定变量加入作用域,类型为 V(未知则 Any) + // Only `List>` is destructured this way: each bound + // name takes `V`, or `Any` when that is unknown. let value_ty = match iter_type { Type::List(inner) => match &**inner { Type::Map(_k, v) => Some((**v).clone()), _ => None, }, - // 直接迭代 Map 时 create_iterator 产生 [key,value] 对,不适配对象解构 + // Iterating a map yields `[key, value]` pairs, which object + // destructuring does not fit. _ => None, } .unwrap_or(Type::Any); @@ -789,7 +1144,8 @@ impl Stmt { type_checker.add_local_type(name.clone(), value_ty.clone()); } ForPattern::Ignore => {} - // 对于嵌套模式,保守地继续使用相同的 value_ty + // A nested pattern keeps the same `value_ty` rather + // than guessing a narrower one. other => { Self::add_pattern_types(other, &value_ty, type_checker)?; } @@ -801,6 +1157,22 @@ impl Stmt { } } +/// The first name a list of binders repeats. +/// +/// The list twin of [`crate::expr::duplicate_binding`], for the binders that +/// are a sequence of names rather than a pattern: a parameter list, a struct's +/// fields. +fn first_repeat<'a>(names: impl Iterator) -> Option { + let mut seen: Vec<&str> = Vec::new(); + for name in names { + if seen.contains(&name) { + return Some(name.to_string()); + } + seen.push(name); + } + None +} + impl Program { /// Registers every top-level function's signature before any body is /// checked. @@ -870,6 +1242,7 @@ impl Program { type_checker.add_function_sig( name.clone(), FunctionSig { + origin: Default::default(), positional, named, return_type: Some(returns), @@ -879,26 +1252,289 @@ impl Program { } } - /// 类型检查程序 + /// Register every `struct`, `trait` and `type` alias before the ordered + /// walk. + /// + /// Function signatures are hoisted (see + /// [`Self::predeclare_function_signatures`]) so calling one declared below + /// is ordinary. Type declarations were not, which no one noticed while an + /// undeclared name silently became `Type::Named` — the annotation checked + /// against nothing either way. The moment unknown names became an error, + /// `fn f() -> Point { … }` above `struct Point { … }` started failing. A + /// declaration's position in the file is not something a type should + /// depend on. + pub(crate) fn predeclare_type_declarations(&self, type_checker: &mut TypeChecker) { + for stmt in &self.statements { + match item_of(stmt) { + Stmt::Struct { name, fields } => { + let fields = fields + .iter() + .map(|(field, ty)| (field.clone(), ty.clone().unwrap_or(Type::Any))) + .collect(); + type_checker.registry_mut().register_struct(StructDef { + name: name.clone(), + fields, + }); + } + Stmt::Trait { name, methods, .. } => { + type_checker.registry_mut().register_trait(TraitDef { + name: name.clone(), + methods: methods.iter().cloned().collect(), + }); + } + Stmt::TypeAlias { name, target } => { + type_checker.registry_mut().register_type_alias(AliasDef { + name: name.clone(), + target_type: target.clone(), + }); + } + // Which types implement which trait, hoisted with the rest. + // + // The registry learned this only at *module load* + // (`VmContext::register_module_types`), which is after every + // type check — so `implements_trait` answered `false` for the + // whole checking pass and `fn render(v: Show)` accepted + // nothing. The impl's *conformance* was already checked in the + // ordered walk below; what was missing was the relation + // itself. + // + // No method indices: they are the compiler's, and this runs + // before compilation. The load-time registration replaces this + // entry (keyed by target type and trait name) with the real + // one, so dispatch is unaffected. + Stmt::Impl { + trait_name: Some(trait_name), + target_type, + .. + } => { + let target_type = type_checker.resolve_aliases(target_type); + type_checker.registry_mut().register_trait_impl(TraitImpl { + trait_name: trait_name.clone(), + target_type, + methods: HashMap::new(), + }); + } + _ => {} + } + } + } + + /// A trait declared twice, and a trait declaring one method twice. + /// + /// A program-level pass for the same reason the method-collision one is: + /// the question is about the *set* of declarations, and by the time the + /// ordered walk runs, `predeclare_type_declarations` has already + /// registered the first `trait T` — so the registry cannot tell a + /// duplicate from a declaration meeting itself. + /// + /// Both mistakes replace something in silence. A second `trait T` replaces + /// the first, and every impl written against the first is then measured + /// against a trait it never saw; a repeated method name inside one trait + /// leaves a signature no impl is measured against at all. Two top-level + /// `fn`s of one name were already refused, and so were two `struct`s. + fn check_trait_declarations(&self) -> Result<()> { + let mut seen: Vec<&str> = Vec::new(); + for stmt in &self.statements { + let Stmt::Trait { name, methods, .. } = item_of(stmt) else { + continue; + }; + if seen.contains(&name.as_str()) { + return Err(anyhow!(format!( + "trait '{name}' is declared twice — the second declaration would replace the first, \ + and every impl written against it" + ))); + } + seen.push(name); + if let Some(dup) = first_repeat(methods.iter().map(|(m, _)| m.as_str())) { + return Err(anyhow!(format!("trait '{name}' declares the method '{dup}' twice"))); + } + } + Ok(()) + } + + /// Every `impl` method name, checked for the two collisions the language + /// resolved silently by taking the last one. + /// + /// A program-level pass rather than something the ordered walk accumulates, + /// for the reason `collect_function_names` is one: the question is about the + /// *set* of declarations, and the checker's registry deliberately **replaces** + /// a re-registered `impl` (a REPL context is reused across runs), so it + /// cannot tell "declared twice here" from "seen again". + /// + /// Two mistakes, one namespace: + /// - the same method defined twice for one type — two `impl Show for P` + /// blocks each with a `show`, or two `impl P` blocks each with a `get`. + /// Two top-level `fn`s of one name were already refused. + /// - a method named like a *field*. `p.get(…)` cannot say which it means, + /// and which one it got depended on the argument count: `p.get()` read + /// the field (the method unreachable), while `p.f(3)` called the method + /// (the field's closure unreachable). + fn check_method_name_collisions(&self) -> Result<()> { + use crate::compat::collections::{HashMap, HashSet}; + + let mut fields_of: HashMap<&str, HashSet<&str>> = HashMap::new(); + for stmt in &self.statements { + if let Stmt::Struct { name, fields } = item_of(stmt) { + fields_of.insert(name.as_str(), fields.iter().map(|(field, _)| field.as_str()).collect()); + } + } + + let mut seen: HashSet<(String, &str)> = HashSet::new(); + for stmt in &self.statements { + let Stmt::Impl { + target_type, methods, .. + } = item_of(stmt) + else { + continue; + }; + let target = target_type.display(); + for method in methods { + let Stmt::Function { name, .. } = item_of(method) else { + continue; + }; + if !seen.insert((target.clone(), name.as_str())) { + return Err(anyhow!(format!( + "`{name}` is defined twice for `{target}`: two definitions of one method, \ + where only the last one could ever run — remove one" + ))); + } + if fields_of + .get(target.as_str()) + .is_some_and(|fields| fields.contains(name.as_str())) + { + return Err(anyhow!(format!( + "`{target}` already has a field named `{name}`, so `.{name}(…)` cannot say which \ + one it means — rename the method or the field" + ))); + } + } + } + Ok(()) + } + + /// Register every `impl` method's *stated* signature before the walk. + /// + /// `fn` and `struct` are hoisted (the two pre-passes above), so source + /// order does not apply to them. `impl` was not: a method reaches the + /// checker by being type-*checked*, which happens in statement order, so a + /// call above the `impl` block found nothing — + /// + /// ```lk + /// struct P { x: Int } + /// let p = P { x: 1 }; + /// println(p.m()); // `P has no method 'm'` + /// impl P { fn m(self) -> Int { return self.x; } } + /// ``` + /// + /// — while moving the `impl` two lines up made the same program check. The + /// same file's *imported* twin was already handled (`typ::imports`'s + /// `seed_impl_methods`), which is how the asymmetry stayed invisible: an + /// impl one `use` away worked and one three lines down did not. + /// + /// Read from the declaration, never inferred — an unannotated parameter is + /// `Any`, exactly as `predeclare_function_signatures` leaves it. The + /// ordered walk replaces each entry with the inferred signature when it + /// reaches the definition, so this only ever makes a method *visible* + /// earlier; it cannot tighten one. + fn predeclare_impl_method_signatures(&self, type_checker: &mut TypeChecker) { + for stmt in &self.statements { + let Stmt::Impl { + target_type, methods, .. + } = item_of(stmt) + else { + continue; + }; + let self_ty = type_checker.resolve_aliases(target_type); + for method in methods { + let Stmt::Function { name, .. } = item_of(method) else { + continue; + }; + let Some((_, function_type)) = crate::typ::declared_signature::signature_of_stmt(item_of(method)) + else { + continue; + }; + type_checker.add_method_sig(&self_ty, name, function_type); + } + } + } + + /// Type-checks a whole program. pub fn type_check(&self, type_checker: &mut TypeChecker) -> Result<()> { + self.check_trait_declarations()?; + self.check_method_name_collisions()?; + self.predeclare_type_declarations(type_checker); self.predeclare_function_signatures(type_checker); + // After the type declarations: an impl target may name an alias. + self.predeclare_impl_method_signatures(type_checker); type_checker.set_pending_top_level(self.top_level_binding_names()); - if type_checker.strict_any() { - let previous_defer = type_checker.begin_deferred_strict_function_checks(); - let result = (|| { - for stmt in &self.statements { - stmt.type_check(type_checker)?; - } - type_checker.finalize_deferred_strict_function_checks() - })(); - type_checker.restore_deferred_strict_function_checks(previous_defer); - return result; + + // Both modes defer: constraints are solved once, at the end, instead of + // at the end of every function. + // + // Only the strict one used to. The other solved the *global* constraint + // pool each time a function finished, so a later function's constraints + // met an earlier one's leftovers and which types were compared depended + // on how much of the file had been read — cutting one example at 40 + // lines failed, at 50 passed, at 60 failed again. The pool cannot be + // made per-function instead: inferring a parameter from a call site + // further down is a feature. + let previous_defer = type_checker.begin_deferred_strict_function_checks(); + let init_order = crate::stmt::init_order::InitOrder::of(self); + let result = (|| { + for stmt in &self.statements { + premature_read_error(&init_order, stmt, type_checker)?; + stmt.type_check(type_checker)?; + } + type_checker.finalize_deferred_strict_function_checks() + })(); + type_checker.restore_deferred_strict_function_checks(previous_defer); + result + } + + /// Type-check every statement, reporting all the errors instead of the first. + /// + /// `type_check` stops at the first failure, which is what a compiler wants: + /// the program is not going to run either way. A tool wants the opposite — + /// one mistyped line should not take the diagnostics for the other forty + /// with it, nor the types recorded for them (see + /// `TypeChecker::observe_bindings`). + pub fn type_check_collecting(&self, type_checker: &mut TypeChecker) -> Vec { + // Collected like any other, so the LSP reports it and keeps going. + let mut collision = Vec::new(); + if let Err(err) = self.check_trait_declarations() { + collision.push(err); + } + if let Err(err) = self.check_method_name_collisions() { + collision.push(err); } + self.predeclare_type_declarations(type_checker); + self.predeclare_function_signatures(type_checker); + type_checker.set_pending_top_level(self.top_level_binding_names()); + + // Deferred whether or not this is a strict run — see `Program::type_check`. + let previous_defer = type_checker.begin_deferred_strict_function_checks(); + let depth = type_checker.scope_depth(); + let mut errors = collision; + let init_order = crate::stmt::init_order::InitOrder::of(self); for stmt in &self.statements { - stmt.type_check(type_checker)?; + if let Err(err) = premature_read_error(&init_order, stmt, type_checker) { + errors.push(err); + } + if let Err(err) = stmt.type_check(type_checker) { + errors.push(err); + // The failed statement returned through the `?` that would have + // closed its scopes; leaving them open would check the next + // statement against bindings it cannot see. + type_checker.unwind_scopes_to(depth); + } } - Ok(()) + + if let Err(err) = type_checker.finalize_deferred_strict_function_checks() { + errors.push(err); + } + type_checker.restore_deferred_strict_function_checks(previous_defer); + errors } } @@ -919,6 +1555,27 @@ impl Program { } } +/// Refuses a top-level statement whose call reaches a binding declared below it. +/// +/// The third use-before-definition case (`stmt::init_order` has the other two +/// and the limits of this one): the direct read was already refused and a +/// function body reading a later binding is ordinary, but a top-level statement +/// that *calls* such a function ran it too early and got `nil`. +fn premature_read_error( + init_order: &crate::stmt::init_order::InitOrder, + stmt: &Stmt, + type_checker: &TypeChecker, +) -> Result<()> { + let Some((binding, callee)) = init_order.premature_read(stmt, type_checker.pending_top_level()) else { + return Ok(()); + }; + Err(anyhow!(format!( + "`{callee}` reads `{binding}`, which is declared further down — the top level runs in \ + order, so `{binding}` is still nil here and `{callee}` would answer with it. Move the \ + declaration of `{binding}` above this line" + ))) +} + fn pattern_names(pattern: &Pattern) -> crate::compat::collections::HashSet { let mut names = crate::compat::collections::HashSet::new(); collect_pattern_names(pattern, &mut names); @@ -966,6 +1623,51 @@ fn item_of(stmt: &Stmt) -> &Stmt { /// cannot see through (a type variable, `Any`, a mismatched shape) yields `Any` /// — permissive on purpose, so an unknown shape never rejects on an invented /// type. +/// Why this `let` pattern can never bind, when that is decidable from the +/// value's type alone. +/// +/// Deliberately narrow. A `let` that destructures a value whose shape is only +/// known at run time (`let [a, b] = f();`) is ordinary LK and raises if the +/// shape disagrees — that is the design, and nothing here touches it. What is +/// caught is the two cases where no value of that type could ever match: +/// +/// - a literal pattern, which binds nothing at all. `let 1 = 2;` raised +/// `Pattern does not match value`, and `let 1 = 1;` ran and bound nothing — +/// the only two outcomes it has. +/// - a destructuring pattern against a definite scalar. `let [a] = 5;` is a +/// list pattern over an `Int`, and no `Int` is a list. A `String` is excluded +/// because a list pattern over one destructures its characters, which the +/// pattern checker already models. +fn impossible_let_pattern(pattern: &Pattern, value_type: &Type, tc: &TypeChecker) -> Option { + match pattern { + Pattern::Literal(value) => Some(format!( + "a `let` binds names, and the pattern `{}` binds none — it can only match or fail. Write \ + `assert(… == {})` if the check is what you meant, or a name if the binding is", + value, value + )), + Pattern::List { .. } | Pattern::Map { .. } => { + let shape = if matches!(pattern, Pattern::List { .. }) { + "list" + } else { + "map" + }; + let resolved = tc.resolve_aliases(value_type); + matches!( + resolved, + Type::Int | Type::Float | Type::Bool | Type::Nil | Type::MachineInt(_) + ) + .then(|| { + format!( + "this `let` destructures a {shape}, and the value has type `{}` — a value of that type \ + has no parts to bind", + resolved.display() + ) + }) + } + _ => None, + } +} + fn bind_pattern_types(pattern: &Pattern, value_ty: &Type, is_const: bool, tc: &mut TypeChecker) { match pattern { Pattern::Variable(name) => tc.add_local_binding(name.clone(), value_ty.clone(), is_const), @@ -1053,25 +1755,6 @@ fn map_value_type(value_ty: &Type) -> Type { } } -/// Collapses distributed alternatives: identical types stay themselves, `Any` -/// anywhere swallows the rest (nothing is known), otherwise a union. -fn union_of(types: impl IntoIterator) -> Type { - let mut out: Vec = Vec::new(); - for ty in types { - if ty == Type::Any { - return Type::Any; - } - if !out.contains(&ty) { - out.push(ty); - } - } - match out.len() { - 0 => Type::Any, - 1 => out.pop().expect("checked len"), - _ => Type::Union(out), - } -} - /// The integer value of a literal expression. /// /// A leading minus needs no special case: the lexer folds it into the literal @@ -1084,6 +1767,35 @@ fn int_literal_value(expr: &crate::expr::Expr) -> Option { match expr { Expr::Literal(LiteralVal::Int(value)) => Some(i128::from(*value)), Expr::Paren(inner) => int_literal_value(inner), + // A *negative* carrier cast to `u64` is a 64-bit bit pattern, and it is + // measured as one — otherwise `let a: usize = 0xFFFF_FFFF_FFFF_FFFF` is + // refused for being a `u64`, which on every target this compiles for is + // the same 64 bits. + // + // The parser builds exactly this shape for a radix literal too wide for + // the carrier (see its `Token::UInt` arm), so this is where the two ends + // meet. Restricted to a negative carrier on purpose: without that, + // `let a: u32 = 5 as u64` would start passing, and an explicit cast + // should not be quietly re-typed. + Expr::Cast(inner, Type::MachineInt(crate::val::IntKind::U64)) => match inner.as_ref() { + Expr::Literal(LiteralVal::Int(value)) if *value < 0 => Some(i128::from(*value as u64)), + _ => None, + }, _ => None, } } + +/// Does a declared return type accept the `nil` a fall-through path produces? +/// +/// `Any` and a type variable do; so does anything optional, which is how a +/// function says "may answer nothing". Everything else is a promise. +fn declared_admits_nil(declared: &Type) -> bool { + match declared { + Type::Nil | Type::Any | Type::Optional(_) => true, + Type::Union(members) => members.iter().any(declared_admits_nil), + // An unresolved variable is inference still in progress, not a promise + // this can hold anyone to. + Type::Variable(_) => true, + _ => false, + } +} diff --git a/core/src/stmt/stmt_parser/bindings.rs b/core/src/stmt/stmt_parser/bindings.rs index 0cd4a10e..81344d81 100644 --- a/core/src/stmt/stmt_parser/bindings.rs +++ b/core/src/stmt/stmt_parser/bindings.rs @@ -14,6 +14,7 @@ impl<'a> StmtParser<'a> { } fn parse_binding_stmt(&mut self, keyword: Token, keyword_str: &'static str, is_const: bool) -> Result { + let keyword_pos = self.pos; self.expect_token(keyword)?; // Parse pattern for binding statement until a top-level ':' (type annotation) @@ -74,8 +75,7 @@ impl<'a> StmtParser<'a> { // Use AST parser to parse the pattern let pattern_tokens = &self.tokens[start_pos..end_pos]; - let mut ast_parser = ExprParser::new(pattern_tokens); - let pattern = ast_parser.parse_pattern()?; + let pattern = ExprParser::parse_whole_pattern(pattern_tokens)?; // Update position self.pos = end_pos; @@ -97,14 +97,19 @@ impl<'a> StmtParser<'a> { pattern, type_annotation, value: Box::new(value), - span: self.current_span(), + // `let` keyword through the end of the pattern — the statement's own + // position. This used to be `self.current_span()`, taken *after* the + // whole statement was consumed: it named the token that follows, so + // a `let`'s type error pointed at the next statement, and the last + // statement in a file had no span at all. + span: self.span_covering(keyword_pos, end_pos.saturating_sub(1)), is_const, }) } pub fn parse_assign_stmt_with_id(&mut self, name: String) -> Result { - // 我们已经在parse_statement中匹配了Id,现在跳过它并继续解析赋值 - self.pos += 1; // 跳过已匹配的 Id token + // `parse_statement` already matched the `Id`; step over it. + self.pos += 1; self.expect_token(Token::Assign)?; let value = self.parse_expression()?; @@ -118,10 +123,47 @@ impl<'a> StmtParser<'a> { } pub fn parse_compound_assign_stmt_with_id(&mut self, name: String) -> Result { - // 我们已经在parse_statement中匹配了Id,现在跳过它并继续解析复合赋值 - self.pos += 1; // 跳过已匹配的 Id token + // `parse_statement` already matched the `Id`; step over it. + self.pos += 1; + + // The bitwise operators are not `BinOp`s — `a & b` is a call to the + // `__lk_bit_*` builtin, and giving them a second spelling in `BinOp` + // would mean a second lowering, a second type rule, and two places for + // them to disagree. So `a &= b` desugars to what `a = a & b` already + // parses to. + // `<<=` / `>>=` are three adjacent tokens, because the lexer never + // emits a shift: `<<` is two `<`, so `<<=` is `<` then `<=`. Adjacency + // is what tells them apart from `a < (b <= c)`, the same test + // `Parser::peek_shift` makes for the shifts themselves. + if let Some(builtin) = self.peek_shift_assign(self.pos) { + self.pos += 2; + let rhs = self.parse_expression()?; + self.expect_token(Token::Semicolon)?; + let value = Expr::Call( + builtin.to_string(), + vec![Box::new(Expr::Var(name.clone())), Box::new(rhs)], + ); + return Ok(Stmt::Assign { + name, + value: Box::new(value), + span: self.current_span(), + }); + } + if let Some(builtin) = bitwise_compound_builtin(&self.tokens[self.pos]) { + self.pos += 1; + let rhs = self.parse_expression()?; + self.expect_token(Token::Semicolon)?; + let value = Expr::Call( + builtin.to_string(), + vec![Box::new(Expr::Var(name.clone())), Box::new(rhs)], + ); + return Ok(Stmt::Assign { + name, + value: Box::new(value), + span: self.current_span(), + }); + } - // 获取复合赋值操作符 let op = match &self.tokens[self.pos] { Token::AddAssign => BinOp::Add, Token::SubAssign => BinOp::Sub, @@ -130,7 +172,7 @@ impl<'a> StmtParser<'a> { Token::ModAssign => BinOp::Mod, _ => return Err(anyhow!("Expected compound assignment operator")), }; - self.pos += 1; // 跳过复合赋值操作符 + self.pos += 1; let value = self.parse_expression()?; self.expect_token(Token::Semicolon)?; @@ -143,6 +185,49 @@ impl<'a> StmtParser<'a> { }) } + /// Where the last segment of an assignment target starts, given the target + /// runs from `start` (the name) to `assign_pos` (the operator). + /// + /// `Some(i)` points at the `[` of a trailing `[key]` or at the `.` of a + /// trailing `.field`. `None` means the tokens in between are not an access + /// chain at all, which is this function's way of saying "not my statement". + fn access_target_last_segment(&self, start: usize, assign_pos: usize) -> Option { + if assign_pos <= start + 1 { + return None; + } + if self.tokens.get(assign_pos - 1) == Some(&Token::RBracket) { + // Back to the `[` that opens it, counting nested brackets so a key + // that is itself an index (`m[ks[0]] = v`) finds the outer one. + let mut depth = 0i32; + let mut i = assign_pos - 1; + loop { + match self.tokens.get(i) { + Some(Token::RBracket) => depth += 1, + Some(Token::LBracket) => { + depth -= 1; + if depth == 0 { + return (i > start).then_some(i); + } + } + None => return None, + _ => {} + } + if i == start { + return None; + } + i -= 1; + } + } + // `.field`, where the field is the token before the operator. + if assign_pos >= start + 3 + && matches!(self.tokens.get(assign_pos - 2), Some(Token::Dot)) + && matches!(self.tokens.get(assign_pos - 1), Some(Token::Id(_) | Token::Str(_))) + { + return Some(assign_pos - 2); + } + None + } + pub fn try_parse_access_assign_stmt_with_id(&mut self, name: String) -> Result> { let start = self.pos; let mut cursor = self.pos + 1; @@ -165,12 +250,21 @@ impl<'a> StmtParser<'a> { | Token::MulAssign | Token::DivAssign | Token::ModAssign + | Token::BitAndAssign + | Token::BitOrAssign + | Token::BitXorAssign if bracket_depth == 0 => { assign_pos = Some(cursor); assign_op = Some(self.tokens[cursor].clone()); break; } + // `<<=` / `>>=`, which are two tokens (see `peek_shift_assign`). + Token::Lt | Token::Gt if bracket_depth == 0 && self.peek_shift_assign(cursor).is_some() => { + assign_pos = Some(cursor); + assign_op = Some(self.tokens[cursor].clone()); + break; + } Token::Semicolon if bracket_depth == 0 => break, _ => cursor += 1, } @@ -179,31 +273,53 @@ impl<'a> StmtParser<'a> { return Ok(None); }; - let key = if self.tokens.get(start + 1) == Some(&Token::LBracket) - && assign_pos >= start + 3 - && self.tokens.get(assign_pos - 1) == Some(&Token::RBracket) - { - let mut parser = ExprParser::new(&self.tokens[start + 2..assign_pos - 1]); + // Where the *last* segment of the target starts. A target is a chain — + // `p.q.n`, `p.m["b"]`, `xs[0][1]` — and the store belongs to its last + // step, applied to everything before it. + // + // This used to read the *first* segment and discard the rest: `p.m["b"] + // = 2` became `p.m = 2` and `p.q.n = 5` became `p.q = 5`, both + // silently, on both engines, and `lk check` had nothing to object to + // when the field was `Any`. A map or a nested struct was destroyed by + // an assignment that reads like an update. + let Some(seg_start) = self.access_target_last_segment(start, assign_pos) else { + return Ok(None); + }; + let key = if self.tokens.get(seg_start) == Some(&Token::LBracket) { + let mut parser = self.expr_parser(&self.tokens[seg_start + 1..assign_pos - 1], None); parser.parse()? - } else if self.tokens.get(start + 1) == Some(&Token::Dot) { - match self.tokens.get(start + 2) { + } else { + match self.tokens.get(seg_start + 1) { Some(Token::Id(field)) => Expr::Literal(LiteralVal::from_str(field.as_str())), Some(Token::Str(field)) => Expr::Literal(LiteralVal::from_str(field.as_str())), other => { + let found = other.map_or_else(|| "end of input".to_string(), crate::token::token_lexeme); return Err(anyhow!( - self.err(&format!("Expected field name in assignment target, found {:?}", other)) + self.err(&format!("Expected field name in assignment target, found `{found}`")) )); } } - } else { - return Ok(None); }; - - self.pos = assign_pos + 1; + // Everything before the last segment. One token means the target is + // `name`, which is the shape this function has always handled + // and whose desugar re-binds the name; anything longer is a chain, and + // the store lands on the container that chain names. + let base_is_the_name = seg_start == start + 1; + + let shift_assign = self.peek_shift_assign(assign_pos); + self.pos = assign_pos + if shift_assign.is_some() { 2 } else { 1 }; let rhs = self.parse_expression()?; self.expect_token(Token::Semicolon)?; - let current = Expr::Access(Box::new(Expr::Var(name.clone())), Box::new(key.clone())); + // The container the store lands on: the name itself for a one-segment + // target, the chain before the last segment otherwise. + let base = if base_is_the_name { + Expr::Var(name.clone()) + } else { + let mut parser = self.expr_parser(&self.tokens[start..seg_start], None); + parser.parse()? + }; + let current = Expr::Access(Box::new(base.clone()), Box::new(key.clone())); let value = match assign_op.expect("assignment operator found") { Token::Assign => rhs, Token::AddAssign => Expr::Bin(Box::new(current), BinOp::Add, Box::new(rhs)), @@ -211,9 +327,33 @@ impl<'a> StmtParser<'a> { Token::MulAssign => Expr::Bin(Box::new(current), BinOp::Mul, Box::new(rhs)), Token::DivAssign => Expr::Bin(Box::new(current), BinOp::Div, Box::new(rhs)), Token::ModAssign => Expr::Bin(Box::new(current), BinOp::Mod, Box::new(rhs)), - _ => unreachable!(), + token => { + let builtin = shift_assign + .or_else(|| bitwise_compound_builtin(&token)) + .expect("assignment operator matched above"); + Expr::Call(builtin.to_string(), vec![Box::new(current), Box::new(rhs)]) + } }; + // A chain has no name to re-bind: the store mutates the container the + // chain names, and every container in this language is a heap value, so + // the change is visible through it. `p.m.set("b", 2)` — the spelling + // that always worked — is the same operation. + if !base_is_the_name { + let setter = if self.tokens.get(seg_start) == Some(&Token::LBracket) { + "__lk_set_index" + } else { + "__lk_set_field" + }; + let store = Expr::CallExpr( + Box::new(Expr::Var(setter.to_string())), + vec![Box::new(base), Box::new(key), Box::new(value)], + ); + return Ok(Some(Stmt::Expr { + value: Box::new(store), + span: self.current_span(), + })); + } if self.tokens.get(start + 1) == Some(&Token::LBracket) && matches!(key, Expr::Literal(LiteralVal::Int(_))) { let list_set = Expr::CallExpr( Box::new(Expr::Access( @@ -243,12 +383,13 @@ impl<'a> StmtParser<'a> { Box::new(Expr::Var("__lk_set_index".to_string())), vec![Box::new(Expr::Var(name)), Box::new(key), Box::new(value)], ); - Ok(Some(Stmt::Expr(Box::new(map_set)))) + Ok(Some(Stmt::expr(Box::new(map_set)))) } } pub fn parse_define_stmt_with_id(&mut self, name: String) -> Result { // consume Id (already peeked), ':' and '=' + let name_pos = self.pos; self.pos += 1; // Id self.expect_token(Token::Colon)?; self.expect_token(Token::Assign)?; @@ -258,6 +399,9 @@ impl<'a> StmtParser<'a> { Ok(Stmt::Define { name, value: Box::new(value), + // The name alone: `x := v` has no annotation slot, so a hint goes + // right after `x`, which is where the span ends. + span: self.span_covering(name_pos, name_pos), }) } @@ -276,7 +420,7 @@ impl<'a> StmtParser<'a> { pub fn parse_return_stmt(&mut self) -> Result { self.expect_token(Token::Return)?; - // 检查是否有返回值(如果下一个token不是分号,则有返回值) + // A `;` right here means the `return` carries no value. let value = if !self.eof() && self.tokens[self.pos] != Token::Semicolon { Some(Box::new(self.parse_expression()?)) } else { @@ -288,3 +432,34 @@ impl<'a> StmtParser<'a> { Ok(Stmt::Return { value }) } } + +/// The `__lk_bit_*` builtin a bitwise compound assignment desugars to. +fn bitwise_compound_builtin(token: &Token) -> Option<&'static str> { + match token { + Token::BitAndAssign => Some("__lk_bit_and"), + Token::BitOrAssign => Some("__lk_bit_or"), + Token::BitXorAssign => Some("__lk_bit_xor"), + _ => None, + } +} + +impl<'a> super::StmtParser<'a> { + /// `<<=` / `>>=` at `at`, as the `__lk_shl` / `__lk_shr` builtin. + /// + /// Two tokens (`Lt Le` / `Gt Ge`) that have to be *adjacent* in the source: + /// `a < b <= c` is three tokens too, and only the spans tell them apart. + pub(crate) fn peek_shift_assign(&self, at: usize) -> Option<&'static str> { + let builtin = match (self.tokens.get(at)?, self.tokens.get(at + 1)?) { + (Token::Lt, Token::Le) => "__lk_shl", + (Token::Gt, Token::Ge) => "__lk_shr", + _ => return None, + }; + if let Some(spans) = &self.token_spans + && let (Some(first), Some(second)) = (spans.get(at), spans.get(at + 1)) + && first.end.offset != second.start.offset + { + return None; + } + Some(builtin) + } +} diff --git a/core/src/stmt/stmt_parser/blocks.rs b/core/src/stmt/stmt_parser/blocks.rs index de7b9605..d82761f7 100644 --- a/core/src/stmt/stmt_parser/blocks.rs +++ b/core/src/stmt/stmt_parser/blocks.rs @@ -1,7 +1,7 @@ use super::StmtParser; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::{ast::Parser as ExprParser, expr::Expr, stmt::Stmt, token::Token}; +use crate::{expr::Expr, stmt::Stmt, token::Token}; use anyhow::{Result, anyhow}; impl<'a> StmtParser<'a> { @@ -10,7 +10,6 @@ impl<'a> StmtParser<'a> { let mut statements = Vec::new(); while !self.eof() && self.tokens[self.pos] != Token::RBrace { - // 跳过空语句 if self.tokens[self.pos] == Token::Semicolon { statements.push(Box::new(Stmt::Empty)); self.pos += 1; @@ -27,9 +26,32 @@ impl<'a> StmtParser<'a> { } pub fn parse_expr_stmt(&mut self) -> Result { - let expr = self.parse_expression()?; - self.expect_token(Token::Semicolon)?; - Ok(Stmt::Expr(Box::new(expr))) + // Taken before the expression is parsed, and ended before the `;`: this + // is the one statement whose span exists so that an *argument* type + // error has somewhere to point. `f("x");` used to report the mistake + // with no position at all, because a bare call is a `Stmt::Expr` and + // that was the only variant carrying none. + let start_pos = self.pos; + let expr = self.parse_statement_expression()?; + // An expression that *ends in a block* needs no `;`. + // + // `if c { … }` never did, because it was a statement. `match x { … }` + // and `unsafe { … }` are expressions, so they did — the same shape on + // the page, one of them demanding punctuation the other refuses. + // Ending in `}` is the whole rule, so a construct added later inherits + // it instead of joining the exception list. + let ends_in_block = self.pos > 0 && self.tokens[self.pos - 1] == Token::RBrace; + if ends_in_block { + if !self.eof() && self.tokens[self.pos] == Token::Semicolon { + self.pos += 1; + } + } else { + self.expect_token(Token::Semicolon)?; + } + Ok(Stmt::Expr { + value: Box::new(expr), + span: self.span_covering(start_pos, self.pos.saturating_sub(1)), + }) } pub fn parse_expression(&mut self) -> Result { @@ -37,17 +59,47 @@ impl<'a> StmtParser<'a> { } pub fn parse_expression_with_options(&mut self, stop_at_for_loop_body: bool) -> Result { - // 找到表达式的结束位置 + self.parse_expression_slice(stop_at_for_loop_body, false) + } + + /// The expression of an expression *statement*. + /// + /// Differs from [`Self::parse_expression`] in one way: a leading braced + /// construct ends the expression at its closing `}`, because that is where + /// the statement ends. As an operand it must not — `return match x { … } + /// == nil;` compares the match's value, and stopping at the brace would + /// silently drop the comparison. + fn parse_statement_expression(&mut self) -> Result { + self.parse_expression_slice(false, true) + } + + fn parse_expression_slice(&mut self, stop_at_for_loop_body: bool, end_at_block: bool) -> Result { let start_pos = self.pos; let mut depth = 0; let mut end_pos = start_pos; + // `if` is an expression, so a top-level `else` can belong to the + // expression being sliced rather than to an enclosing `if` *statement*. + // Count the unmatched `if`s seen so far and hand the `else` to the + // nearest one; only a genuinely dangling `else` ends the slice, which + // is what this used to assume unconditionally. + let mut unmatched_ifs = 0usize; + // An expression that *begins* with a braced construct also *ends* at + // that construct's closing `}` when it stands as a statement: + // `match x { … } println("next");` is two statements, not one + // expression with leftover tokens. Conditions never reach this — they + // stop at the `{` that opens the body (`stop_at_for_loop_body`). + let starts_with_block_expr = end_at_block + && matches!( + self.tokens.get(start_pos), + Some(Token::Match | Token::Unsafe | Token::If) + ); while end_pos < self.len { let token = &self.tokens[end_pos]; match token { Token::LBrace if depth == 0 && stop_at_for_loop_body => { - break; // for循环体的开始 + break; // the `for` body starts here } Token::LParen | Token::LBrace | Token::LBracket => { depth += 1; @@ -55,17 +107,22 @@ impl<'a> StmtParser<'a> { } Token::RParen => { if depth == 0 { - break; // 条件表达式的结束 + break; // the condition ends here } depth -= 1; end_pos += 1; } Token::RBrace => { if depth == 0 { - break; // 块的结束 + break; // the block ends here } depth -= 1; end_pos += 1; + // The construct this expression opened with just closed. + // An `else` may still follow an `if`; nothing else can. + if depth == 0 && starts_with_block_expr && !matches!(self.tokens.get(end_pos), Some(Token::Else)) { + break; + } } Token::RBracket => { depth -= 1; @@ -74,8 +131,16 @@ impl<'a> StmtParser<'a> { Token::Semicolon if depth == 0 => { break; } + Token::If if depth == 0 => { + unmatched_ifs += 1; + end_pos += 1; + } Token::Else if depth == 0 => { - break; + if unmatched_ifs == 0 { + break; + } + unmatched_ifs -= 1; + end_pos += 1; } _ => { end_pos += 1; @@ -84,20 +149,26 @@ impl<'a> StmtParser<'a> { } if end_pos == start_pos { + // In a `while`/`for` header the only expression that can start with + // `{` is a map literal, and this `{` is the body's. Say the way out + // rather than only that this is wrong — the same wording `if` and + // `match` use (`ast::Parser::parse_header_expr_before_brace`). + if stop_at_for_loop_body && matches!(self.tokens.get(start_pos), Some(Token::LBrace)) { + return Err(anyhow!(self.err( + "Expected an expression before '{': a `{` here opens the body, \ + so a map literal must be parenthesised — `({…}) { … }`" + ))); + } return Err(anyhow!(self.err("Expected expression"))); } - // 使用表达式解析器解析这部分 tokens - let expr_tokens = &self.tokens[start_pos..end_pos]; let expr_spans = self.token_spans.map(|spans| &spans[start_pos..end_pos]); - let mut expr_parser = if let Some(spans) = expr_spans { - ExprParser::new_with_spans(expr_tokens, spans) - } else { - ExprParser::new(expr_tokens) - }; + let mut expr_parser = self.expr_parser(&self.tokens[start_pos..end_pos], expr_spans); + // `parse` (not `parse_expr`) is what rejects a tail this slice's + // expression did not consume, which is how `if a = 2 { … }` is refused + // on this path. let expr = expr_parser.parse()?; - // 更新位置 self.pos = end_pos; Ok(expr) diff --git a/core/src/stmt/stmt_parser/control.rs b/core/src/stmt/stmt_parser/control.rs index 4830ab82..38f13d99 100644 --- a/core/src/stmt/stmt_parser/control.rs +++ b/core/src/stmt/stmt_parser/control.rs @@ -21,17 +21,32 @@ impl<'a> StmtParser<'a> { self.expect_token(Token::Semicolon)?; let closure = Expr::Closure { params: Vec::new(), + param_types: Vec::new(), + return_type: None, body: Box::new(operand), }; - Ok(Stmt::Expr(Box::new(Expr::Call( + Ok(Stmt::expr(Box::new(Expr::Call( "spawn".to_string(), vec![Box::new(closure)], )))) } + /// `try { … } catch e { … }` in statement position — the same node the + /// expression parser builds, with its value discarded. `if` and `match` sit + /// in statement position the same way; there is nothing here that a second + /// AST node would say. pub fn parse_try_stmt(&mut self) -> Result { + // A `try` that is the last thing here is a block's *tail*, so it is + // parsed as the expression it is — same treatment as `if`, and for the + // same reason: `try { try { … 1 } catch e { 2 } } catch e { 3 }` needs + // the inner one to be a value. Anywhere else it stays a statement, + // whose blocks are ordinary statement blocks. + let keyword_pos = self.pos; + if let Some(stmt) = self.try_parse_tail_expression_stmt(keyword_pos)? { + return Ok(stmt); + } self.expect_token(Token::Try)?; - let Stmt::Block { statements: body_stmts } = self.parse_block_stmt()? else { + let Stmt::Block { statements: body } = self.parse_block_stmt()? else { bail!("`try` body must be a block"); }; self.expect_token(Token::Catch)?; @@ -43,22 +58,20 @@ impl<'a> StmtParser<'a> { } _ => bail!("expected an identifier after `catch`"), }; - let Stmt::Block { - statements: handler_stmts, - } = self.parse_block_stmt()? - else { + let Stmt::Block { statements: handler } = self.parse_block_stmt()? else { bail!("`catch` body must be a block"); }; - Ok(Stmt::Try { - body: body_stmts, + Ok(Stmt::expr(Box::new(Expr::Try { + body, catch_var, - handler: handler_stmts, - }) + handler, + }))) } - /// 解析 if 语句 + /// Parses an `if` statement. pub fn parse_if_stmt(&mut self) -> Result { + let keyword_pos = self.pos; self.expect_token(Token::If)?; // Check if this is an "if let" statement @@ -92,7 +105,21 @@ impl<'a> StmtParser<'a> { else_stmt, }) } else { - // Regular if statement + // Regular `if`. + // + // When its branches are `{ … }`, this is the *expression* form — + // the same construct, with the value discarded. Parsing it here + // rather than as `Stmt::If` over statement-blocks is what makes + // `if c { if d { 1 } else { 2 } } else { 3 }` work: a statement + // block demands a `;` after every statement, so a branch whose + // last line is the value it produces would not parse. + // + // The braceless forms (`if (c) return 1;`) have no block and no + // value, and keep the statement path below. + if let Some(stmt) = self.try_parse_if_expression_stmt(keyword_pos)? { + return Ok(stmt); + } + let condition = if !self.eof() && self.tokens[self.pos] == Token::LParen { // Standard form: if (cond) stmt self.pos += 1; // consume '(' @@ -122,7 +149,81 @@ impl<'a> StmtParser<'a> { } } - /// 解析 while 语句 + /// Does the `if` at `keyword_pos` take a `{ … }` branch? + /// + /// Decided by scanning rather than by inspecting the parsed expression: + /// the answer is needed *before* the expression exists, to choose which + /// parser to run. (It also used to be that constant folding could delete + /// the conditional outright — `if false { 1 } else { 2 }` came back as the + /// surviving block. Folding no longer discards an unchecked branch, but + /// the scan is still what decides.) + fn if_branch_is_braced(&self, keyword_pos: usize) -> bool { + let mut depth = 0i32; + let mut index = keyword_pos + 1; + while index < self.len { + match &self.tokens[index] { + Token::LParen | Token::LBracket => depth += 1, + Token::RParen | Token::RBracket => depth -= 1, + Token::LBrace if depth == 0 => return true, + // A statement ends the search: `if (c) return 1;` has no block. + Token::Semicolon if depth == 0 => return false, + _ => {} + } + index += 1; + } + false + } + + /// Parse a *trailing* keyword-led expression (`if …`, `try …`) as an + /// expression statement, or answer `None` when it is not the last item. + /// + /// `keyword_pos` indexes the keyword token itself, since the expression + /// parser has to see it. The sub-parser runs over a token slice and reports + /// how much it consumed, so a `None` costs nothing: the caller is exactly + /// where it was. + fn try_parse_tail_expression_stmt(&mut self, keyword_pos: usize) -> Result> { + let spans = self.token_spans.map(|spans| &spans[keyword_pos..]); + let mut parser = self.expr_parser(&self.tokens[keyword_pos..], spans); + let (expr, consumed) = match parser.parse_prefix() { + Ok(parsed) => parsed, + // A syntax error is shape information: these tokens may still be a + // statement, and `try { … } catch e { }` is exactly that. Budget + // exhaustion is not — the statement path would fail it too, and + // retrying at every level doubles the work per level. + Err(err) if err.downcast_ref::().is_some() => return Err(err), + Err(_) => return Ok(None), + }; + // Only when it is the *last* thing here: that is a block's tail, where + // the value is what the block evaluates to. Anywhere else it is a + // statement and has to stay one — its branches may `return`, `break` or + // `continue`, and those lower as control flow out of the enclosing + // function or loop, not as a value. + let mut end = keyword_pos + consumed; + if end < self.len && self.tokens[end] == Token::Semicolon { + end += 1; + } + if end != self.len { + return Ok(None); + } + self.pos = end; + Ok(Some(Stmt::expr(Box::new(expr)))) + } + + /// Parse a *trailing* `if … { … }` as an expression statement, or answer + /// `None` when this `if` is not the braced form or is not the last item. + /// + /// `keyword_pos` indexes the `if` token itself, since the expression parser + /// has to see it. The sub-parser runs over a token slice and reports how + /// much it consumed, so a `None` here costs nothing: the caller is exactly + /// where it was. + fn try_parse_if_expression_stmt(&mut self, keyword_pos: usize) -> Result> { + if !self.if_branch_is_braced(keyword_pos) { + return Ok(None); + } + self.try_parse_tail_expression_stmt(keyword_pos) + } + + /// Parses a `while` statement. pub fn parse_while_stmt(&mut self) -> Result { self.expect_token(Token::While)?; @@ -148,12 +249,14 @@ impl<'a> StmtParser<'a> { body, }) } else { - // Regular while statement - self.expect_token(Token::LParen)?; - - let condition = self.parse_expression()?; - - self.expect_token(Token::RParen)?; + // Regular `while`. + // + // The condition stops at the top-level `{` that opens the body, + // exactly as `if`'s does. Parentheses used to be *required* here + // and optional there, for no reason either form could explain — + // `while (i < 3) { … }` still parses, because a parenthesised + // expression is an expression. + let condition = strip_condition_parens(self.parse_expression_with_options(true)?); let body = Box::new(self.parse_statement()?); Ok(Stmt::While { @@ -163,11 +266,10 @@ impl<'a> StmtParser<'a> { } } - /// 解析 for 语句 + /// Parses a `for` statement. pub fn parse_for_stmt(&mut self) -> Result { - self.expect_token(Token::For)?; // 消费 'for' + self.expect_token(Token::For)?; - // 解析模式 (变量名或解构) let mut pattern = self.parse_for_pattern()?; if !self.eof() && self.tokens[self.pos] == Token::Comma { let mut patterns = vec![pattern]; @@ -178,12 +280,11 @@ impl<'a> StmtParser<'a> { pattern = ForPattern::Tuple(patterns); } - self.expect_token(Token::In)?; // 消费 'in' + self.expect_token(Token::In)?; - // 解析可迭代表达式 - 在for循环中遇到LBrace时停止 + // The iterable stops at `{`, which starts the body rather than a map. let iterable = self.parse_expression_with_options(true)?; - // 解析循环体 let body = Box::new(self.parse_statement()?); Ok(Stmt::For { @@ -193,26 +294,25 @@ impl<'a> StmtParser<'a> { }) } - /// 解析 for 循环的模式 + /// Parses a `for` loop's binding pattern. pub fn parse_for_pattern(&mut self) -> Result { match &self.tokens[self.pos] { - // 忽略模式: _ + // `_` Token::Id(name) if name == "_" => { self.pos += 1; Ok(ForPattern::Ignore) } - // 简单变量: identifier + // A plain name. Token::Id(name) => { let var_name = name.clone(); self.pos += 1; Ok(ForPattern::Variable(var_name)) } - // 元组模式: (a, b, c) + // `(a, b, c)` Token::LParen => { - self.pos += 1; // 消费 '(' + self.pos += 1; let mut patterns = Vec::new(); - // 处理空元组 () if !self.eof() && self.tokens[self.pos] == Token::RParen { self.pos += 1; return Ok(ForPattern::Tuple(patterns)); @@ -227,8 +327,8 @@ impl<'a> StmtParser<'a> { match &self.tokens[self.pos] { Token::Comma => { - self.pos += 1; // 消费 ',' - // 允许尾随逗号: (a, b,) + self.pos += 1; + // A trailing comma is allowed. if !self.eof() && self.tokens[self.pos] == Token::RParen { break; } @@ -239,27 +339,25 @@ impl<'a> StmtParser<'a> { } } - self.pos += 1; // 消费 ')' + self.pos += 1; Ok(ForPattern::Tuple(patterns)) } - // 数组模式: [a, b] 或 [a, b, ..rest] + // `[a, b]` or `[a, b, ..rest]` Token::LBracket => { - self.pos += 1; // 消费 '[' + self.pos += 1; let mut patterns = Vec::new(); let mut rest = None; - // 处理空数组 [] if !self.eof() && self.tokens[self.pos] == Token::RBracket { self.pos += 1; return Ok(ForPattern::Array { patterns, rest }); } loop { - // 检查剩余模式 .. if !self.eof() && self.tokens[self.pos] == Token::Range { - self.pos += 1; // 消费 '..' + self.pos += 1; - // 可选的剩余变量名 + // The rest binding may be anonymous. if !self.eof() && let Token::Id(name) = &self.tokens[self.pos] { @@ -267,7 +365,7 @@ impl<'a> StmtParser<'a> { self.pos += 1; } - // 剩余模式后不能再有其他模式 + // Nothing may follow a rest pattern. if self.eof() { return Err(anyhow!(self.err("Expected ']' after rest pattern"))); } @@ -296,8 +394,8 @@ impl<'a> StmtParser<'a> { match &self.tokens[self.pos] { Token::Comma => { - self.pos += 1; // 消费 ',' - // 允许尾随逗号: [a, b,] + self.pos += 1; + // A trailing comma is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBracket { break; } @@ -308,15 +406,14 @@ impl<'a> StmtParser<'a> { } } - self.pos += 1; // 消费 ']' + self.pos += 1; Ok(ForPattern::Array { patterns, rest }) } - // 对象模式: {"k1": v1, "k2": v2} + // `{"k1": v1, "k2": v2}` Token::LBrace => { - self.pos += 1; // 消费 '{' + self.pos += 1; let mut entries: Vec<(String, ForPattern)> = Vec::new(); - // 处理空对象 {} if !self.eof() && self.tokens[self.pos] == Token::RBrace { self.pos += 1; return Ok(ForPattern::Object(entries)); @@ -327,7 +424,7 @@ impl<'a> StmtParser<'a> { return Err(anyhow!(self.err("Expected string key in object pattern"))); } - // 键必须是字符串字面量 + // The key is a string literal. let key = if let Token::Str(s) = &self.tokens[self.pos] { let k = s.clone(); self.pos += 1; @@ -336,10 +433,9 @@ impl<'a> StmtParser<'a> { return Err(anyhow!(self.err("Expected string key in object pattern"))); }; - // 冒号 self.expect_token(Token::Colon)?; - // 值部分可以是任意 for 模式(变量、_、元组、数组、嵌套对象等) + // The value is any `for` pattern, nesting included. let value_pattern = self.parse_for_pattern()?; entries.push((key, value_pattern)); @@ -350,8 +446,8 @@ impl<'a> StmtParser<'a> { match &self.tokens[self.pos] { Token::Comma => { - self.pos += 1; // 继续解析下一个键值 - // 允许尾随逗号 + self.pos += 1; + // A trailing comma is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBrace { break; } @@ -364,10 +460,29 @@ impl<'a> StmtParser<'a> { } } - self.pos += 1; // 消费 '}' + self.pos += 1; Ok(ForPattern::Object(entries)) } _ => Err(anyhow!(self.err("Expected pattern after 'for'"))), } } } + +/// A condition with its outer parentheses removed. +/// +/// `Expr::Paren` carries no meaning — it exists so the formatter can print the +/// source back. But the loop analyses match on expression *shape*, and the +/// wrapper hides it: after `while` stopped requiring parentheses, the +/// still-legal `while (i < 3)` began parsing as `Paren(i < 3)` where it used +/// to be `i < 3`, and the constant `3` stopped being recognised as +/// loop-invariant — it became a loop-carried block parameter, reloaded every +/// iteration. Nothing was wrong with the answer, only with the code. +/// +/// The `if` statement never had this because it consumed the parentheses as +/// tokens; stripping here restores that, for both. +fn strip_condition_parens(mut condition: Expr) -> Expr { + while let Expr::Paren(inner) = condition { + condition = *inner; + } + condition +} diff --git a/core/src/stmt/stmt_parser/declarations.rs b/core/src/stmt/stmt_parser/declarations.rs index cc42e1cb..84a2aa6a 100644 --- a/core/src/stmt/stmt_parser/declarations.rs +++ b/core/src/stmt/stmt_parser/declarations.rs @@ -25,7 +25,6 @@ impl<'a> StmtParser<'a> { pub fn parse_struct_stmt(&mut self) -> Result { self.expect_token(Token::Struct)?; - // 名称 let name = if let Token::Id(id) = &self.tokens[self.pos] { let n = id.clone(); self.pos += 1; @@ -34,31 +33,34 @@ impl<'a> StmtParser<'a> { return Err(anyhow!(self.err("Expected struct name after 'struct'"))); }; - // 字段块 self.expect_token(Token::LBrace)?; let mut fields: Vec<(String, Option)> = Vec::new(); - // 允许空结构体 + // An empty struct is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBrace { self.pos += 1; return Ok(Stmt::Struct { name, fields }); } loop { - // 字段名 + // A field is only ever reached through `.` or a struct literal, so + // a keyword names one unambiguously (`struct Row { type: String }`). let field_name = if let Token::Id(id) = &self.tokens[self.pos] { let s = id.clone(); self.pos += 1; s + } else if let Some(word) = crate::token::keyword_as_name(&self.tokens[self.pos]) { + self.pos += 1; + word.to_string() } else { return Err(anyhow!(self.err("Expected field name in struct"))); }; - // ':' 类型(可选;未注解视为 Any) + // The annotation is optional; an unannotated field is `Any`. let mut ty: Option = None; if !self.eof() && self.tokens[self.pos] == Token::Colon { self.pos += 1; // consume ':' - // 复用具名参数的类型解析(直至 ',' 或 '}') + // The named-parameter type parser, which stops at ',' or '}'. let parsed = self.parse_inline_type_until_named_delim()?; ty = Some(parsed); } @@ -71,7 +73,7 @@ impl<'a> StmtParser<'a> { match &self.tokens[self.pos] { Token::Comma => { self.pos += 1; - // 允许尾随逗号 + // A trailing comma is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBrace { self.pos += 1; break; @@ -88,11 +90,10 @@ impl<'a> StmtParser<'a> { Ok(Stmt::Struct { name, fields }) } - /// 解析 trait 语句:trait Name { fn method(params[: type]...) [-> type]; ... } + /// Parses `trait Name { fn method(params[: type]…) [-> type]; … }`. pub fn parse_trait_stmt(&mut self) -> Result { self.expect_token(Token::Trait)?; - // trait 名称 let name = if let Token::Id(id) = &self.tokens[self.pos] { let n = id.clone(); self.pos += 1; @@ -104,44 +105,81 @@ impl<'a> StmtParser<'a> { self.expect_token(Token::LBrace)?; let mut methods: Vec<(String, Type)> = Vec::new(); + let mut default_methods: Vec = Vec::new(); - // 允许空 trait + // An empty trait is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBrace { self.pos += 1; - return Ok(Stmt::Trait { name, methods }); + return Ok(Stmt::Trait { + name, + methods, + default_methods, + }); } while !self.eof() && self.tokens[self.pos] != Token::RBrace { - // 每个方法声明以 fn 开始 + // A method with a body is a **default**: implementors that do not + // write it get this one. It parses as the ordinary function an + // `impl` block would hold, so the signature below is read back off + // the parsed node rather than parsed twice. + if let Some(method) = self.try_parse_trait_default_method()? { + let Stmt::Function { + name: mname, + param_types, + named_params, + return_type, + .. + } = &method + else { + return Err(anyhow!(self.err("Expected a method in trait"))); + }; + methods.push(( + mname.clone(), + Type::Function { + params: param_types.iter().map(|ty| ty.clone().unwrap_or(Type::Any)).collect(), + named_params: named_params + .iter() + .map(|named| crate::val::FunctionNamedParamType { + name: named.name.clone(), + ty: named.type_annotation.clone().unwrap_or(Type::Any), + has_default: named.default.is_some(), + }) + .collect(), + return_type: Box::new(return_type.clone().unwrap_or(Type::Any)), + }, + )); + default_methods.push(method); + continue; + } self.expect_token(Token::Fn)?; - // 方法名 let mname = if let Token::Id(id) = &self.tokens[self.pos] { let m = id.clone(); self.pos += 1; m + } else if let Some(word) = crate::token::keyword_as_name(&self.tokens[self.pos]) { + // A trait method is reached through `.` like any other member. + self.pos += 1; + word.to_string() } else { return Err(anyhow!(self.err("Expected method name in trait"))); }; - // 参数列表(仅用于签名) + // The parameter list is the signature; there is no body. self.expect_token(Token::LParen)?; let mut param_types: Vec = Vec::new(); while !self.eof() && self.tokens[self.pos] != Token::RParen { - // 参数名 if let Token::Id(_param_name) = &self.tokens[self.pos] { self.pos += 1; // consume name } else { return Err(anyhow!(self.err("Expected parameter name in trait method"))); } - // 可选类型注解 let mut pty: Type = Type::Any; if !self.eof() && self.tokens[self.pos] == Token::Colon { self.pos += 1; // ':' pty = self.parse_inline_type_until_param_delim()?; } param_types.push(pty); - // 分隔符 if !self.eof() && self.tokens[self.pos] == Token::Comma { self.pos += 1; } else if !self.eof() && self.tokens[self.pos] == Token::RParen { @@ -154,7 +192,6 @@ impl<'a> StmtParser<'a> { } self.expect_token(Token::RParen)?; - // 可选返回类型 let mut ret_ty: Type = Type::Any; if !self.eof() && self.tokens[self.pos] == Token::FnArrow { self.pos += 1; // '->' @@ -162,7 +199,6 @@ impl<'a> StmtParser<'a> { // parse_inline_type_until_semicolon stops before ';' self.expect_token(Token::Semicolon)?; } else { - // 末尾分号(无返回类型时) self.expect_token(Token::Semicolon)?; } @@ -175,36 +211,120 @@ impl<'a> StmtParser<'a> { } self.expect_token(Token::RBrace)?; - Ok(Stmt::Trait { name, methods }) + Ok(Stmt::Trait { + name, + methods, + default_methods, + }) + } + + /// A trait method written with a body, or `None` when the next declaration + /// is a bare signature. + /// + /// Deciding needs a look-ahead: the signature and the default start + /// identically and only diverge at the `;` or `{` after the return type. + /// Scanning for it is cheaper than parsing twice, and it keeps the two + /// forms from having two parsers. + fn try_parse_trait_default_method(&mut self) -> Result> { + if self.eof() || self.tokens[self.pos] != Token::Fn { + return Ok(None); + } + if !self.trait_method_has_body() { + return Ok(None); + } + let previous = core::mem::replace(&mut self.in_member_body, true); + let parsed = self.parse_function_stmt(); + self.in_member_body = previous; + Ok(Some(parsed?)) + } + + /// Whether the `fn` at the cursor is followed by a body rather than a `;`. + /// + /// Walks to the end of the parameter list by paren depth, then past an + /// optional return type, and reports which of `{` / `;` comes first. A + /// return type may itself contain braces (`Map` does not, but + /// a closure type can), so the scan tracks every bracket kind. + fn trait_method_has_body(&self) -> bool { + let mut index = self.pos + 1; // past `fn` + // method name + if index < self.tokens.len() && matches!(self.tokens[index], Token::Id(_)) { + index += 1; + } + if index >= self.tokens.len() || self.tokens[index] != Token::LParen { + return false; + } + let mut depth = 0usize; + while index < self.tokens.len() { + match self.tokens[index] { + Token::LParen => depth += 1, + Token::RParen => { + depth -= 1; + if depth == 0 { + index += 1; + break; + } + } + _ => {} + } + index += 1; + } + // Past the parameter list: whichever of `{` and `;` comes first decides. + let mut nesting = 0usize; + while index < self.tokens.len() { + match self.tokens[index] { + Token::LBracket | Token::LParen => nesting += 1, + Token::RBracket | Token::RParen => nesting = nesting.saturating_sub(1), + Token::LBrace if nesting == 0 => return true, + Token::Semicolon if nesting == 0 => return false, + _ => {} + } + index += 1; + } + false } - /// 解析 impl 语句:impl Trait for Type { fn method(...) { ... } } + /// `impl Trait for Type { … }`, or `impl Type { … }` for methods that + /// belong to the type itself. + /// + /// The inherent form used to be a syntax error ("Expected 'for' in impl + /// statement"), and there is no UFCS either — so the only way to give a + /// struct a method was to declare an *empty* trait and implement that: + /// + /// ```lk + /// trait Methods { } + /// impl Methods for Point { fn norm(self) -> Int { … } } + /// ``` + /// + /// Everything else was already in place; the machinery registers methods + /// per type and dispatches on the type, not on the trait. Only this + /// spelling was missing. pub fn parse_impl_stmt(&mut self) -> Result { self.expect_token(Token::Impl)?; - // trait 名称 - let trait_name = if let Token::Id(id) = &self.tokens[self.pos] { - let n = id.clone(); + let first_name = if let Token::Id(id) = &self.tokens[self.pos] { + let name = id.clone(); self.pos += 1; - n + name } else { - return Err(anyhow!(self.err("Expected trait name after 'impl'"))); + return Err(anyhow!(self.err("Expected a trait or type name after 'impl'"))); }; - // 'for' - if self.eof() || self.tokens[self.pos] != Token::For { - return Err(anyhow!(self.err("Expected 'for' in impl statement"))); - } - self.pos += 1; - - // 目标类型(直到 '{') - let target_type = self.parse_inline_type_until_block_start()?; + // `impl Trait for Type` names two things; `impl Type` names one. + let (trait_name, target_type) = if !self.eof() && self.tokens[self.pos] == Token::For { + self.pos += 1; + (Some(first_name), self.parse_inline_type_until_block_start()?) + } else { + // The name already read *is* the target type, and it may carry + // generic arguments — so it is re-parsed from where it started. + self.pos -= 1; + (None, self.parse_inline_type_until_block_start()?) + }; self.expect_token(Token::LBrace)?; let mut methods: Vec = Vec::new(); - // 允许空 impl + // An empty impl is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBrace { self.pos += 1; return Ok(Stmt::Impl { @@ -220,11 +340,32 @@ impl<'a> StmtParser<'a> { } else { Vec::new() }; - // 只允许方法定义(fn),可带属性 + // Only `fn` items, optionally attributed. if self.tokens[self.pos] != Token::Fn { return Err(anyhow!(self.err("Expected 'fn' in impl block"))); } - let m = self.parse_function_stmt()?; + let previous = core::mem::replace(&mut self.in_member_body, true); + let parsed = self.parse_function_stmt(); + self.in_member_body = previous; + let m = parsed?; + // Two methods of one name in one block: the second silently won, + // and the first was compiled and never reachable. Nothing else in + // the language lets a declaration be shadowed by a sibling. + if let Stmt::Function { name, .. } = &m { + let name = name.clone(); + let already = methods.iter().any(|existing| { + let item = match existing { + Stmt::Attributed { item, .. } => item.as_ref(), + other => other, + }; + matches!(item, Stmt::Function { name: existing, .. } if *existing == name) + }); + if already { + return Err(anyhow!( + self.err(&alloc::format!("method `{name}` is defined twice in this impl block")) + )); + } + } if attributes.is_empty() { methods.push(m); } else { diff --git a/core/src/stmt/stmt_parser/function.rs b/core/src/stmt/stmt_parser/function.rs index 29ee9742..3789dcc0 100644 --- a/core/src/stmt/stmt_parser/function.rs +++ b/core/src/stmt/stmt_parser/function.rs @@ -13,16 +13,29 @@ impl<'a> StmtParser<'a> { pub fn parse_function_stmt(&mut self) -> Result { self.expect_token(Token::Fn)?; - // 解析函数名 let name = if let Token::Id(id) = &self.tokens[self.pos] { let name = id.clone(); self.pos += 1; name + } else if let Some(word) = self + .in_member_body + .then(|| crate::token::keyword_as_name(&self.tokens[self.pos])) + .flatten() + { + // A method name, not a global one — reached through `.`, so a + // keyword says it unambiguously (`db.select()`). + self.pos += 1; + word.to_string() + } else if let Some(word) = crate::token::keyword_as_name(&self.tokens[self.pos]) { + // Refused, but in the language's words: the reader needs to know it + // is a keyword and where one *is* allowed. + return Err(anyhow!(self.err(&alloc::format!( + "`{word}` is a keyword, so it cannot name a top-level function — a call to one is a bare name, where `{word}(…)` could not be told from the `{word}` statement. It *can* name a method or a field" + )))); } else { return Err(anyhow!(self.err("Expected function name"))); }; - // 解析参数列表 self.expect_token(Token::LParen)?; let mut params: Vec = Vec::new(); let mut param_types: Vec> = Vec::new(); @@ -31,7 +44,8 @@ impl<'a> StmtParser<'a> { let mut saw_default_positional = false; while !self.eof() && self.tokens[self.pos] != Token::RParen { - // 若遇到具名参数块,则解析之;具名块必须位于位置参数之后 + // The named-parameter block, which must follow the positional + // parameters. if self.tokens[self.pos] == Token::LBrace { if saw_named_block { return Err(anyhow!(self.err("Duplicate named parameter block"))); @@ -39,11 +53,10 @@ impl<'a> StmtParser<'a> { saw_named_block = true; let named = self.parse_named_param_block()?; named_params.extend(named); - // 允许块后跟逗号 + // A comma may follow the block. if !self.eof() && self.tokens[self.pos] == Token::Comma { self.pos += 1; } - // 继续循环以期待 ')' 结束 continue; } @@ -53,7 +66,6 @@ impl<'a> StmtParser<'a> { )); } - // 参数名 let param_name = if let Token::Id(param) = &self.tokens[self.pos] { let p = param.clone(); self.pos += 1; @@ -62,7 +74,6 @@ impl<'a> StmtParser<'a> { return Err(anyhow!(self.err("Expected parameter name or '{' for named block"))); }; - // 可选的参数类型注解 `: Type` let mut parsed_type: Option = None; if !self.eof() && self.tokens[self.pos] == Token::Colon { self.pos += 1; // consume ':' @@ -89,9 +100,8 @@ impl<'a> StmtParser<'a> { param_types.push(parsed_type); } - // 分隔符:逗号或结束 if !self.eof() && self.tokens[self.pos] == Token::Comma { - self.pos += 1; // 继续下一个参数 + self.pos += 1; } else if !self.eof() && self.tokens[self.pos] == Token::RParen { // end of params } else if self.eof() { @@ -103,7 +113,6 @@ impl<'a> StmtParser<'a> { self.expect_token(Token::RParen)?; - // 可选的返回类型 `-> Type` let mut return_type: Option = None; if !self.eof() && self.tokens[self.pos] == Token::FnArrow { self.pos += 1; // consume '->' @@ -111,7 +120,7 @@ impl<'a> StmtParser<'a> { return_type = Some(ty); } - // 解析函数体 (必须是块语句) + // The body has to be a block. let body = Box::new(self.parse_block_stmt()?); Ok(Stmt::Function { @@ -124,19 +133,18 @@ impl<'a> StmtParser<'a> { }) } - /// 解析具名参数块:形如 `{a: T, b: ?U = default}` + /// Parses a named-parameter block: `{a: T, b: ?U = default}`. pub fn parse_named_param_block(&mut self) -> Result> { self.expect_token(Token::LBrace)?; let mut named_params: Vec = Vec::new(); - // 允许空块 + // An empty block is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBrace { self.pos += 1; return Ok(named_params); } loop { - // 名称 let name = if let Token::Id(id) = &self.tokens[self.pos] { let n = id.clone(); self.pos += 1; @@ -145,11 +153,9 @@ impl<'a> StmtParser<'a> { return Err(anyhow!(self.err("Expected identifier in named parameter block"))); }; - // ':' 类型 self.expect_token(Token::Colon)?; let ty = self.parse_inline_type_until_named_delim()?; - // 可选默认值 `= expr` let mut default_expr: Option = None; if !self.eof() && self.tokens[self.pos] == Token::Assign { self.pos += 1; // consume '=' @@ -163,14 +169,13 @@ impl<'a> StmtParser<'a> { default: default_expr, }); - // 分隔符处理:逗号继续,右花括号结束 if self.eof() { return Err(anyhow!(self.err("Unexpected end in named parameter block"))); } match &self.tokens[self.pos] { Token::Comma => { self.pos += 1; - // 允许尾随逗号:{a: T,} + // A trailing comma is allowed. if !self.eof() && self.tokens[self.pos] == Token::RBrace { self.pos += 1; break; @@ -190,7 +195,8 @@ impl<'a> StmtParser<'a> { Ok(named_params) } - /// 将参数类型解析到 ',' 或 '}'(深度为 0)之前,不消耗分隔符 + /// Parses a parameter type up to the ',' or '}' at depth 0, leaving the + /// separator unconsumed. pub fn parse_inline_type_until_named_delim(&mut self) -> Result { let start_pos = self.pos; let mut tokens: Vec<&Token> = Vec::new(); @@ -270,6 +276,16 @@ impl<'a> StmtParser<'a> { } let type_str = self.tokens_to_type_string(&tokens); - Type::parse(&type_str).ok_or_else(|| anyhow!(self.err(&format!("Invalid type: {}", type_str)))) + if let Some(ty) = Type::parse(&type_str) { + return Ok(ty); + } + // Rewind to the type's first token before reporting: the collector + // stopped at whatever ended the annotation, and both the `found …` + // context and the span come from the position — so without this the + // message pointed at the `,` or the `)` that is not the problem. + self.pos = start_pos; + let message = crate::type_syntax::function_type_hint(self.tokens, start_pos) + .map_or_else(|| alloc::format!("Invalid type: {type_str}"), String::from); + Err(anyhow!(self.err(&message))) } } diff --git a/core/src/stmt/stmt_parser/helpers.rs b/core/src/stmt/stmt_parser/helpers.rs index a20e4c2a..a0759b74 100644 --- a/core/src/stmt/stmt_parser/helpers.rs +++ b/core/src/stmt/stmt_parser/helpers.rs @@ -10,6 +10,21 @@ use crate::{ use anyhow::{Result, anyhow}; impl<'a> StmtParser<'a> { + /// An expression parser over a token sub-slice that continues *this* + /// parser's nesting budget. + /// + /// Statement and expression nesting interleave — `if c { if c { … } }` + /// alternates between the two parsers — so starting each crossing back at + /// zero would leave the combined nesting unbounded, one slice at a time. + pub(crate) fn expr_parser<'b>(&self, tokens: &'b [Token], spans: Option<&'b [Span]>) -> ExprParser<'b> { + let mut parser = match spans { + Some(spans) => ExprParser::new_with_spans(tokens, spans), + None => ExprParser::new(tokens), + }; + parser.depth = self.depth; + parser + } + pub(super) fn eof(&self) -> bool { self.pos >= self.len } @@ -23,7 +38,7 @@ impl<'a> StmtParser<'a> { if core::mem::discriminant(&self.tokens[self.pos]) != core::mem::discriminant(&expected) { return Err(anyhow!( - self.err(&format!("Expected {:?}, found {:?}", expected, self.tokens[self.pos])) + self.err(&format!("Expected `{}`", crate::token::token_lexeme(&expected))) )); } @@ -50,96 +65,33 @@ impl<'a> StmtParser<'a> { } } + /// The `: T` of a `let`, a parameter, or a field. + /// + /// The collecting and rendering live in [`crate::type_syntax`], shared with + /// the closure parser — this had been the only copy until a lambda needed + /// the same thing in a position that cannot reach this parser. pub(super) fn parse_type_annotation(&mut self) -> Result { - let mut type_tokens = Vec::new(); - let mut paren: i32 = 0; - let mut bracket: i32 = 0; - let mut brace: i32 = 0; - let mut angle: i32 = 0; - - // Collect tokens that make up the type annotation until we hit a token that can't be part of a type - while !self.eof() { - match &self.tokens[self.pos] { - Token::LParen => { - paren += 1; - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } - Token::RParen => { - if paren > 0 { - paren -= 1; - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } else { - break; - } - } - Token::LBracket => { - bracket += 1; - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } - Token::RBracket => { - if bracket > 0 { - bracket -= 1; - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } else { - break; - } - } - Token::LBrace => { - brace += 1; - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } - Token::RBrace => { - if brace > 0 { - brace -= 1; - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } else { - break; - } - } - Token::Lt => { - angle += 1; - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } - Token::Gt => { - if angle > 0 { - angle -= 1; - } - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } - Token::Assign if paren == 0 && bracket == 0 && brace == 0 && angle == 0 => break, - Token::Id(_) - | Token::Comma - | Token::Colon - | Token::Assign - | Token::FnArrow - | Token::Question - // `*` starts a pointer type (`*u8`, `*mut u32`). It is the same - // token as multiplication, but a type position never contains - // one, so there is nothing to disambiguate. - | Token::Mul - | Token::Pipe => { - type_tokens.push(&self.tokens[self.pos]); - self.pos += 1; - } - _ => break, + let Some((ty, end)) = + crate::type_syntax::parse_type_at(self.tokens, self.pos, crate::type_syntax::StopAt::Union) + else { + // Two different reports, told apart by whether anything + // type-shaped was there at all — an empty position is a missing + // annotation, a non-empty one is a bad type. + // One rule, said the same way from every type position: a + // Rust-shaped `fn(Int) -> Int` is the mis-spelling worth naming, + // and which collector ran decides nothing about the message. + if let Some(hint) = crate::type_syntax::function_type_hint(self.tokens, self.pos) { + return Err(anyhow!(self.err(hint))); } - } - - if type_tokens.is_empty() { - return Err(anyhow!(self.err("Expected type annotation"))); - } - - let type_str = self.tokens_to_type_string(&type_tokens); - let parsed_type = Type::parse(&type_str); - parsed_type.ok_or_else(|| anyhow!(self.err(&format!("Invalid type: {}", type_str)))) + let spelled = crate::type_syntax::spelling_at(self.tokens, self.pos, crate::type_syntax::StopAt::Union); + return Err(anyhow!(if spelled.is_empty() { + self.err("Expected type annotation") + } else { + self.err(&format!("Invalid type: {spelled}")) + })); + }; + self.pos = end; + Ok(ty) } pub(super) fn parse_inline_type_until_param_delim(&mut self) -> Result { @@ -212,7 +164,17 @@ impl<'a> StmtParser<'a> { } let type_str = self.tokens_to_type_string(&tokens); - Type::parse(&type_str).ok_or_else(|| anyhow!(self.err(&format!("Invalid type: {}", type_str)))) + if let Some(ty) = Type::parse(&type_str) { + return Ok(ty); + } + // Rewind to the type's first token before reporting: the collector + // stopped at whatever ended the annotation, and both the `found …` + // context and the span come from the position — so without this the + // message pointed at the `,` or the `)` that is not the problem. + self.pos = start_pos; + let message = crate::type_syntax::function_type_hint(self.tokens, start_pos) + .map_or_else(|| alloc::format!("Invalid type: {type_str}"), String::from); + Err(anyhow!(self.err(&message))) } pub(super) fn parse_inline_type_until_semicolon(&mut self) -> Result { @@ -288,7 +250,17 @@ impl<'a> StmtParser<'a> { } let type_str = self.tokens_to_type_string(&tokens); - Type::parse(&type_str).ok_or_else(|| anyhow!(self.err(&format!("Invalid type: {}", type_str)))) + if let Some(ty) = Type::parse(&type_str) { + return Ok(ty); + } + // Rewind to the type's first token before reporting: the collector + // stopped at whatever ended the annotation, and both the `found …` + // context and the span come from the position — so without this the + // message pointed at the `,` or the `)` that is not the problem. + self.pos = start_pos; + let message = crate::type_syntax::function_type_hint(self.tokens, start_pos) + .map_or_else(|| alloc::format!("Invalid type: {type_str}"), String::from); + Err(anyhow!(self.err(&message))) } pub(super) fn parse_inline_type_until_block_start(&mut self) -> Result { @@ -353,7 +325,17 @@ impl<'a> StmtParser<'a> { } let type_str = self.tokens_to_type_string(&tokens); - Type::parse(&type_str).ok_or_else(|| anyhow!(self.err(&format!("Invalid type: {}", type_str)))) + if let Some(ty) = Type::parse(&type_str) { + return Ok(ty); + } + // Rewind to the type's first token before reporting: the collector + // stopped at whatever ended the annotation, and both the `found …` + // context and the span come from the position — so without this the + // message pointed at the `,` or the `)` that is not the problem. + self.pos = start_pos; + let message = crate::type_syntax::function_type_hint(self.tokens, start_pos) + .map_or_else(|| alloc::format!("Invalid type: {type_str}"), String::from); + Err(anyhow!(self.err(&message))) } pub(super) fn parse_inline_expr_until_named_delim(&mut self) -> Result { @@ -414,13 +396,8 @@ impl<'a> StmtParser<'a> { return Err(anyhow!(self.err("Expected expression for default value"))); } - let expr_tokens = &self.tokens[start_pos..end_pos]; let expr_spans = self.token_spans.map(|spans| &spans[start_pos..end_pos]); - let mut expr_parser = if let Some(spans) = expr_spans { - ExprParser::new_with_spans(expr_tokens, spans) - } else { - ExprParser::new(expr_tokens) - }; + let mut expr_parser = self.expr_parser(&self.tokens[start_pos..end_pos], expr_spans); let expr = expr_parser.parse()?; self.pos = end_pos; Ok(expr) @@ -428,13 +405,25 @@ impl<'a> StmtParser<'a> { pub(super) fn err(&self, msg: &str) -> String { let ctx = if let Some(c) = self.tokens.get(self.pos) { - format!("found {:?}", c) + // `token_lexeme`, not `{:?}`: a reader is told what they typed, so + // the message has to spell it the way they typed it. Every + // statement-level syntax error used to name the *variant* — + // `found Semicolon`, `found LBrace`, `found Fn`. + format!("found `{}`", crate::token::token_lexeme(c)) } else { "found end of input".to_string() }; format!("Syntax error: {} ({})", msg, ctx) } + /// The span covering tokens `from..=to`. + pub(super) fn span_covering(&self, from: usize, to: usize) -> Option { + let spans = self.token_spans.as_ref()?; + let start = spans.get(from)?; + let end = spans.get(to.max(from))?; + Some(Span::new(start.start.clone(), end.end.clone())) + } + pub(super) fn current_span(&self) -> Option { if let Some(spans) = &self.token_spans { if self.pos < spans.len() { @@ -478,8 +467,7 @@ impl<'a> StmtParser<'a> { // Use AST parser to parse the pattern let pattern_tokens = &self.tokens[start_pos..end_pos]; - let mut ast_parser = ExprParser::new(pattern_tokens); - let pattern = ast_parser.parse_pattern()?; + let pattern = ExprParser::parse_whole_pattern(pattern_tokens)?; // Update position self.pos = end_pos; @@ -487,57 +475,18 @@ impl<'a> StmtParser<'a> { Ok(pattern) } + /// The written form of a collected type annotation. + /// + /// Three positions still collect their own tokens with their own stop rules + /// (`parse_inline_type_until_param_delim` and the two in `function.rs`); + /// they render through the shared spelling, which is what the rest of the + /// parser uses. There used to be a second copy here, with its own token + /// table — and that table listed no keyword at all, so a spelling holding + /// one rendered as the Debug name (`Fn(Int) -> Int`, `Nil`). + /// + /// TODO(remove): give each of the three a `StopAt` variant and this + /// forwarding method goes away with them. pub(super) fn tokens_to_type_string(&self, tokens: &[&Token]) -> String { - let mut result = String::new(); - - for (i, token) in tokens.iter().enumerate() { - if i > 0 { - match token { - Token::Pipe => result.push_str(" | "), - Token::Lt => result.push('<'), - Token::Gt | Token::Comma | Token::RParen | Token::RBracket | Token::RBrace => { - result.push_str(&self.token_to_string(token)); - } - _ => { - if !matches!(tokens.get(i - 1), Some(Token::Lt)) { - result.push(' '); - } - result.push_str(&self.token_to_string(token)); - } - } - } else { - result.push_str(&self.token_to_string(token)); - } - } - - result - } - - pub(super) fn token_to_string(&self, token: &Token) -> String { - match token { - Token::Id(name) => name.clone(), - Token::Str(s) => format!("\"{}\"", s), - Token::Int(i) => i.to_string(), - Token::Float(f) => f.to_string(), - Token::Bool(b) => b.to_string(), - Token::LParen => "(".to_string(), - Token::RParen => ")".to_string(), - Token::LBrace => "{".to_string(), - Token::RBrace => "}".to_string(), - Token::LBracket => "[".to_string(), - Token::RBracket => "]".to_string(), - Token::Comma => ",".to_string(), - Token::Colon => ":".to_string(), - Token::ColonColon => "::".to_string(), - Token::Assign => "=".to_string(), - Token::Pipe => "|".to_string(), - Token::Question => "?".to_string(), - Token::FnArrow => "->".to_string(), - Token::Lt => "<".to_string(), - Token::Gt => ">".to_string(), - // Pointer types: `*u8`, `*mut u32`. - Token::Mul => "*".to_string(), - _ => format!("{:?}", token), - } + crate::type_syntax::spelling(tokens) } } diff --git a/core/src/stmt/stmt_parser/mod.rs b/core/src/stmt/stmt_parser/mod.rs index ac31f405..c6fcbc3c 100644 --- a/core/src/stmt/stmt_parser/mod.rs +++ b/core/src/stmt/stmt_parser/mod.rs @@ -5,6 +5,24 @@ pub struct StmtParser<'a> { pub(crate) pos: usize, pub(crate) len: usize, pub(crate) token_spans: Option<&'a [Span]>, + /// Inside an `impl` or `trait` body, where a `fn` declares a **member**. + /// + /// A member is only ever reached through `.`, so a keyword names one + /// unambiguously. A *top-level* `fn` keeps the restriction: a call to it is + /// a bare name in expression position, where `select(1)` and `select { … }` + /// would have to be told apart. + pub(crate) in_member_body: bool, + /// Live nesting depth of `parse_statement`, bounded by + /// [`crate::ast::parser::MAX_PARSE_DEPTH`]. + /// + /// The statement twin of `ast::parser::Parser::depth`. Statement parsing is + /// recursive descent too — `if { if { … } }` is one Rust frame per level — + /// and *every consumer downstream inherits the depth*: the type checker + /// walks the same tree, and it is the one that ran out first. + /// `lk check` on 170 nested `if`s aborted the process with + /// `fatal runtime error: stack overflow` (exit 134), no line to blame, + /// while parsing the same file alone succeeded. + pub(crate) depth: usize, } impl<'a> StmtParser<'a> { @@ -15,6 +33,8 @@ impl<'a> StmtParser<'a> { pos: 0, len, token_spans: None, + in_member_body: false, + depth: 0, } } @@ -25,6 +45,8 @@ impl<'a> StmtParser<'a> { pos: 0, len, token_spans: Some(spans), + in_member_body: false, + depth: 0, } } } diff --git a/core/src/stmt/stmt_parser/program.rs b/core/src/stmt/stmt_parser/program.rs index ee801a85..bfd57e08 100644 --- a/core/src/stmt/stmt_parser/program.rs +++ b/core/src/stmt/stmt_parser/program.rs @@ -8,12 +8,11 @@ use crate::{ use anyhow::{Result, anyhow}; impl<'a> StmtParser<'a> { - /// 解析整个程序 + /// Parses a whole program. pub fn parse_program(&mut self) -> Result { let mut statements = Vec::new(); while !self.eof() { - // 跳过空语句 if self.tokens[self.pos] == Token::Semicolon { statements.push(Box::new(Stmt::Empty)); self.pos += 1; @@ -31,7 +30,6 @@ impl<'a> StmtParser<'a> { let mut statements = Vec::new(); while !self.eof() { - // 跳过空语句 if self.tokens[self.pos] == Token::Semicolon { statements.push(Box::new(Stmt::Empty)); self.pos += 1; @@ -189,12 +187,50 @@ impl<'a> StmtParser<'a> { (statements, errors) } - /// 解析单个语句 + /// `defer ` — run it when the function leaves, whichever way. + /// + /// The statement is parsed here and *erased* by `desugar_defers`, which + /// rewrites the enclosing function so it appears before every `return` and + /// at the end, in reverse order. Nothing downstream ever sees one. + fn parse_defer_stmt(&mut self) -> Result { + let span = self.current_span(); + self.expect_token(Token::Defer)?; + let body = self.parse_statement()?; + Ok(Stmt::Defer { + body: Box::new(body), + span, + }) + } + + /// Parses one statement. + /// + /// The single choke point every level of statement nesting passes through: + /// a block parses its statements here, and `if`/`while`/`for`/`try` parse + /// their bodies as blocks. Bounding it here bounds every consumer that + /// walks the tree afterwards — which is the point, because the one that ran + /// out of stack first was the *type checker*, not this. pub fn parse_statement(&mut self) -> Result { if self.eof() { return Ok(Stmt::Empty); } + if self.depth >= crate::ast::parser::MAX_PARSE_DEPTH { + return Err( + anyhow::Error::new(crate::ast::parser::NestingTooDeep).context(self.err(&alloc::format!( + "nesting too deep (more than {} levels)", + crate::ast::parser::MAX_PARSE_DEPTH + ))), + ); + } + self.depth += 1; + // Decremented on the error path too, like the expression parser's: + // a bounded parse that fails must not leave the counter raised for + // whatever the caller tries next. + let parsed = self.parse_statement_inner(); + self.depth -= 1; + parsed + } + fn parse_statement_inner(&mut self) -> Result { match &self.tokens[self.pos] { Token::Hash => self.parse_attributed_stmt(), Token::Use => self.parse_import_stmt(), @@ -209,13 +245,69 @@ impl<'a> StmtParser<'a> { Token::Impl => self.parse_impl_stmt(), Token::Let => self.parse_let_stmt(), Token::Const => self.parse_const_stmt(), + Token::Defer => self.parse_defer_stmt(), Token::Break => self.parse_break_stmt(), Token::Continue => self.parse_continue_stmt(), Token::Return => self.parse_return_stmt(), Token::Fn => self.parse_function_stmt(), Token::LBrace => self.parse_block_stmt(), Token::Id(id) => { - // 优先解析短声明 `id := expr` 以避免与标签 `id:` 冲突 + // The one mis-spelling worth naming here, for the same reason + // `type_syntax::function_type_hint` names `fn(Int) -> Int`: + // somebody who read the macro documentation writes `export fn`, + // and what they got back was "Unexpected tokens at end (found + // Fn)" pointing at `export` — a message that names neither what + // `export` is nor that a function does not need it. This + // repository's own fixtures write `export fn` in four places + // (`macro_system/proc_deps.rs`), which only never showed + // because those tests hash the file instead of parsing it. + // The same reason, for the keyword somebody arrives with from + // another language. Each of these produced "Unexpected tokens + // at end" pointing at the word itself, which names neither the + // mistake nor the spelling that works — and the word is the + // first thing anybody types. + if let Some(Token::Id(name)) = self.peek_ahead(1) + && matches!(id.as_str(), "function" | "func" | "def" | "fun") + { + let message = alloc::format!( + "`{id}` does not declare a function in LK — the keyword is `fn`, as in \ + `fn {name}(x: Int) -> Int {{ … }}`" + ); + return Err(anyhow!(message)); + } + // `elif` is Python's; a chain here is `else if`, and the word + // lexes as an ordinary identifier so nothing else reports it. + if id == "elif" { + return Err(anyhow!( + "`elif` is not a keyword in LK — chain the branches with `else if`".to_string() + )); + } + if id == "export" + && let Some(next) = self.peek_ahead(1) + && matches!( + next, + Token::Fn + | Token::Struct + | Token::Const + | Token::Let + | Token::Trait + | Token::Impl + | Token::Type + ) + { + let message = "`export` applies to `macro_rules!` only \ + (`export macro_rules! name { … }`). A top-level `fn`, `struct`, `const` or \ + `type` needs no export — it is already importable with \ + `use { name } from module;`. For a native symbol name, the spelling is the \ + attribute `#[export]`"; + // Plain, with no span of its own: the caller re-wraps a + // statement error into a `ParseError` carrying the current + // token's span, and attaching one here made the rendered + // line read `… at 1:1-7 at 1:1-7`. + return Err(anyhow!(message)); + } + // The short declaration `id := expr` is tried first, so it is + // not read as the label `id:`. if self.peek_ahead(1) == Some(&Token::Colon) && self.peek_ahead(2) == Some(&Token::Assign) { self.parse_define_stmt_with_id(id.clone()) } else if matches!(self.peek_ahead(1), Some(Token::LBracket | Token::Dot)) @@ -223,7 +315,6 @@ impl<'a> StmtParser<'a> { { Ok(stmt) } else if self.peek_ahead(1) == Some(&Token::Assign) { - // 赋值 (id = expr;) self.parse_assign_stmt_with_id(id.clone()) } else if matches!( self.peek_ahead(1), @@ -232,7 +323,11 @@ impl<'a> StmtParser<'a> { | Some(&Token::MulAssign) | Some(&Token::DivAssign) | Some(&Token::ModAssign) - ) { + | Some(&Token::BitAndAssign) + | Some(&Token::BitOrAssign) + | Some(&Token::BitXorAssign) + ) || self.peek_shift_assign(self.pos + 1).is_some() + { self.parse_compound_assign_stmt_with_id(id.clone()) } else if self.peek_ahead(1) == Some(&Token::Colon) { // Label + statement (id: stmt) is not yet supported; treat as expression fallback diff --git a/core/src/stmt/stmt_test.rs b/core/src/stmt/stmt_test.rs index 15cccf96..a7c8b37f 100644 --- a/core/src/stmt/stmt_test.rs +++ b/core/src/stmt/stmt_test.rs @@ -1,5 +1,154 @@ #[cfg(test)] mod tests { + /// The keyword somebody arrives with from another language gets named. + /// + /// `function f() { … }`, `def f(): …`, `func f() int { … }` and `elif` + /// each reported "Unexpected tokens at end" pointing at the word itself — + /// a message that names neither the mistake nor the spelling that works, + /// for the word that is the first thing anybody types. Same reason + /// `export fn` (#201) and `let mut` are named. + /// + /// The negative half matters as much: these are ordinary identifiers, so + /// a variable or a function actually called `def` must be unaffected. + #[test] + fn a_function_keyword_from_another_language_is_named() { + for (source, expected) in [ + ("function f() { return 1; }\n", "the keyword is `fn`"), + ("def f(): return 1\n", "the keyword is `fn`"), + ("func f() -> Int { return 1; }\n", "the keyword is `fn`"), + ( + "let a = 1;\nif a > 0 { println(1); } elif a < 0 { println(2); }\n", + "`else if`", + ), + ("let f = (x) => x + 1;\n", "`|x| x + 1`"), + ] { + let error = crate::syntax::parse_program_source(source, Default::default()) + .expect_err("this is a syntax error") + .to_string(); + assert!(error.contains(expected), "{source}: {error}"); + } + + for source in [ + "let function = 1;\nprintln(function);\n", + "fn def(x: Int) -> Int { return x; }\nprintln(def(1));\n", + "let func = |x: Int| x + 1;\nprintln(func(1));\n", + ] { + crate::syntax::parse_program_source(source, Default::default()).unwrap_or_else(|e| panic!("{source}: {e}")); + } + } + + /// A header expression's leftover tokens used to be dropped in silence. + /// + /// `if a = 2 { … }` as the *last* statement of a file went through the + /// tail-expression path, whose sub-parser stops at the first token it + /// cannot continue with and whose leftovers nothing checked. The program + /// parsed as `if a { … }`, type-checked, and ran with the assignment gone + /// — the `=`-for-`==` slip, accepted as a wrong answer. One statement + /// earlier the same line was a syntax error, because that path parses the + /// condition with `Parser::parse`, which does check. + /// + /// Both halves are asserted: the shape is refused wherever it appears, and + /// the refusal names `==` rather than only reporting a stray token. + #[test] + fn an_assignment_in_a_condition_is_refused_and_named() { + let shapes = [ + "let a = 1;\nif a = 2 { println(1); }\n", + "let a = 1;\nif a = 2 { println(1); }\nprintln(a);\n", + "fn f() -> Int {\n let a = 1;\n if a = 2 { println(1); }\n return a;\n}\n", + "let a = 1;\nwhile a = 2 { break; }\n", + ]; + for source in shapes { + let error = crate::syntax::parse_program_source(source, Default::default()) + .expect_err("an assignment cannot be a condition") + .to_string(); + assert!(error.contains("`==`"), "{source}: {error}"); + } + + // The negative half: a condition that *is* an expression still parses, + // including the one whose header ends in a call. + for source in [ + "let a = 1;\nif a > 0 { println(1); }\n", + "let xs = [1];\nif xs.len() > 0 { println(1); }\n", + "let a = 1;\nmatch a { _ => { println(1); } }\n", + ] { + crate::syntax::parse_program_source(source, Default::default()).unwrap_or_else(|e| panic!("{source}: {e}")); + } + } + + /// Deeply nested statements used to abort the process. + /// + /// Two parsers, one budget. `if c { … }` alternates between the statement + /// parser and the expression parser, and each crossing used to build a + /// sub-parser starting back at depth zero, so neither counter ever + /// accumulated — 400 levels walked straight off a libtest thread's 2MiB + /// stack (`fatal runtime error: stack overflow`, exit 134: no line, no + /// message, and on bare metal no guard page to trap it either). + /// + /// Three assertions, because no two of them are satisfiable by one + /// mistake: the depth a program may reach is accepted; one past the bound + /// is a *syntax error*; and so is a depth far past it, which is the case a + /// per-parser budget got wrong. + #[test] + fn deeply_nested_statements_error_instead_of_overflowing_the_stack() { + // Two shapes, because they are refused by different halves of the one + // budget: `if` carries a condition, so its nest is refused by the + // expression parser, while a bare block has no expression in it at all + // and is refused by the statement parser. + fn nested_blocks(levels: usize) -> String { + let mut src = String::from("fn main() -> Int {\n"); + for _ in 0..levels { + src.push_str("{\n"); + } + src.push_str("println(1);\n"); + for _ in 0..levels { + src.push_str("}\n"); + } + src.push_str("return 0;\n}\n"); + src + } + + fn nested(levels: usize) -> String { + let mut src = String::from("fn main() -> Int {\n"); + for _ in 0..levels { + src.push_str("if true {\n"); + } + src.push_str("println(1);\n"); + for _ in 0..levels { + src.push_str("}\n"); + } + src.push_str("return 0;\n}\n"); + src + } + + // Not a formula: a source level costs a little over two frames of + // budget (the construct, the block it takes as a body, and the + // crossings between the two parsers), so where exactly the bound lands + // is measured. The deepest brace nesting in this repository's own `.lk` + // corpus, counting the `fn`/`impl`/`struct` levels, is 6. + // A quarter of the budget, which is comfortably inside it either way: + // one source level costs a little over two frames, so the deepest + // accepted nest is 30 levels at the `std` value and 7 at bare metal's. + let real_code = crate::ast::parser::MAX_PARSE_DEPTH / 4; + crate::syntax::parse_program_source(&nested(real_code), Default::default()) + .expect("a program may nest deeper than anything real code does"); + + // The last of these is also the regression test for the *time* it + // takes: the speculative tail-expression parse used to swallow the + // failure and let the statement path retry, doubling the work at every + // level, and 256 levels did not finish in five minutes. + // The whole budget, which is past the bound for either shape: a bare + // block spends one frame per level and an `if` a little over two. + let past_the_bound = crate::ast::parser::MAX_PARSE_DEPTH; + for levels in [past_the_bound, past_the_bound * 8] { + for source in [nested(levels), nested_blocks(levels)] { + let error = crate::syntax::parse_program_source(&source, Default::default()) + .expect_err("past the bound is refused, not aborted") + .to_string(); + assert!(error.contains("nesting too deep"), "{levels} levels: {error}"); + } + } + } + #[cfg(not(feature = "std"))] use crate::compat::prelude::*; use crate::vm::ProgramExec; @@ -231,7 +380,7 @@ mod tests { #[test] fn test_complex_program() { - // 简化程序,避免无限循环 + // Kept small so the loop terminates. let program = parse_program( r#" let n = 3; @@ -809,7 +958,7 @@ mod tests { let err = result.unwrap_err(); assert!( err.to_string() - .contains("For loop iterable must be List, String, Map, or Set") + .contains("For loop iterable must be List, String, Map, Set, Bytes, Slice or Tuple") ); } @@ -893,7 +1042,8 @@ mod tests { "#, ); let result = program.execute().expect("Failed to execute"); - expect_result_int(&result, 5); + // `/` yields a Float, so `/=` does too: `15 / 3` is `5.0`, not `5`. + expect_result_float(&result, 5.0); } #[test] @@ -966,12 +1116,12 @@ mod tests { x += 5; // x = 15 x *= 2; // x = 30 x -= 10; // x = 20 - x /= 4; // x = 5 + x /= 4; // x = 5.0 — `/` yields a Float return x; "#, ); let result = program.execute().expect("Failed to execute"); - expect_result_int(&result, 5); + expect_result_float(&result, 5.0); } #[test] @@ -1121,15 +1271,17 @@ mod tests { "#, ); let mut checker = TypeChecker::new_strict(); - assert!(program.type_check(&mut checker).is_ok()); + let outcome = program.type_check(&mut checker); + assert!(outcome.is_ok(), "strict check failed: {:?}", outcome.err()); } + /// `go ;` is parse-time sugar: a zero-param closure over the /// operand handed to the `spawn` builtin, handle discarded. #[test] fn go_statement_desugars_to_spawn_closure() { use crate::expr::Expr; let program = parse_program("go f(1, 2);"); - let Stmt::Expr(call) = program.statements[0].as_ref() else { + let Stmt::Expr { value: call, .. } = program.statements[0].as_ref() else { panic!("go must desugar to an expression statement"); }; let Expr::Call(name, args) = call.as_ref() else { @@ -1137,7 +1289,7 @@ mod tests { }; assert_eq!(name, "spawn"); assert_eq!(args.len(), 1); - let Expr::Closure { params, body } = args[0].as_ref() else { + let Expr::Closure { params, body, .. } = args[0].as_ref() else { panic!("spawn argument must be a closure"); }; assert!(params.is_empty()); @@ -1157,4 +1309,29 @@ mod tests { let program = parse_program("let golang = 1; let gopher = golang + 1; return gopher;"); assert_eq!(program.statements.len(), 3); } + + /// A keyword names a field and a method, and the statements it belongs to + /// still parse. A *top-level* `fn` keeps the restriction, because a call to + /// one is a bare name where `select(1)` could not be told from the `select` + /// statement — and says so. + #[test] + fn a_keyword_can_name_a_field_and_a_method() { + let (result, _) = execute_source_with_ctx( + r#" + struct Row { type: String, select: Int } + impl Row { fn match(self) -> Int { return self.select * 2; } } + trait Runner { fn go(self) -> Int; } + impl Runner for Row { fn go(self) -> Int { return self.select; } } + let r = Row { type: "t", select: 21 }; + return r.match() + r.go() + r.select; + "#, + ); + assert_eq!(result, RuntimeVal::Int(42 + 21 + 21)); + + let error = crate::syntax::parse_program_source("fn select() -> Int { return 1; }", Default::default()) + .expect_err("a top-level `fn` keeps the restriction"); + let text = alloc::format!("{error:#}"); + assert!(text.contains("`select` is a keyword"), "{text}"); + assert!(text.contains("method or a field"), "{text}"); + } } diff --git a/core/src/stmt/struct_ctors.rs b/core/src/stmt/struct_ctors.rs new file mode 100644 index 00000000..83b7d14b --- /dev/null +++ b/core/src/stmt/struct_ctors.rs @@ -0,0 +1,137 @@ +//! A constructor function beside every `struct`, so an imported type can be +//! built by the module that owns it. +//! +//! `module.Type { … }` used to be a syntax error and `use { Pt } from "types"` +//! could not reach a type at all, so a module that declared a type could not +//! let its users make one — every such module had to hand-write a `make`. +//! +//! The reason it could not simply be allowed is that a type's identity carries +//! its defining module (`val::TypeScope`): a `Pt` built in the importer is *not* +//! the `Pt` that `impl Norm for Pt` was registered against, and it would not +//! dispatch. `NewObject` names only the type, and the executor scopes it to +//! whichever module is running. +//! +//! So the object is built *by the defining module*: each `struct S { a, b }` +//! also gets +//! +//! ```lk +//! fn S$new({a: A, b: B}) -> S { return S { a: a, b: b }; } +//! ``` +//! +//! and `m.S { a: 1, b: 2 }` is parse-time sugar for `m.S$new(a: 1, b: 2)` (see +//! `ast::parser`). The call runs inside `m`, so the scope, the declaration's +//! field order and trait dispatch are all simply right — no new opcode, no +//! artifact change, and nothing for the AOT lowering to learn. +//! +//! **Named parameters, not positional**, so the caller needs no knowledge of +//! the declaration: it passes the same `field: value` pairs the literal is +//! written with, and a missing or misspelled one is the callee's own arity +//! error rather than something this pass has to check. +//! +//! `$` is untokenizable, so the name cannot collide with anything a program can +//! write — the same trick `try$call` and `select$block` use. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; + +use crate::expr::Expr; +use crate::stmt::{NamedParamDecl, Stmt}; + +/// The constructor name for `struct S`. +pub fn constructor_name(struct_name: &str) -> String { + alloc::format!("{struct_name}$new") +} + +/// The struct a function constructs, if its name is a constructor's. +/// +/// The inverse of [`constructor_name`], and the only place the `$new` spelling +/// is decoded. Three things need it and none of them may spell it themselves: +/// the type checker (so a field error says *field*, not "named argument"), the +/// AOT lowering (so the call's result carries the struct's type identity, which +/// is what makes a method on it devirtualize), and this module. +pub fn constructed_struct_name(function_name: &str) -> Option<&str> { + function_name.strip_suffix("$new") +} + +/// Registers the generated constructor needs beyond one per field: the type +/// name `NewObject` reads, the object being built, and the scratch each field +/// value passes through. +const CONSTRUCTOR_OVERHEAD_REGISTERS: usize = 3; + +/// The widest struct that can have a constructor, and therefore exist. +pub const MAX_STRUCT_FIELDS: usize = (u8::MAX as usize + 1) - CONSTRUCTOR_OVERHEAD_REGISTERS; + +/// Adds a constructor function after every top-level `struct` declaration. +/// +/// The constructor takes one *named parameter* per field, and parameters are +/// locals — so the width of a struct is bounded by the register file, and the +/// bound belongs here rather than wherever the generated body happens to run +/// out. A 254-field `struct` used to compile to "this function needs more than +/// 256 registers … split the body into smaller functions", pointing at a body +/// the program does not contain and offering advice that cannot be followed: +/// the declaration is one statement and emits no code of its own. +pub fn add_struct_constructors(statements: &mut Vec>) -> Result<(), String> { + let mut out: Vec> = Vec::with_capacity(statements.len()); + for stmt in statements.drain(..) { + let ctor = match struct_declaration(&stmt) { + Some((name, fields)) if fields.len() > MAX_STRUCT_FIELDS => { + return Err(alloc::format!( + "struct `{name}` has {} fields, and {MAX_STRUCT_FIELDS} is the most one can have: \ + building it takes a generated constructor with one parameter per field, and a \ + function's parameters share the same 256 registers as its temporaries. Split the \ + type, or hold this many values in a map", + fields.len() + )); + } + Some((name, fields)) => Some(constructor_for(name, fields)), + None => None, + }; + out.push(stmt); + if let Some(ctor) = ctor { + out.push(Box::new(ctor)); + } + } + *statements = out; + Ok(()) +} + +/// A struct declaration's name and fields, as the AST holds them. +type StructDecl<'a> = (&'a str, &'a [(String, Option)]); + +fn struct_declaration(stmt: &Stmt) -> Option> { + match stmt { + Stmt::Attributed { item, .. } => struct_declaration(item), + Stmt::Struct { name, fields } => Some((name.as_str(), fields.as_slice())), + _ => None, + } +} + +fn constructor_for(name: &str, fields: &[(String, Option)]) -> Stmt { + let named_params = fields + .iter() + .map(|(field, ty)| NamedParamDecl { + name: field.clone(), + type_annotation: ty.clone(), + default: None, + }) + .collect(); + let literal_fields = fields + .iter() + .map(|(field, _)| (field.clone(), Box::new(Expr::Var(field.clone())))) + .collect(); + Stmt::Function { + name: constructor_name(name), + params: Vec::new(), + param_types: Vec::new(), + named_params, + return_type: Some(crate::val::Type::Named(name.to_string())), + body: Box::new(Stmt::Block { + statements: vec![Box::new(Stmt::Return { + value: Some(Box::new(Expr::StructLiteral { + name: name.to_string(), + fields: literal_fields, + })), + })], + }), + } +} diff --git a/core/src/stmt/trait_defaults.rs b/core/src/stmt/trait_defaults.rs new file mode 100644 index 00000000..96af9df0 --- /dev/null +++ b/core/src/stmt/trait_defaults.rs @@ -0,0 +1,118 @@ +//! Trait default methods, erased before anything downstream sees them. +//! +//! `trait Greet { fn hi(self) -> String { return "hi"; } }` gives every +//! implementor a `hi` unless it writes its own. This module is what makes that +//! true: after macros and before the type checker, each `impl Trait for Type` +//! gets a copy of the trait's bodies for the methods it left out. +//! +//! **Copied per implementing type, not shared.** Dispatch in this language is +//! indexed by the *target type* (see `val::TypeScope`), and `self` in a default +//! body is the implementing type — so a copy is both the simplest lowering and +//! the correct one. The cost is one compiled function per implementor, which is +//! what writing the method out by hand would have cost anyway. +//! +//! Nothing downstream learns that defaults exist: the type checker, the VM +//! compiler and the AOT lowering all see an ordinary `impl` block. That is the +//! same shape `defer` uses (see [`crate::stmt::defer`]) and for the same +//! reason — a rewrite of the code's shape is easier to keep correct than a +//! second dispatch rule. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; + +use crate::compat::collections::HashMap; +use crate::stmt::Stmt; + +/// Fills every `impl Trait for Type` with the trait's default bodies for the +/// methods it did not write. +/// +/// A trait declared *after* the impl that uses it still applies: the traits are +/// collected in one pass over the whole program first. Declaration order is a +/// property of the file, not of the language. +pub fn apply_trait_defaults(statements: &mut [Box]) { + let defaults = collect_defaults(statements); + if defaults.is_empty() { + return; + } + for stmt in statements.iter_mut() { + fill_impl(stmt, &defaults); + } +} + +/// Fill impls from defaults a *previous* program declared. +/// +/// For a caller running a sequence of programs against one session — the REPL, +/// where each input is its own program. `trait T { fn m(self) -> Int { … } }` on +/// one line and `impl T for S { … }` on the next left the impl without the +/// default body, and the checker reported "Method 'm' required by trait 'T' not +/// implemented for type 'S'" — for a method the source never had to write. +/// +/// Applying this after [`apply_trait_defaults`] is harmless: an impl that +/// already has the method keeps its own. +pub fn apply_carried_trait_defaults(statements: &mut [Box], carried: &HashMap>) { + if carried.is_empty() { + return; + } + for stmt in statements.iter_mut() { + fill_impl(stmt, carried); + } +} + +/// The default method bodies each `trait` in `statements` declares, for a +/// caller that has to carry them — see [`apply_carried_trait_defaults`]. +pub fn trait_defaults_of(statements: &[Box]) -> HashMap> { + collect_defaults(statements) +} + +fn collect_defaults(statements: &[Box]) -> HashMap> { + let mut out: HashMap> = HashMap::new(); + for stmt in statements { + let item = match stmt.as_ref() { + Stmt::Attributed { item, .. } => item.as_ref(), + other => other, + }; + if let Stmt::Trait { + name, default_methods, .. + } = item + && !default_methods.is_empty() + { + out.insert(name.clone(), default_methods.clone()); + } + } + out +} + +fn fill_impl(stmt: &mut Stmt, defaults: &HashMap>) { + if let Stmt::Attributed { item, .. } = stmt { + fill_impl(item, defaults); + return; + } + let Stmt::Impl { + trait_name: Some(trait_name), + methods, + .. + } = stmt + else { + return; + }; + let Some(trait_defaults) = defaults.get(trait_name.as_str()) else { + return; + }; + for default in trait_defaults { + let Some(name) = method_name(default) else { + continue; + }; + if methods.iter().any(|m| method_name(m) == Some(name)) { + continue; + } + methods.push(default.clone()); + } +} + +fn method_name(stmt: &Stmt) -> Option<&str> { + match stmt { + Stmt::Attributed { item, .. } => method_name(item), + Stmt::Function { name, .. } => Some(name.as_str()), + _ => None, + } +} diff --git a/core/src/syntax.rs b/core/src/syntax.rs index 4e55d584..29bc88fd 100644 --- a/core/src/syntax.rs +++ b/core/src/syntax.rs @@ -6,9 +6,9 @@ use crate::{ ast::Parser as ExprParser, expr::Expr, macro_system::{ - AstMacroOrigin, MacroExpandOptions, MacroTokenOrigin, MacroTrace, ProcMacroDependency, - ProcMacroDependencyRecorder, ProcMacroOptions, ProcMacroProviders, expand_ast_macros_with_metadata, - expand_macros, + AstMacroOrigin, MacroDefinitions, MacroExpandOptions, MacroTokenOrigin, MacroTrace, PackageMacroModuleResolver, + ProcMacroDependency, ProcMacroDependencyRecorder, ProcMacroOptions, ProcMacroProviders, + expand_ast_macros_with_metadata, expand_macros, }, stmt::{Program, StmtParser}, token::{ParseError, Token, Tokenizer}, @@ -24,6 +24,16 @@ pub struct ParseOptions { pub base_dir: Option, pub macro_features: Vec, pub proc_macro_providers: ProcMacroProviders, + /// How a package-named macro import finds its module — see + /// [`PackageMacroModuleResolver`]. Defaulted to the package manager's own + /// lookup, which is what makes this the *only* place the two meet. + pub package_macro_resolver: Option, + /// `macro_rules!` definitions from an earlier parse to keep in scope. + /// + /// Empty for a single-shot compile, where a source text carries its own + /// definitions. A REPL parses each input separately and so must carry them + /// itself, or a macro stops existing at the end of the line that defined it. + pub carried_macro_definitions: MacroDefinitions, } #[derive(Debug, Clone)] @@ -33,6 +43,9 @@ pub struct SourceExpansion { pub origins: Vec, pub trace: Vec, pub proc_macro_dependencies: Vec, + /// What this source defined, for a caller that parses again and wants the + /// definitions still in scope — see [`ParseOptions::carried_macro_definitions`]. + pub macro_definitions: MacroDefinitions, } #[derive(Debug, Clone)] @@ -53,6 +66,11 @@ impl Default for ParseOptions { base_dir: None, macro_features: Vec::new(), proc_macro_providers: ProcMacroProviders::default(), + #[cfg(feature = "std")] + package_macro_resolver: Some(crate::package::macro_module_root), + #[cfg(not(feature = "std"))] + package_macro_resolver: None, + carried_macro_definitions: MacroDefinitions::default(), } } } @@ -75,12 +93,26 @@ pub fn expand_program_source(source: &str, options: ParseOptions) -> Result Option { - let type_error = err.downcast_ref::()?; - let expr = type_error.expr.as_ref()?; - span_for_expr(expr, tokens, spans) + typed_error_span(err.downcast_ref::()?, tokens, spans) +} + +/// The span of a type error that has already been unwrapped from `anyhow`. +/// +/// A tool that caches type errors cannot keep the `anyhow::Error` — it is not +/// `Clone` — but `TypeError` is, and this is all the span lookup ever needed. +pub fn typed_error_span( + type_error: &typ::TypeError, + tokens: &[Token], + spans: &[crate::token::Span], +) -> Option { + span_for_expr(type_error.expr.as_ref()?, tokens, spans) } fn format_macro_origin_stack(origin: &MacroTokenOrigin) -> String { @@ -210,11 +256,12 @@ fn span_for_literal(value: &LiteralVal, tokens: &[Token], spans: &[crate::token: spans, |token| matches!(token, Token::Str(lit) if Some(lit.as_str()) == value.as_str()), ), - LiteralVal::Int(expected) => find_token_span( - tokens, - spans, - |token| matches!(token, Token::Int(actual) if actual == expected), - ), + LiteralVal::Int(expected) => find_token_span(tokens, spans, |token| { + matches!(token, Token::Int(actual) if actual == expected) + // The bit-pattern spelling of the same carrier: the AST kept + // the `i64`, so this is the token it came from. + || matches!(token, Token::UInt { value, .. } if *value as i64 == *expected) + }), LiteralVal::Float(expected) => find_token_span( tokens, spans, @@ -251,12 +298,30 @@ fn origin_for_span<'a>(origins: &'a [MacroTokenOrigin], span: &crate::token::Spa }) } +/// Render a token stream back to source, one statement per line. +/// +/// The line breaks are the point. `lk macro expand` exists to be *read* — it is +/// the debugging tool `docs/macros.md` points at — and it used to answer with +/// the whole program on a single line: a 78-line example came back as one +/// 832-character line. Nothing downstream could help either, because `lk fmt` +/// is a line re-indenter and there was one line. +/// +/// Two breaks, both exact rather than guessed: +/// +/// - after `;`, which ends a statement in this language and nothing else; +/// - after a `}` whose *next* token starts a declaration. A `}` alone is not a +/// break — `let m = {"a": 1};` would gain one before its own semicolon — so +/// the following token decides. pub fn render_tokens(tokens: &[Token]) -> String { let mut output = String::new(); let mut prev: Option<&Token> = None; - for token in tokens { + for (index, token) in tokens.iter().enumerate() { let lexeme = token_lexeme(token); - if should_insert_space(prev, token) { + if output.ends_with('\n') { + // A fresh line owns its indentation; `lk fmt` supplies the rest. + } else if breaks_line_after(prev, token, tokens.get(index + 1)) { + output.push('\n'); + } else if should_insert_space(prev, token) { output.push(' '); } output.push_str(&lexeme); @@ -265,6 +330,24 @@ pub fn render_tokens(tokens: &[Token]) -> String { output } +/// Does a line end *before* `token`? +fn breaks_line_after(prev: Option<&Token>, token: &Token, _next: Option<&Token>) -> bool { + match prev { + Some(Token::Semicolon) => true, + Some(Token::RBrace) => starts_declaration(token), + _ => false, + } +} + +/// Tokens that can only begin a new top-level item, so a `}` before one is the +/// end of the previous item rather than part of an expression. +fn starts_declaration(token: &Token) -> bool { + matches!( + token, + Token::Fn | Token::Let | Token::Struct | Token::Impl | Token::Trait | Token::Use | Token::Hash + ) +} + pub fn render_program(program: &Program) -> String { let mut output = String::new(); for stmt in &program.statements { @@ -308,3 +391,48 @@ fn should_insert_space(prev: Option<&Token>, current: &Token) -> bool { } true } + +#[cfg(test)] +mod render_test { + #[cfg(not(feature = "std"))] + use crate::compat::prelude::*; + + use super::{ParseOptions, render_tokens, tokenize_and_expand}; + + /// The expansion comes back one statement per line. + /// + /// `lk macro expand` is the tool `docs/macros.md` points at for reading + /// what a macro produced, and it used to answer with the whole program on + /// one line — a 78-line example came back as a single 832-character line. + /// Nothing downstream could help either: `lk fmt` is a line re-indenter, + /// and there was one line. + #[test] + fn rendered_tokens_are_one_statement_per_line() { + let source = "fn a() -> Int { return 1; }\nfn b() -> Int { return a() + 1; }\nlet c = b();\n"; + let (tokens, _) = tokenize_and_expand(source, ParseOptions::default()).expect("expand"); + assert_eq!( + render_tokens(&tokens).lines().collect::>(), + vec![ + "fn a () -> Int {return 1;", + "}", + "fn b () -> Int {return a () + 1;", + "}", + "let c = b ();" + ] + ); + } + + /// A `}` that closes a map literal is not the end of a statement. + /// + /// Breaking on every `}` would put the `;` of `let m = {"a": 1};` on a line + /// of its own, which is why the *next* token decides. + #[test] + fn a_map_literals_brace_does_not_end_a_line() { + let source = "let m = {\"a\": 1};\nlet n = 2;\n"; + let (tokens, _) = tokenize_and_expand(source, ParseOptions::default()).expect("expand"); + assert_eq!( + render_tokens(&tokens).lines().collect::>(), + vec!["let m = {\"a\" : 1};", "let n = 2;"] + ); + } +} diff --git a/core/src/token.rs b/core/src/token.rs index 4f676e88..3bc08191 100644 --- a/core/src/token.rs +++ b/core/src/token.rs @@ -10,6 +10,145 @@ pub use lexer::*; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; +/// One piece of a template string's content: plain text, or a `${…}` interior. +/// +/// Both borrow from the content they were scanned out of, so reassembling a +/// template is a matter of writing the literals back verbatim and wrapping each +/// (possibly rewritten) expression in `${…}`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TemplateSegment<'a> { + Literal(&'a str), + /// The text between `${` and its matching `}`, braces excluded. + Expr(&'a str), +} + +/// Split a `TemplateString` token's content into literal and `${…}` pieces. +/// +/// One scanner, because there used to be two and they disagreed: the lexer +/// balances nested braces when deciding where an interpolation ends, and the +/// parser's copy did not, so `"${R {}}"` reached the parser as `R {` and the +/// struct-literal parser read past the end of its stream and panicked. +/// +/// It is shared for a second reason now. A template is *one token* to the macro +/// expander, and its interior is only tokenized later, by the parser — so +/// nothing inside `${…}` was ever expanded or substituted. Teaching the expander +/// to look inside means it has to agree with the parser about where "inside" +/// starts and stops, and the way to guarantee that is to not have a second copy. +pub fn split_template_string(content: &str) -> Result>, TemplateScanError> { + let mut segments = Vec::new(); + let mut literal_start = 0usize; + let mut expr_start = None::; + let mut depth = 0usize; + + let chars: Vec<(usize, char)> = content.char_indices().collect(); + let mut index = 0usize; + while index < chars.len() { + let (byte_pos, ch) = chars[index]; + match expr_start { + Some(start) => { + if ch == '{' { + depth += 1; + } else if ch == '}' && depth > 0 { + depth -= 1; + } else if ch == '}' { + segments.push(TemplateSegment::Expr(&content[start..byte_pos])); + expr_start = None; + literal_start = byte_pos + ch.len_utf8(); + } + index += 1; + } + None if ch == '$' && index + 1 < chars.len() && chars[index + 1].1 == '{' => { + if literal_start < byte_pos { + segments.push(TemplateSegment::Literal(&content[literal_start..byte_pos])); + } + index += 2; + expr_start = Some(if index < chars.len() { + chars[index].0 + } else { + content.len() + }); + } + None => index += 1, + } + } + + if expr_start.is_some() { + return Err(TemplateScanError::Unclosed); + } + if literal_start < content.len() { + segments.push(TemplateSegment::Literal(&content[literal_start..])); + } + Ok(segments) +} + +/// Write segments back out as template content, inverse of [`split_template_string`]. +pub fn join_template_segments(segments: &[TemplateSegment<'_>]) -> String { + let mut out = String::new(); + for segment in segments { + match segment { + TemplateSegment::Literal(text) => out.push_str(text), + TemplateSegment::Expr(text) => { + out.push_str("${"); + out.push_str(text); + out.push('}'); + } + } + } + out +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TemplateScanError { + Unclosed, +} + +/// The word a keyword token spells, when it may stand in for an identifier. +/// +/// Keywords are reserved *everywhere*, which is more than the grammar needs: a +/// **member** is always reached through `.` or declared inside a `struct` / +/// `impl` / `trait` body, and none of those positions can start a statement. So +/// `db.select()`, `parser.match(x)` and `struct Row { type: String }` were +/// syntax errors for no reason a reader could act on. +/// +/// The value literals (`true`, `false`, `nil`) are deliberately absent: they are +/// values, not keywords, and `p.nil` reads as nothing. +/// +/// A *top-level* `fn` keeps the restriction — a call to it is a bare name in +/// expression position, where `select(1)` and `select { … }` would have to be +/// told apart. +pub fn keyword_as_name(token: &Token) -> Option<&'static str> { + Some(match token { + Token::In => "in", + Token::If => "if", + Token::Else => "else", + Token::While => "while", + Token::Let => "let", + Token::Const => "const", + Token::Break => "break", + Token::Continue => "continue", + Token::Defer => "defer", + Token::Return => "return", + Token::Fn => "fn", + Token::Use => "use", + Token::From => "from", + Token::As => "as", + Token::For => "for", + Token::Go => "go", + Token::Match => "match", + Token::Unsafe => "unsafe", + Token::Try => "try", + Token::Catch => "catch", + Token::Select => "select", + Token::Case => "case", + Token::Default => "default", + Token::Type => "type", + Token::Struct => "struct", + Token::Trait => "trait", + Token::Impl => "impl", + _ => return None, + }) +} + /// The source text a token was written as. /// /// Lives here rather than in the macro system: it is a property of `Token` @@ -32,8 +171,13 @@ pub fn token_lexeme(token: &Token) -> String { Token::Semicolon => ";".to_string(), Token::Dollar => "$".to_string(), Token::Hash => "#".to_string(), + Token::At => "@".to_string(), + Token::Defer => "defer".to_string(), Token::Assign => "=".to_string(), Token::AddAssign => "+=".to_string(), + Token::BitAndAssign => "&=".to_string(), + Token::BitOrAssign => "|=".to_string(), + Token::BitXorAssign => "^=".to_string(), Token::SubAssign => "-=".to_string(), Token::MulAssign => "*=".to_string(), Token::DivAssign => "/=".to_string(), @@ -49,6 +193,7 @@ pub fn token_lexeme(token: &Token) -> String { Token::And => "&&".to_string(), Token::Or => "||".to_string(), Token::BitAnd => "&".to_string(), + Token::BitXor => "^".to_string(), Token::BitNot => "~".to_string(), Token::Not => "!".to_string(), Token::Add => "+".to_string(), @@ -92,8 +237,28 @@ pub fn token_lexeme(token: &Token) -> String { Token::Str(value) => format!("\"{}\"", value.escape_default()), Token::TemplateString(value) => format!("\"{}\"", value.escape_default()), Token::Int(value) => value.to_string(), + // Printed back at the radix it was written at: the decimal spelling of + // a 64-bit mask is not what anyone wrote, and this text is what + // `lk macro expand` shows. (`Token::Int` does *not* keep its radix, so + // a mask that fits in an `i64` still comes back in decimal — see the + // note on `Token::UInt`.) + Token::UInt { value, radix } => render_radix(*value, *radix), Token::Float(value) => value.to_string(), Token::Bool(value) => value.to_string(), Token::Id(value) => value.clone(), } } + +/// A `u64` literal written back at the radix it was written at. +/// +/// The separators a programmer used (`0x3F20_0000`) are not recoverable — the +/// lexer drops them — so this is the digits without them, which is the closest +/// this can get without keeping the lexeme itself. +pub fn render_radix(value: u64, radix: u32) -> alloc::string::String { + match radix { + 16 => alloc::format!("0x{value:X}"), + 8 => alloc::format!("0o{value:o}"), + 2 => alloc::format!("0b{value:b}"), + _ => alloc::format!("{value}"), + } +} diff --git a/core/src/token/error.rs b/core/src/token/error.rs index 4479d954..4519bd91 100644 --- a/core/src/token/error.rs +++ b/core/src/token/error.rs @@ -84,6 +84,22 @@ impl ParseError { } } + /// Whether the parse stopped because the input *ran out*, rather than + /// because it was wrong. + /// + /// Both parsers end a message with the context `(found end of input)` when + /// they reach the end of the token stream expecting more (`StmtParser::err` + /// and `Parser::err`, which is where that text is produced). It is the only + /// way to tell the two apart from outside: the message is the whole error. + /// + /// For a caller reading a *stream* of input — the REPL, deciding whether to + /// read another line. `let out = nums` followed by `.map(…)` on the next + /// line is one statement typed over two lines, and bracket depth cannot see + /// that: the first line closes every bracket it opens. + pub fn wants_more_input(&self) -> bool { + self.message.ends_with("(found end of input)") + } + pub fn display_with_source(&self, source: &str) -> String { self.display_with_source_color(source, false) } @@ -193,3 +209,43 @@ mod tests { assert_eq!(err2.to_string(), "syntax error at 2:10-10"); } } + +#[cfg(all(test, feature = "std"))] +mod wants_more_input_tests { + use crate::syntax::{ParseOptions, parse_program_source}; + + fn error_of(source: &str) -> crate::token::ParseError { + parse_program_source(source, ParseOptions::default()).expect_err("this source must not parse") + } + + /// The marker `ParseError::wants_more_input` reads is produced by the two + /// parsers' `err` helpers. If either stops writing it, this fails rather + /// than the REPL quietly going back to treating a half-typed statement as a + /// finished one. + #[test] + fn an_input_that_stopped_early_says_so() { + for source in [ + "let out = nums", + "let out = nums\n .map(|v| v)", + "fn f(a: Int", + "let m = {\"a\": 1", + ] { + assert!( + error_of(source).wants_more_input(), + "expected `{source}` to read as unfinished" + ); + } + } + + /// And an input that is *wrong* rather than unfinished does not, or the + /// session would wait forever for a line that cannot help. + #[test] + fn an_input_that_is_wrong_does_not() { + for source in ["fn 3() {}", "let x = ;", "let x = 1 2;"] { + assert!( + !error_of(source).wants_more_input(), + "expected `{source}` to read as wrong, not unfinished" + ); + } + } +} diff --git a/core/src/token/lexer.rs b/core/src/token/lexer.rs index 7accffc3..2a679641 100644 --- a/core/src/token/lexer.rs +++ b/core/src/token/lexer.rs @@ -7,26 +7,43 @@ use anyhow::{Result, anyhow}; #[derive(Debug, Clone, PartialEq)] pub enum Token { - LParen, // ( - RParen, // ) - LBrace, // { - RBrace, // } - LBracket, // [ - RBracket, // ] - Dot, // . - ColonColon, // :: - OptionalDot, // ?. - Colon, // : - Comma, // , - Semicolon, // ; - Dollar, // $ - Hash, // # + LParen, // ( + RParen, // ) + LBrace, // { + RBrace, // } + LBracket, // [ + RBracket, // ] + Dot, // . + ColonColon, // :: + OptionalDot, // ?. + Colon, // : + Comma, // , + Semicolon, // ; + Dollar, // $ + Hash, // # + /// `defer` — run this when the function is done, whichever way it leaves. + Defer, + /// `@`, which the grammar gives no meaning to. + /// + /// It exists so `macro_rules!` can use it the way Rust's do: as the marker + /// on an internal rule (`(@from $prev:expr, …)`), where the point is a token + /// that is legal in a token stream and illegal in every position a user + /// would write by hand — so a caller cannot reach the internal rules by + /// accident. LK's macros are Rust-shaped, and a macro ported from Rust hits + /// this within the first ten minutes. + /// + /// Outside a macro it is still an error; it is a *parse* error now rather + /// than a lexer one, which is the same answer with a better message. + At, // @ Assign, // = AddAssign, // += SubAssign, // -= MulAssign, // *= DivAssign, // /= ModAssign, // %= + BitAndAssign, // &= + BitOrAssign, // |= + BitXorAssign, // ^= Nil, // nil Eq, // == Ne, // != @@ -38,6 +55,7 @@ pub enum Token { And, // && Or, // || BitAnd, // & + BitXor, // ^ BitNot, // ~ Not, // ! Add, // + @@ -92,9 +110,28 @@ pub enum Token { Str(String), // "abc" TemplateString(String), // Formatted string content with ${...} Int(i64), // 1 - Float(f64), // 1.1 - Bool(bool), // true, false - Id(String), // identifier + /// A literal that needs all 64 bits: `0x8000_0000_0000_0000` upwards, and + /// the decimal spelling of the same numbers. + /// + /// Separate from `Int` because the carrier cannot hold the distinction. The + /// lexer reads `0x…` as a *bit pattern* (see `parse_radix_int`), so a value + /// above `i64::MAX` comes back as a negative `i64` — indistinguishable from + /// the `-1` a programmer wrote, and this language has no unary minus to tell + /// them apart by shape. Keeping the `u64` here is what lets the parser turn + /// the first into `… as u64` and leave the second refused. + /// + /// `radix` is how it was written, so re-rendering it (`lk macro expand`) + /// gives the text back rather than a re-spelling. Load-bearing now that + /// decimal reaches here too: `18446744073709551615` printed as + /// `0xFFFFFFFFFFFFFFFF` is not what anyone wrote either. + UInt { + value: u64, + /// 10, 16, 8 or 2. + radix: u32, + }, + Float(f64), // 1.1 + Bool(bool), // true, false + Id(String), // identifier } const ASCII_WHITESPACE: u8 = 1 << 0; @@ -152,18 +189,8 @@ fn is_ident_continue(c: char) -> bool { } } -#[inline] -fn is_alnum_char(c: char) -> bool { - let flags = ascii_flags(c); - if flags != 0 { - flags & (ASCII_ALPHA | ASCII_DIGIT) != 0 - } else { - c.is_alphanumeric() - } -} - /// [chars] and [idx] can be used for syntax error reporting. -pub struct Tokenizer<'a> { +pub struct Tokenizer { chars: Vec, idx: usize, len: usize, @@ -171,10 +198,9 @@ pub struct Tokenizer<'a> { pub token_spans: Option>, line: u32, column: u32, - input: &'a str, } -impl<'a> Tokenizer<'a> { +impl Tokenizer { pub fn tokenize(s: &str) -> Result> { let chars: Vec = s.chars().collect(); let mut t = Tokenizer { @@ -185,7 +211,6 @@ impl<'a> Tokenizer<'a> { token_spans: None, line: 1, column: 1, - input: s, }; t.parse()?; Ok(t.tokens) @@ -219,7 +244,7 @@ impl<'a> Tokenizer<'a> { } /// Create a tokenizer with enhanced error reporting - pub fn new_enhanced(input: &'a str) -> Self { + pub fn new_enhanced(input: &str) -> Self { let chars: Vec = input.chars().collect(); Self { len: chars.len(), @@ -229,7 +254,6 @@ impl<'a> Tokenizer<'a> { token_spans: Some(Vec::with_capacity(input.len() / 4)), line: 1, column: 1, - input, } } @@ -269,8 +293,18 @@ impl<'a> Tokenizer<'a> { }; let l_idx = self.idx.saturating_sub(5); let r_idx = if r_idx > self.len { self.len } else { r_idx }; - let chars = &self.chars[l_idx..r_idx]; - let chars: String = chars.iter().collect(); + // Escaped, not raw: the snippet is source text, and a newline in it + // used to break the message across lines — an error a caller renders + // with a caret cannot have its own line breaks. + let chars: String = self.chars[l_idx..r_idx] + .iter() + .flat_map(|c| match c { + '\n' => "\\n".chars().collect::>(), + '\r' => "\\r".chars().collect(), + '\t' => "\\t".chars().collect(), + other => alloc::vec![*other], + }) + .collect(); let c = self.chars.get(self.idx); let ctx = if let Some(&c) = c { format!("'{}' at index {}, near '{}'", c, self.idx, chars) @@ -278,25 +312,10 @@ impl<'a> Tokenizer<'a> { format!("at end, near '{}'", chars) }; - // Use the stored input for better context if needed - let line_context = self.get_line_context(); - format!( - "Syntax error:\n{} ({})\nLine {}: {}", - msg.as_ref(), - ctx, - self.line, - line_context - ) - } - - /// Get the current line from input for error context - fn get_line_context(&self) -> String { - let target = (self.line as usize).saturating_sub(1); - self.input - .lines() - .nth(target) - .map(|line| line.to_string()) - .unwrap_or_default() + // One line, and the same "Syntax error: " prefix the statement parser + // uses — this used to be three lines with the source line embedded, which + // a caller that renders its own caret cannot lay out. + format!("Syntax error: {} ({})", msg.as_ref(), ctx) } fn advance_char(&mut self) { @@ -346,6 +365,37 @@ impl<'a> Tokenizer<'a> { Err(anyhow!(self.err("Block comment not closed"))) } + /// `u{XXXX}` after a backslash — one to six hex digits naming a Unicode + /// scalar value. Leaves `self.idx` just past the closing brace. + fn read_braced_unicode_escape(&mut self) -> Result { + self.advance_char(); // past 'u' + if self.eof() || self.chars[self.idx] != '{' { + return Err(anyhow!( + self.err("`\\u` must be followed by `{...}`, as in `\\u{4e2d}`") + )); + } + self.advance_char(); // past '{' + let mut digits = String::new(); + while !self.eof() && self.chars[self.idx] != '}' { + digits.push(self.chars[self.idx]); + self.advance_char(); + } + if self.eof() { + return Err(anyhow!(self.err("Unterminated `\\u{...}` escape"))); + } + self.advance_char(); // past '}' + if digits.is_empty() || digits.len() > 6 || !digits.chars().all(|c| c.is_ascii_hexdigit()) { + return Err(anyhow!( + self.err("`\\u{...}` takes one to six hex digits, as in `\\u{4e2d}`") + )); + } + let code = u32::from_str_radix(&digits, 16).expect("checked hex digits"); + // Surrogates and anything past U+10FFFF are not characters; rejecting + // them here is the difference between a clear message and a string that + // silently is not what it says. + char::from_u32(code).ok_or_else(|| anyhow!(self.err(alloc::format!("`\\u{{{digits}}}` is not a character")))) + } + fn parse_str(&mut self) -> Result<()> { // Supports interpolation inside '"' or '\'' using only ${...} let mut content = String::new(); @@ -404,8 +454,21 @@ impl<'a> Tokenizer<'a> { '"' => content.push('"'), '$' => content.push('$'), '0' => content.push('\0'), + // `\u{4e2d}` — a character by code point, the only way + // to write one that cannot be typed: a zero-width + // joiner, a non-breaking space, an astral emoji. There + // was none, and an unknown escape is kept verbatim, so + // `"\u{4e2d}"` printed itself back. + 'u' => { + let scalar = self.read_braced_unicode_escape()?; + content.push(scalar); + continue; + } _ => { - // For unknown escape sequences, keep the backslash and the character + // An unknown escape keeps its backslash rather than + // failing. That is load-bearing, not laxity: a regex + // pattern is an ordinary string here, and `"\\d"` + // has to survive to reach the engine. content.push('\\'); content.push(escaped_char); } @@ -420,7 +483,12 @@ impl<'a> Tokenizer<'a> { } } - Err(anyhow!(self.err("String not closed"))) + // Where it *opened* is the useful position: the error is discovered at + // end of input, which is nowhere near the quote that has no partner. + Err(anyhow!(self.err(format!( + "String not closed — the quote at {}:{} has no partner", + start_pos.line, start_pos.column + )))) } /// Parse Rust-style raw string literals: r"...", r#"..."#, r##"..."##, ... @@ -577,7 +645,28 @@ impl<'a> Tokenizer<'a> { } else { match num.parse() { Ok(i) => Token::Int(i), - Err(_) => return Err(anyhow!("{}: {}", self.err("Invalid int"), num)), + // Above `i64::MAX`, in decimal. The radix path has read these as + // `UInt` since `u64` existed; decimal did not, so `u64::MAX` had + // a hexadecimal spelling and no decimal one — the same number, + // accepted one way and a syntax error the other. + // + // Only unsigned overflow reaches `UInt`: a leading `-` is part + // of `num` here, so `-18446744073709551615` still fails both + // parses and stays refused. + Err(_) => match num.parse::() { + Ok(value) => Token::UInt { value, radix: 10 }, + // Digits that parse as neither: too big for 64 bits, or + // negative and too big. "Invalid int" said the number was + // malformed, which it is not — it is out of range, and the + // range is the thing the reader needs. + Err(_) => { + return Err(anyhow!( + "{}: {}", + self.err("integer literal out of range (Int is i64, and u64 is the widest)"), + num + )); + } + }, } }; let end_pos = self.current_position(); @@ -675,6 +764,10 @@ impl<'a> Tokenizer<'a> { self.push_span_only(Token::Continue, sp); return Ok(()); } + if let Some(sp) = match_kw(self, "defer") { + self.push_span_only(Token::Defer, sp); + return Ok(()); + } if let Some(sp) = match_kw(self, "return") { self.push_span_only(Token::Return, sp); return Ok(()); @@ -805,7 +898,14 @@ impl<'a> Tokenizer<'a> { let value = u64::from_str_radix(&digits, radix) .map_err(|_| anyhow!("{}: {}", self.err("Integer literal out of range"), digits))?; let end_pos = self.current_position(); - self.push_with_span(Token::Int(value as i64), start_pos, end_pos); + // Above `i64::MAX` the carrier is out of room, and *which* number was + // written stops being recoverable from it. `UInt` keeps it — see the + // variant's own note. + let token = match i64::try_from(value) { + Ok(fits) => Token::Int(fits), + Err(_) => Token::UInt { value, radix }, + }; + self.push_with_span(token, start_pos, end_pos); Ok(()) } @@ -880,6 +980,13 @@ impl<'a> Tokenizer<'a> { self.push_with_span(Token::Hash, start, end); Ok(()) } + '@' => { + let start = self.current_position(); + self.advance_char(); + let end = self.current_position(); + self.push_with_span(Token::At, start, end); + Ok(()) + } ',' => { let start = self.current_position(); self.advance_char(); @@ -967,6 +1074,12 @@ impl<'a> Tokenizer<'a> { let end = self.current_position(); self.push_with_span(Token::And, start, end); Ok(()) + } else if self.chars.get(self.idx + 1) == Some(&'=') { + self.advance_char(); + self.advance_char(); + let end = self.current_position(); + self.push_with_span(Token::BitAndAssign, start, end); + Ok(()) } else { self.advance_char(); let end = self.current_position(); @@ -986,24 +1099,17 @@ impl<'a> Tokenizer<'a> { // Disambiguate by looking behind at the previous non-whitespace char. // If the previous significant char indicates we're in the middle of an // expression (identifier, literal, closing bracket/paren/brace), treat as OR. - // If we're at expression start or after a delimiter like '=', '(', '{', ',', ';', - // treat as an empty-parameter closure "|| expr". - let mut prev_idx = start.offset.saturating_sub(1); - while prev_idx > 0 && is_space_char(self.chars[prev_idx]) { - prev_idx = prev_idx.saturating_sub(1); - } - let prev_char = if start.offset == 0 { - None - } else { - Some(self.chars[prev_idx]) - }; - - let is_after_expr = matches!(prev_char, Some(')' | ']' | '}' | '"' | '\'' | '`')) - || matches!(prev_char, Some(c) if is_alnum_char(c)); - - let is_after_delim = matches!(prev_char, None | Some('=' | '(' | '{' | ',' | ';' | ':')); - - if is_after_delim && !is_after_expr { + // `||` is a zero-parameter closure exactly where a *value* + // is expected, and the logical operator everywhere else — + // the same question `signed_number_can_start` answers for + // `-5`, so it is the same predicate. + // + // It used to look at the previous *character* and accept + // only `= ( { , ; :`. A character cannot see a keyword, so + // `return || 1;` lexed as a logical or ("Unexpected token: + // Or") while `let f = || 1;` was fine, and `[|| 1]` failed + // on the missing `[`. + if self.operand_can_start() { // Empty-parameter closure context: emit two Pipe tokens with spans let mid_pos = Position::new(start.line, start.column + 1, start.offset + 1); self.push_with_span(Token::Pipe, start, mid_pos.clone()); @@ -1012,6 +1118,10 @@ impl<'a> Tokenizer<'a> { // Logical OR self.push_with_span(Token::Or, start, end); } + } else if self.idx < self.len && self.chars[self.idx] == '=' { + self.advance_char(); + let end = self.current_position(); + self.push_with_span(Token::BitOrAssign, start, end); } else { // Single | for union types or closure start let end = self.current_position(); @@ -1110,7 +1220,6 @@ impl<'a> Tokenizer<'a> { Ok(()) } } - // Removed '@' context access; treat as unknown punctuation. '=' => { let start = self.current_position(); if self.expect("==") { @@ -1148,6 +1257,19 @@ impl<'a> Tokenizer<'a> { self.push_with_span(Token::BitNot, start, end); Ok(()) } + '^' => { + let start = self.current_position(); + self.advance_char(); + if self.idx < self.len && self.chars[self.idx] == '=' { + self.advance_char(); + let end = self.current_position(); + self.push_with_span(Token::BitXorAssign, start, end); + return Ok(()); + } + let end = self.current_position(); + self.push_with_span(Token::BitXor, start, end); + Ok(()) + } '>' => { let start = self.current_position(); if self.expect(">=") { @@ -1224,7 +1346,8 @@ impl<'a> Tokenizer<'a> { fn is_punctuation(&self, c: char) -> bool { matches!( c, - '(' | ')' + '@' | '(' + | ')' | '{' | '}' | '[' @@ -1238,6 +1361,7 @@ impl<'a> Tokenizer<'a> { | '#' | '&' | '|' + | '^' | '~' | '+' | '-' @@ -1251,6 +1375,16 @@ impl<'a> Tokenizer<'a> { ) } + /// Is a *value* expected at this point? + /// + /// Answers for the two places the lexer has to know: a leading `-` starts a + /// negative literal rather than a subtraction, and `||` opens a + /// zero-parameter closure rather than a logical or. One predicate, so the + /// two cannot disagree about what "here comes a value" means. + fn operand_can_start(&self) -> bool { + self.signed_number_can_start() + } + fn signed_number_can_start(&self) -> bool { let Some(previous) = self.tokens.last() else { return true; @@ -1266,8 +1400,12 @@ impl<'a> Tokenizer<'a> { | Token::Semicolon | Token::Dollar | Token::Hash + | Token::At | Token::Assign | Token::AddAssign + | Token::BitAndAssign + | Token::BitOrAssign + | Token::BitXorAssign | Token::SubAssign | Token::MulAssign | Token::DivAssign @@ -1282,6 +1420,7 @@ impl<'a> Tokenizer<'a> { | Token::And | Token::Or | Token::BitAnd + | Token::BitXor | Token::Not | Token::Add | Token::Sub @@ -1306,7 +1445,7 @@ impl<'a> Tokenizer<'a> { } } -impl<'a> Tokenizer<'a> { +impl Tokenizer { fn push_with_span(&mut self, token: Token, start: Position, end: Position) { self.tokens.push(token); if let Some(spans) = &mut self.token_spans { diff --git a/core/src/token/token_test.rs b/core/src/token/token_test.rs index 5ee18463..629d5dde 100644 --- a/core/src/token/token_test.rs +++ b/core/src/token/token_test.rs @@ -89,9 +89,21 @@ mod tests { assert_eq!(tokens, expected); } + /// `||` between two operands is the logical operator. + /// + /// The input used to be an operator soup (`>=<= && || == != ! > <`) with + /// nothing between the operators, where `||` sits exactly where a *value* + /// is expected — which is where it opens a zero-parameter closure. Real + /// code has operands, so the test has them now: the question the lexer + /// answers is "is a value expected here", and soup cannot ask it. #[test] fn punctuations() { - let t2 = Tokenizer::tokenize(">=<= && || == != ! > <"); + let t2 = Tokenizer::tokenize("a >= b <= c && d || e == f != g ! h > i < j"); + let operators: Vec = t2 + .unwrap() + .into_iter() + .filter(|token| !matches!(token, Token::Id(_))) + .collect(); let e2 = vec![ Token::Ge, Token::Le, @@ -103,7 +115,7 @@ mod tests { Token::Gt, Token::Lt, ]; - assert_eq!(t2.unwrap(), e2); + assert_eq!(operators, e2); } #[test] @@ -174,6 +186,44 @@ mod tests { assert_eq!(t, vec![Token::Str("Unknown\\xEscape".to_string())]); } + /// A character by code point — the only way to write one that cannot be + /// typed: a zero-width joiner, a non-breaking space, an astral emoji. + /// There was no such escape, and an unknown one is kept verbatim, so + /// `"\u{4e2d}"` used to print itself back. + #[test] + fn braced_unicode_escape() { + for (source, expected) in [ + (r#""\u{4e2d}""#, "\u{4e2d}"), + (r#""\u{41}""#, "A"), + (r#""\u{1F600}""#, "\u{1F600}"), + (r#""a\u{4e2d}b""#, "a\u{4e2d}b"), + ] { + let tokens = Tokenizer::tokenize(source).expect(source); + assert_eq!(tokens, vec![Token::Str(expected.to_string())], "{source}"); + } + + for source in [ + r#""\u4e2d""#, // no braces + r#""\u{}""#, // no digits + r#""\u{1234567}""#, // more than six + r#""\u{zz}""#, // not hex + r#""\u{D800}""#, // a surrogate is not a character + r#""\u{110000}""#, // past the last code point + r#""\u{4e2d""#, // unterminated + ] { + assert!(Tokenizer::tokenize(source).is_err(), "{source} should not lex"); + } + } + + /// An unknown escape keeps its backslash rather than failing, and that is + /// load-bearing: a regex pattern is an ordinary string here, so `"\d"` has + /// to survive to reach the engine. + #[test] + fn unknown_escapes_survive_for_regex_patterns() { + let tokens = Tokenizer::tokenize(r#""\d+\s*\w""#).expect("lex"); + assert_eq!(tokens, vec![Token::Str("\\d+\\s*\\w".to_string())]); + } + #[test] fn string_escape_incomplete() { // Test incomplete escape sequence at end of string @@ -901,12 +951,73 @@ line2""#, /// A full-width mask is a bit pattern, not an out-of-range number. Refusing /// the top bit would make `0xFFFF_FFFF_FFFF_FFFF` unwritable. + /// + /// It comes back as `UInt` rather than a wrapped `Int`, and that is the whole + /// point: as an `i64` carrier it is `-1`, indistinguishable from the `-1` a + /// programmer wrote — and this language has no unary minus to tell the two + /// apart by shape. The parser turns `UInt` into `… as u64`, which is why + /// `let x: u64 = 0xFFFF_FFFF_FFFF_FFFF` is accepted and `let x: u8 = -1` + /// stays refused. + /// The *decimal* spelling of the same numbers reaches `UInt` too. + /// + /// It did not: `0xFFFF_FFFF_FFFF_FFFF` was accepted and + /// `18446744073709551615` was `Invalid int` — one number, one spelling + /// taken and the other refused, in a language that has a `u64` type. The + /// radix travels with the token so re-rendering (`lk macro expand`) gives + /// the text back instead of re-spelling a mask in decimal or a decimal + /// number in hex. + #[test] + fn a_decimal_literal_above_i64_max_is_a_u64_too() { + assert_eq!( + Tokenizer::tokenize("18446744073709551615").unwrap(), + vec![Token::UInt { + value: u64::MAX, + radix: 10 + }] + ); + assert_eq!( + Tokenizer::tokenize("9223372036854775808").unwrap(), + vec![Token::UInt { + value: 1 << 63, + radix: 10 + }] + ); + // One below still fits the signed carrier, so nothing changes there. + assert_eq!( + Tokenizer::tokenize("9223372036854775807").unwrap(), + vec![Token::Int(i64::MAX)] + ); + // Past `u64` is out of range, and says so — it used to say the literal + // was invalid, which it is not. + let message = Tokenizer::tokenize("99999999999999999999999999") + .expect_err("past u64") + .to_string(); + assert!(message.contains("out of range"), "got: {message}"); + // A negative one is refused by both parses, which is what keeps + // `let y: u8 = -1` refused. + assert!(Tokenizer::tokenize("-18446744073709551615").is_err()); + } + #[test] fn radix_literals_accept_the_full_bit_pattern() { - assert_eq!(Tokenizer::tokenize("0xFFFFFFFFFFFFFFFF").unwrap(), vec![Token::Int(-1)]); + assert_eq!( + Tokenizer::tokenize("0xFFFFFFFFFFFFFFFF").unwrap(), + vec![Token::UInt { + value: u64::MAX, + radix: 16 + }] + ); assert_eq!( Tokenizer::tokenize("0x8000000000000000").unwrap(), - vec![Token::Int(i64::MIN)] + vec![Token::UInt { + value: 1 << 63, + radix: 16 + }] + ); + // One below is still an `Int`: the carrier has room, so nothing is lost. + assert_eq!( + Tokenizer::tokenize("0x7FFFFFFFFFFFFFFF").unwrap(), + vec![Token::Int(i64::MAX)] ); } @@ -915,4 +1026,61 @@ line2""#, assert!(Tokenizer::tokenize("0x").is_err()); assert!(Tokenizer::tokenize("0b").is_err()); } + + /// A lexer error is one line, and an unterminated string says where it + /// opened. + /// + /// It used to be three: `"Syntax error:\n{msg}\nLine {n}: {source}"`, with + /// the near context copied raw, so a newline inside it broke the message + /// again, and with `Line {n}` naming the line the *scan* reached — at end + /// of input that is one past the file, so the field printed empty. The + /// caller renders the offending line with a caret itself; this only has to + /// say what is wrong and where the quote is. + #[test] + fn an_unterminated_string_names_its_opening_quote_on_one_line() { + let error = Tokenizer::tokenize("let s = \"abc\nlet b = 2;\n") + .expect_err("an unterminated string is an error") + .to_string(); + assert!(!error.contains('\n'), "the message is one line: {error}"); + assert!(error.contains("1:9"), "it names the opening quote: {error}"); + assert!(error.contains("String not closed"), "{error}"); + } +} + +/// A keyword may name a **member** — a field or a method — because a member is +/// only ever reached through `.` or declared inside a `struct`/`impl`/`trait` +/// body, and none of those positions can start a statement. Reserving them +/// everywhere was more than the grammar needed: `db.select()`, +/// `parser.match(x)` and `struct Row { type: String }` were syntax errors. +#[test] +fn every_keyword_can_name_a_member() { + #[cfg(not(feature = "std"))] + use crate::compat::prelude::*; + use crate::token::{Token, keyword_as_name}; + + for (token, word) in [ + (Token::Select, "select"), + (Token::Match, "match"), + (Token::Try, "try"), + (Token::Go, "go"), + (Token::Use, "use"), + (Token::Type, "type"), + (Token::As, "as"), + (Token::Impl, "impl"), + (Token::Trait, "trait"), + (Token::Defer, "defer"), + (Token::Fn, "fn"), + (Token::Return, "return"), + ] { + assert_eq!(keyword_as_name(&token), Some(word), "{token:?}"); + } + + // The value literals are values, not keywords: `p.nil` reads as nothing. + assert_eq!(keyword_as_name(&Token::Nil), None); + assert_eq!(keyword_as_name(&Token::Bool(true)), None); + // Nor is punctuation a name. + assert_eq!(keyword_as_name(&Token::LBrace), None); + assert_eq!(keyword_as_name(&Token::Comma), None); + // An identifier goes down the ordinary path, not this one. + assert_eq!(keyword_as_name(&Token::Id("select".to_string())), None); } diff --git a/core/src/typ.rs b/core/src/typ.rs index e3cbfe29..34ae4514 100644 --- a/core/src/typ.rs +++ b/core/src/typ.rs @@ -1,15 +1,22 @@ +mod builtin_method_sig; +pub(crate) mod declared_signature; /// Cross-file signatures. `std` only: it reads the imported file, and a target /// without a filesystem has no file imports to resolve. #[cfg(feature = "std")] mod imports; +pub(crate) mod stdlib_sig; mod type_checker; mod type_system; #[cfg(test)] mod function_infer_test; #[cfg(test)] +mod observation_test; +#[cfg(test)] mod or_pattern_binding_test; #[cfg(test)] +mod stdlib_sig_test; +#[cfg(test)] mod type_checker_test; #[cfg(test)] mod type_system_test; @@ -18,7 +25,16 @@ mod type_system_test; // here so `crate::typ::Numeric*` call sites stay stable. Breaks the val -> typ // dependency (a step toward extracting values into an L0 crate). pub use crate::val::{NumericClass, NumericHierarchy}; +pub use builtin_method_sig::{ + BUILTIN_METHODS, BuiltinMethodSig, BuiltinParam, BuiltinReceiverKind, ResolvedBuiltinMethod, builtin_method_arity, + builtin_method_signature, builtin_method_signature_with, builtin_methods_for, receiver_kind, slice_of, +}; #[cfg(feature = "std")] pub use imports::seed_imported_signatures; +pub use stdlib_sig::{ + ResolvedStdlibParam, ResolvedStdlibSig, StdlibCallableSig, StdlibGlobalArity, StdlibParamSig, + has_stdlib_signatures, register_stdlib_global, register_stdlib_signatures, stdlib_global_arity, + stdlib_global_is_declared, stdlib_module_is_declared, stdlib_path_is_declared, stdlib_signature, type_from_text, +}; pub use type_checker::*; pub use type_system::*; diff --git a/core/src/typ/builtin_method_sig.rs b/core/src/typ/builtin_method_sig.rs new file mode 100644 index 00000000..f5095506 --- /dev/null +++ b/core/src/typ/builtin_method_sig.rs @@ -0,0 +1,1301 @@ +//! The signatures of the built-in container methods. +//! +//! `xs.take(2)` is as much a part of the language as `math.abs(x)`, but until +//! this table existed only the second one had a declared type. The checker knew +//! seven method names — `len`, `is_empty`, `get`, `set`, `add`, `push`, `clear` +//! — and everything else was `Any`: `xs.first()` told you nothing, and +//! `xs.take("2")` was not an error until it ran. +//! +//! It also knew them by hand, in a `match` that was the *fourth* copy of this +//! knowledge: +//! +//! | copy | what it held | +//! | --- | --- | +//! | `vm/context/core_methods*` | the implementation and its arity checks | +//! | `typ/type_checker/expressions.rs` | types, for seven of them | +//! | `completion/src/lib.rs` | which names to offer per receiver | +//! | `lsp/src/server/handlers.rs` | the text shown in signature help | +//! +//! They had already drifted — completion offered no `slice`/`sort`/`pop`, and +//! signature help still described `take(list, n)` as "n <= 0 returns []", which +//! stopped being true when a negative count started raising. This module is the +//! one they now derive from, the same move `stdlib_sig` made for the modules. +//! +//! # The placeholders +//! +//! A module function's parameters are concrete; a method's are relative to its +//! receiver. So four names in the type texts below stand for parts of it, and +//! [`builtin_method_signature`] substitutes them: +//! +//! - `Elem` — a list's, set's or window's element type +//! - `Key`, `Val` — a map's +//! - `Self` — the receiver type itself, for the methods that hand it back +//! +//! Anything else is ordinary LK type text and is parsed as such. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; +use crate::val::Type; + +/// Which receiver a method belongs to — the type-level mirror of +/// `vm::context::core_methods`'s `BuiltinReceiver`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BuiltinReceiverKind { + List, + /// A `Bytes` handle. A sequence like the two below it, with the *read* half + /// of the list surface and none of the transforming half: `map` cannot + /// answer a `Bytes`, because a callback may return something that is not a + /// byte. `to_list` is the way across. + Bytes, + /// A window over a list (`xs.slice(a, b)`), which is its own type: it has + /// `to_list` and no `push`. + Slice, + Map, + Set, + Str, +} + +/// One declared parameter. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BuiltinParam { + pub name: &'static str, + /// LK type text, possibly using the placeholders above. + pub ty: &'static str, + /// May be omitted at the call site. + pub optional: bool, +} + +const fn p(name: &'static str, ty: &'static str) -> BuiltinParam { + BuiltinParam { + name, + ty, + optional: false, + } +} + +const fn opt(name: &'static str, ty: &'static str) -> BuiltinParam { + BuiltinParam { + name, + ty, + optional: true, + } +} + +/// One built-in method, as declared. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BuiltinMethodSig { + pub receiver: BuiltinReceiverKind, + pub name: &'static str, + pub params: &'static [BuiltinParam], + pub returns: &'static str, + /// One line, shown on hover and in completion. + pub docs: &'static str, + /// Which parameter, if any, is a callback applied to each element — its + /// first parameter is the receiver's element type. + /// + /// This is what lets `xs.map(|x| …)` type `x` at all: the closure arrives + /// with a fresh variable for its parameter, and nothing but the receiver + /// can say what it holds. + pub elementwise_callback: Option, + /// The last declared parameter may repeat, so a call may pass more + /// arguments than there are parameters. Only `format` needs it — a template + /// takes as many values as it has placeholders — and without it the checker + /// would reject `"{} {}".format(a, b)` for having "too many" arguments. + pub variadic: bool, +} + +use BuiltinReceiverKind::{Bytes, List, Map, Set, Slice, Str}; + +const fn m( + receiver: BuiltinReceiverKind, + name: &'static str, + params: &'static [BuiltinParam], + returns: &'static str, + docs: &'static str, +) -> BuiltinMethodSig { + BuiltinMethodSig { + receiver, + name, + params, + returns, + docs, + elementwise_callback: None, + variadic: false, + } +} + +/// [`m`] for a method whose last parameter may repeat. +const fn variadic_m( + receiver: BuiltinReceiverKind, + name: &'static str, + params: &'static [BuiltinParam], + returns: &'static str, + docs: &'static str, +) -> BuiltinMethodSig { + BuiltinMethodSig { + receiver, + name, + params, + returns, + docs, + elementwise_callback: None, + variadic: true, + } +} + +/// [`m`] for a method whose parameter `callback` is applied to each element. +const fn hof( + receiver: BuiltinReceiverKind, + name: &'static str, + params: &'static [BuiltinParam], + returns: &'static str, + docs: &'static str, + callback: usize, +) -> BuiltinMethodSig { + BuiltinMethodSig { + receiver, + name, + params, + returns, + docs, + elementwise_callback: Some(callback), + variadic: false, + } +} + +/// Every built-in method the language has. +/// +/// The return types describe what the implementation *does*, not what would be +/// tidy. `remove_at` really does hand back a two-element `[rest, removed]`, and +/// saying so here is what lets the checker reject `xs.remove_at(0) + 1`; +/// writing `Elem` because that reads better would make the table a wish. +pub const BUILTIN_METHODS: &[BuiltinMethodSig] = &[ + // ---- List ---- + m(List, "len", &[], "Int", "Number of elements"), + m(List, "is_empty", &[], "Bool", "Whether the list has no elements"), + m(List, "first", &[], "Elem?", "First element, or nil when empty"), + m(List, "last", &[], "Elem?", "Last element, or nil when empty"), + m( + List, + "get", + &[p("index", "Int")], + "Elem?", + "Element at `index` (negative counts from the end), or nil when out of range", + ), + m( + List, + "index_of", + &[p("value", "Any")], + "Int?", + "Position of the first equal element, or nil", + ), + // `index_of`'s sibling — how many rather than where. It was declared on + // `Str` alone, so `"aa".count("a")` answered 2 while `[1, 1].count(1)` was + // "List has no method 'count'". + m( + List, + "count", + &[p("value", "Any")], + "Int", + "How many elements equal `value`", + ), + m( + List, + "contains", + &[p("value", "Any")], + "Bool", + "Whether an element is equal to `value`", + ), + m( + List, + "push", + &[p("value", "Elem")], + "Self", + "The list with `value` appended", + ), + m( + List, + "clear", + &[], + "Self", + "Removes every element, in place; answers the list", + ), + m( + List, + "pop", + &[], + "Elem?", + "Removes and returns the last element, or nil when empty", + ), + m( + List, + "insert", + &[p("index", "Int"), p("value", "Elem")], + "Self", + "Inserts at `index`, in place; answers the list so calls chain", + ), + m( + List, + "remove_at", + &[p("index", "Int")], + "Elem", + "Removes the element at `index`, in place, and returns it", + ), + m( + List, + "set", + &[p("index", "Int"), p("value", "Elem")], + "Self", + "Writes `index` in place; answers the list so calls chain", + ), + m(List, "sort", &[], "Self", "A sorted copy (the receiver is untouched)"), + // The three reductions. `min`/`max` answer `Elem?` for the same reason + // `first` does — an empty list has none — and they use `sort`'s order, so + // `xs.sort().first()` and `xs.min()` cannot disagree. + m( + List, + "min", + &[], + "Elem?", + "The smallest element by `sort`'s order, or nil when empty", + ), + m( + List, + "max", + &[], + "Elem?", + "The largest element by `sort`'s order, or nil when empty", + ), + m( + List, + "sum", + &[], + "Any", + "The numbers added up (0 when empty); a non-number raises", + ), + m(List, "reverse", &[], "Self", "A reversed copy"), + // The inverse of `Bytes::to_list`, whose only spelling was the constructor + // `bytes.from_list(xs)` in another module. + m( + List, + "to_bytes", + &[], + "Bytes", + "The list as bytes; every item must be an Int in 0..=255", + ), + m( + List, + "unique", + &[], + "Self", + "A copy without later duplicates, order preserved", + ), + m( + List, + "take", + &[p("count", "Int")], + "Self", + "The first `count` elements; a negative count raises", + ), + m( + List, + "skip", + &[p("count", "Int")], + "Self", + "Everything after the first `count` elements; a negative count raises", + ), + m(List, "concat", &[p("other", "Self")], "Self", "The two lists joined"), + m(List, "chain", &[p("other", "Self")], "Self", "The two lists joined"), + m( + List, + "chunk", + &[p("size", "Int")], + "List", + "Groups of `size` elements; the size must be positive", + ), + m( + List, + "enumerate", + &[], + "List>", + "Each element paired with its position", + ), + m( + List, + "zip", + &[p("other", "List<_>")], + "List>", + "Elements paired positionally, up to the shorter length", + ), + m(List, "flatten", &[], "List", "One nesting level removed"), + m( + List, + "join", + &[p("separator", "String")], + "String", + "The elements joined; every element must be a String", + ), + m( + List, + "slice", + &[p("start", "Int"), opt("end", "Int")], + "Slice", + "A window over `[start, end)` — a view, not a copy (`to_list` copies)", + ), + // The higher-order three. `map`'s result element type is the callback's + // return type, which nothing here can name — `CallbackResult` is the + // placeholder the *call site* fills in, and `Any` is what it means when the + // argument is not a function literal the checker can read. + hof( + List, + "map", + &[p("transform", "Fn")], + "List", + "Each element through `transform`", + 0, + ), + hof( + List, + "filter", + &[p("predicate", "Fn")], + "Self", + "The elements `predicate` keeps (only nil and false drop one)", + 0, + ), + m( + List, + "reduce", + &[p("initial", "Any"), p("accumulate", "Fn")], + "Any", + "Folds `accumulate` over the elements from `initial`", + ), + // ---- Slice (a window over a list) ---- + m(Slice, "len", &[], "Int", "Number of elements in the window"), + m(Slice, "is_empty", &[], "Bool", "Whether the window is empty"), + m( + Slice, + "get", + &[p("index", "Int")], + "Elem?", + "Element at `index` within the window, or nil when outside it", + ), + m( + Slice, + "slice", + &[p("start", "Int"), opt("end", "Int")], + "Self", + "A narrower window, resolved against the original list", + ), + m(Slice, "to_list", &[], "List", "A copy of the window's elements"), + m( + Slice, + "first", + &[], + "Elem?", + "First element, or nil when the window is empty", + ), + m( + Slice, + "last", + &[], + "Elem?", + "Last element, or nil when the window is empty", + ), + m( + Slice, + "min", + &[], + "Elem?", + "The smallest element in the window, or nil when empty", + ), + m( + Slice, + "max", + &[], + "Elem?", + "The largest element in the window, or nil when empty", + ), + m(Slice, "sum", &[], "Any", "The window's numbers added up (0 when empty)"), + m( + Slice, + "contains", + &[p("value", "Any")], + "Bool", + "Whether an element of the window equals `value`", + ), + m( + Slice, + "index_of", + &[p("value", "Any")], + "Int?", + "Position within the window of the first equal element, or nil", + ), + m( + Slice, + "count", + &[p("value", "Any")], + "Int", + "How many elements in the window equal `value`", + ), + // A window is a range of its source, and a reversed range is not one — so + // this materializes where `take`/`skip`/`slice` answer sub-windows, the + // same rule `map` follows here. + m(Slice, "reverse", &[], "List", "The window's elements, reversed"), + m( + Slice, + "sort", + &[], + "List", + "The window's elements in ascending order", + ), + m(Slice, "enumerate", &[], "List>", "`[index, element]` pairs"), + m( + Slice, + "zip", + &[p("other", "List<_>")], + "List>", + "Pairs with `other`", + ), + m( + Slice, + "chain", + &[p("other", "List<_>")], + "List", + "The window's elements then `other`'s", + ), + m( + Slice, + "chunk", + &[p("size", "Int")], + "List>", + "Groups of `size` elements", + ), + // `concat` is `chain` under its other name; the window answers a list for + // the same reason. + m( + Slice, + "concat", + &[p("other", "List<_>")], + "List", + "The window's elements then `other`'s", + ), + m( + Slice, + "join", + &[p("separator", "String")], + "String", + "Elements joined by `separator`", + ), + m( + Slice, + "unique", + &[], + "List", + "The window's elements with later duplicates dropped, order kept", + ), + // A contiguous run of a window is still a window; what `filter` keeps is + // not contiguous, so it materializes a list. + m( + Slice, + "take", + &[p("count", "Int")], + "Self", + "The window's first `count` elements", + ), + m( + Slice, + "skip", + &[p("count", "Int")], + "Self", + "The window without its first `count` elements", + ), + hof( + Slice, + "map", + &[p("transform", "Fn")], + "List", + "Each element through `transform`", + 0, + ), + hof( + Slice, + "filter", + &[p("predicate", "Fn")], + "List", + "The elements `predicate` keeps", + 0, + ), + m( + Slice, + "reduce", + &[p("initial", "Any"), p("accumulate", "Fn")], + "Any", + "Folds `accumulate` over the window from `initial`", + ), + // ---- Bytes ---- + // + // The read half of the list surface, and the elements are `Int`. Every one + // of these means on a `Bytes` exactly what it means on a `List`, which is + // the test for belonging here. + m(Bytes, "len", &[], "Int", "Number of bytes"), + m(Bytes, "is_empty", &[], "Bool", "Whether there are no bytes"), + m(Bytes, "first", &[], "Int?", "First byte, or nil when empty"), + m(Bytes, "last", &[], "Int?", "Last byte, or nil when empty"), + m(Bytes, "min", &[], "Int?", "Smallest byte, or nil when empty"), + m(Bytes, "max", &[], "Int?", "Largest byte, or nil when empty"), + m(Bytes, "sum", &[], "Int", "The bytes added up (0 when empty)"), + m( + Bytes, + "get", + &[p("index", "Int")], + "Int?", + "Byte at `index` (negative counts from the end), or nil when out of range", + ), + m( + Bytes, + "contains", + &[p("value", "Any")], + "Bool", + "Whether a byte equals `value`", + ), + m( + Bytes, + "index_of", + &[p("value", "Any")], + "Int?", + "Position of the first byte equal to `value`, or nil", + ), + m( + Bytes, + "count", + &[p("value", "Any")], + "Int", + "How many bytes equal `value`", + ), + // Shape-preserving and element-type-independent, so a `Bytes` again — the + // reading `take`, `skip`, `slice` and `concat` already take here. + m(Bytes, "reverse", &[], "Bytes", "The bytes in reverse order"), + // Byte values are ordered scalars, so both mean here what they mean on a + // `List`, and both keep the carrier. + m(Bytes, "sort", &[], "Bytes", "The bytes in ascending order"), + // The operations whose answer is a *list of the elements*: they mean the + // same here as on a `List` and cannot keep the carrier, so they answer one. + m(Bytes, "enumerate", &[], "List>", "`[index, byte]` pairs"), + m( + Bytes, + "zip", + &[p("other", "List<_>")], + "List>", + "Pairs with `other`", + ), + m( + Bytes, + "chain", + &[p("other", "List<_>")], + "List", + "The bytes then `other`'s elements", + ), + m( + Bytes, + "chunk", + &[p("size", "Int")], + "List>", + "Groups of `size` bytes", + ), + m( + Bytes, + "join", + &[p("separator", "String")], + "String", + "Byte values joined by `separator`", + ), + m( + Bytes, + "unique", + &[], + "Bytes", + "The bytes with later duplicates dropped, order kept", + ), + m( + Bytes, + "slice", + &[p("start", "Int"), opt("end", "Int")], + "Bytes", + "The bytes in `[start, end)` — a copy, since `Bytes` has no cheap sub-range", + ), + m(Bytes, "to_list", &[], "List", "The bytes as a list of numbers"), + // The three that used to be reachable only as `bytes.f(b, …)`. + m( + Bytes, + "to_string_utf8", + &[], + "String", + "The bytes decoded as UTF-8; raises when they are not", + ), + m( + Bytes, + "to_string_lossy", + &[], + "String", + "The bytes decoded as UTF-8, with every invalid sequence replaced", + ), + m( + Bytes, + "concat", + &[p("other", "Bytes")], + "Bytes", + "These bytes followed by `other`'s", + ), + // Transforms. The rule is whether the result's elements can be something + // the receiver could not hold: `filter` keeps a subset, so it is still + // `Bytes`; `map` may answer anything, so it is a list. + m(Bytes, "take", &[p("count", "Int")], "Bytes", "The first `count` bytes"), + m( + Bytes, + "skip", + &[p("count", "Int")], + "Bytes", + "Everything after the first `count` bytes", + ), + hof( + Bytes, + "map", + &[p("transform", "Fn")], + "List", + "Each byte through `transform`", + 0, + ), + hof( + Bytes, + "filter", + &[p("predicate", "Fn")], + "Bytes", + "The bytes `predicate` keeps", + 0, + ), + m( + Bytes, + "reduce", + &[p("initial", "Any"), p("accumulate", "Fn")], + "Any", + "Folds `accumulate` over the bytes from `initial`", + ), + // ---- Map ---- + m(Map, "len", &[], "Int", "Number of entries"), + m(Map, "is_empty", &[], "Bool", "Whether the map has no entries"), + // The default is optional *here* too: the runtime has always accepted + // `get(key, default)` and only the declaration said otherwise, so the one + // form that avoids a nil check was a type error. + m( + Map, + "get", + &[p("key", "Key"), opt("default", "Val")], + "Val?", + "Value for `key`, or `default` when absent (nil without one)", + ), + m( + Map, + "set", + &[p("key", "Key"), p("value", "Val")], + "Self", + "Writes an entry, in place; answers the map so calls chain", + ), + m(Map, "has", &[p("key", "Any")], "Bool", "Whether `key` is present"), + m( + Map, + "delete", + &[p("key", "Any")], + "Val?", + "Removes `key`, returning its value", + ), + m( + Map, + "clear", + &[], + "Self", + "Removes every entry, in place; answers the map", + ), + m(Map, "keys", &[], "List", "The keys, in the map's iteration order"), + m( + Map, + "values", + &[], + "List", + "The values, in the map's iteration order", + ), + // ---- Set ---- + m(Set, "len", &[], "Int", "Number of members"), + m(Set, "is_empty", &[], "Bool", "Whether the set has no members"), + // Membership is `contains` on every value container — the four sequence + // types and this one. `has` is a *map's* spelling, where the question is + // about a key and "contains" would not say which of the two it means. + m( + Set, + "contains", + &[p("value", "Any")], + "Bool", + "Whether `value` is a member", + ), + m( + Set, + "add", + &[p("value", "Elem")], + "Bool", + "Adds a member, reporting whether it was new", + ), + m( + Set, + "delete", + &[p("value", "Any")], + "Bool", + "Removes a member, reporting whether it was there", + ), + m( + Set, + "clear", + &[], + "Self", + "Removes every member, in place; answers the set", + ), + m(Set, "values", &[], "List", "The members"), + // The set operations. A `Set` that can only add, delete, test a member and + // hand back a list is a deduplicating bag; these are what make it a set. + // The answers are filled in a stated order — the receiver's members first, + // then the argument's — because a set's iteration order is its hash order, + // so two sets with the same members can still iterate differently. + m(Set, "union", &[p("other", "Set")], "Self", "The members of both"), + m( + Set, + "intersection", + &[p("other", "Set")], + "Self", + "The members present in both", + ), + m( + Set, + "difference", + &[p("other", "Set")], + "Self", + "The members not in `other`", + ), + m( + Set, + "symmetric_difference", + &[p("other", "Set")], + "Self", + "The members in exactly one of the two", + ), + m( + Set, + "is_subset", + &[p("other", "Set")], + "Bool", + "Whether every member is also in `other`", + ), + m( + Set, + "is_superset", + &[p("other", "Set")], + "Bool", + "Whether every member of `other` is also here", + ), + m( + Set, + "is_disjoint", + &[p("other", "Set")], + "Bool", + "Whether the two share no member", + ), + // ---- String ---- + // + // Every position here is a *character* position, not a byte offset — see + // `util::text`. `bytes()` is the way down to bytes, deliberately explicit. + m(Str, "len", &[], "Int", "Number of characters"), + m(Str, "is_empty", &[], "Bool", "Whether the string has no characters"), + m(Str, "lower", &[], "String", "Lowercased"), + m(Str, "upper", &[], "String", "Uppercased"), + m(Str, "trim", &[], "String", "Without leading or trailing whitespace"), + m(Str, "reverse", &[], "String", "Characters in reverse order"), + m( + Str, + "repeat", + &[p("count", "Int")], + "String", + "The string repeated `count` times", + ), + m( + Str, + "starts_with", + &[p("prefix", "String")], + "Bool", + "Whether it starts with `prefix`", + ), + m( + Str, + "ends_with", + &[p("suffix", "String")], + "Bool", + "Whether it ends with `suffix`", + ), + m( + Str, + "contains", + &[p("needle", "Any")], + "Bool", + "Whether `needle` occurs", + ), + // The read surface every other sequence has. `slice` in particular reads + // the same as `List`/`Slice`/`Bytes` — start and end, not start and length + // — because `xs.slice(1, 3)` and `s.substring(1, 3)` taking different + // windows from the same numbers is a trap, not a feature. + m( + Str, + "slice", + &[p("start", "Int"), opt("end", "Int")], + "String", + "Characters in `[start, end)`, clamped; to the end without `end`", + ), + m( + Str, + "index_of", + &[p("needle", "Any")], + "Int?", + "Character position of the first occurrence, or nil", + ), + m( + Str, + "get", + &[p("index", "Int")], + "String?", + "The character at `index`, or nil", + ), + m(Str, "first", &[], "String?", "First character, or nil when empty"), + m(Str, "last", &[], "String?", "Last character, or nil when empty"), + m( + Str, + "take", + &[p("count", "Int")], + "String", + "The first `count` characters", + ), + m( + Str, + "skip", + &[p("count", "Int")], + "String", + "Everything after the first `count` characters", + ), + m( + Str, + "replace", + &[p("from", "String"), p("to", "String"), opt("all", "Bool")], + "String", + "Occurrences of `from` replaced; `all: false` replaces only the first", + ), + m( + Str, + "split", + &[p("delimiter", "String")], + "List", + "Split on `delimiter`", + ), + m(Str, "chars", &[], "List", "One string per character"), + m( + Str, + "bytes", + &[], + "Bytes", + "The UTF-8 bytes — the explicit way down from characters", + ), + m( + Str, + "byte_at", + &[p("index", "Int")], + "Int?", + "The byte at a *byte* offset, or nil when out of range", + ), + // The nine that used to exist only as `string` module functions. A module + // function whose first parameter is the receiver *is* a method, and having + // it in only one of the two places meant `s.strip("-")` did not exist while + // `string.strip(s, "-")` did. + m( + Str, + "capitalize", + &[], + "String", + "First character upper, the rest lower", + ), + m( + Str, + "title", + &[], + "String", + "First character of each whitespace-separated word upper, the rest lower", + ), + m( + Str, + "count", + &[p("needle", "Any")], + "Int", + "How many non-overlapping occurrences of `needle` there are", + ), + m( + Str, + "strip", + &[p("chars", "String")], + "String", + "Without leading or trailing characters that are in `chars`", + ), + m( + Str, + "strip_prefix", + &[p("prefix", "String")], + "String?", + "Without `prefix`, or nil when it does not start with it", + ), + m( + Str, + "strip_suffix", + &[p("suffix", "String")], + "String?", + "Without `suffix`, or nil when it does not end with it", + ), + m( + Str, + "pad_left", + &[p("width", "Int"), opt("fill", "String")], + "String", + "Widened to `width` characters by repeating `fill` (a space) on the left", + ), + m( + Str, + "pad_right", + &[p("width", "Int"), opt("fill", "String")], + "String", + "Widened to `width` characters by repeating `fill` (a space) on the right", + ), + variadic_m( + Str, + "format", + &[opt("values", "Any")], + "String", + "The receiver as a template: each `{}` takes the next value", + ), +]; + +/// A built-in method's signature with the receiver's types substituted in. +#[derive(Debug, Clone, PartialEq)] +pub struct ResolvedBuiltinMethod { + pub name: &'static str, + pub params: Vec<(&'static str, Type)>, + /// How many leading arguments a call must supply. + pub required: usize, + pub return_type: Type, + pub docs: &'static str, + /// See [`BuiltinMethodSig::elementwise_callback`]. + pub elementwise_callback: Option, + /// The receiver's element type, for a caller that needs to constrain a + /// callback's parameter against it. + pub elem: Type, + /// See [`BuiltinMethodSig::variadic`]. + pub variadic: bool, +} + +/// What the placeholders stand for, given a concrete receiver. +struct Bindings { + kind: BuiltinReceiverKind, + elem: Type, + key: Type, + val: Type, + receiver: Type, + /// What the call site says a callback parameter returns, if it could tell. + callback_result: Option, +} + +/// The declared signature of `method` on `receiver`, or `None` when the +/// receiver is not a built-in container or has no such method. +/// +/// `None` is the "say nothing" answer, not "this is an error": a receiver whose +/// type is still a variable, or a user type with a trait method of the same +/// name, must be left to the rest of the checker. +pub fn builtin_method_signature(receiver: &Type, method: &str) -> Option { + builtin_method_signature_with(receiver, method, None) +} + +/// [`builtin_method_signature`] with the call site's answer for +/// `CallbackResult` — the type the callback argument returns. +/// +/// `None` means the call site could not tell, and the placeholder widens to +/// `Any`, which is what every `map` used to be. +pub fn builtin_method_signature_with( + receiver: &Type, + method: &str, + callback_result: Option, +) -> Option { + let mut bindings = bind(receiver)?; + bindings.callback_result = callback_result; + let declared = BUILTIN_METHODS + .iter() + .find(|sig| sig.receiver == bindings.kind && sig.name == method)?; + Some(ResolvedBuiltinMethod { + name: declared.name, + params: declared + .params + .iter() + .map(|param| (param.name, resolve(param.ty, &bindings))) + .collect(), + required: declared.params.iter().filter(|param| !param.optional).count(), + return_type: resolve(declared.returns, &bindings), + docs: declared.docs, + elementwise_callback: declared.elementwise_callback, + elem: bindings.elem.clone(), + variadic: declared.variadic, + }) +} + +/// Every method available on `receiver`, for completion and signature help. +/// How many positional arguments a declared method takes: `(required, most)`. +/// +/// Answers `None` for a name this table does not declare, which is the signal +/// to leave the question to the implementation. +/// +/// The runtime dispatchers used to state their own arity in a `bail!` guard, +/// so the declaration and the implementation were two sources that drifted: +/// `bytes.slice`, `map.get` and `str.slice` each accepted a form the checker +/// rejected, or the reverse. This is the one source. +pub fn builtin_method_arity(receiver: BuiltinReceiverKind, method: &str) -> Option<(usize, usize)> { + let sig = BUILTIN_METHODS + .iter() + .find(|sig| sig.receiver == receiver && sig.name == method)?; + let required = sig.params.iter().filter(|param| !param.optional).count(); + Some((required, sig.params.len())) +} + +pub fn builtin_methods_for(receiver: BuiltinReceiverKind) -> impl Iterator { + BUILTIN_METHODS.iter().filter(move |sig| sig.receiver == receiver) +} + +/// The receiver kind a concrete type dispatches as, mirroring +/// `builtin_receiver_kind` in the VM. +pub fn receiver_kind(receiver: &Type) -> Option { + bind(receiver).map(|b| b.kind) +} + +fn bind(receiver: &Type) -> Option { + let (kind, elem, key, val) = match receiver { + Type::List(elem) => (List, (**elem).clone(), Type::Any, Type::Any), + Type::Set(elem) => (Set, (**elem).clone(), Type::Any, Type::Any), + Type::Map(k, v) => (Map, Type::Any, (**k).clone(), (**v).clone()), + Type::String => (Str, Type::Any, Type::Any, Type::Any), + // A window carries its element type: `xs.slice(..)` on a `List` is + // a `Slice`, and losing that is what made `let s: String = w[0]` + // type-check for as long as the window was an opaque handle. + Type::Named(name) if name == "Bytes" => (Bytes, Type::Int, Type::Any, Type::Any), + Type::Generic { name, params } if name == "Slice" => ( + Slice, + params.first().cloned().unwrap_or(Type::Any), + Type::Any, + Type::Any, + ), + // A tuple is a list (`is_assignable_to` says so), so it answers the + // list methods — with the element type it can honestly state. + Type::Tuple(elems) => { + let elem = if elems.is_empty() { + Type::Any + } else if elems.iter().all(|e| *e == elems[0]) { + elems[0].clone() + } else { + Type::Any + }; + (List, elem, Type::Any, Type::Any) + } + _ => return None, + }; + Some(Bindings { + kind, + elem, + key, + val, + receiver: receiver.clone(), + callback_result: None, + }) +} + +fn resolve(text: &str, bindings: &Bindings) -> Type { + let parsed = Type::parse(text).unwrap_or(Type::Any); + substitute(&parsed, bindings) +} + +/// Replace the placeholder names with what they stand for. +/// +/// They arrive as `Type::Named` because the parser has never heard of them, +/// which is exactly what makes this a substitution rather than a special case +/// in the parser. +fn substitute(ty: &Type, bindings: &Bindings) -> Type { + match ty { + Type::Named(name) => match name.as_str() { + "Elem" => bindings.elem.clone(), + "Key" => bindings.key.clone(), + "Val" => bindings.val.clone(), + "Self" => bindings.receiver.clone(), + "CallbackResult" => bindings.callback_result.clone().unwrap_or(Type::Any), + // Not a placeholder: hand it to the same table the standard + // library's declarations go through, so `Fn` and `Bytes` mean here + // what they mean there rather than becoming a named type nothing + // is assignable to. + _ => crate::typ::type_from_text(name), + }, + Type::List(inner) => Type::List(Box::new(substitute(inner, bindings))), + Type::Set(inner) => Type::Set(Box::new(substitute(inner, bindings))), + Type::Optional(inner) => Type::Optional(Box::new(substitute(inner, bindings))), + Type::Boxed(inner) => Type::Boxed(Box::new(substitute(inner, bindings))), + Type::Map(k, v) => Type::Map(Box::new(substitute(k, bindings)), Box::new(substitute(v, bindings))), + Type::Tuple(elems) => Type::Tuple(elems.iter().map(|e| substitute(e, bindings)).collect()), + Type::Union(arms) => Type::Union(arms.iter().map(|a| substitute(a, bindings)).collect()), + Type::Generic { name, params } => Type::Generic { + name: name.clone(), + params: params.iter().map(|p| substitute(p, bindings)).collect(), + }, + other => other.clone(), + } +} + +/// The type a window over `List` has. +pub fn slice_of(elem: Type) -> Type { + Type::Generic { + name: "Slice".to_string(), + params: vec![elem], + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sig(receiver: Type, method: &str) -> ResolvedBuiltinMethod { + builtin_method_signature(&receiver, method).unwrap_or_else(|| panic!("no signature for {method}")) + } + + fn list_of(elem: Type) -> Type { + Type::List(Box::new(elem)) + } + + #[test] + fn a_methods_types_come_from_its_receiver() { + assert_eq!( + sig(list_of(Type::Int), "first").return_type, + Type::Optional(Box::new(Type::Int)) + ); + assert_eq!( + sig(list_of(Type::String), "get").return_type, + Type::Optional(Box::new(Type::String)) + ); + // `Self` really is the receiver, element type included. + assert_eq!(sig(list_of(Type::Int), "sort").return_type, list_of(Type::Int)); + assert_eq!( + sig(list_of(Type::Int), "chunk").return_type, + list_of(list_of(Type::Int)) + ); + assert_eq!( + sig(Type::Map(Box::new(Type::String), Box::new(Type::Int)), "keys").return_type, + list_of(Type::String) + ); + assert_eq!( + sig(Type::Map(Box::new(Type::String), Box::new(Type::Int)), "get").return_type, + Type::Optional(Box::new(Type::Int)) + ); + } + + #[test] + fn a_window_keeps_the_element_type_of_the_list_it_windows() { + // The whole reason `Slice` is generic: as an opaque handle it made + // `let s: String = xs.slice(0, 1)[0];` type-check. + let window = sig(list_of(Type::Int), "slice").return_type; + assert_eq!(window, slice_of(Type::Int)); + assert_eq!( + sig(window.clone(), "get").return_type, + Type::Optional(Box::new(Type::Int)) + ); + assert_eq!(sig(window.clone(), "to_list").return_type, list_of(Type::Int)); + // A window of a window is still a window over the same elements. + assert_eq!(sig(window, "slice").return_type, slice_of(Type::Int)); + } + + #[test] + fn optional_parameters_are_the_ones_that_may_be_left_out() { + let slice = sig(list_of(Type::Int), "slice"); + assert_eq!(slice.params.len(), 2); + assert_eq!(slice.required, 1); + assert_eq!(slice.params[0].1, Type::Int); + } + + /// A callback parameter must not become a named type nothing can satisfy. + #[test] + fn a_callback_parameter_accepts_a_function() { + let map = sig(list_of(Type::Int), "map"); + assert_eq!(map.params[0].1, Type::Any); + assert_eq!(map.return_type, list_of(Type::Any)); + // `filter` keeps the element type; only `map` cannot know it. + assert_eq!(sig(list_of(Type::Int), "filter").return_type, list_of(Type::Int)); + } + + #[test] + fn a_receiver_the_table_has_nothing_for_says_nothing() { + assert!(builtin_method_signature(&Type::Int, "len").is_none()); + assert!(builtin_method_signature(&Type::Variable("a".into()), "len").is_none()); + // A list has no `clear` — the checker used to accept `xs.clear()` and + // the VM answers "List has no method 'clear'". + assert!(builtin_method_signature(&list_of(Type::Int), "clear").is_some()); + assert!(builtin_method_signature(&Type::Set(Box::new(Type::Int)), "clear").is_some()); + } + + /// Every entry must name a type the checker can act on. A typo in a + /// declaration would otherwise widen that method to `Any` silently, which + /// is the state this table exists to end. + #[test] + fn every_declared_type_resolves() { + let receivers = [ + (List, list_of(Type::Int)), + (Bytes, Type::Named("Bytes".to_string())), + (Slice, slice_of(Type::Int)), + (Map, Type::Map(Box::new(Type::String), Box::new(Type::Int))), + (Set, Type::Set(Box::new(Type::Int))), + (Str, Type::String), + ]; + for declared in BUILTIN_METHODS { + let receiver = receivers + .iter() + .find(|(kind, _)| *kind == declared.receiver) + .map(|(_, ty)| ty.clone()) + .expect("every receiver kind has a sample"); + let resolved = builtin_method_signature(&receiver, declared.name) + .unwrap_or_else(|| panic!("{:?}.{} did not resolve", declared.receiver, declared.name)); + for ((name, ty), param) in resolved.params.iter().zip(declared.params) { + // `Fn` and `Any` are the two texts that *mean* "unconstrained": + // a callback's signature is not stated here, and `reduce`'s + // seed genuinely is any value. Every other text reaching `Any` + // is a name the checker does not know — a typo, or a type this + // table has not been taught — and the method would be silently + // unchecked, which is the state it exists to end. + if matches!(param.ty, "Fn" | "Any") { + continue; + } + assert_ne!( + *ty, + Type::Any, + "{:?}.{}({name}: {}) resolved to Any", + declared.receiver, + declared.name, + param.ty + ); + } + assert!( + declared.returns == "Any" || resolved.return_type != Type::Any, + "{:?}.{} -> {} resolved to Any", + declared.receiver, + declared.name, + declared.returns + ); + } + } +} diff --git a/core/src/typ/declared_signature.rs b/core/src/typ/declared_signature.rs new file mode 100644 index 00000000..13cd6429 --- /dev/null +++ b/core/src/typ/declared_signature.rs @@ -0,0 +1,75 @@ +//! A `fn` declaration's *stated* signature — read, never inferred. +//! +//! Two callers need this before any body is checked, for the same reason: a +//! signature has to be visible from a call site the ordered walk has not +//! reached yet. `typ::imports` needs it for an imported `impl`, and +//! `Program::predeclare_impl_method_signatures` for a local one below its call. +//! +//! It lives here rather than in `typ::imports` because that module is `std` +//! only (it reads files) while this reads nothing but the AST — and a no_std +//! build of `lk-core` compiles the local pre-pass too. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; + +use crate::stmt::Stmt; +use crate::typ::{FunctionSig, NamedParamSig}; +use crate::val::{FunctionNamedParamType, Type}; + +/// The stated signature of one `fn` declaration, wherever it stands — top level +/// or inside an `impl`, where the receiver is simply its first parameter. +/// +/// Shared with the program's own `impl` pre-pass (`Program::predeclare_impl_ +/// method_signatures`): an imported impl and a local one below its call site +/// are the same problem — the signature has to be readable from the +/// declaration, before any body is checked. +pub(crate) fn signature_of_stmt(stmt: &Stmt) -> Option<(FunctionSig, Type)> { + let Stmt::Function { + params, + param_types, + named_params, + return_type, + .. + } = stmt + else { + return None; + }; + let positional: Vec = (0..params.len()) + .map(|i| param_types.get(i).cloned().flatten().unwrap_or(Type::Any)) + .collect(); + let annotated: Vec = (0..params.len()) + .map(|i| param_types.get(i).cloned().flatten().is_some()) + .collect(); + let named: Vec = named_params + .iter() + .map(|param| NamedParamSig { + name: param.name.clone(), + ty: param.type_annotation.clone().unwrap_or(Type::Any), + has_default: param.default.is_some(), + }) + .collect(); + let returns = return_type.clone().unwrap_or(Type::Any); + let named_annotations: Vec = named + .iter() + .map(|param| FunctionNamedParamType { + name: param.name.clone(), + ty: param.ty.clone(), + has_default: param.has_default, + }) + .collect(); + let function_type = Type::Function { + params: positional.clone(), + named_params: named_annotations, + return_type: Box::new(returns.clone()), + }; + Some(( + FunctionSig { + origin: Default::default(), + positional, + named, + return_type: Some(returns), + annotated, + }, + function_type, + )) +} diff --git a/core/src/typ/imports.rs b/core/src/typ/imports.rs index 17ad460e..d46490d6 100644 --- a/core/src/typ/imports.rs +++ b/core/src/typ/imports.rs @@ -18,8 +18,10 @@ use std::path::{Path, PathBuf}; use crate::stmt::{ImportSource, ImportStmt, Program, Stmt}; use crate::syntax::{ParseOptions, parse_program_source}; -use crate::typ::{FunctionSig, NamedParamSig, TypeChecker}; -use crate::val::{FunctionNamedParamType, Type}; +use crate::typ::declared_signature::signature_of_stmt; +use crate::typ::{FunctionSig, TypeChecker}; +use crate::typ::{StructDef, TraitDef, TraitImpl, TypeAlias}; +use crate::val::Type; /// Registers a signature for every function `program` imports from a file. /// @@ -42,18 +44,50 @@ pub fn seed_imported_signatures(program: &Program, base_dir: &Path, checker: &mu let Some(dep) = load(base_dir, path) else { continue; }; + seed_declared_types(&dep, checker); + seed_impl_methods(&dep, checker); for item in items { let bound = item.alias.clone().unwrap_or_else(|| item.name.clone()); - if let Some((signature, function_type)) = signature_of(&dep, &item.name) { + // A type imported by name is constructible by that name: + // the import binds the declaring module's generated + // constructor, so `P { … }` has something to call. + if checker.registry().get_struct(&item.name).is_some() { + checker.registry_mut().mark_constructible_import(&bound, &item.name); + } + if let Some((mut signature, function_type)) = signature_of(&dep, &item.name) { + // Where it came from, for the one rule that turns on it: + // a named parameter's default is filled by the compiler + // from the callee's declaration, which a caller in + // another module does not have. + signature.origin = crate::typ::SigOrigin::Imported; checker.add_function_sig(bound.clone(), signature); checker.add_local_type(bound, function_type); } } } - // `use "lib";` and `use * as m from "lib";` bind a namespace, whose - // members are reached as `m.f`. Member types are a separate - // mechanism from function signatures, so they are left alone here - // rather than half-registered under a made-up name. + // `use * as m from "lib";` binds a namespace whose members are + // reached as `m.f` — not a free `f`, since two namespaces may each + // export one. + ImportStmt::Namespace { + alias, + source: ImportSource::File(path), + } => { + let Some(dep) = load(base_dir, path) else { + continue; + }; + seed_namespace(alias, &dep, checker); + } + // `use "lib";` binds the file's stem, which is the name the module + // resolver defines it under. + ImportStmt::File { path } => { + let Some(namespace) = Path::new(path).file_stem().and_then(|stem| stem.to_str()) else { + continue; + }; + let Some(dep) = load(base_dir, path) else { + continue; + }; + seed_namespace(namespace, &dep, checker); + } _ => continue, } } @@ -118,60 +152,127 @@ fn load(base_dir: &Path, import_path: &str) -> Option { .ok() } +/// Registers every stated function signature in `dep` under `namespace`. +fn seed_namespace(namespace: &str, dep: &Program, checker: &mut TypeChecker) { + seed_declared_types(dep, checker); + seed_impl_methods(dep, checker); + for stmt in &dep.statements { + let Stmt::Function { name, .. } = item_of(stmt) else { + continue; + }; + if let Some((_, function_type)) = signature_of(dep, name) { + checker.add_imported_member(namespace, name.clone(), function_type); + } + } +} + +/// Register the `struct`s and `trait`s an imported module declares. +/// +/// A type crosses a module boundary by its bare name — `use * as L from +/// "./leaf"; fn passthru(v: Int) -> Deep` names `Deep`, not `L.Deep` — so the +/// importing file's checker has to know it. Only functions were seeded, which +/// went unnoticed while an unknown name silently became `Type::Named`: the +/// annotation type-checked against nothing and the program ran anyway. +fn seed_declared_types(dep: &Program, checker: &mut TypeChecker) { + for stmt in &dep.statements { + match item_of(stmt) { + Stmt::Struct { name, fields } => { + let fields = fields + .iter() + .map(|(field, ty)| (field.clone(), ty.clone().unwrap_or(Type::Any))) + .collect(); + checker.registry_mut().register_imported_struct(StructDef { + name: name.clone(), + fields, + }); + } + Stmt::Trait { name, methods, .. } => { + checker.registry_mut().register_trait(TraitDef { + name: name.clone(), + methods: methods.iter().cloned().collect(), + }); + } + // A `type` alias is a declared name like the other two, and crosses + // a module boundary the same way. + Stmt::TypeAlias { name, target } => { + checker.registry_mut().register_type_alias(TypeAlias { + name: name.clone(), + target_type: target.clone(), + }); + } + // Which imported type implements which imported trait. The + // *methods* crossed already (`seed_impl_methods`); the relation did + // not, so a trait written as a type accepted nothing from another + // module — `use "shapes"; shapes.render(c)` with `render(v: Shape)` + // reported "expected Shape, got Cat" for a `Cat` that implements it. + // + // No method indices, as in `predeclare_type_declarations`: they are + // the compiler's, and this runs before compilation. + Stmt::Impl { + trait_name: Some(trait_name), + target_type, + .. + } => { + let target_type = checker.resolve_aliases(target_type); + checker.registry_mut().register_trait_impl(TraitImpl { + trait_name: trait_name.clone(), + target_type, + methods: hashbrown::HashMap::new(), + }); + } + _ => {} + } + } +} + +/// Register the method signatures an imported module's `impl` blocks declare. +/// +/// A method reaches the checker by being *type-checked*: `stmt_impl`'s `Impl` +/// arm sets the impl's self type and each method body's check calls +/// `add_method_sig`. That only ever happens for the program's own statements, so +/// a method on an imported type was unknown to the checker — and unknown means +/// unchecked, not rejected: the call fell through to `Any`. Same file, +/// `impl Show for Int { fn show(self) -> String … }` and `a.show(1, 2)` was +/// refused ("Method expects 0 arguments"); with the impl one `use` away the same +/// call passed. +/// +/// The signature is read from the declaration, not inferred: an imported body is +/// not re-checked here, so an unannotated parameter is `Any` exactly as it is for +/// an imported free function. That keeps this from *tightening* anything — it +/// only makes the arity and the annotated types visible. +fn seed_impl_methods(dep: &Program, checker: &mut TypeChecker) { + for stmt in &dep.statements { + let Stmt::Impl { + target_type, methods, .. + } = item_of(stmt) + else { + continue; + }; + // Aliases resolve against the importing checker, which already has the + // dependency's `type` declarations (`seed_declared_types` ran first). + let self_ty = checker.resolve_aliases(target_type); + for method in methods { + let Stmt::Function { name, .. } = item_of(method) else { + continue; + }; + let Some((_, function_type)) = signature_of_stmt(item_of(method)) else { + continue; + }; + checker.add_method_sig(&self_ty, name, function_type); + } + } +} + /// The stated signature of a top-level `fn` in `program`. fn signature_of(program: &Program, name: &str) -> Option<(FunctionSig, Type)> { for stmt in &program.statements { - let Stmt::Function { - name: declared, - params, - param_types, - named_params, - return_type, - .. - } = item_of(stmt) - else { + let Stmt::Function { name: declared, .. } = item_of(stmt) else { continue; }; if declared != name { continue; } - let positional: Vec = (0..params.len()) - .map(|i| param_types.get(i).cloned().flatten().unwrap_or(Type::Any)) - .collect(); - let annotated: Vec = (0..params.len()) - .map(|i| param_types.get(i).cloned().flatten().is_some()) - .collect(); - let named: Vec = named_params - .iter() - .map(|param| NamedParamSig { - name: param.name.clone(), - ty: param.type_annotation.clone().unwrap_or(Type::Any), - has_default: param.default.is_some(), - }) - .collect(); - let returns = return_type.clone().unwrap_or(Type::Any); - let named_annotations: Vec = named - .iter() - .map(|param| FunctionNamedParamType { - name: param.name.clone(), - ty: param.ty.clone(), - has_default: param.has_default, - }) - .collect(); - let function_type = Type::Function { - params: positional.clone(), - named_params: named_annotations, - return_type: Box::new(returns.clone()), - }; - return Some(( - FunctionSig { - positional, - named, - return_type: Some(returns), - annotated, - }, - function_type, - )); + return signature_of_stmt(item_of(stmt)); } None } diff --git a/core/src/typ/observation_test.rs b/core/src/typ/observation_test.rs new file mode 100644 index 00000000..b96b190a --- /dev/null +++ b/core/src/typ/observation_test.rs @@ -0,0 +1,114 @@ +#[cfg(test)] +mod tests { + #[cfg(not(feature = "std"))] + use crate::compat::prelude::*; + use crate::{ + typ::{ObservedBinding, TypeChecker}, + val::Type, + }; + + fn observe(src: &str) -> Vec { + let program = crate::syntax::parse_program_source(src, Default::default()).expect("parse program"); + let mut checker = TypeChecker::new(); + checker.observe_bindings(); + program.type_check_collecting(&mut checker); + checker.take_observations() + } + + fn type_of<'a>(observations: &'a [ObservedBinding], name: &str) -> Option<&'a Type> { + observations + .iter() + .find(|binding| binding.name == name) + .map(|binding| &binding.ty) + } + + #[test] + fn bindings_are_recorded_with_their_inferred_types() { + let observed = observe("let a = 1; let b = \"x\"; let c = 1.5;"); + + assert_eq!(type_of(&observed, "a"), Some(&Type::Int)); + assert_eq!(type_of(&observed, "b"), Some(&Type::String)); + assert_eq!(type_of(&observed, "c"), Some(&Type::Float)); + } + + #[test] + fn a_binding_that_reads_an_earlier_one_is_recorded_too() { + // The whole point of recording during a program-wide check: `y` has a + // type only because `x` is in scope, which no per-expression inference + // in a fresh checker can know. + let observed = observe("let x = 2; let y = x + 1;"); + + assert_eq!(type_of(&observed, "y"), Some(&Type::Int)); + } + + #[test] + fn bindings_inside_a_function_body_are_recorded() { + // Recorded while the body's scope is live. A traversal afterwards would + // find the scope popped and `total` gone with it. + let observed = observe("fn f(n: Int) -> Int { let total = n * 2; return total; }"); + + assert_eq!(type_of(&observed, "total"), Some(&Type::Int)); + } + + #[test] + fn a_binding_carries_the_span_of_its_statement() { + let observed = observe("let a = 1;\nlet b = 2;"); + + let b = observed.iter().find(|binding| binding.name == "b").expect("b recorded"); + assert_eq!(b.span.start.line, 2, "second let is on line 2"); + } + + #[test] + fn each_name_of_a_destructuring_pattern_gets_its_own_type() { + let observed = observe("let [n, s] = [1, \"x\"];"); + + assert_eq!(type_of(&observed, "n"), Some(&Type::Int)); + assert_eq!(type_of(&observed, "s"), Some(&Type::String)); + } + + #[test] + fn a_short_declaration_types_what_it_binds() { + // `:=` used to be skipped by the checker outright, so the name it bound + // had no type and everything downstream of it fell back to a fresh type + // variable. + let observed = observe("x := 2;\nlet doubled = x * 2;"); + + assert_eq!(type_of(&observed, "x"), Some(&Type::Int)); + assert_eq!(type_of(&observed, "doubled"), Some(&Type::Int)); + } + + #[test] + fn collecting_reports_every_bad_statement_not_just_the_first() { + let program = crate::syntax::parse_program_source("let a: Int = \"x\"; let b: Bool = 1;", Default::default()) + .expect("parse program"); + let mut checker = TypeChecker::new(); + + let errors = program.type_check_collecting(&mut checker); + + assert_eq!( + errors.len(), + 2, + "both mismatches should be reported: {:?}", + errors.iter().map(ToString::to_string).collect::>() + ); + } + + #[test] + fn a_failed_statement_does_not_cost_the_next_one_its_types() { + // What error recovery buys an editor: one mistyped line used to take + // the type hints for every line below it. + let observed = observe("let bad: Int = \"x\"; let good = 7;"); + + assert_eq!(type_of(&observed, "good"), Some(&Type::Int)); + } + + #[test] + fn nothing_is_recorded_unless_asked_for() { + let program = crate::syntax::parse_program_source("let a = 1;", Default::default()).expect("parse program"); + let mut checker = TypeChecker::new(); + + program.type_check_collecting(&mut checker); + + assert!(checker.take_observations().is_empty()); + } +} diff --git a/core/src/typ/stdlib_sig.rs b/core/src/typ/stdlib_sig.rs new file mode 100644 index 00000000..8059abf2 --- /dev/null +++ b/core/src/typ/stdlib_sig.rs @@ -0,0 +1,365 @@ +//! The signatures the standard library hands to the type checker. +//! +//! `core` cannot depend on the standard library — the dependency runs the other +//! way — so what crosses the boundary is the declaration text that +//! `#[stdlib_export(params(...), returns = ...)]` already spells, not a +//! [`Type`]: those tables are `const`, and `Type` owns `Box`/`String`. Each text +//! becomes a `Type` once, the first time a signature is looked up. +//! +//! The alternative was the table this replaces: a hand-written `match` in the +//! type checker covering three modules out of twenty-three, with `math.abs` and +//! `env.get` typed `Any` because keeping a second copy accurate by hand is work +//! nobody signed up for. Generating both from the one declaration is what makes +//! the coverage total and the drift impossible. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; +use crate::{ + compat::{once::OnceLock, sync::Mutex}, + val::{FunctionNamedParamType, Type}, +}; +use hashbrown::HashMap; + +/// One parameter of a stdlib callable, as declared. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StdlibParamSig { + pub name: &'static str, + /// LK type source text — `String`, `Int?`, `List`, `Int | Float`. + pub ty: &'static str, + /// Declared `name?: T`: may be omitted at the call site. + pub optional: bool, + /// Declared inside `named(...)`: passed by name rather than by position. + pub named: bool, + pub has_default: bool, +} + +/// One stdlib callable's declared signature. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StdlibCallableSig { + /// Dotted path, as the language spells it: `string.trim`, `encoding.json.parse`. + pub path: &'static str, + pub params: &'static [StdlibParamSig], + /// LK type source text for the return type. + pub returns: &'static str, + /// False when the export declares more than one parameter list. + /// + /// An overloaded callable has no single type, and guessing one of its arms + /// would reject valid calls to the others — so the checker is told nothing + /// and falls back to inference, which is what it did before this table + /// existed. + pub single_arity: bool, +} + +/// One resolved parameter. +#[derive(Debug, Clone, PartialEq)] +pub struct ResolvedStdlibParam { + pub name: String, + pub ty: Type, + pub optional: bool, + pub named: bool, + pub has_default: bool, +} + +/// A callable's signature resolved into checker types. +#[derive(Debug, Clone, PartialEq)] +pub struct ResolvedStdlibSig { + /// Every declared parameter, in order. + /// + /// A `named(...)` parameter stays in this list: the generated arity check + /// lets one be passed positionally too, so dropping it here would make + /// `string.replace(text, pattern, with)` look like it had too many + /// arguments. + pub params: Vec, + pub return_type: Type, +} + +impl ResolvedStdlibSig { + /// How many leading arguments a call must supply. + pub fn required_params(&self) -> usize { + self.params.iter().filter(|param| !param.optional).count() + } + + /// The named parameters, in the shape a function type wants. + pub fn named_params(&self) -> Vec { + self.params + .iter() + .filter(|param| param.named) + .map(|param| FunctionNamedParamType { + name: param.name.clone(), + ty: if param.optional { + Type::Optional(Box::new(param.ty.clone())) + } else { + param.ty.clone() + }, + has_default: param.has_default, + }) + .collect() + } +} + +static REGISTRY: OnceLock>> = OnceLock::new(); + +fn registry() -> &'static Mutex> { + REGISTRY.get_or_init(|| Mutex::new(HashMap::new())) +} + +/// Record what a stdlib module declares. Registering the same path twice with +/// the same signature is fine — module registration is idempotent and happens +/// per `ModuleRegistry`, not once per process. +pub fn register_stdlib_signatures(signatures: &'static [StdlibCallableSig]) { + // `lock()` is fallible on std (a poisoned mutex) and infallible on the + // no_std shim, so `let Ok(..) else` reads as an irrefutable pattern there. + // `ok()` says the same thing in one shape both profiles accept. + let Some(mut registry) = registry().lock().ok() else { + return; + }; + for signature in signatures { + registry.insert(signature.path, *signature); + } +} + +/// The declared signature for a dotted path, resolved into checker types. +pub fn stdlib_signature(path: &str) -> Option { + let declared = { + let registry = registry().lock().ok()?; + *registry.get(path)? + }; + if !declared.single_arity { + return None; + } + Some(resolve(&declared)) +} + +/// The names the standard library registers as *global* callables — `println`, +/// `assert`, `spawn`, … — as told by whoever registered them. +/// +/// Kept apart from the signature registry above because it answers a different +/// question: those are dotted module members with declared parameters, while a +/// global is a bare name whose parameters are checked inside its own body. What +/// the checker can still say about one is the rule that holds for *all* of +/// them, and there is exactly one: a builtin global takes no named arguments. +/// How many positional arguments a builtin global accepts. +/// +/// `max: None` is genuinely variadic — `println` takes what it is given. The +/// bounded ones state their real range here, which is the point: the same fact +/// used to live in up to three places (the registry's arity, the native body's +/// own check, and a hand-written arm in the type checker), and only three +/// globals had it in the one place the checker could see. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct StdlibGlobalArity { + pub min: u16, + pub max: Option, +} + +fn global_arities() -> &'static Mutex> { + static ARITIES: OnceLock>> = OnceLock::new(); + ARITIES.get_or_init(|| Mutex::new(HashMap::new())) +} + +/// Records a builtin global and how many arguments it takes. Called once per +/// global at registration. +pub fn register_stdlib_global(name: &'static str, min: u16, max: Option) { + // Written as a combinator for the reason `register_stdlib_signatures` gives: + // the lock is fallible on std (a poisoned mutex) and infallible on the + // no_std shim, so a `let Ok(..) else` reads as an irrefutable pattern there. + let _ = global_arities() + .lock() + .map(|mut arities| arities.insert(name, StdlibGlobalArity { min, max })); +} + +/// Whether the standard library registers a global callable of this name. +pub fn stdlib_global_is_declared(name: &str) -> bool { + global_arities() + .lock() + .map(|arities| arities.contains_key(name)) + .unwrap_or(false) +} + +/// The declared argument count of a builtin global. +pub fn stdlib_global_arity(name: &str) -> Option { + global_arities().lock().ok()?.get(name).copied() +} + +/// Whether some stdlib module declares this exact dotted path. +/// +/// Membership, not "can I resolve a signature": `stdlib_signature` gives up on +/// anything that is not single-arity, so asking *it* would call `path.join` +/// undeclared and reject a working program. +pub fn stdlib_path_is_declared(path: &str) -> bool { + registry() + .lock() + .map(|registry| registry.contains_key(path)) + .unwrap_or(false) +} + +/// Whether some stdlib module of this name declares anything. +/// +/// The question a call site has to ask before saying "no such member": a dotted +/// call is `a.b.c()` for *any* `a`, so without this a struct field access would +/// be judged against the standard library. +pub fn stdlib_module_is_declared(module: &str) -> bool { + let prefix = alloc::format!("{module}."); + registry() + .lock() + .map(|registry| registry.keys().any(|path| path.starts_with(&prefix))) + .unwrap_or(false) +} + +/// True when any stdlib module has registered signatures. +/// +/// Lets a caller tell "this path has no declared signature" apart from "no +/// standard library is linked in at all", which is the case in `core`'s own +/// tests and on bare metal. +pub fn has_stdlib_signatures() -> bool { + registry().lock().map(|registry| !registry.is_empty()).unwrap_or(false) +} + +fn resolve(declared: &StdlibCallableSig) -> ResolvedStdlibSig { + ResolvedStdlibSig { + params: declared + .params + .iter() + .map(|param| ResolvedStdlibParam { + name: param.name.to_string(), + ty: type_from_text(param.ty), + optional: param.optional, + named: param.named, + has_default: param.has_default, + }) + .collect(), + return_type: type_from_text(declared.returns), + } +} + +/// Type names the standard library documents but the language has no variant +/// for. Spelled out rather than inferred, so that adding one is a decision +/// somebody makes rather than a silent widening to `Any`. +/// +/// These all name runtime handles that are opaque to the type system. `Number` +/// used to be here too, as the one true alias; it is a type the language can +/// write now, so `Type::parse` answers it and this table does not. +const DOCUMENTED_ALIASES: &[(&str, AliasTarget)] = &[ + // `Number` is not here any more: the language parses it now + // (`NUMBER_TYPE_NAME`), so `Type::parse` below answers it and this table + // does not need a second copy of the answer. + // Runtime handles. The checker cannot see *into* one, but it can tell them + // apart from each other and from everything else, which is the part that + // catches `bytes.slice(some_string, …)`. They were `Any` until now, so a + // handle stopped being checked the moment it was produced. + ("Bytes", AliasTarget::Handle), + ("Resource", AliasTarget::Handle), + ("Stream", AliasTarget::Handle), + ("Cursor", AliasTarget::Handle), + // A window, parameterised: bare `Slice` means `Slice`, so a + // declaration can accept one over any element type. As `Named("Slice")` it + // was a *different type* from the `Slice` values actually are. + ("Slice", AliasTarget::SliceOfAny), + // These two already have a type of their own; naming them would invent a + // second spelling for a type the language can write. + ("Task", AliasTarget::TaskOfAny), + ("Channel", AliasTarget::ChannelOfAny), + // `Value` is whatever `encoding.json.parse` decoded — genuinely any value, + // not an opaque handle. `Fn` is a callable whose signature the declaration + // does not state. + ("Value", AliasTarget::Anything), + ("Fn", AliasTarget::Anything), +]; + +/// Does the standard library document `name` as a runtime handle? +/// +/// These have no `Type` variant, so they reach the checker as `Type::Named` +/// and a user may legitimately write one in an annotation — which is why the +/// unknown-name check has to ask. +pub fn is_documented_handle_type(name: &str) -> bool { + DOCUMENTED_ALIASES.iter().any(|(declared, _)| *declared == name) +} + +#[derive(Clone, Copy)] +enum AliasTarget { + SliceOfAny, + /// A named type with no structure the checker can look inside. + Handle, + TaskOfAny, + ChannelOfAny, + Anything, +} + +/// Turn one declaration's type text into a checker type. +/// +/// Anything unrecognised lands on `Any`, never on `Type::Named`: a named type +/// the checker has never heard of does not merely fail to help, it makes an +/// ordinary call *fail to type-check*. `stdlib_sig_test` pins which texts take +/// this path, so a new one shows up as a test failure rather than as a silently +/// untyped function. +pub fn type_from_text(text: &str) -> Type { + let text = text.trim(); + if text.is_empty() { + return Type::Any; + } + + // A union is resolved arm by arm: `Bytes | String` has an opaque arm that + // would otherwise poison the whole type. + if let Some(arms) = split_union(text) { + let mut resolved = Vec::with_capacity(arms.len()); + for arm in &arms { + let ty = type_from_text(arm); + if ty == Type::Any { + // One unconstrained arm makes the union unconstrained. + return Type::Any; + } + resolved.push(ty); + } + return Type::Union(resolved); + } + + if let Some(inner) = text.strip_suffix('?') { + let inner = type_from_text(inner); + return if inner == Type::Any { + Type::Any + } else { + Type::Optional(Box::new(inner)) + }; + } + + if let Some((name, target)) = DOCUMENTED_ALIASES.iter().find(|(name, _)| *name == text) { + return match target { + AliasTarget::Handle => Type::Named((*name).to_string()), + AliasTarget::SliceOfAny => crate::typ::slice_of(Type::Any), + AliasTarget::TaskOfAny => Type::Task(Box::new(Type::Any)), + AliasTarget::ChannelOfAny => Type::Channel(Box::new(Type::Any)), + AliasTarget::Anything => Type::Any, + }; + } + + match Type::parse(text) { + // `Named` here means the text is neither a language type nor a + // documented alias — a typo in a declaration, or a type this table has + // not been taught yet. Either way the checker must not act on it. + Some(Type::Named(_)) | None => Type::Any, + Some(ty) => ty, + } +} + +/// Split `A | B` at the top level, returning `None` when there is no top-level +/// `|` to split on. +fn split_union(text: &str) -> Option> { + let mut depth = 0i32; + let mut arms = Vec::new(); + let mut start = 0usize; + for (idx, ch) in text.char_indices() { + match ch { + '<' | '[' | '(' => depth += 1, + '>' | ']' | ')' => depth -= 1, + '|' if depth == 0 => { + arms.push(text[start..idx].trim()); + start = idx + 1; + } + _ => {} + } + } + if arms.is_empty() { + return None; + } + arms.push(text[start..].trim()); + Some(arms) +} diff --git a/core/src/typ/stdlib_sig_test.rs b/core/src/typ/stdlib_sig_test.rs new file mode 100644 index 00000000..d964f0d7 --- /dev/null +++ b/core/src/typ/stdlib_sig_test.rs @@ -0,0 +1,154 @@ +use super::stdlib_sig::*; +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; +use crate::val::Type; + +#[test] +fn language_types_resolve_as_themselves() { + assert_eq!(type_from_text("Int"), Type::Int); + assert_eq!(type_from_text("String"), Type::String); + assert_eq!(type_from_text("Bool"), Type::Bool); + assert_eq!(type_from_text("Nil"), Type::Nil); + assert_eq!(type_from_text("List"), Type::List(Box::new(Type::String))); + assert_eq!( + type_from_text("Map"), + Type::Map(Box::new(Type::String), Box::new(Type::Int)) + ); + assert_eq!(type_from_text("String?"), Type::Optional(Box::new(Type::String))); + assert_eq!(type_from_text("Int | Float"), Type::Union(vec![Type::Int, Type::Float])); +} + +#[test] +fn number_is_the_documented_spelling_of_int_or_float() { + assert_eq!(type_from_text("Number"), Type::Union(vec![Type::Int, Type::Float])); +} + +#[test] +fn runtime_handles_are_named_types_not_any() { + // The checker cannot see inside a handle, but it can tell one from another + // and from everything else — which is what stops `bytes.slice(a_string, …)`. + for text in ["Bytes", "Resource", "Stream", "Cursor"] { + assert_eq!(type_from_text(text), Type::Named(text.to_string())); + } + // `Slice` is the exception: it is parameterised, and bare means + // `Slice`. As a plain named type it was a *different type* from the + // `Slice` a window actually is, so a declaration written `Slice` + // accepted no window at all. + assert_eq!(type_from_text("Slice"), crate::typ::slice_of(Type::Any)); + // `Task`/`Channel` have types of their own; naming them would invent a + // second spelling for something the language can already write. + assert_eq!(type_from_text("Task"), Type::Task(Box::new(Type::Any))); + assert_eq!(type_from_text("Channel"), Type::Channel(Box::new(Type::Any))); + // `Value` really is any value — it is what `encoding.json.parse` decoded. + assert_eq!(type_from_text("Value"), Type::Any); + assert_eq!(type_from_text("Fn"), Type::Any); +} + +#[test] +fn a_union_of_a_handle_and_a_value_keeps_both_arms() { + // `fs.write(path: String, data: Bytes | String)` accepts either, and now + // says so — the arms used to collapse to `Any` because `Bytes` did. + assert_eq!( + type_from_text("Bytes | String"), + Type::Union(vec![Type::Named("Bytes".to_string()), Type::String]) + ); +} + +#[test] +fn unknown_text_widens_to_any_rather_than_naming_a_type() { + // `Type::Named("Frobnicate")` would make every call to the function fail: + // no argument the checker can infer is assignable to a type it has never + // seen declared. + assert_eq!(type_from_text("Frobnicate"), Type::Any); + assert_eq!(type_from_text(""), Type::Any); +} + +#[test] +fn optional_of_a_handle_is_an_optional_handle() { + assert_eq!( + type_from_text("Resource?"), + Type::Optional(Box::new(Type::Named("Resource".to_string()))) + ); +} + +#[test] +fn resolving_splits_positional_named_and_optional_parameters() { + const PARAMS: &[StdlibParamSig] = &[ + StdlibParamSig { + name: "value", + ty: "Int", + optional: false, + named: false, + has_default: false, + }, + StdlibParamSig { + name: "min", + ty: "Int", + optional: true, + named: true, + has_default: true, + }, + ]; + const SIGS: &[StdlibCallableSig] = &[StdlibCallableSig { + path: "test_only.clamp", + params: PARAMS, + returns: "Int", + single_arity: true, + }]; + register_stdlib_signatures(SIGS); + + let resolved = stdlib_signature("test_only.clamp").expect("registered signature"); + // `min` stays in `params`: the generated arity check lets it be passed + // positionally, so leaving it out here would report too many arguments. + assert_eq!(resolved.params.len(), 2); + assert_eq!(resolved.params[0].ty, Type::Int); + assert!(!resolved.params[0].optional); + assert_eq!(resolved.required_params(), 1); + assert_eq!(resolved.return_type, Type::Int); + + let named = resolved.named_params(); + assert_eq!(named.len(), 1); + assert_eq!(named[0].name, "min"); + assert_eq!(named[0].ty, Type::Optional(Box::new(Type::Int))); + assert!(named[0].has_default); +} + +#[test] +fn an_overloaded_callable_declares_no_type() { + const SIGS: &[StdlibCallableSig] = &[StdlibCallableSig { + path: "test_only.overloaded", + params: &[], + returns: "Int", + single_arity: false, + }]; + register_stdlib_signatures(SIGS); + + assert!( + stdlib_signature("test_only.overloaded").is_none(), + "an overloaded callable has no single signature to hand the checker" + ); +} + +#[test] +fn a_registered_signature_is_looked_up_by_path() { + const PARAMS: &[StdlibParamSig] = &[StdlibParamSig { + name: "text", + ty: "String", + optional: false, + named: false, + has_default: false, + }]; + const SIGS: &[StdlibCallableSig] = &[StdlibCallableSig { + path: "test_only.len", + params: PARAMS, + returns: "Int", + single_arity: true, + }]; + register_stdlib_signatures(SIGS); + + let resolved = stdlib_signature("test_only.len").expect("registered signature"); + assert_eq!(resolved.params.len(), 1); + assert_eq!(resolved.params[0].ty, Type::String); + assert_eq!(resolved.return_type, Type::Int); + assert!(stdlib_signature("test_only.missing").is_none()); +} diff --git a/core/src/typ/type_checker.rs b/core/src/typ/type_checker.rs index 48d86836..686b2fb6 100644 --- a/core/src/typ/type_checker.rs +++ b/core/src/typ/type_checker.rs @@ -3,6 +3,7 @@ use crate::compat::collections::HashSet; use crate::compat::prelude::*; use crate::{ expr::Expr, + token::Span, typ::{TypeInferenceEngine, TypeRegistry}, val::{FunctionNamedParamType, Type}, }; @@ -10,6 +11,7 @@ use anyhow::Result; use hashbrown::HashMap; mod expressions; +pub use expressions::builtin_machine_result; mod patterns; #[cfg(test)] @@ -28,6 +30,20 @@ impl TypeCheckerOptions { } } +/// A binding and the type it was bound to, at the position it was written. +#[derive(Debug, Clone, PartialEq)] +pub struct ObservedBinding { + /// The `let`/`const` keyword through the end of the pattern. + pub span: Span, + pub name: String, + pub ty: Type, + /// True when the source already spells this type out. + /// + /// An inlay hint exists to show what was left unwritten, so it skips these; + /// hover wants them all. + pub annotated: bool, +} + /// Type checking error with location information #[derive(Debug, Clone)] pub struct TypeError { @@ -35,8 +51,40 @@ pub struct TypeError { pub expected: Option, pub actual: Option, pub expr: Option, + /// The statement the error was raised in. + /// + /// `Expr` carries no position, so an error about an expression can only be + /// placed by searching the token stream for something that looks like it — + /// which finds the *first* match, not this one (`let a = 1; let b = 1;` + /// reported the second one's error on the first). The enclosing statement + /// does have a position, and `Stmt::type_check` attaches it on the way out, + /// innermost first. + pub span: Option, pub function_name: Option, pub parameter_name: Option, + /// Whether this is a **lint** rather than a rejection. + /// + /// The implicit-`Any` finding is advice — `lk check` only reports it under + /// `--strict`, and a program carrying it compiles and runs. The editor + /// ran the strict checker unconditionally and rendered everything it said + /// as `ERROR`, so three of this repository's own examples showed a red + /// error in any LSP client while `lk check` accepted them. + /// + /// Carried by the producer rather than recovered by the consumer: the only + /// other way to tell is to match on the message text. + pub lint: bool, +} + +impl TypeError { + /// Remember the statement this error came from, if it does not know already. + /// + /// Innermost wins: a nested statement attaches its own span before an outer + /// one gets the chance, and the inner one is the smaller, truer range. + pub fn attach_span(&mut self, span: &Span) { + if self.span.is_none() { + self.span = Some(span.clone()); + } + } } impl core::fmt::Display for TypeError { @@ -45,6 +93,28 @@ impl core::fmt::Display for TypeError { if let (Some(expected), Some(actual)) = (&self.expected, &self.actual) { write!(f, " (expected {}, got {})", expected.display(), actual.display())?; } + // `TypeError` carries three things that say *where*: the offending + // expression, the function it was an argument to, and the statement's + // span. None of them were rendered, so `Argument 1 has the wrong type + // (expected Int, got Int?)` was the whole message — and in a + // four-thousand-line program that is not a diagnostic. Finding the one + // real instance of it took a bisect script that then got fooled by + // forward references. + // + // The expression is printed rather than the span because it is the field + // that is actually populated on this path: `Stmt::Expr` (a bare call + // statement, which is where the argument checks live) is the one + // statement variant carrying no span at all. Naming that is a separate + // piece of work; printing what we have is not blocked on it. + if let Some(func) = &self.function_name { + write!(f, " in `{func}`")?; + } + if let Some(expr) = &self.expr { + write!(f, " at `{expr}`")?; + } + if let Some(span) = &self.span { + write!(f, " ({})", span.start)?; + } Ok(()) } } @@ -60,13 +130,25 @@ pub struct TypeChecker { /// Type inference engine inference_engine: TypeInferenceEngine, - /// Local variable types - local_types: HashMap, - /// Tracks const bindings in current scope - const_locals: HashSet, - /// Snapshot stack for simple scope management - scope_stack: Vec>, - const_stack: Vec>, + /// Local variable types, as a stack of scopes — innermost last, index 0 the + /// one that is always there. + /// + /// It was one flat map plus a **clone of it per scope**: entering a block + /// copied every binding then in scope, so checking the n-th top-level + /// function copied the n-1 declared before it. That is quadratic in the size + /// of the file, and it showed — 250 functions type-checked in 0.04s, 500 in + /// 0.17s, 1000 in 0.70s, 2000 in 3.03s, 4000 in 23s, while 2000 top-level + /// `let`s (which declare nothing to copy) took no measurable time at all. + /// + /// Layers instead: entering a scope pushes an empty map and leaving it pops + /// one, both O(1), and a lookup walks outward from the innermost — one or + /// two layers in practice. The visible semantics are unchanged, including the + /// part that matters: a binding added inside a scope, or an existing name + /// rebound there, is gone when the scope ends, because it went into that + /// scope's own layer. + local_types: Vec>, + /// Const bindings, layered the same way and for the same reason. + const_locals: Vec>, /// Function signatures indexed by name (for static checking of CallNamed) function_sigs: HashMap, /// Behaviour options @@ -94,6 +176,36 @@ pub struct TypeChecker { pending_strict_functions: Vec, /// Program-level type checking enables this so later call sites can refine earlier function declarations. defer_strict_function_checks: bool, + /// Members of a namespace bound by `use "lib";` or `use * as m from "lib";`, + /// keyed by the binding then the member name. + /// + /// Separate from `function_sigs` because the name that reaches the checker + /// is `m.f`, not `f`: two namespaces may each export an `f`, and neither of + /// them is a free function. + imported_members: HashMap>, + /// Names bound to a **standard library module** by `use m;` / `use m as a;`. + /// + /// The binding shadows whatever the name meant before, and one of those + /// names is also a callable global: after `use chan;`, `chan(1)` is a call + /// of the module *object*. The VM says so at run time ("this value is not a + /// function: it is a Map"), the native backend ignored the import and + /// called the builtin anyway, and `lk check` said nothing — one program, + /// three answers. The check belongs here, before either engine runs. + /// The *module* each is bound to, so `use math as m;` can answer what `m` + /// is — an alias had the name recorded and not the module, so a member + /// check on it had nothing to look up. + imported_stdlib_modules: HashMap, + /// Bindings recorded as they are bound, when a caller asked to be told. + /// + /// `None` for the compiler's own runs: a check exists to produce an error or + /// nothing, and recording every binding would be work for a result nobody + /// reads. An editor wants exactly the opposite — the types, at their + /// positions, for a file that may not even check cleanly. + /// + /// Recorded *during* the walk for the same reason `return_frames` is: it is + /// the only time the binding's scope is still live. A traversal afterwards + /// sees every nested scope already popped and every local gone with it. + observations: Option>, /// Return types observed while checking the body of the function (or closure) /// currently being checked, innermost last. /// @@ -103,6 +215,14 @@ pub struct TypeChecker { /// `fn f() -> Int { if c { let r: Int = 7; return r; } … }` inferred `r` as a /// fresh type variable and rejected valid code. return_frames: Vec>, + /// The declared return type of each open frame, when the callable wrote one. + /// + /// Pushed and popped in lockstep with `return_frames` — a `return`'s value + /// has to be checked *against* the declaration, not merely compared with it + /// afterwards, because a lambda typed in isolation does not match the + /// function type written for it (`fn make() -> (Int) -> Int { return |x| … + /// }` was rejected). + declared_returns: Vec>, } impl Default for TypeChecker { @@ -118,8 +238,10 @@ impl TypeChecker { expected, actual, expr, + span: None, function_name: None, parameter_name: None, + lint: false, }; anyhow::Error::new(te) } @@ -139,8 +261,10 @@ impl TypeChecker { expected: None, actual: None, expr: None, + span: None, function_name: Some(function_name.to_string()), parameter_name: parameter_name.map(str::to_string), + lint: true, }) } /// Create a new type checker with default (non-strict) behaviour @@ -166,15 +290,13 @@ impl TypeChecker { /// Create a type checker with existing registry and custom options pub fn with_registry_and_options(registry: TypeRegistry, options: TypeCheckerOptions) -> Self { - let inference_engine = TypeInferenceEngine::new(registry.clone()); + let inference_engine = TypeInferenceEngine::new(); Self { registry, inference_engine, - local_types: HashMap::new(), - const_locals: HashSet::new(), - scope_stack: Vec::new(), - const_stack: Vec::new(), + local_types: alloc::vec![HashMap::new()], + const_locals: alloc::vec![HashSet::new()], function_sigs: HashMap::new(), options, impl_self_type: None, @@ -183,7 +305,146 @@ impl TypeChecker { method_sigs: HashMap::new(), pending_strict_functions: Vec::new(), defer_strict_function_checks: false, + imported_members: HashMap::new(), + imported_stdlib_modules: HashMap::new(), + observations: None, return_frames: Vec::new(), + declared_returns: Vec::new(), + } + } + + /// Record what `namespace.member` is, for a namespace bound by an import. + pub fn add_imported_member(&mut self, namespace: &str, member: String, ty: Type) { + self.imported_members + .entry(namespace.to_string()) + .or_default() + .insert(member, ty); + } + + /// The type of `namespace.member`, if the namespace was imported. + pub fn imported_member_type(&self, namespace: &str, member: &str) -> Option { + self.imported_members.get(namespace)?.get(member).cloned() + } + + /// Records that `name` is bound to the standard library module `module` + /// (the two differ for `use math as m;`). + pub fn add_imported_stdlib_module(&mut self, name: String, module: String) { + self.imported_stdlib_modules.insert(name, module); + } + + /// Whether this name is bound to a standard library module here. + pub(crate) fn is_imported_stdlib_module(&self, name: &str) -> bool { + self.imported_stdlib_modules.contains_key(name) + } + + /// The module a name is bound to, or the name itself when it binds nothing. + /// + /// `use math as m;` makes `m.nope(1)` a member check against `math`. It + /// used to be a check against a module called `m`, which does not exist, so + /// nothing was checked and the program died at run time with "nil is not a + /// function" — the sentence the unaliased spelling had already stopped + /// giving. + pub(crate) fn resolve_stdlib_alias<'a>(&'a self, name: &'a str) -> &'a str { + self.imported_stdlib_modules.get(name).map_or(name, String::as_str) + } + + /// Whether this name is a namespace bound by `use * as name from "…"`. + /// + /// Asked before saying "no such member": a namespace knows everything it + /// exports, so a name it does not have is a mistake — the same one + /// `has no member` reports for a standard library module. Without it + /// `lib.nothere()` type-checked and died with "nil is not a function". + pub(crate) fn is_imported_namespace(&self, name: &str) -> bool { + self.imported_members.contains_key(name) + } + + /// Whether the program declares a function of this name. + /// + /// A program may shadow a builtin — `fn assert(...)` is its own function, + /// with its own rules about named arguments. + pub(crate) fn has_user_function(&self, name: &str) -> bool { + self.function_sigs.contains_key(name) + } + + /// Whether a *local* shadows this name. + /// + /// Deliberately not [`Self::lookup_binding`], which counts a namespace as a + /// binding: that is what disqualifies the standard-library reading of + /// `math.f()`, and it is the opposite of what the namespace check wants — + /// there the namespace is the thing being asked about, and only a local can + /// take the name away from it. + pub(crate) fn has_local_binding(&self, name: &str) -> bool { + self.lookup_local(name).is_some() + } + + /// Each function's inferred return type, by name. + /// + /// The signatures are already here — an editor showing `-> String` after a + /// parameter list has no reason to re-derive it from the token stream. + /// A `Vec` rather than a map so the caller picks its own container — this + /// crate's `HashMap` is `hashbrown`'s, which is not the one the LSP holds. + pub fn function_return_types(&self) -> Vec<(String, Type)> { + self.function_sigs + .iter() + .filter_map(|(name, sig)| sig.return_type.clone().map(|ty| (name.clone(), ty))) + .collect() + } + + /// Start recording every binding this checker binds, with its position. + pub fn observe_bindings(&mut self) { + self.observations = Some(Vec::new()); + } + + /// Take what was recorded, leaving recording on. + pub fn take_observations(&mut self) -> Vec { + match &mut self.observations { + Some(observations) => core::mem::take(observations), + None => Vec::new(), + } + } + + /// Record the types just bound by a pattern at `span`. + /// + /// Reads the types back out of the environment rather than taking one: a + /// pattern distributes its value's type over its names, so `let [a, b] = f()` + /// binds two different types and neither of them is the type of `f()`. + pub fn record_bindings<'a>(&mut self, span: &Span, annotated: bool, names: impl Iterator) { + if self.observations.is_none() { + return; + } + let recorded: Vec = names + .filter_map(|name| { + self.lookup_local(name).map(|ty| ObservedBinding { + span: span.clone(), + name: name.to_string(), + ty: ty.clone(), + annotated, + }) + }) + .collect(); + if let Some(observations) = &mut self.observations { + observations.extend(recorded); + } + } + + /// How deep the scope stack is, for unwinding after a failed statement. + /// + /// Counted as the number of *pushed* scopes, so the always-present outermost + /// layer does not show — the same number this answered when scopes were + /// snapshots. + pub fn scope_depth(&self) -> usize { + self.local_types.len() - 1 + } + + /// Pop scopes until the stack is `depth` deep. + /// + /// A statement that fails mid-body leaves its scopes open — it returned + /// through the `?` that would have popped them. Without this the *next* + /// statement is checked inside the failed one's scope, and reports errors + /// about bindings that are not in fact visible to it. + pub fn unwind_scopes_to(&mut self, depth: usize) { + while self.scope_depth() > depth { + self.pop_scope(); } } @@ -202,8 +463,46 @@ impl TypeChecker { core::mem::replace(&mut self.impl_self_type, ty) } + /// The key a method is registered and looked up under. + /// + /// A builtin container's element type is **erased** here, because the + /// runtime erases it too: `heap_dispatch_type` reports every list as + /// `List`, a `TypedList::Mixed` having nothing else to report. Keying + /// on the static type instead meant `impl T for List` registered under + /// `List` while a call on `[1, 2]` looked up `List` — so the + /// method existed and could not be found, and the checker rejected the call + /// before the runtime (which would have found it) ever ran. `String` and + /// `Map` worked only because neither takes that path. fn method_sig_key(&self, receiver: &Type, name: &str) -> (String, String) { - (self.resolve_aliases(receiver).display(), name.to_string()) + ( + Self::dispatch_type(&self.resolve_aliases(receiver)).display(), + name.to_string(), + ) + } + + /// The type a receiver dispatches on — see [`Self::method_sig_key`]. + pub(crate) fn dispatch_type(resolved: &Type) -> Type { + match resolved { + Type::List(_) => Type::List(Box::new(Type::Any)), + Type::Map(_, _) => Type::Map(Box::new(Type::Any), Box::new(Type::Any)), + Type::Set(_) => Type::Set(Box::new(Type::Any)), + // A window is `Slice` and its impl target is written `Slice`, + // so the element has to be dropped here as it is for the three + // above — otherwise a `Slice` receiver keys on `Slice`, + // finds nothing, and `impl Slice { … }` is a block whose methods + // cannot be called. Every other built-in container was normalized; + // this one was missed because its type is `Generic` rather than a + // variant of its own. + Type::Generic { name, params } if matches!(name.as_str(), "Slice" | "Stream") && params.len() == 1 => { + Type::Generic { + name: name.clone(), + params: vec![Type::Any], + } + } + Type::Task(_) => Type::Task(Box::new(Type::Any)), + Type::Channel(_) => Type::Channel(Box::new(Type::Any)), + other => other.clone(), + } } pub fn add_method_sig(&mut self, receiver: &Type, name: &str, sig: Type) { @@ -225,6 +524,11 @@ impl TypeChecker { self.unsafe_depth > 0 } + /// The top-level bindings not yet reached (see the field docs). + pub fn pending_top_level(&self) -> &HashSet { + &self.pending_top_level + } + /// Records the top-level bindings not yet reached (see the field docs). pub fn set_pending_top_level(&mut self, names: HashSet) { self.pending_top_level = names; @@ -257,6 +561,134 @@ impl TypeChecker { self.unsafe_depth = self.unsafe_depth.saturating_sub(1); } + /// Reject a type annotation naming a type nothing declares. + /// + /// `Type::Named` is the parser's answer for any identifier in type + /// position, so a typo used to become a type: `let x: Strng = "a";` + /// reported "expected Strng, but expression has type String" — an error + /// about the *value*, pointing away from the misspelling. Worse in a + /// signature: `fn f(v: Nonexistent)` made the function uncallable and + /// blamed every caller ("Argument 1 has the wrong type"). + /// + /// Known names are the declared ones — structs, traits, aliases — plus the + /// runtime handles the standard library documents, which have no `Type` + /// variant of their own. + pub fn check_type_annotation(&self, ty: &Type, context: &str) -> Result<()> { + match ty { + Type::Named(name) => { + // No escape hatch for a bare `T`: LK has no generic parameter + // syntax (`fn f(…)` does not parse), so a single uppercase + // letter in type position is an undeclared name like any + // other. Exempting it was a guess, and it let exactly the + // errors this check exists to replace through — `fn f(v: T)` + // still said "Argument 1 has the wrong type (expected T)". + if self.registry.resolve_type(name).is_some() || crate::typ::stdlib_sig::is_documented_handle_type(name) + { + return Ok(()); + } + let hint = self.suggest_type_name(name); + Err(Self::type_err( + &alloc::format!("Unknown type '{name}' in {context}{hint}"), + None, + None, + None, + )) + } + Type::List(inner) | Type::Optional(inner) | Type::Set(inner) | Type::Boxed(inner) => { + self.check_type_annotation(inner, context) + } + Type::Map(key, value) => { + self.check_type_annotation(key, context)?; + self.check_type_annotation(value, context) + } + Type::Union(variants) | Type::Tuple(variants) => { + variants.iter().try_for_each(|v| self.check_type_annotation(v, context)) + } + // A trait's method signatures arrive as one of these, and were the + // last annotation nothing looked inside: a trait could promise a + // type that does not exist, and every impl of it was then measured + // against nothing. + Type::Function { + params, + named_params, + return_type, + } => { + params.iter().try_for_each(|p| self.check_type_annotation(p, context))?; + named_params + .iter() + .try_for_each(|p| self.check_type_annotation(&p.ty, context))?; + self.check_type_annotation(return_type, context) + } + Type::Task(inner) | Type::Channel(inner) => self.check_type_annotation(inner, context), + _ => Ok(()), + } + } + + /// What the writer probably meant, as a trailing ` — did you mean …` or + /// the empty string. + /// + /// A bare "Unknown type 'bool'" is accurate and useless: LK spells it + /// `Bool`, and someone arriving from Rust or Python writes `bool`, `str`, + /// `int` by reflex. The near-misses that matter are a case difference, a + /// typo, and a handful of names from other languages that LK deliberately + /// does not have — `f32` among them, which is a decision (one float type, + /// spelled `Float` or `f64`) rather than an omission. + fn suggest_type_name(&self, name: &str) -> alloc::string::String { + // Spellings LK deliberately does not have, and what to write instead. + const FOREIGN: &[(&str, &str)] = &[ + ("str", "String"), + ("char", "String"), + ("void", "Nil"), + ("none", "Nil"), + ("null", "Nil"), + ("f32", "Float"), + ("f64", "Float"), + ("i128", "Int"), + ("u128", "Int"), + ("boolean", "Bool"), + ("dict", "Map"), + ("array", "List"), + ("vec", "List"), + ]; + + let mut candidates: Vec = crate::val::PRIMITIVE_TYPES + .iter() + .map(|(spelling, _)| (*spelling).to_string()) + .chain( + crate::val::TYPE_SPELLINGS + .iter() + .map(|(spelling, _)| (*spelling).to_string()), + ) + .chain(core::iter::once(crate::val::NUMBER_TYPE_NAME.to_string())) + .chain(crate::val::IntKind::ALL.iter().map(|kind| kind.name().to_string())) + .collect(); + candidates.extend(self.registry.declared_type_names()); + + // A case difference first: it is the likeliest mistake and the surest + // answer. + if let Some(exact) = candidates.iter().find(|candidate| candidate.eq_ignore_ascii_case(name)) { + return alloc::format!(" — did you mean `{exact}`?"); + } + if let Some((_, replacement)) = FOREIGN.iter().find(|(foreign, _)| foreign.eq_ignore_ascii_case(name)) { + return alloc::format!(" — LK spells that `{replacement}`"); + } + // Then a typo, measured rather than guessed: one edit for a short name, + // two for a longer one, so `Strng` finds `String` and `Foo` does not + // find `Int`. + let budget = if name.len() <= 4 { 1 } else { 2 }; + let mut best: Option<(usize, &alloc::string::String)> = None; + for candidate in &candidates { + let distance = edit_distance(name, candidate); + if distance <= budget && best.is_none_or(|(previous, _)| distance < previous) { + best = Some((distance, candidate)); + } + } + match best { + Some((_, candidate)) => alloc::format!(" — did you mean `{candidate}`?"), + None => alloc::string::String::new(), + } + } + pub fn resolve_aliases(&self, ty: &Type) -> Type { let mut visiting = HashSet::new(); self.resolve_aliases_internal(ty, &mut visiting) @@ -264,6 +696,7 @@ impl TypeChecker { fn resolve_aliases_internal(&self, ty: &Type, visiting: &mut HashSet) -> Type { match ty { + Type::Unknown => Type::Unknown, Type::Named(name) => { if let Some(alias) = self.registry.get_type_alias(name) { if !visiting.insert(name.clone()) { @@ -353,7 +786,98 @@ impl TypeChecker { pub fn is_assignable(&self, from: &Type, to: &Type) -> bool { let lhs = self.resolve_aliases(from); let rhs = self.resolve_aliases(to); - lhs.is_assignable_to(&rhs) + // Through the trait oracle: a trait names a type, and the tables that + // say which types implement it are here rather than in the walk. + // Without it `fn render(v: Show)` accepted nothing at all, and the only + // way to write "anything with a `show`" was to leave the parameter + // untyped — the declaration and the dispatch both already worked, and + // the *type* was the missing third of the feature. + lhs.is_assignable_to_with(&rhs, self.registry()) + } + + /// Whether `value`, whose inferred type is `value_ty`, may be written where + /// `expected` is declared. + /// + /// [`Self::is_assignable`], plus the one rule that has to be stated + /// alongside it every time: a container **literal** is checked + /// covariantly. Containers are invariant because a widening is an alias, + /// and a literal has no second name. + /// + /// The rule lived in two copies — the `let` statement's and the call + /// argument's — and the positions that had neither refused what those two + /// accept: `S { f: [1] }` for a `List` field, and + /// `fn f() -> List { return [1]; }`, while + /// `let f: List = [1];` was fine. + pub fn value_fits(&self, value: &crate::expr::Expr, value_ty: &Type, expected: &Type) -> bool { + if self.is_assignable(value_ty, expected) { + return true; + } + let mut value = value; + while let crate::expr::Expr::Paren(inner) = value { + value = inner; + } + let expected = self.resolve_aliases(expected); + if self.container_literal_fits(value, &self.resolve_aliases(value_ty), &expected) { + return true; + } + // The second literal rule, and it was split across the same two + // positions: a machine integer does not convert implicitly — that rule + // is what makes `u8 + Int` an error rather than a silent widening — but + // a literal has no type of its own to preserve. `f(0x3f8)` for + // `fn f(port: u16)` is the ordinary way to call a driver. + Self::int_literal_fits_machine_int(&expected, value) + } + + /// The container-literal half of [`Self::value_fits`], **recursively**. + /// + /// A literal nested in a literal is fresh too, so the exemption has to + /// reach it: `let xs: List> = [[1]];` and + /// `let xs: List = [5];` were refused because the rule compared types + /// one level down and stopped, where `List` is not assignable to + /// `List` and `Int` is not a `u8` — both of which are true of a + /// *variable* and neither of a literal. + /// + /// The element types come from the inference already done (a homogeneous + /// literal is a `List`, a heterogeneous one a `Tuple`), so nothing is + /// re-checked; only the exemption walks down. + fn container_literal_fits(&self, value: &crate::expr::Expr, value_ty: &Type, expected: &Type) -> bool { + use crate::expr::Expr; + let element_types = |count: usize| -> Option> { + match value_ty { + Type::List(elem) => Some(alloc::vec![(**elem).clone(); count]), + Type::Tuple(elems) if elems.len() == count => Some(elems.clone()), + _ => None, + } + }; + match (value, expected) { + (Expr::List(items), Type::List(elem)) => element_types(items.len()) + .is_some_and(|tys| items.iter().zip(tys).all(|(item, ty)| self.value_fits(item, &ty, elem))), + (Expr::Map(pairs), Type::Map(key, value_type)) => { + let Type::Map(actual_key, actual_value) = value_ty else { + return false; + }; + actual_key.is_assignable_to_with(key, self.registry()) + && pairs.iter().all(|(_, v)| self.value_fits(v, actual_value, value_type)) + } + _ => false, + } + } + + /// Whether `value` is an integer literal in range for a machine-int + /// `expected`. Out of range is not "not a literal": it is a refusal, and + /// having a range is the whole point of a fixed width. + fn int_literal_fits_machine_int(expected: &Type, value: &crate::expr::Expr) -> bool { + let Type::MachineInt(kind) = expected else { + return false; + }; + let mut value = value; + while let crate::expr::Expr::Paren(inner) = value { + value = inner; + } + let crate::expr::Expr::Literal(crate::val::LiteralVal::Int(literal)) = value else { + return false; + }; + kind.accepts_literal(i128::from(*literal)) } /// Register a function signature for static checking by name @@ -362,13 +886,35 @@ impl TypeChecker { } /// Retrieve a function signature by name + /// Every function name the checker currently holds a signature for. + /// + /// For a caller checking a *sequence* of programs against one checker — see + /// `ReplVmSession::execute_program`, which uses it to keep a declaration at + /// the generality it was declared with. + pub fn declared_function_names(&self) -> Vec { + self.function_sigs.keys().cloned().collect() + } + pub fn get_function_sig(&self, name: &str) -> Option<&FunctionSig> { self.function_sigs.get(name) } /// Solve type constraints and return final types pub fn solve_constraints(&mut self) -> Result> { - self.inference_engine.solve_constraints() + let Self { + inference_engine, + registry, + .. + } = self; + inference_engine.solve_constraints(registry) + } + + /// Forget what inference has learned, keeping every declaration. + /// + /// See [`TypeInferenceEngine::forget_inferences`] — this is for a caller + /// checking a sequence of independent programs against one checker. + pub fn forget_inferences(&mut self) { + self.inference_engine.forget_inferences(); } /// Add a type constraint via the inference engine (for use by external type-checking passes). @@ -403,7 +949,7 @@ impl TypeChecker { /// Get the inferred type for a local variable pub fn get_local_type(&self, name: &str) -> Option<&Type> { - self.local_types.get(name) + self.lookup_local(name) } /// Add a type annotation for a local variable @@ -414,17 +960,35 @@ impl TypeChecker { /// Add a type annotation with mutability information pub fn add_local_binding(&mut self, name: String, typ: Type, is_const: bool) { let normalized = self.resolve_aliases(&typ); - self.local_types.insert(name.clone(), normalized); + // Into the innermost scope: rebinding a name from an outer one shadows it + // for the rest of this scope and leaves it alone afterwards, which is + // what the snapshot-and-restore did. + let scope = self + .local_types + .last_mut() + .expect("the outermost scope is never popped"); + scope.insert(name.clone(), normalized); + let consts = self + .const_locals + .last_mut() + .expect("the outermost scope is never popped"); if is_const { - self.const_locals.insert(name); + consts.insert(name); } else { - self.const_locals.remove(name.as_str()); + consts.remove(name.as_str()); } } /// Check whether a local binding is const pub fn is_const_local(&self, name: &str) -> bool { - self.const_locals.contains(name) + // Innermost first, like a type lookup: a name rebound in this scope is + // this scope's binding, const or not. + self.const_locals + .iter() + .rev() + .zip(self.local_types.iter().rev()) + .find(|(_, types)| types.contains_key(name)) + .is_some_and(|(consts, _)| consts.contains(name)) } /// Get the type registry @@ -438,12 +1002,49 @@ impl TypeChecker { } /// Opens a return-collection frame for a function or closure body. - pub fn push_return_frame(&mut self) { + pub fn push_return_frame(&mut self, declared: Option) { self.return_frames.push(Vec::new()); + self.declared_returns.push(declared); + } + + /// The declared return type of the innermost open frame. + pub fn declared_return(&self) -> Option { + self.declared_returns.last().cloned().flatten() + } + + /// The kind of declaration already bound to `name` at the top level, if any. + /// + /// A `fn` and a type declaration are **hoisted**: mutual recursion works, so + /// a `fn` is visible before the line it is written on. Source order + /// therefore does not apply to them, and "a `let` shadows it" has no + /// coherent meaning — which showed as `fn pick() {…}` then `let pick = …;` + /// resolving to the `let` *in either order*, silently. Two `fn`s of one name + /// were already refused; this is the same collision. + pub fn top_level_declaration_kind(&self, name: &str) -> Option<&'static str> { + if self.get_function_sig(name).is_some() { + return Some("function"); + } + if self.registry.get_struct(name).is_some() { + return Some("struct"); + } + if self.registry.get_type_alias(name).is_some() { + return Some("type alias"); + } + None + } + + /// Whether the walk is currently inside a function or closure body. /// Whether the walk is currently inside a function or closure body. + /// + /// The return frames answer this exactly — one is open for the duration of + /// every callable body and nothing else — so there is no second piece of + /// bookkeeping to keep in step. + pub fn inside_callable_body(&self) -> bool { + !self.return_frames.is_empty() } /// Closes the innermost frame and yields the return types seen in it. pub fn pop_return_frame(&mut self) -> Vec { + self.declared_returns.pop(); self.return_frames.pop().unwrap_or_default() } @@ -457,23 +1058,47 @@ impl TypeChecker { /// Enter a new scope for local variables pub fn push_scope(&mut self) { - // Snapshot current locals; modifications in the new scope are discarded on pop - self.scope_stack.push(self.local_types.clone()); - self.const_stack.push(self.const_locals.clone()); + self.local_types.push(HashMap::new()); + self.const_locals.push(HashSet::new()); } - /// Exit the current scope + /// Exit the current scope, discarding what it bound. + /// + /// The outermost layer is never popped: it is the scope every check starts + /// in, and an unbalanced `pop_scope` used to silently leave the checker with + /// the *previous* snapshot instead. pub fn pop_scope(&mut self) { - if let Some(prev) = self.scope_stack.pop() { - self.local_types = prev; + if self.local_types.len() > 1 { + self.local_types.pop(); } - if let Some(prev) = self.const_stack.pop() { - self.const_locals = prev; + if self.const_locals.len() > 1 { + self.const_locals.pop(); } } + /// The type bound to `name`, searching from the innermost scope outward. + /// Whether a name is bound as a value here — a local, or a namespace an + /// import brought in. + /// + /// Asked before judging a dotted call against the standard library: `math` + /// is a module *unless* the program bound something to that name, in which + /// case `math.f()` is an ordinary field access and none of the library's + /// business. + pub(crate) fn lookup_binding(&self, name: &str) -> Option { + self.lookup_local(name) + .cloned() + .or_else(|| self.imported_members.contains_key(name).then_some(Type::Any)) + } + + fn lookup_local(&self, name: &str) -> Option<&Type> { + self.local_types.iter().rev().find_map(|scope| scope.get(name)) + } + fn apply_substitutions_to_environment(&mut self, subs: &HashMap) { - for ty in self.local_types.values_mut() { + // Every layer: this runs once, after the whole program, when only the + // outermost is left — writing to all of them keeps that true if it ever + // runs somewhere deeper. + for ty in self.local_types.iter_mut().flat_map(|scope| scope.values_mut()) { *ty = ty.substitute(subs); } for sig in self.function_sigs.values_mut() { @@ -489,6 +1114,12 @@ impl TypeChecker { pending_functions: &[PendingStrictFunction], subs: &HashMap, ) -> Result<()> { + // The check this defers is the strict-Any one, so a non-strict run has + // nothing to do here. It matters now that *both* modes defer: the + // deferral used to imply strictness, and it no longer does. + if !self.strict_any() { + return Ok(()); + } for pending in pending_functions { let mut issues = Vec::new(); let mut first_param_name = None; @@ -531,6 +1162,8 @@ impl TypeChecker { pub struct FunctionSig { pub positional: Vec, pub named: Vec, + /// See [`SigOrigin`] — read only for the named-default rule. + pub origin: SigOrigin, pub return_type: Option, /// Which positional parameters the source *annotated*, in order. /// @@ -563,6 +1196,23 @@ pub struct NamedParamSig { pub has_default: bool, } +/// Where a signature came from. +/// +/// Only one thing turns on it, and it is not a type rule: a named parameter's +/// *default* is materialized by the compiler at the call site, from the +/// callee's own declaration, and a caller in another module does not have that +/// declaration. So omitting a defaulted named argument works within a module +/// and fails across one — at run time, with `missing required named argument`, +/// about a parameter that is not required. See `docs/semantics.md`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SigOrigin { + /// Declared in the program being checked. + #[default] + ThisModule, + /// Brought in by `use { … } from "…";`. + Imported, +} + #[derive(Debug, Clone, PartialEq)] pub struct PendingStrictFunction { pub name: String, @@ -578,3 +1228,82 @@ pub struct PendingStrictParam { pub ty: Type, pub annotated: bool, } + +/// Levenshtein distance, for the "did you mean" hints. +/// +/// `pub(crate)` because the compiler asks the same question about *value* +/// names: a typo in `nope` and a typo in `Strng` deserve the same answer, and +/// two distance functions would be two answers waiting to differ. +pub(crate) fn edit_distance(left: &str, right: &str) -> usize { + let left: Vec = left.chars().collect(); + let right: Vec = right.chars().collect(); + // Damerau: two adjacent characters swapped is **one** edit, not two. + // + // That is the commonest typo there is, and under plain Levenshtein it cost + // the same as two unrelated mistakes — so `nmae` never found `name` (budget + // 1 for a short name) while `nmme` did. One row of history is all the + // transposition case needs. + let mut before_previous: Vec = vec![0; right.len() + 1]; + let mut previous: Vec = (0..=right.len()).collect(); + let mut current = vec![0usize; right.len() + 1]; + for (i, l) in left.iter().enumerate() { + current[0] = i + 1; + for (j, r) in right.iter().enumerate() { + let substitute = previous[j] + usize::from(l != r); + let mut best = substitute.min(previous[j + 1] + 1).min(current[j] + 1); + if i > 0 && j > 0 && *l == right[j - 1] && left[i - 1] == *r { + best = best.min(before_previous[j - 1] + 1); + } + current[j + 1] = best; + } + core::mem::swap(&mut before_previous, &mut previous); + core::mem::swap(&mut previous, &mut current); + } + previous[right.len()] +} + +/// Whether a value of this type could ever be a map key or a set member. +/// +/// The rule is `RuntimeMapKey::from_value`'s, and it is one rule for both +/// questions because a set *is* a map's key set: nil, Bool, Int and String, and +/// nothing else. Float is out because `0.0 == -0.0` while their bits differ and +/// NaN is not equal to itself; containers are out because a key that can be +/// mutated is a record you can no longer find. See `docs/semantics.md`. +/// +/// The runtime enforced it and the checker did not, so `Set([1.5])` and +/// `{1.5: "a"}` type-checked and raised at run time — with the key type sitting +/// right there in the literal. +/// +/// Answers `false` only when the type is *certainly* unusable. `Any`, a type +/// variable and any union pass: a `Int | Float` value may well be the Int at run +/// time, and refusing a working program is worse than letting the runtime have +/// the last word on one that is not. +pub(crate) fn type_is_certainly_not_a_key(ty: &Type) -> bool { + matches!( + ty, + Type::Float | Type::List(_) | Type::Map(_, _) | Type::Set(_) | Type::Tuple(_) + ) +} + +/// Collapses distributed alternatives: identical types stay themselves, `Any` +/// anywhere swallows the rest (nothing is known), otherwise a union. +/// +/// Lives here rather than beside one of its callers because there are now two +/// of them in different modules — pattern distribution and a closure's return +/// type — and "collapse a set of alternatives" is one rule, not two. +pub(crate) fn union_of(types: impl IntoIterator) -> Type { + let mut out: Vec = Vec::new(); + for ty in types { + if ty == Type::Any { + return Type::Any; + } + if !out.contains(&ty) { + out.push(ty); + } + } + match out.len() { + 0 => Type::Any, + 1 => out.pop().expect("checked len"), + _ => Type::Union(out), + } +} diff --git a/core/src/typ/type_checker/expressions.rs b/core/src/typ/type_checker/expressions.rs index 5ef5f041..cf99fb0d 100644 --- a/core/src/typ/type_checker/expressions.rs +++ b/core/src/typ/type_checker/expressions.rs @@ -8,7 +8,7 @@ use super::{NamedParamSig, TypeChecker}; use crate::expr::Expr; use crate::operator::{BinOp, UnaryOp}; use crate::typ::{NumericClass, NumericHierarchy}; -use crate::val::{FunctionNamedParamType, LiteralVal, Type}; +use crate::val::{FunctionNamedParamType, IntKind, LiteralVal, Type}; use anyhow::{Result, anyhow}; use hashbrown::HashMap; @@ -128,38 +128,142 @@ impl TypeChecker { Ok(target.clone()) } + /// The one rule for what may stand as a condition. + /// + /// LK's rule is *truthiness*: every value is a condition, and only `nil` + /// and `false` are falsy. That is what the executor implements + /// (`truthy_unchecked`), what `dyn.truthy` implements for native code, and + /// what `examples/syntax/null_coalescing.lk` demonstrates with `if (0)`. + /// + /// There used to be a second rule: `? :` demanded exactly `Bool` and + /// rejected even an unresolved type variable, so + /// `fn g(x) { return x ? "y" : "n"; }` was a type error while + /// `fn g(x) { if x { … } }` was fine. With `if` now an expression, keeping + /// both would mean the same syntax typed differently depending on whether + /// its value was used. The condition is still *checked* — an ill-typed + /// expression is still an error — it is just not required to be `Bool`. + pub(crate) fn check_condition(&mut self, condition: &Expr) -> Result<()> { + self.check_expr(condition)?; + Ok(()) + } + /// Checks an `unsafe` block's contents. /// - /// `Expr::Block` on its own type-checks to `Any` without looking inside — - /// blocks are mostly produced by desugars, which are checked before they - /// are built. That is fine for those, but it would make `unsafe { … }` a - /// hole in the type system: precisely the construct that needs *more* - /// scrutiny would get none. So the statements are checked here. + /// Once the only place a block's contents were looked at: `Expr::Block` + /// itself type-checked to `Any` without looking inside, on the grounds that + /// blocks mostly come from desugars checked before they are built. That + /// made `unsafe { … }` a hole in the type system — precisely the construct + /// that needs *more* scrutiny getting none — and, it turned out, closure + /// bodies too. `Expr::Block` now checks itself, and this stays as the entry + /// point that also accepts a non-block `unsafe` operand. + /// + /// The block's *type* is its last statement's, when that statement is an + /// expression — which is not a new rule but the type catching up with one. + /// The executor already evaluates an `unsafe` block to exactly that value, + /// trailing semicolon included: `unsafe { 7; }` is 7. + /// + /// Typing it `Any` instead had a cost that shows up wherever this construct + /// is actually used. Every device read in a driver is one: + /// + /// ```lk + /// let value = unsafe { volatile_read_u32(address as *mut u32) }; + /// return value as Int; + /// ``` + /// + /// The binding and the cast are both laundering — there to turn `Any` back + /// into the `Int` the read always produced. A cast written to satisfy the + /// checker rather than to state something is a cast that will one day be + /// wrong and say nothing, which is the opposite of what `unsafe` is for. /// - /// The block's own type stays `Any` for now, matching `Expr::Block`; a - /// block that evaluates to a typed value is a separate change. - fn check_unsafe_body(&mut self, inner: &Expr) -> Result { + /// A last statement that is *not* an expression leaves the block `Any`, as + /// before. Those shapes (`unsafe { let x = …; }`) have no value the + /// executor promises, and inventing one here would be a claim rather than a + /// description. + pub(crate) fn check_block_value(&mut self, inner: &Expr) -> Result { let Expr::Block(statements) = inner else { return self.check_expr(inner); }; - for stmt in statements { + self.check_statements_value(statements) + } + + /// A statement sequence's type: its last statement's, when that statement + /// is an expression. Shared by `Expr::Block` and by both halves of + /// `Expr::Try`, which evaluate to their tails the same way. + pub(crate) fn check_statements_value(&mut self, statements: &[Box]) -> Result { + let Some((last, leading)) = statements.split_last() else { + return Ok(Type::Any); + }; + for stmt in leading { stmt.type_check(self)?; } + if let crate::stmt::Stmt::Expr { value: expr, .. } = last.as_ref() { + return self.check_expr(expr); + } + last.type_check(self)?; Ok(Type::Any) } - /// The `cpu_*` intrinsics: barriers, interrupt masking, wait-for-interrupt. + /// The type of a two-branch value — `if`/`else`, or `try`/`catch`. + /// + /// One branch `nil` and the other not makes the value *optional*, not a + /// contradiction: an `if` with no `else` synthesises a nil branch, and a + /// `catch` that only logs has no value either. + /// + /// Two branches of *different* types make a **union**, which is the rule a + /// function with two `return`s has always followed — `fn f() { if c { + /// return 1; } return "x"; }` is `Int | String`. Written as a constraint + /// instead, the two shapes disagreed with each other and with themselves: + /// `if c { xs } else { "x" }` type-checked (the constraint was recorded and + /// nobody solved it on that path) while `try { xs } catch e { "${e}" }` — + /// the most ordinary way to write a `catch`, since the caught value renders + /// as text — was rejected outright. + /// + /// A branch whose type is still a *variable* keeps the constraint: that is + /// inference in progress, not a value with two types, and unifying it is + /// how a lambda parameter learns what it holds. + pub(crate) fn unify_branch_values(&mut self, first: Type, second: Type) -> Result { + let nullable = |value: &Type| Type::Optional(Box::new(value.clone())); + let resolved_first = self.resolve_aliases(&first); + let resolved_second = self.resolve_aliases(&second); + if resolved_first == Type::Nil && resolved_second != Type::Nil { + return Ok(nullable(&second)); + } + if resolved_second == Type::Nil && resolved_first != Type::Nil { + return Ok(nullable(&first)); + } + if resolved_first != resolved_second + && !has_type_variable(&resolved_first) + && !has_type_variable(&resolved_second) + { + return Ok(union_of(resolved_first, resolved_second)); + } + self.inference_engine.add_constraint(first.clone(), second); + Ok(first) + } + + /// The `cpu_*` intrinsics: barriers, interrupt masking, wait-for-interrupt, + /// and the system-control instructions (descriptor tables, CR2/CR3, the + /// TLB). /// - /// These need `unsafe` for a different reason than pointers do — nothing - /// here can corrupt memory. Masking interrupts or parking the core changes + /// These need `unsafe` for a different reason than pointers do — a barrier + /// cannot corrupt memory. Masking interrupts or parking the core changes /// the machine's state in a way the rest of the program's correctness may /// depend on, and getting the nesting wrong deadlocks rather than crashes. /// Marking it makes the region auditable. + /// + /// The system-control half earns the same keyword far more directly: a + /// malformed descriptor table is not a fault the kernel gets to report, + /// because the CPU faults trying to report it and the machine resets. fn check_cpu_builtin(&mut self, name: &str, args: &[Box]) -> Result> { let (arity, result) = match name { "cpu_barrier" | "cpu_compiler_barrier" | "cpu_wait_for_interrupt" => (0, Type::Nil), - "cpu_irq_save" | "cpu_timestamp" => (0, Type::Int), - "cpu_irq_restore" => (1, Type::Nil), + "cpu_irq_save" | "cpu_timestamp" | "cpu_read_cr2" | "cpu_read_cr3" => (0, Type::Int), + "cpu_irq_restore" + | "cpu_load_task_register" + | "cpu_write_cr3" + | "cpu_invalidate_page" + | "cpu_raise_interrupt" => (1, Type::Nil), + "cpu_load_idt" | "cpu_load_gdt" | "cpu_reload_segments" => (2, Type::Nil), _ => return Ok(None), }; if args.len() != arity { @@ -171,12 +275,27 @@ impl TypeChecker { program's correctness can depend on" )); } - if name == "cpu_irq_restore" { - let saved = self.check_expr(&args[0])?; - if !self.is_assignable(&saved, &Type::Int) { + // Every operand of every one of these is a machine word — a port, a + // selector, a physical address, a saved flag. Checked in one loop + // rather than per intrinsic: the arm that gets forgotten is the one + // whose argument is never visited by the checker at all, and the + // lowering then rejects it as a type mismatch with no source location. + for (index, arg) in args.iter().enumerate() { + let actual = self.check_expr(arg)?; + if !self.is_assignable(&actual, &Type::Int) { + // `cpu_irq_restore` says where the value should have come + // from; the nesting discipline is the thing being got wrong + // when this fires, and naming the type is no help. + if name == "cpu_irq_restore" { + return Err(anyhow!( + "cpu_irq_restore expects the value returned by cpu_irq_save, got {}", + actual.display() + )); + } return Err(anyhow!( - "cpu_irq_restore expects the value returned by cpu_irq_save, got {}", - saved.display() + "{name} expects Int for argument {}, got {}", + index + 1, + actual.display() )); } } @@ -244,6 +363,61 @@ impl TypeChecker { /// compiler has no access to the type checker — the width lives in the name /// instead. It also makes the volatile-ness explicit, which `*p` never is /// in any language. + /// `symbol_address("name")` and `call_address_2(addr, a, b)` — the two + /// halves of a driver table. + /// + /// They had no entry here at all, which meant a call to either produced + /// `Any` and neither its arity nor its arguments were checked. `Any` + /// spreads: subtracting two addresses to measure a stride gave something + /// with no `as Int` out of it, and the error named the cast rather than the + /// missing type. Both of those cost real time in this repository. + /// + /// The name has to be a *literal*, and saying so here is the point. A + /// relocation is a name resolved at link time; there is nothing to look one + /// up in at run time, so a variable name cannot work — and without this it + /// type-checked, ran under the VM (which refuses), and failed to lower + /// natively with a message about an unsupported opcode. + fn check_address_builtin(&mut self, name: &str, args: &[Box]) -> Result> { + let arity = match name { + "symbol_address" => 1, + "call_address_2" => 3, + _ => return Ok(None), + }; + if args.len() != arity { + return Err(anyhow!("{name} expects {arity} argument(s), got {}", args.len())); + } + if !self.in_unsafe() { + return Err(anyhow!( + "{name} requires an `unsafe` block: a code address is a number, and nothing here \ + can check that the one you have is code" + )); + } + if name == "symbol_address" { + if !matches!( + args[0].as_ref(), + Expr::Literal(crate::val::LiteralVal::String(_) | crate::val::LiteralVal::ShortStr(_)) + ) { + return Err(anyhow!( + "symbol_address needs a literal name: it becomes a relocation, which is a name \ + resolved when the image is linked, and there is nothing to look one up in at \ + run time" + )); + } + } else { + for (index, arg) in args.iter().enumerate() { + let actual = self.check_expr(arg)?; + if !self.is_assignable(&actual, &Type::Int) { + return Err(anyhow!( + "call_address_2 expects Int for argument {}, got {}", + index + 1, + actual.display() + )); + } + } + } + Ok(Some(Type::Int)) + } + fn check_volatile_builtin(&mut self, name: &str, args: &[Box]) -> Result> { if let Some(result) = self.check_cpu_builtin(name, args)? { return Ok(Some(result)); @@ -251,6 +425,9 @@ impl TypeChecker { if let Some(result) = self.check_port_builtin(name, args)? { return Ok(Some(result)); } + if let Some(result) = self.check_address_builtin(name, args)? { + return Ok(Some(result)); + } let Some((is_write, kind)) = parse_volatile_builtin(name) else { return Ok(None); }; @@ -321,7 +498,7 @@ impl TypeChecker { // unchecked operations permitted inside it. Expr::Unsafe(inner) => { self.enter_unsafe(); - let result = self.check_unsafe_body(inner); + let result = self.check_block_value(inner); self.exit_unsafe(); result } @@ -338,15 +515,88 @@ impl TypeChecker { Expr::List(items) => self.check_list(items), Expr::Map(pairs) => self.check_map(pairs), Expr::StructLiteral { name, fields } => { - // If struct is known, enforce field presence and types; otherwise, accept as named type + // Imported by name, so the literal builds the declaring + // module's type through the constructor that import bound. The + // rest of this arm is then the ordinary local-struct check + // against *that* type: its schema, and its name as the result, + // which is what `use { P as Q } from "m"` needs — the value is + // a `P`, and only the spelling here is `Q`. + let declared = self.registry.constructible_import_target(name).map(String::from); + let name = declared.as_deref().unwrap_or(name); + // A name nothing declares is refused rather than built. + // + // Accepting it produced a *value*: `Nope { a: 1 }` answered + // `Nope{a:1}`, and `P { x: 4 }` for a `P` declared in an + // imported module answered something that renders `P{x:4}` and + // reports `typeof` `P` while having none of `P`'s methods — + // the error then surfaced at the call site as "P has no method + // 'norm'", far from the construction. A struct literal carries + // its type's identity or it is not that type; the two spellings + // that *do* carry it (`geo.P { … }` and a constructor the + // module exports) both answer 16 for `p.norm()`. + if self.registry.get_struct(name).is_none() { + return Err(Self::type_err( + &alloc::format!( + "no type named `{name}` is declared here — a struct literal names a type, \ + and this module declares none by that name. A type from another module \ + is reached through its module (`m.{name} {{ … }}`), by importing it by \ + name (`use {{ {name} }} from \"m\";`), or through a constructor that \ + module exports" + ), + None, + None, + Some(expr.clone()), + )); + } + // Known, but only because another module declares it. Building + // it here stamps *this* module's `TypeScope`, so the result + // renders the same and answers the same `typeof` while having + // none of the type's methods — the error then surfaces at the + // call site ("has no method …"), far from the construction. The + // two spellings that carry the declaring module's identity both + // work, so the answer is to name one of them. + if self.registry.is_imported_struct(name) && declared.is_none() { + return Err(Self::type_err( + &alloc::format!( + "`{name}` is declared in another module and this file only sees it \ + through its namespace, so a bare `{name} {{ … }}` here would build a \ + different type that happens to share the name — it would have none of \ + `{name}`'s methods. Write `m.{name} {{ … }}`, or import the type by \ + name (`use {{ {name} }} from \"m\";`), which binds the constructor \ + that module generates beside it" + ), + None, + None, + Some(expr.clone()), + )); + } if let Some(sd) = self.registry.get_struct(name) { let schema = sd.fields.clone(); + // A field written twice. The second value is the one that + // lands (the literal builds an ordered map, and a repeat + // updates in place), so the first is a value nothing can + // read — the same mistake a repeated parameter name or a + // repeated binding in a pattern is, and refused for the + // same reason. + for (index, (fname, _)) in fields.iter().enumerate() { + if fields.iter().take(index).any(|(earlier, _)| earlier == fname) { + return Err(Self::type_err( + &alloc::format!( + "field `{fname}` is written twice in this `{name}` literal — the second value \ + replaces the first before anything can read it" + ), + None, + None, + Some(expr.clone()), + )); + } + } // Provided -> check existence and type for (fname, fexpr) in fields { let expected = schema.get(fname).cloned(); - let at = self.check_expr(fexpr)?; + let at = self.check_expr_against(fexpr, expected.as_ref())?; if let Some(expected) = expected { - if !self.is_assignable(&at, &expected) { + if !self.value_fits(fexpr, &at, &expected) { return Err(Self::type_err( &format!("Field '{}' type mismatch in struct '{}'", fname, name), Some(expected.clone()), @@ -381,7 +631,7 @@ impl TypeChecker { } } } - Ok(Type::Named(name.clone())) + Ok(Type::Named(name.to_string())) } // Access operations @@ -389,21 +639,16 @@ impl TypeChecker { Expr::NullishCoalescing(expr, default) => self.check_nullish_coalescing(expr, default), Expr::OptionalAccess(expr, field) => self.check_optional_chaining(expr, field), Expr::Conditional(cond, then_expr, else_expr) => { - // condition must be Bool - let cond_ty = self.check_expr(cond)?; - if cond_ty != Type::Bool { - return Err(Self::type_err( - "Ternary condition must be Bool", - Some(Type::Bool), - Some(cond_ty), - Some(*cond.clone()), - )); - } - let then_ty = self.check_expr(then_expr)?; - let else_ty = self.check_expr(else_expr)?; - // unify then/else types; return the unified type (prefer then_ty) - self.inference_engine.add_constraint(then_ty.clone(), else_ty.clone()); - Ok(then_ty) + self.check_condition(cond)?; + // The arms are blocks when this came from `if … { … } else + // { … }`, and plain expressions when it came from `? :`. Both + // are values; `check_block_value` answers for either. + let then_ty = self.check_block_value(then_expr)?; + let else_ty = self.check_block_value(else_expr)?; + // `let r = if c { "a" };` used to report "Cannot unify String + // with Nil" — the expression form could not do what the + // statement form does. See `unify_branch_values`. + self.unify_branch_values(then_ty, else_ty) } // Functions - handle both Call (string name) and CallExpr (expression) Expr::Call(func, args) => { @@ -414,6 +659,27 @@ impl TypeChecker { if let Some(result) = self.check_volatile_builtin(func, args)? { return Ok(result); } + // A shift or a bitwise operation keeps the width it is given. + // + // The parser desugars `a << b` and `a & b` into calls before + // anything knows a type, so without this the result of masking a + // `u32` is an ordinary `Any` — and the next thing done with it + // is a width mistake. `let bits = probed & mask;` in a PCI + // driver was exactly that: every piece around it checked, and + // the whole did not. + // + // This is the *last* piece of the unsigned-`u64` work rather than + // the first, and the order mattered: on its own it makes + // `let top = one << 63; top < one;` type-check, and until the + // compiler rewrote that comparison to its unsigned form the + // answer was `true`. A rule that turns a compile error into a + // wrong answer is worse than the error. + if let Some(result) = self.check_shift_builtin(func, args)? { + return Ok(result); + } + if let Some(result) = self.check_merge_fields_builtin(func, args)? { + return Ok(result); + } // For Call with string name, create a variable expression for the function let func_expr = Expr::Var(func.clone()); self.check_function_call(&func_expr, args) @@ -421,14 +687,109 @@ impl TypeChecker { Expr::CallExpr(func_expr, args) => { // Source-level calls parse to `CallExpr`; `Call` is only built // by internal desugars. - if let Expr::Var(name) = func_expr.as_ref() - && let Some(result) = self.check_volatile_builtin(name, args)? + if let Expr::Var(name) = func_expr.as_ref() { + // `m[k] = v` arrives here as `__lk_set_index(m, k, v)`: the + // parser desugars it and, until now, only the bytecode + // compiler knew the name — so the *key* of an index + // assignment was the one place the key rule was never asked + // about, and `m[1.5] = "a"` raised at run time. Only when the + // container is provably a map: a list's index is an ordinary + // `Int` position, and a receiver of unknown type is nobody's + // business to refuse here. + if name == "__lk_set_index" + && let [container, key, value] = args.as_slice() + { + let container_ty = self.check_expr(container)?; + if matches!(self.resolve_aliases(&container_ty), Type::Map(_, _)) { + let key_ty = self.check_expr(key)?; + let key_ty = self.resolve_aliases(&key_ty); + if crate::typ::type_checker::type_is_certainly_not_a_key(&key_ty) { + return Err(Self::type_err( + &format!( + "{} cannot be a map key — only nil, Bool, Int and String can", + key_ty.display() + ), + None, + Some(key_ty), + Some(key.as_ref().clone()), + )); + } + } + self.check_container_store(&container_ty, key, value)?; + } + // `s.f = v` and `m.f = v` arrive as `__lk_set_field(s, "f", v)`. + if name == "__lk_set_field" + && let [container, key, value] = args.as_slice() + { + let container_ty = self.check_expr(container)?; + self.check_container_store(&container_ty, key, value)?; + } + } + // The third spelling of the same store: `l[0] = v` with a + // literal index desugars to `list.set(l, 0, v)` (the typed-list + // path the bytecode compiler recognizes), not to + // `__lk_set_index`. Three desugars, one rule. + if let Expr::Access(base, member) = func_expr.as_ref() + && matches!(base.as_ref(), Expr::Var(v) if v == "list") + && matches!(member.as_ref(), Expr::Literal(lit) if lit.as_str() == Some("set")) + && let [container, key, value] = args.as_slice() { - return Ok(result); + let container_ty = self.check_expr(container)?; + self.check_container_store(&container_ty, key, value)?; + } + if let Expr::Var(name) = func_expr.as_ref() { + if let Some(result) = self.check_volatile_builtin(name, args)? { + return Ok(result); + } + // Both shapes, because name resolution rewrites a plain + // call: `__lk_shl(a, b)` is a `Call` in the parser's output + // and a `CallExpr(Var(…))` by the time this sees it. + // Matching only the first is why the first version of this + // looked correct and changed nothing — the same trap the + // compiler's width inference fell into, in the same words. + if let Some(result) = self.check_shift_builtin(name, args)? { + return Ok(result); + } + if let Some(result) = self.check_merge_fields_builtin(name, args)? { + return Ok(result); + } } self.check_function_call(func_expr, args) } Expr::CallNamed(callee, pos_args, named_args) => { + // The struct-name this callee constructs, when it is the hidden + // constructor `module.Type { … }` desugars to. The desugar is + // meant to be invisible, so its errors have to speak *fields* + // — "Missing required named argument: y" described the shape the + // parser produced, not the one the reader wrote. + let constructed_struct = constructed_struct_name(callee); + // A builtin global takes no named arguments — none of them + // declares any, and each refuses at run time in these words. + // Saying it here is the same rule, one call earlier, with a + // span. + // + // It used to be *accidentally* early: the compiler bailed on any + // named call it had no signature for, which caught this and also + // caught `use { f } from "m"; f(a: 1)`, a perfectly good call. + // Removing that bail left this one to the run time until here. + // + // Only when nothing shadows the name: a local or a user function + // called `assert` is that program's own, and its rules are its + // own too. + if let Expr::Var(name) = callee.as_ref() + && !named_args.is_empty() + && crate::typ::stdlib_global_is_declared(name) + && !self.has_local_binding(name) + && self.registry.get_struct(name).is_none() + && !self.has_user_function(name) + { + return Err(Self::type_err( + &format!("{name}() does not accept named arguments"), + None, + None, + Some(callee.as_ref().clone()), + )); + } // Struct constructor sugar: TypeName(field: expr, ...) if let Expr::Var(name) = callee.as_ref() && let Some(sd) = self.registry.get_struct(name) @@ -464,9 +825,9 @@ impl TypeChecker { Some(e.as_ref().clone()), )); } - let at = self.check_expr(e)?; + let at = self.check_expr_against(e, schema.get(n))?; if let Some(expected) = schema.get(n) - && !self.is_assignable(&at, expected) + && !self.value_fits(e, &at, expected) { return Err(Self::type_err( &format!("Field '{}' type mismatch in struct '{}'", n, name), @@ -513,25 +874,57 @@ impl TypeChecker { } // If callee is a variable and we have a signature, enforce named rules + let mut instantiated_return: Option = None; if let Expr::Var(name) = callee.as_ref() - && let Some(sig) = self.get_function_sig(name).cloned() + && let Some(declared) = self.get_function_sig(name).cloned() { + // The declared signature, plus this call's own reading of + // its type variables — see the note in `calls.rs`, which + // this mirrors for the named-argument spelling. + let sig = declared; + instantiated_return = sig.return_type.clone(); // Check positional arity if sig.positional.len() != pos_types.len() { - return Err(Self::type_err( - &format!( + // A positional parameter passed by name is the shape + // somebody arrives with from Python, Swift or Kotlin. + // Reported as a count, it read as "you passed none" — + // true, and no help at all. Named parameters here are + // the ones declared in the trailing `{ … }` block. + let by_name: Vec<&str> = named_types + .iter() + .map(|(n, _)| n.as_str()) + .filter(|n| sig.named.iter().all(|declared| declared.name != **n)) + .collect(); + let message = if by_name.is_empty() { + format!( "Function '{}' expects {} positional args, got {}", name, sig.positional.len(), pos_types.len() - ), - None, - None, - None, - )); + ) + } else { + format!( + "Function \'{}\' has no named parameter `{}` — a positional parameter is passed by position, \ + and a named one is declared in a trailing `{{ … }}` block, as in `fn f(a: Int, {{ b: Int? = 1 }})`", + name, + by_name.join("`, `") + ) + }; + return Err(Self::type_err(&message, None, None, None)); } - // Constrain positional types + // Constrain positional types, and at the same time read + // this instance's variables off the arguments. + // + // The constraint alone is not enough to type the call: + // checking is one pass, and the solver does not run again + // until the enclosing function ends — long after the `let` + // that reads the result has been checked. So the binding is + // also computed here, structurally, which is all an + // *instance* needs: the parameter side is a pattern whose + // variables belong to this call and nothing else. + let mut instance_bindings: HashMap = HashMap::new(); for (pt, at) in sig.positional.iter().zip(pos_types.iter()) { + bind_instance_variables(pt, &self.resolve_aliases(at), &mut instance_bindings); self.inference_engine.add_constraint(pt.clone(), at.clone()); } @@ -554,7 +947,7 @@ impl TypeChecker { } if !sig_lookup.contains_key(key) { return Err(Self::type_err( - &format!("Unknown named argument: {}", n), + &unknown_named_message(constructed_struct.as_deref(), n), None, None, None, @@ -566,7 +959,39 @@ impl TypeChecker { let is_optional = matches!(decl.ty, Type::Optional(_)); if !is_optional && !decl.has_default && !seen.contains(decl.name.as_str()) { return Err(Self::type_err( - &format!("Missing required named argument: {}", decl.name), + &missing_named_message(constructed_struct.as_deref(), &decl.name), + None, + None, + None, + )); + } + // A default is filled by the *compiler*, at the call + // site, out of the callee's own declaration — which is + // what lets it read an earlier argument + // (`fn f(x: Int, {y: Int = x + 1})`). A caller in + // another module does not have that declaration, and + // the runtime path that places named arguments has no + // notion of a default at all. So this worked within a + // module and failed across one, at run time, saying + // `missing required named argument` about a parameter + // that is not required. + // + // Said here instead, where it can name the way out. + // docs/semantics.md has the design that would remove + // the limitation (a callee-side prologue plus a mask of + // which named arguments were supplied) and the three + // that were ruled out. + if decl.has_default + && sig.origin == crate::typ::SigOrigin::Imported + && !seen.contains(decl.name.as_str()) + { + return Err(Self::type_err( + &format!( + "`{}` has a default, and a default cannot be filled across a module \ + boundary yet — it is materialized where the call is written, from a \ + declaration this module does not have. Pass `{}:` explicitly here", + decl.name, decl.name + ), None, None, None, @@ -580,9 +1005,19 @@ impl TypeChecker { } for (n, at) in &named_types { if let Some(decl_ty) = name_to_ty.get(n.as_str()) { + bind_instance_variables(decl_ty, &self.resolve_aliases(at), &mut instance_bindings); self.inference_engine.add_constraint(decl_ty.clone(), at.clone()); } } + instantiated_return = + instantiated_return.map(|ty| substitute_outside_unions(&ty, &instance_bindings)); + } + + // This call's own return type, from this call's own instance + // of the signature. Taking it from `callee_type` instead would + // hand back the shared one every call to this function has. + if let Some(return_type) = instantiated_return { + return Ok(return_type); } // Fall back to callee function type for return @@ -613,7 +1048,7 @@ impl TypeChecker { let key = n.as_str(); if !decl_map.contains_key(key) { return Err(Self::type_err( - &format!("Unknown named argument: {}", n), + &unknown_named_message(constructed_struct.as_deref(), n), None, None, None, @@ -627,7 +1062,7 @@ impl TypeChecker { let is_optional = matches!(decl.ty, Type::Optional(_)) || decl.has_default; if !is_optional && !provided.contains(decl.name.as_str()) { return Err(Self::type_err( - &format!("Missing required named argument: {}", decl.name), + &missing_named_message(constructed_struct.as_deref(), &decl.name), None, None, None, @@ -668,31 +1103,12 @@ impl TypeChecker { } Ok(Type::List(Box::new(Type::Int))) } - Expr::Closure { params, body } => { - // Infer closure as a function type with param type variables and an inferred return - let mut param_types = Vec::with_capacity(params.len()); - for _ in params { - param_types.push(self.inference_engine.fresh_type_var()); - } - // Body type is inferred by checking the body expression. Its own - // return frame: a `return` inside a closure body belongs to the - // closure, and must not be collected as a return of the enclosing - // function (whose declared type it would then have to satisfy). - self.push_return_frame(); - // Like a named function's body: a closure runs when it is - // called, which is after the top level has finished, so it may - // read a binding declared below it. - let pending = self.suspend_pending_top_level(); - let ret_type = self.check_expr(body); - self.restore_pending_top_level(pending); - let _ = self.pop_return_frame(); - let ret_type = ret_type?; - Ok(Type::Function { - params: param_types, - named_params: Vec::new(), - return_type: Box::new(ret_type), - }) - } + Expr::Closure { + params, + param_types, + return_type, + body, + } => self.check_closure(params, param_types, return_type.as_deref(), body, &[]), Expr::Match { value, arms } => { // Check the matched value type let value_type = self.check_expr(value)?; @@ -706,6 +1122,24 @@ impl TypeChecker { )); } + // An arm after an unguarded catch-all can never run. You wrote + // a case you believe happens, and it does not — silently, so + // nothing ever says the branch is dead. LK refuses rather than + // warns because it has no warning channel, and a loud refusal is + // what it does elsewhere for the same shape of mistake (a + // zero-step range, a `let` over a declared name). + // + // A *guarded* catch-all is conditional, so it dominates nothing + // — the same distinction the fall-through detection draws. + if let Some(dead) = first_arm_after_catch_all(arms) { + return Err(Self::type_err( + "this match arm can never run: an earlier arm matches every value", + None, + None, + Some(dead.clone()), + )); + } + // Check all arms have compatible types let mut result_type: Option = None; for arm in arms { @@ -713,10 +1147,13 @@ impl TypeChecker { self.check_pattern_against_type(&arm.pattern, &value_type)?; // Arm body is checked in a scope with pattern bindings available - let locals_snapshot = self.local_types.clone(); - self.add_bindings_for_pattern(&arm.pattern, &value_type)?; - let arm_type = self.check_expr(&arm.body)?; - self.local_types = locals_snapshot; + // A scope, not a snapshot of every visible binding: the arm's + // pattern bindings live in their own layer and go away with it. + self.push_scope(); + let bound = self.add_bindings_for_pattern(&arm.pattern, &value_type); + let arm_type = bound.and_then(|()| self.check_expr(&arm.body)); + self.pop_scope(); + let arm_type = arm_type?; if let Some(existing_type) = &result_type { // Add constraint that all arms should return the same type @@ -727,11 +1164,63 @@ impl TypeChecker { } } - result_type - .ok_or_else(|| Self::type_err("Match expression has no arms", None, None, Some(expr.clone()))) + let result_type = result_type + .ok_or_else(|| Self::type_err("Match expression has no arms", None, None, Some(expr.clone())))?; + + // A `match` that can miss evaluates to `nil` — that is the + // language's rule, and it was the *type* that ignored it: + // `let r: String = match x { 1 => "one" };` type-checked and + // held nil, and `r.len()` was approved and then failed at + // runtime with "Len target expected string/list/map/set, got + // Nil". The value really can be nil, so the type says so. + if matches_every_value(arms, &self.resolve_aliases(&value_type)) { + Ok(result_type) + } else { + Ok(Type::Optional(Box::new(result_type))) + } } Expr::Paren(expr) => self.check_expr(expr), - Expr::Block(_) => Ok(Type::Any), + // Straight-line scopes, which is why this is a node rather than a + // rewrite into `let [ok, e] = try$call(|| { body })`: through a + // closure the checker saw a fresh type variable for every local + // assigned inside the body. + Expr::Try { + body, + catch_var, + handler, + } => { + self.push_scope(); + let body_ty = self.check_statements_value(body)?; + self.pop_scope(); + + self.push_scope(); + // The caught value is the message string for a plain raise and + // the raised value itself for `error(v)`, so the binding is as + // wide as the top type (see `vm::exec::handler`). + self.add_local_type(catch_var.clone(), Type::Any); + let handler_ty = self.check_statements_value(handler)?; + self.pop_scope(); + + self.unify_branch_values(body_ty, handler_ty) + } + // A block is checked like any other expression, and evaluates to + // its tail (`check_statements_value`). + // + // Skipping the contents — which is what this did, on the grounds + // that blocks mostly come from desugars already checked before they + // were built — meant a **closure's** body was never checked at all, + // because that is a block too. `let f = |x| { let s: String = 1; + // return x; };` was accepted; the same `let` at top level is not. + // A whole class of code, invisible to the checker. + // + // Its own scope, for the reason a block is one everywhere else: an + // inner `let` must not be visible after the block. + Expr::Block(statements) => { + self.push_scope(); + let ty = self.check_statements_value(statements); + self.pop_scope(); + ty + } } } @@ -739,7 +1228,12 @@ impl TypeChecker { pub fn infer_resolved_type(&mut self, expr: &Expr) -> Result { let ty = self.check_expr(expr)?; // Attempt to solve constraints and substitute into the resulting type - match self.inference_engine.solve_constraints() { + let Self { + inference_engine, + registry, + .. + } = self; + match inference_engine.solve_constraints(registry) { Ok(subs) => Ok(ty.substitute(&subs)), Err(_) => Ok(ty), // On failure, return the unsolved type to avoid hard errors in tooling } @@ -758,7 +1252,7 @@ impl TypeChecker { /// Check identifier type fn check_identifier(&mut self, name: &str) -> Result { // Check local variables first - if let Some(typ) = self.local_types.get(name) { + if let Some(typ) = self.get_local_type(name) { return Ok(typ.clone()); } @@ -781,7 +1275,7 @@ impl TypeChecker { // Otherwise, assume it's a dynamic variable (type inference needed) let var_type = self.inference_engine.fresh_type_var(); - self.local_types.insert(name.to_string(), var_type.clone()); + self.add_local_type(name.to_string(), var_type.clone()); Ok(var_type) } @@ -800,40 +1294,144 @@ impl TypeChecker { BinOp::Add => { let left_resolved = self.resolve_aliases(&left_type); let right_resolved = self.resolve_aliases(&right_type); - if matches!(left_resolved, Type::List(_)) || matches!(right_resolved, Type::List(_)) { + // A `Tuple` is what a heterogeneous list *literal* infers to, + // and it is a list everywhere else — it indexes, has a `len`, + // iterates and is `in`-searchable. Leaving it out here did not + // merely refuse a program: `"" + [1, "a"]` fell through to the + // string path and was typed `String`, while both executors + // answer the list `["", 1, "a"]`. A wrong type is worse than a + // refusal, because it propagates — `let v: String = ...` passed + // `lk check` and held a list. + if matches!(left_resolved, Type::List(_) | Type::Tuple(_)) + || matches!(right_resolved, Type::List(_) | Type::Tuple(_)) + { return self.check_list_addition(left_expr, &left_type, right_expr, &right_type); } + // Two maps merge, the right side winning. Both executors have + // implemented this all along — the VM's `Add` has a map arm + // (`merge_typed_maps`, which keeps the left's key order) and so + // does `lkrt_dyn_add` — and only the checker refused, so + // `a + b` ran when the types were erased to `Any` and was + // "the left operand must be numeric types" when they were not. + // The rule this breaks is written down: `lk check` answers the + // executors' question. + if matches!(left_resolved, Type::Map(_, _)) || matches!(right_resolved, Type::Map(_, _)) { + return self.check_map_addition(left_expr, &left_type, right_expr, &right_type); + } if self.is_string_like(&left_type) || self.is_string_like(&right_type) { self.check_string_addition(left_expr, &left_type, right_expr, &right_type) } else { self.check_numeric_bin_op(left_expr, &left_type, right_expr, &right_type, op) } } - BinOp::Mul if self.is_string_like(&left_type) || self.is_string_like(&right_type) => { - let left_string = self.is_string_like(&left_type); - let right_string = self.is_string_like(&right_type); - let left_int = matches!(self.resolve_aliases(&left_type), Type::Int); - let right_int = matches!(self.resolve_aliases(&right_type), Type::Int); - if (left_string && right_int) || (left_int && right_string) { - Ok(Type::String) - } else { - self.check_numeric_bin_op(left_expr, &left_type, right_expr, &right_type, op) + // `"ab" * 3` is a type error, and says so with the operation the + // language actually has. + // + // This arm used to answer `String` — a string-repetition rule that + // **no executor implements**: `lk check` passed the program and + // running it raised `* expects Int or Float, got String and Int`. + // The checker's core promise is that it catches this before the run, + // so a rule for a feature that does not exist is worse than no rule. + // + // Removed rather than implemented, because the operation is already + // here: `"ab".repeat(3)` answers `"ababab"`. Adding the operator + // would give one operation two spellings. + BinOp::Mul + if (self.is_string_like(&left_type) && matches!(self.resolve_aliases(&right_type), Type::Int)) + || (matches!(self.resolve_aliases(&left_type), Type::Int) && self.is_string_like(&right_type)) => + { + Err(Self::type_err( + "`*` does not repeat a string — write `text.repeat(count)`", + None, + Some(Type::String), + Some(left_expr.clone()), + )) + } + // `-` removes: `xs - ys` drops every element of `ys`, `m - n` drops + // every key of `n`. Both executors have implemented it all along + // (the VM's `dynamic_sub` has list and map arms, and its own error + // says "expected numbers or list/map lhs"), the tutorial documents + // it (`[1, 2, 3] - [2] // [1, 3]`), and only the checker refused — + // so it ran with the types erased to `Any` and was "the left + // operand must be numeric types" without. The same defect `+` had, + // in the operator beside it. + BinOp::Sub => { + let left_resolved = self.resolve_aliases(&left_type); + let right_resolved = self.resolve_aliases(&right_type); + // The *left* side decides, in the interpreter's own order: a + // list on the left removes, then a map on the left removes, + // and only then is the right side's kind a reason to complain. + // Reading either side first sent `{"a": 1} - [1]` — a map + // minus a key that happens to be a list — to the list rule, + // which then said the left operand was not a list. + // + // A `Tuple` is a list here for the reason it is one in `+`: a + // heterogeneous literal is still a list, and leaving it out + // sent `[1, "a"] - 1` to the numeric rule. + if matches!(left_resolved, Type::List(_) | Type::Tuple(_)) { + return self.check_list_removal(left_expr, &left_type, right_expr, &right_type); } + if matches!(left_resolved, Type::Map(_, _)) { + return self.check_map_removal(left_expr, &left_type, right_expr, &right_type); + } + if matches!(right_resolved, Type::List(_) | Type::Tuple(_)) { + return self.check_list_removal(left_expr, &left_type, right_expr, &right_type); + } + if matches!(right_resolved, Type::Map(_, _)) { + return self.check_map_removal(left_expr, &left_type, right_expr, &right_type); + } + self.check_numeric_bin_op(left_expr, &left_type, right_expr, &right_type, op) } - BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => { + BinOp::Mul | BinOp::Div | BinOp::Mod => { self.check_numeric_bin_op(left_expr, &left_type, right_expr, &right_type, op) } BinOp::Eq | BinOp::Ne => { - self.inference_engine - .add_constraint(left_type.clone(), right_type.clone()); + // Comparing two values of different concrete types is legal and + // answers false. `x == nil` is the shape this language is made + // of; a constraint between the operands asserts they must be + // the *same* type, which is not what `==` means — it made + // `let x = nil; x == "k"` a type conflict. + // + // Kept when either side is still undetermined: `if x == 1` is + // real evidence about `x`, and the checker has no other source + // for it. + // Kept when either side is still undetermined *and* neither is + // `nil`: `if x == 1` is real evidence about `x`, but `if x == nil` + // is not evidence that `x` **is** nil — it is a test for the one + // case where it might be. Binding it to `Nil` is how + // `if (val == nil) { … } return [true, val];` came to think `val` + // was nil on the path where it demonstrably is not. + let comparing_against_nil = left_type == Type::Nil || right_type == Type::Nil; + if (left_type.contains_variables() || right_type.contains_variables()) && !comparing_against_nil { + self.inference_engine + .add_constraint(left_type.clone(), right_type.clone()); + } Ok(Type::Bool) } BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => { self.check_ordering_operands(left_expr, &left_type, right_expr, &right_type)?; Ok(Type::Bool) } + // A `Tuple` is what a heterogeneous list *literal* infers to, and a + // `String` contains substrings — both were containers everywhere + // else (indexing, `len`, method dispatch) and rejected only here. + // `"a" in "abc"` therefore worked as a folded literal and was a + // type error one line later with the same value in a variable. + // `Bytes` and `Slice` were the last two: both index, both have a + // `len`, both iterate, and `Bytes` even has a `contains` method — + // `in` was the one place they were not containers. The VM had no + // arm for either either, so this is not a checker-only relaxation. + // `Any` is the next one in that same queue, and the last: it + // indexes, has a `len`, iterates, dispatches methods and takes a + // `push` — `in` was the one place an erased container was not a + // container. What `Any` means is "checked when it runs", and both + // executors do check it there. BinOp::In => match self.resolve_aliases(&right_type) { - Type::List(_) | Type::Map(_, _) | Type::Set(_) => Ok(Type::Bool), + Type::List(_) | Type::Map(_, _) | Type::Set(_) | Type::Tuple(_) | Type::String | Type::Any => { + Ok(Type::Bool) + } + Type::Named(name) if name == "Bytes" => Ok(Type::Bool), + Type::Generic { name, .. } if name == "Slice" => Ok(Type::Bool), other => Err(Self::type_err( "'in' operator requires container type", Some(Type::List(Box::new(Type::Any))), @@ -874,6 +1472,155 @@ impl TypeChecker { } } + /// An integer literal's value, seeing through parentheses. + fn int_literal_operand(expr: &Expr) -> Option { + match expr { + Expr::Literal(crate::val::LiteralVal::Int(value)) => Some(i128::from(*value)), + Expr::Paren(inner) => Self::int_literal_operand(inner), + _ => None, + } + } + + /// The result of a machine-int operation whose other operand was a literal. + /// + /// The range is checked here rather than left to wrap, because having one is + /// the whole point of asking for a fixed width — `port + 300` on a `u8` is a + /// mistake worth being told about where it was written. + fn machine_literal_result(kind: lk_values::IntKind, literal: i128, expr: &Expr) -> Result { + if !kind.accepts_literal(literal) { + return Err(Self::type_err( + "literal is out of range for the machine integer it is used with", + Some(Type::MachineInt(kind)), + None, + Some(expr.clone()), + )); + } + Ok(Type::MachineInt(kind)) + } + + /// `P { ..base, … }`, whose base has to be something with fields. + /// + /// The spread desugars to `__lk_merge_fields(base, overlay)`, and nothing + /// looked at the base: `P { ..5 }` type-checked and died at run time with + /// `__lk_merge_fields base must be Object, Map, or Nil, got Int` — a + /// sentence naming the desugaring rather than what the reader wrote. + /// + /// `Any` and an unresolved variable stay permissive, as everywhere else: + /// those are the dynamic and inference paths. + fn check_merge_fields_builtin(&mut self, func: &str, args: &[Box]) -> Result> { + if func != "__lk_merge_fields" || args.len() != 2 { + return Ok(None); + } + let base = self.check_expr(&args[0])?; + let overlay = self.check_expr(&args[1])?; + let spreadable = |ty: &Type| { + matches!( + self.resolve_aliases(ty), + Type::Any | Type::Unknown | Type::Variable(_) | Type::Nil | Type::Map(_, _) | Type::Named(_) + ) + }; + if !spreadable(&base) { + return Err(Self::type_err( + "a `..base` spread copies another value's fields, so the base has to be a struct, \ + a map, or nil", + None, + Some(base), + Some(args[0].as_ref().clone()), + )); + } + let _ = overlay; + Ok(Some(Type::Any)) + } + + /// `a << b` / `a >> b`, whose result is `a`'s type when that is a machine + /// integer. /// `a << b` / `a >> b`, whose result is `a`'s type when that is a machine + /// integer. + /// + /// The shift *amount* is deliberately not constrained to the same width — + /// `flags << 3` is what people write, and requiring `3 as u32` there is the + /// ceremony that gets fixed widths abandoned. + fn check_shift_builtin(&mut self, func: &str, args: &[Box]) -> Result> { + /// Which argument a side names, so the error can quote the operand the + /// reader wrote rather than the desugared builtin call. + fn ty_expr_for<'a>(which: &str, args: &'a [Box]) -> &'a Expr { + if which == "left" { &args[0] } else { &args[1] } + } + + let arity = match func { + "__lk_shl" | "__lk_shr" | "__lk_shr_u" | "__lk_bit_and" | "__lk_bit_or" | "__lk_bit_xor" => 2, + "__lk_bit_not" => 1, + _ => return Ok(None), + }; + if args.len() != arity { + return Ok(None); + } + let left = self.check_expr(&args[0])?; + let right = if arity == 2 { + Some(self.check_expr(&args[1])?) + } else { + None + }; + let resolved = self.resolve_aliases(&left); + // A bit operation on a non-integer is an error, not "not my business". + // + // This used to check the left operand's *shape* and return `None` for + // anything that was not a machine int — and `None` means "no opinion", + // so the caller typed the call dynamically and said nothing. The right + // operand was not looked at at all (`let _ = ...`). + // + // What that let through: `(bdf / 0x800) & 0x1f`. In LK `/` always yields + // a `Float` (docs/semantics.md), so that is a Float meeting `&` — the VM + // raises at runtime (`bit_arg` requires an Int) and the AOT refuses to + // lower it, while `lk check` passed it in silence. Three behaviours for + // one program, and it had been sitting in seven functions of the x86 + // PCI driver. + // + // `Any` and an unresolved variable stay permissive: those are the + // dynamic and inference paths, where the answer is not known yet. + for (which, ty) in [Some(("left", &left)), right.as_ref().map(|r| ("right", r))] + .into_iter() + .flatten() + { + let resolved = self.resolve_aliases(ty); + // `Boxed` unwraps first: a boxed value is the dynamic escape hatch + // just as `Any` is, and `1 << 12` types as `Box` today (the + // shift builtin only claims a type for a *machine* int left + // operand). Rejecting that shape was a false positive, and the + // `shift/with_mask` differential is what said so. + let mut probe = &resolved; + while let Type::Boxed(inner) = probe { + probe = inner; + } + if !matches!(probe, Type::Int | Type::MachineInt(_) | Type::Any | Type::Variable(_)) { + return Err(Self::type_err( + &alloc::format!("the {which} operand of a bit operation must be an Int"), + Some(Type::Int), + Some(probe.clone()), + Some(ty_expr_for(which, args).clone()), + )); + } + } + Ok(match resolved { + Type::MachineInt(_) => Some(resolved), + _ => None, + }) + } + + /// `"a" + x` — a string joined to something. + /// + /// The answer is a `String` *unless the other operand might be a list*, in + /// which case it might be a list: a list operand wins over a string one, so + /// `"a" + [1, 2]` is `["a", 1, 2]`. An erased operand might be one at run + /// time and this used to promise `String` anyway, so + /// + /// ```lk + /// fn f(v: Any) -> String { return "a" + v; } + /// f([1, 2]) // ["a",1,2] + /// ``` + /// + /// passed `lk check` and answered a list. The native build read the + /// promise and unboxed the result as a string, which raised — one back end + /// answering and the other refusing, on a type the checker had invented. fn check_string_addition( &mut self, _left_expr: &Expr, @@ -881,12 +1628,25 @@ impl TypeChecker { _right_expr: &Expr, right_ty: &Type, ) -> Result { + // A type *variable* is not one of these: `coerce_to_string` below binds + // it to `String`, so by the time this answers the operand really is a + // string. `Any` is a declared escape hatch and cannot be bound — that + // is the whole difference, and it is why `x + "!"` stays a `String` + // while `v + "!"` with `v: Any` does not. + let could_be_a_list = |ty: &Type| matches!(ty, Type::Any | Type::Union(_) | Type::Unknown); + let erased = + could_be_a_list(&self.resolve_aliases(left_ty)) || could_be_a_list(&self.resolve_aliases(right_ty)); self.coerce_to_string(left_ty); self.coerce_to_string(right_ty); - Ok(Type::String) + Ok(if erased { Type::Any } else { Type::String }) } - fn check_list_addition( + /// `m + n` — the two maps merged, the right side winning on a shared key. + /// + /// The answer's key and value types follow `check_list_addition`'s rule: + /// whichever side subsumes the other, or `Any` when neither does. A merge + /// can only produce keys and values the two operands already had. + fn check_map_addition( &mut self, left_expr: &Expr, left_ty: &Type, @@ -896,19 +1656,27 @@ impl TypeChecker { let left_resolved = self.resolve_aliases(left_ty); let right_resolved = self.resolve_aliases(right_ty); match (left_resolved, right_resolved) { - (Type::List(left_inner), Type::List(right_inner)) => { - let elem_ty = if self.is_assignable(left_inner.as_ref(), right_inner.as_ref()) { - (*left_inner).clone() - } else if self.is_assignable(right_inner.as_ref(), left_inner.as_ref()) { - (*right_inner).clone() - } else { - Type::Any - }; - Ok(Type::List(Box::new(elem_ty))) + (Type::Map(left_key, left_value), Type::Map(right_key, right_value)) => { + let key = self.wider_of(left_key.as_ref(), right_key.as_ref()); + let value = self.wider_of(left_value.as_ref(), right_value.as_ref()); + Ok(Type::Map(Box::new(key), Box::new(value))) } - (Type::List(_), other) => Err(Self::type_err( - "List concatenation requires both operands to be lists", - Some(Type::List(Box::new(Type::Any))), + // One erased operand, same rule as `in` and list concatenation: + // `Any` is a container until it runs. Neither key nor value type + // is known, so the result is the widest map. + (Type::Map(_, _), Type::Any) | (Type::Any, Type::Map(_, _)) | (Type::Any, Type::Any) => { + Ok(Type::Map(Box::new(Type::Any), Box::new(Type::Any))) + } + // A string on the other side is not a failed merge, it is a + // *concatenation* — the map renders the way `print` renders it, the + // same as `"${m}"`. This refused while the executors answered, and + // the message named an operation the program had not written: + // `"v=" + {"k": 1}` was "map merge requires both operands to be + // maps". + (Type::Map(_, _), Type::String) | (Type::String, Type::Map(_, _)) => Ok(Type::String), + (Type::Map(_, _), other) | (other, Type::Map(_, _)) => Err(Self::type_err( + "map merge requires both operands to be maps", + Some(Type::Map(Box::new(Type::Any), Box::new(Type::Any))), Some(other), Some(Expr::Bin( Box::new(left_expr.clone()), @@ -916,16 +1684,208 @@ impl TypeChecker { Box::new(right_expr.clone()), )), )), - (other, Type::List(_)) => Err(Self::type_err( - "List concatenation requires both operands to be lists", + _ => unreachable!("check_map_addition is only reached when a side is a map"), + } + } + + /// Whether a key type can be shown never to match the map's. + /// + /// Both have to be concrete and unrelated. `Any`, a type variable and a + /// union stay out, because the constraint they would have added is doing + /// real inference — `m[k]` is how an unbound `k` learns it is a key. + fn definitely_not_key(&mut self, field_ty: &Type, key_ty: &Type) -> bool { + let concrete = |t: &Type| { + matches!( + t, + Type::Int | Type::Float | Type::Bool | Type::String | Type::Nil | Type::List(_) | Type::Set(_) + ) || matches!(t, Type::Map(_, _)) + }; + concrete(field_ty) + && concrete(key_ty) + && !self.is_assignable(field_ty, key_ty) + && !self.is_assignable(key_ty, field_ty) + } + + /// A `Tuple` read as the list it is, everything else unchanged. + /// + /// A heterogeneous list *literal* infers to `Tuple`, and a tuple is a list + /// everywhere else — it indexes, has a `len`, iterates, is `in`-searchable. + /// The container operators are where it kept being left out. + fn as_list_type(&mut self, ty: &Type) -> Type { + match self.resolve_aliases(ty) { + Type::Tuple(elems) => { + let elem = elems + .iter() + .fold(None, |acc: Option, e| match acc { + None => Some(e.clone()), + Some(acc) => Some(self.wider_of(&acc, e)), + }) + .unwrap_or(Type::Any); + Type::List(Box::new(elem)) + } + other => other, + } + } + + /// Whichever of the two subsumes the other, or `Any` when neither does. + /// + /// The rule `check_list_addition` uses for a concatenation's element type, + /// named once now that the map merge needs it for both halves of its key + /// and value. + /// + /// `Any` is answered before asking, because `is_assignable` reads it in the + /// other direction: an `Any` may be used *where any type is expected*, so + /// `is_assignable(Any, Int)` is true and the subsumption question comes + /// back "`Int` is wider". It is not — `Any` is the one that holds anything, + /// and taking `Int` from it is how a merged `Map` was typed + /// `Map` and then held a string: + /// + /// ```lk + /// fn f(m: Map) -> Int { + /// let v: Int = (m + {"a": 1})["z"]; // accepted, held "not an int" + /// return 0; + /// } + /// ``` + /// + /// The escape hatch is for *passing* a value, not for narrowing one. + fn wider_of(&mut self, left: &Type, right: &Type) -> Type { + if matches!(left, Type::Any) || matches!(right, Type::Any) { + return Type::Any; + } + if self.is_assignable(left, right) { + right.clone() + } else if self.is_assignable(right, left) { + left.clone() + } else { + Type::Any + } + } + + /// `xs - ys` — `xs` without the elements `ys` holds. + /// + /// The answer keeps the receiver's element type: removal takes elements + /// away and never introduces one, so nothing widens. + fn check_list_removal( + &mut self, + left_expr: &Expr, + left_ty: &Type, + right_expr: &Expr, + right_ty: &Type, + ) -> Result { + let left_resolved = self.as_list_type(left_ty); + match (left_resolved, self.resolve_aliases(right_ty)) { + (Type::List(left_inner), Type::List(_) | Type::Tuple(_)) => Ok(Type::List(left_inner)), + // Same rule again. The known side's element type does not survive + // an erased operand, so the result is the widest list. + (Type::List(inner), Type::Any) => Ok(Type::List(inner)), + (Type::Any, Type::List(_) | Type::Tuple(_)) | (Type::Any, Type::Any) => Ok(Type::List(Box::new(Type::Any))), + // `xs - v` removes the first element equal to `v`. The VM has an + // arm for it (`remove_first_list_value`) beside the list-minus-list + // one, and so does `lkrt_dyn_sub`; only the checker refused, which + // is the defect `Any + Any` map merge was. Removal never introduces + // an element, so nothing widens — the receiver's type is kept, as + // this function's doc already says. + (Type::List(inner), _) => Ok(Type::List(inner)), + (other, Type::List(_) | Type::Tuple(_)) => Err(Self::type_err( + "list removal requires a list on the left", Some(Type::List(Box::new(Type::Any))), Some(other), Some(Expr::Bin( Box::new(left_expr.clone()), - BinOp::Add, + BinOp::Sub, Box::new(right_expr.clone()), )), )), + _ => unreachable!("check_list_removal is only reached when a side is a list"), + } + } + + /// `m - n` — `m` without the keys `n` holds. Keeps `m`'s types, for the + /// reason [`check_list_removal`] gives. + fn check_map_removal( + &mut self, + left_expr: &Expr, + left_ty: &Type, + right_expr: &Expr, + right_ty: &Type, + ) -> Result { + match (self.resolve_aliases(left_ty), self.resolve_aliases(right_ty)) { + (Type::Map(left_key, left_value), Type::Map(_, _)) => Ok(Type::Map(left_key, left_value)), + // `m - k` removes that one key, the VM's arm beside the + // map-minus-map one — and it takes *any* value, the way + // `m.delete(k)` does. A value that cannot be a key cannot be in the + // map, so removing it removes nothing; removal looks a key up + // rather than building one, which is the line `m[k]` and + // `m.set(k, v)` stay on the other side of. + (Type::Map(key, value), _) => Ok(Type::Map(key, value)), + // Only the right-map case is left: a map on the *left* takes + // anything, per the arm above. + (other, Type::Map(_, _)) => Err(Self::type_err( + "map removal requires a map on the left", + Some(Type::Map(Box::new(Type::Any), Box::new(Type::Any))), + Some(other), + Some(Expr::Bin( + Box::new(left_expr.clone()), + BinOp::Sub, + Box::new(right_expr.clone()), + )), + )), + _ => unreachable!("check_map_removal is only reached when a side is a map"), + } + } + + fn check_list_addition( + &mut self, + left_expr: &Expr, + left_ty: &Type, + right_expr: &Expr, + right_ty: &Type, + ) -> Result { + let left_resolved = self.as_list_type(left_ty); + let right_resolved = self.as_list_type(right_ty); + match (left_resolved, right_resolved) { + (Type::List(left_inner), Type::List(right_inner)) => { + // `wider_of`, whose doc has said all along that it is "the rule + // `check_list_addition` uses" — while this spelled out the + // opposite tie-break and picked the *narrower* side in both + // branches. `Int <: Float`, so `[1] + [1.5]` was typed + // `List` and held `1.5`: + // + // let v: List = [1] + [1.5]; // accepted + // let n: Int = v[1]; // accepted + // typeof(v[1]) // Float + // + // The map merge, which does call `wider_of`, answered + // `Map` for the same pair of types. + let elem_ty = self.wider_of(left_inner.as_ref(), right_inner.as_ref()); + Ok(Type::List(Box::new(elem_ty))) + } + // One erased operand is the same rule `in` follows: `Any` is a + // container until it runs. The element type is unknown, so the + // result is the widest list rather than the known side's. + (Type::List(_), Type::Any) | (Type::Any, Type::List(_)) | (Type::Any, Type::Any) => { + Ok(Type::List(Box::new(Type::Any))) + } + // A list operand absorbs the other one, in position: the VM's + // `Add` prepends for `"p=" + [1, 2]` and appends for + // `[1, 2] + "x"`, and `lkrt_dyn_add` says the rule out loud — + // "a list operand wins over a string one, so `"p=" + [1, 2]` is + // the list `["p=", 1, 2]` and not the text `p=[1,2]`". + // + // Both executors have answered this all along and only the checker + // refused, and only when it could see the types — so the same + // expression ran with `Any` operands and was a type error with + // known ones. That is the defect `Any + Any` map merge was fixed + // for, stated the same way: `lk check` answers the executors' + // question, and a rule neither executor has is as much a defect as + // a missing one. + // + // No operand kind is excluded, because none raises: a set, a byte + // string, a map and a nil all land in the list beside the elements. + (Type::List(inner), other) | (other, Type::List(inner)) => { + let elem = self.wider_of(inner.as_ref(), &other); + Ok(Type::List(Box::new(elem))) + } _ => Err(Self::type_err( "List concatenation requires both operands to be lists", Some(Type::List(Box::new(Type::Any))), @@ -969,7 +1929,38 @@ impl TypeChecker { // division is what the hardware does. return Ok(Type::MachineInt(*left_kind)); } - // A machine integer on one side only is a width mistake, not a promotion. + // An integer *literal* takes the machine width of the other side. + // + // `let x: u8 = 5` already works — a literal is retyped rather than + // rejected, because requiring `5 as u8` there would make a fixed width + // unusable. `reg + 1` is the same need with more force: `reg + (1 as u32)` + // at every increment is what gets fixed widths abandoned in favour of + // `Int`, which is the opposite of what asking for a width was for. + // + // This half alone is a *miscompile*, and it was one for a round: the + // checker says `u8` while the compiler goes on materialising the literal + // as an ordinary `Int` and doing 64-bit arithmetic, so `255u8 + 1` + // answers 256 with the type still claiming `u8`. The other half is + // `adopt_machine_width_for_literal` in the compiler, which normalises the + // literal to that width before the operation, so the wrap that follows + // has two proven operands to agree about. + // + // Only a literal. A *variable* of another numeric type is still a width + // mistake — that is the rule this preserves — and a literal out of range + // says so, measured against the width it was used with. + if let Type::MachineInt(kind) = &resolved_left + && !matches!(resolved_right, Type::MachineInt(_)) + && let Some(literal) = Self::int_literal_operand(right_expr) + { + return Self::machine_literal_result(*kind, literal, right_expr); + } + if let Type::MachineInt(kind) = &resolved_right + && !matches!(resolved_left, Type::MachineInt(_)) + && let Some(literal) = Self::int_literal_operand(left_expr) + { + return Self::machine_literal_result(*kind, literal, left_expr); + } + // A machine integer on one side only is a width mistake, not a promotion. if matches!(resolved_left, Type::MachineInt(_)) || matches!(resolved_right, Type::MachineInt(_)) { let (offending, expr) = if matches!(resolved_left, Type::MachineInt(_)) { (&resolved_right, right_expr) @@ -984,9 +1975,26 @@ impl TypeChecker { )); } - let left_class = self.classify_numeric_operand(left_ty, &resolved_left, left_expr, "左侧")?; - let right_class = self.classify_numeric_operand(right_ty, &resolved_right, right_expr, "右侧")?; - + let left_class = self.classify_numeric_operand(left_ty, &resolved_left, left_expr, "the left operand")?; + let right_class = self.classify_numeric_operand(right_ty, &resolved_right, right_expr, "the right operand")?; + + // `/` yields a `Float`, even for two `Int`s. + // + // That was always the design — `docs/semantics.md` states it and + // explains the consequence (an integer midpoint has to be written + // `math.floor((lo + hi) / 2)`), and `examples/syntax/operators.lk` + // asserts `15 / 4 > 3.7`. Only the *executor* never implemented it: + // both backends divided as integers, and the constant folder split the + // difference by keeping an `Int` when the literals happened to divide + // evenly. So one expression had three answers: + // + // ```text + // println(7 / 2); → 3.5 (folded) + // let a = 7; let b = 2; println(a / b); → 3 (runtime) + // ``` + // + // The runtimes moved to this rule rather than the other way around: + // this one is what the language says it is, in three places. let mut result_class = NumericHierarchy::result(left_class, right_class); if matches!(op, BinOp::Div) && result_class == NumericClass::Int { result_class = NumericClass::Float; @@ -1010,7 +2018,7 @@ impl TypeChecker { return Ok(NumericClass::Int); } Err(Self::type_err( - &format!("{label} must by numeric types"), + &format!("{label} must be numeric types"), Some(NumericHierarchy::expected_type()), Some(resolved.clone()), Some(expr.clone()), @@ -1048,6 +2056,18 @@ impl TypeChecker { } Ok(()) } + // A literal takes the width of what it is compared against, the same + // as in arithmetic: `reg > 0` and `count < 8` are what driver code + // is made of, and `reg > (0 as u32)` is the ceremony that gets fixed + // widths abandoned. The range is still checked against that width. + (Type::MachineInt(kind), _) if Self::int_literal_operand(right_expr).is_some() => { + let literal = Self::int_literal_operand(right_expr).expect("checked"); + Self::machine_literal_result(*kind, literal, right_expr).map(|_| ()) + } + (_, Type::MachineInt(kind)) if Self::int_literal_operand(left_expr).is_some() => { + let literal = Self::int_literal_operand(left_expr).expect("checked"); + Self::machine_literal_result(*kind, literal, left_expr).map(|_| ()) + } (Type::MachineInt(kind), other) => Err(Self::type_err( "machine integers do not mix with other numeric types; cast explicitly", Some(Type::MachineInt(*kind)), @@ -1060,17 +2080,76 @@ impl TypeChecker { Some(other.clone()), Some(left_expr.clone()), )), + // Strings order lexicographically, as they already did everywhere + // else: `list.sort()` puts them in that order, the constant folder + // folds `"a" < "b"`, and the executor's `number_compare` has had a + // string arm all along. Only this rule said no, so the one way to + // ask a string which came first was to sort a two-element list. + (Type::String, Type::String) => Ok(()), + // A String against something else. The fall-through below reported + // "the left operand must be numeric", which blames the wrong thing: + // a String *is* orderable, just not against a number. An ordering + // compares two of a kind. + (Type::String, other) => self.orders_against_a_string(other, right_expr), + (other, Type::String) => self.orders_against_a_string(other, left_expr), _ => { - self.ensure_numeric_operand(left_ty, left_expr, "左侧")?; - self.ensure_numeric_operand(right_ty, right_expr, "右侧")?; + self.ensure_orderable_operand(left_ty, left_expr)?; + self.ensure_orderable_operand(right_ty, right_expr)?; Ok(()) } } } - fn ensure_numeric_operand(&mut self, ty: &Type, expr: &Expr, label: &'static str) -> Result { + /// One side of an ordering: a number or a string. + /// + /// This position used to borrow `classify_numeric_operand`, which is + /// *arithmetic's* rule and reports the expected set as + /// `Int | Float | Box` — a set that stopped being right when strings + /// became orderable. It also answered the wrong question about `[1,2] < [1,3]`: + /// the point is that a list has no ordering at all, not that it is not a + /// number. Arithmetic keeps that rule, which is correct there (a string + /// concatenates through a different arm). + fn ensure_orderable_operand(&mut self, ty: &Type, expr: &Expr) -> Result<()> { let resolved = self.resolve_aliases(ty); - self.classify_numeric_operand(ty, &resolved, expr, label) + if NumericHierarchy::classify(&resolved).is_some() || matches!(resolved, Type::String) { + return Ok(()); + } + if ty.contains_variables() { + self.inference_engine.add_constraint(ty.clone(), Type::Int); + return Ok(()); + } + if matches!(resolved, Type::Any | Type::Boxed(_)) { + return Ok(()); + } + Err(Self::type_err( + "`<`, `<=`, `>` and `>=` order numbers and strings; this type has no ordering", + None, + Some(resolved), + Some(expr.clone()), + )) + } + + /// The other side of an ordering whose one side is a `String`. + /// + /// Unresolved becomes a `String` — this is a comparison of strings, which is + /// the only thing a string orders against. Dynamic (`Any`, a box) is decided + /// at run time, as everywhere else. Anything concrete and not a string is the + /// mistake, and the report names the *pair* rather than accusing one side of + /// not being a number. + fn orders_against_a_string(&mut self, other: &Type, other_expr: &Expr) -> Result<()> { + match other { + Type::Variable(_) => { + self.inference_engine.add_constraint(other.clone(), Type::String); + Ok(()) + } + Type::Any | Type::Boxed(_) => Ok(()), + _ => Err(Self::type_err( + "an ordering compares two of a kind: a String orders against a String, a number against a number", + Some(Type::String), + Some(other.clone()), + Some(other_expr.clone()), + )), + } } /// Check logical operation types (&&, ||) @@ -1086,16 +2165,74 @@ impl TypeChecker { } /// Check unary operation types + /// Whether a value of this type could be a `Bool` or a `Nil` at run time. + /// + /// `Optional(Int)` counts: it is nil sometimes, and `!x` on the nil is a + /// program that works. Only a type with no `Bool` and no `Nil` anywhere in + /// it is certainly wrong. + fn may_be_bool_or_nil(ty: &Type) -> bool { + match ty { + Type::Bool | Type::Nil | Type::Any | Type::Unknown | Type::Variable(_) => true, + Type::Optional(_) => true, + Type::Union(members) => members.iter().any(Self::may_be_bool_or_nil), + _ => false, + } + } + fn check_unary_op(&mut self, op: &UnaryOp, expr: &Expr) -> Result { let expr_type = self.check_expr(expr)?; match op { + // `!` takes a `Bool` or a `Nil`, which is the runtime's rule + // (`Not expected Bool or Nil, got Int`) — and the checker used to + // accept *anything*, so `!5` passed `lk check` and raised when it + // ran. Its sibling `&&` has always been checked; only `!` was + // waved through. + // + // Rejected only when the operand *cannot* be either. A variable, an + // `Any`, or a union with a `Bool` or a `Nil` in it may still be one + // at run time, and this is a language where that is the ordinary + // case — the check names what is certainly wrong, not everything it + // cannot prove right. UnaryOp::Not => { - if matches!(self.resolve_aliases(&expr_type), Type::Variable(_)) { + let resolved = self.resolve_aliases(&expr_type); + if matches!(resolved, Type::Variable(_)) { self.inference_engine.add_constraint(expr_type, Type::Any); + } else if !Self::may_be_bool_or_nil(&resolved) { + return Err(Self::type_err( + "Not expected Bool or Nil", + Some(Type::Bool), + Some(resolved.clone()), + Some(expr.clone()), + )); } Ok(Type::Bool) } + // Negation keeps the operand's type: an `Int` stays an `Int`, a + // `Float` a `Float`. Widening to a `Number` union would throw away + // the width the rest of the checker relies on, and `-x` never + // changes it. + // + // Machine integers are admitted for the signed kinds only. `-x` on + // a `u8` has no answer the writer could have meant: the negation + // does not fit the type, and wrapping to `256 - x` silently is + // worse than saying so. + UnaryOp::Neg => { + let resolved = self.resolve_aliases(&expr_type); + if let Type::MachineInt(kind) = resolved { + return match kind { + IntKind::I8 | IntKind::I16 | IntKind::I32 | IntKind::I64 | IntKind::Isize => Ok(expr_type), + _ => Err(Self::type_err( + "cannot negate an unsigned integer", + Some(Type::Int), + Some(resolved.clone()), + Some(expr.clone()), + )), + }; + } + self.classify_numeric_operand(&expr_type, &resolved, expr, "negation operand")?; + Ok(expr_type) + } } } @@ -1152,6 +2289,21 @@ impl TypeChecker { for (k, v) in pairs { let kt = self.check_expr(k)?; let vt = self.check_expr(v)?; + // Same rule as a set member, because a set *is* a key set. Each key + // is checked on its own: a literal names its key one by one, so + // `{1.5: "a"}` is settled here rather than at run time. + let resolved_key = self.resolve_aliases(&kt); + if crate::typ::type_checker::type_is_certainly_not_a_key(&resolved_key) { + return Err(Self::type_err( + &format!( + "{} cannot be a map key — only nil, Bool, Int and String can", + resolved_key.display() + ), + None, + Some(resolved_key), + Some(k.as_ref().clone()), + )); + } match kt { Type::Union(ts) => key_tys.extend(ts), other => key_tys.push(other), @@ -1189,6 +2341,112 @@ impl TypeChecker { Ok(Type::Map(Box::new(key_type), Box::new(value_type))) } + /// One store into a container, checked against what the container's type + /// declares it holds. + /// + /// Every assignment that is not a plain `name = value` reaches this: the + /// parser desugars `l[i] = v`, `m[k] = v` and `s.f = v` into hidden calls + /// (`__lk_set_index`, `__lk_set_field`, `list.set`), and until now nothing + /// checked the *value* against the declaration. `l.set(0, "a")` on a + /// `List` was refused while `l[0] = "a"` — the same operation, the + /// other spelling — was accepted, and `let n: Int = l[0]` then type-checked + /// and held a String. A struct field was the same: `s.x = "a"` on + /// `struct S { x: Int }`. + /// + /// `Any` on either side is the language's dynamic escape hatch and passes, + /// as it does everywhere else. + fn check_container_store(&mut self, container_ty: &Type, key: &Expr, value: &Expr) -> Result<()> { + let container = self.resolve_aliases(container_ty); + let declared = match &container { + Type::List(elem) => (**elem).clone(), + Type::Map(key_ty, val) => { + // The key too: a `Map` accepted `m[7] = 2` and + // then held an Int key. The rule that a Float cannot be a key + // *at all* was already asked; this is the declared key type. + let actual_key = self.check_expr(key)?; + if !actual_key.contains_variables() + && !key_ty.contains_variables() + && !self.is_assignable(&actual_key, key_ty) + { + return Err(Self::type_err( + "map key has the wrong type", + Some((**key_ty).clone()), + Some(actual_key), + Some(key.clone()), + )); + } + (**val).clone() + } + // A heterogeneous list literal infers `Tuple`, and its positions + // have *different* types, so a store is checked per position. With + // an index that is not a literal the position is unknown, so the + // value has to fit every one of them — the read `l[0]` is typed + // from position 0, and a dynamic store landing there must not + // break it. `let l = [1, "a"]; l[0] = 2.5;` used to be accepted, + // and `let n: Int = l[0]` then held 2.5. + Type::Tuple(elems) => match key { + Expr::Literal(LiteralVal::Int(index)) if (*index as usize) < elems.len() => { + elems[*index as usize].clone() + } + _ => { + let value_ty = self.check_expr(value)?; + if value_ty.contains_variables() { + return Ok(()); + } + for elem in elems.iter() { + if !elem.contains_variables() && !self.is_assignable(&value_ty, elem) { + return Err(Self::type_err( + "stored value has the wrong type for some position of this tuple", + Some(elem.clone()), + Some(value_ty), + Some(value.clone()), + )); + } + } + return Ok(()); + } + }, + // A struct's field names its own type. A field name that does not + // resolve is an error *here* rather than the field-access path's + // business: a store has no access expression for that path to see. + // `p.z = 3` desugars straight to `__lk_set_field(p, "z", 3)`, so + // while reading `p.z` was refused, writing it was accepted — and + // the two back ends then disagreed about the value, the interpreter + // growing the field and the compiled build dropping it. + // + // Only for a struct this checker knows and a field written as a + // literal. An unknown name is somebody else's to refuse, and a + // computed field name is not a struct store at all. + Type::Named(name) if self.registry.get_struct(name).is_some() => match self.struct_field_type(name, key) { + Ok(ty) => ty, + Err(_) if !matches!(key, Expr::Literal(_)) => return Ok(()), + Err(err) => return Err(err), + }, + Type::Named(_) => return Ok(()), + _ => return Ok(()), + }; + let value_ty = self.check_expr(value)?; + // Nothing settled to check against: an unannotated container's element + // type is still a variable, so the store *teaches* it rather than being + // refused by it. Same rule `check_argument` applies to an argument, and + // what keeps `let l = []; l.push(1); l[0] = "a";` working. + if declared.contains_variables() || value_ty.contains_variables() { + self.inference_engine.add_constraint(declared, value_ty); + return Ok(()); + } + // `value_fits`, not bare assignability: a store is a position a value + // is written at, and the literal rules belong to every one of them. + if self.value_fits(value, &value_ty, &declared) { + return Ok(()); + } + Err(Self::type_err( + "stored value has the wrong type", + Some(declared), + Some(value_ty), + Some(value.clone()), + )) + } + fn struct_field_type(&self, struct_name: &str, field: &Expr) -> Result { let Some(def) = self.registry.get_struct(struct_name) else { return Err(Self::type_err( @@ -1233,6 +2491,12 @@ impl TypeChecker { if let Some(function_type) = self.stdlib_access_function_type(expr, field) { return Ok(function_type); } + if let Expr::Var(namespace) = expr + && let Some(member) = stdlib::segment_name(field) + && let Some(member_type) = self.imported_member_type(namespace, member) + { + return Ok(member_type); + } let expr_type = self.check_expr(expr)?; let field_type = self.check_expr(field)?; @@ -1255,7 +2519,55 @@ impl TypeChecker { } Ok((**elem_type).clone()) } + // A `Bytes` indexes like any other sequence, and its elements are + // `Int`. Without this the index fell through to struct-field + // access, so `b[0]` reported "Unknown struct 'Bytes'". + Type::Named(name) if name == "Bytes" => { + // `b[a..c]` is `b.slice(a, c)` written the other way, and it + // answers a `Bytes` for the same reason: every element of the + // answer is still a byte. The two spellings used to disagree — + // the method worked and the range said "Bytes index must be + // integer". + if matches!(&field, Expr::Range { .. }) { + return Ok(Type::Named("Bytes".to_string())); + } + if !self.is_assignable(&field_type, &Type::Int) { + return Err(Self::type_err( + "Bytes index must be integer", + Some(Type::Int), + Some(field_type), + None, + )); + } + Ok(Type::Int) + } + // A window indexes like the list it windows, and yields the same + // element type — which is the point of `Slice` carrying one. + Type::Generic { name, params } if name == "Slice" => { + // A sub-range of a window is a window, which is what + // `w.slice(a, c)` already answers. + if matches!(&field, Expr::Range { .. }) { + return Ok(resolved_expr_type.clone()); + } + if !self.is_assignable(&field_type, &Type::Int) { + return Err(Self::type_err( + "Slice index must be integer", + Some(Type::Int), + Some(field_type), + None, + )); + } + Ok(params.first().cloned().unwrap_or(Type::Any)) + } Type::Tuple(elems) => { + // `t[a..b]` is a slice, the way it is on every other sequence. + // Each arm above carries this guard and this one did not, so a + // heterogeneous literal — which is what a `Tuple` is — was the + // one list that could not be sliced: `[1, "a"][0..2]` said + // "Tuple index must be integer" while `[1, 2][0..2]` answered. + if matches!(&field, Expr::Range { .. }) { + return Ok(Type::List(Box::new(Type::Union(elems.to_vec())))); + } // Field must be integer index; if it's a literal index, pick that element if !self.is_assignable(&field_type, &Type::Int) { return Err(Self::type_err( @@ -1277,8 +2589,19 @@ impl TypeChecker { Ok(u) } Type::Map(key_type, value_type) => { - // Field must match key type - self.inference_engine.add_constraint((**key_type).clone(), field_type); + // Reading a key of another type is a *miss*, not an error: the + // interpreter answers nil, the way it does for a key that is + // simply absent. Constraining the two unified them, so + // `{"k": 1}[0]` was "Cannot unify String with Int" — a message + // about the checker's own machinery, for a lookup that has an + // answer. + // + // Writing is the other side of the line and still refuses: + // `m[0] = 9` would put a key in the map that its type says is + // not there. + if !self.definitely_not_key(&field_type, key_type.as_ref()) { + self.inference_engine.add_constraint((**key_type).clone(), field_type); + } Ok((**value_type).clone()) } Type::String => { @@ -1338,6 +2661,234 @@ impl TypeChecker { } } + /// A closure's type: its parameters, and the type its body has. + /// + /// `expected_params` is what the *call site* already knows about them — + /// `xs.map(|x| …)` on a `List` knows `x` is a `String` before the + /// body is read. Without that, the body is checked with `x` still a free + /// variable, so `x.bogus()` is unknowable rather than wrong, and the + /// element type only arrives afterwards as a constraint, too late to have + /// checked anything. Anything not supplied stays a fresh variable, which is + /// every closure that is not an argument to a method that knows better. + /// Types a closure, with `expected_params` pushed **into** it when the + /// context knows them. + /// + /// `pub(crate)` because two contexts supply them: a call whose callee's + /// parameter is a function type, and a `let` with a function-type + /// annotation (`crate::stmt::stmt_impl::type_check`). Without the second, + /// `let f: (Int) -> Int = |x| { return x + 1; };` was rejected — the + /// lambda was typed in isolation as `('T0) -> Any` and that does not unify + /// with the very annotation written for it, so a lambda could not be + /// annotated at all while a named `fn` assigned to the same binding fine. + /// One element of an aggregate, checked against the declared element type. + /// + /// The expectation flowing in is only half of it: the answer still has to be + /// *checked*. Returning the declared type unconditionally is a claim rather + /// than a description, and it let `let fs: List<(Int) -> String> = [|x| { + /// return x + 1; }];` through. + fn check_element_against(&mut self, expr: &Expr, expected: &Type, what: &str) -> Result<()> { + let actual = self.check_expr_against(expr, Some(expected))?; + if self.is_assignable(&actual, expected) { + return Ok(()); + } + Err(Self::type_err( + &alloc::format!("{what} has the wrong type"), + Some(expected.clone()), + Some(actual), + Some(expr.clone()), + )) + } + + /// Whether a lambda sits at this position (through parentheses). + /// + /// The gate on distributing an expectation into an aggregate: without a + /// lambda there is nothing bidirectionality can change, and the plain path + /// keeps its own inference (a mixed list literal is a `Tuple`, which is not + /// this helper's rule to overturn). + fn holds_closure(expr: &Expr) -> bool { + match expr { + Expr::Closure { .. } => true, + Expr::Paren(inner) => Self::holds_closure(inner), + _ => false, + } + } + + /// Types `expr` **against** what the context declares, when that changes + /// the answer. + /// + /// Today one shape needs it: a lambda. Checked in isolation a lambda types + /// as `('T0) -> Any`, which does not unify with the function type written + /// for it — so every context that declares one had to be taught separately, + /// and each that was not silently rejected the lambda written for it. A + /// `let` was, a struct field was not. + /// + /// Everything else is plain `check_expr`: this is a narrow bidirectional + /// rule, not a second type checker. + pub(crate) fn check_expr_against(&mut self, expr: &Expr, expected: Option<&Type>) -> Result { + let Some(resolved) = expected.map(|ty| self.resolve_aliases(ty)) else { + return self.check_expr(expr); + }; + match (&resolved, expr) { + ( + Type::Function { + params: expected_params, + .. + }, + Expr::Closure { + params, + param_types, + return_type, + body, + }, + ) if expected_params.len() == params.len() => { + self.check_closure(params, param_types, return_type.as_deref(), body, expected_params) + } + // Parentheses are not a type-level construct. + (_, Expr::Paren(inner)) => self.check_expr_against(inner, expected), + // Distributed into an aggregate literal, but *only* when a lambda is + // actually sitting there: `[|x| …]` against `List<(Int) -> Int>`. + // Otherwise the plain path keeps its inference exactly — a list + // literal of mixed types is a `Tuple`, and that rule is not this + // helper's business. + (Type::List(elem), Expr::List(items)) if items.iter().any(|item| Self::holds_closure(item)) => { + for item in items { + self.check_element_against(item, elem, "list element")?; + } + Ok(Type::List(elem.clone())) + } + (Type::Map(key, value), Expr::Map(pairs)) if pairs.iter().any(|(_, v)| Self::holds_closure(v)) => { + for (k, v) in pairs { + self.check_expr(k)?; + self.check_element_against(v, value, "map value")?; + } + Ok(Type::Map(key.clone(), value.clone())) + } + // An optional accepts its payload, so the payload's expectation is + // what a lambda written there has to meet. + (Type::Optional(inner), _) => self.check_expr_against(expr, Some(inner)), + _ => self.check_expr(expr), + } + } + + pub(crate) fn check_closure( + &mut self, + params: &[String], + declared_param_types: &[Option], + declared_return: Option<&Type>, + body: &Expr, + expected_params: &[Type], + ) -> Result { + // A lambda's parameter list is a binder like a `fn`'s: a repeated name + // leaves an argument every call site still has to pass and nothing can + // read. + let mut seen: Vec<&str> = Vec::with_capacity(params.len()); + for param in params { + if seen.contains(¶m.as_str()) { + return Err(Self::type_err( + &alloc::format!( + "`{param}` is declared twice in this lambda's parameters — the second one shadows \ + the first, so nothing can read the argument passed for it" + ), + None, + None, + None, + )); + } + seen.push(param); + } + // What the closure *says* wins over what the context expects, which + // wins over a fresh variable. A declaration is the author stating the + // type; a context is an inference about it. + let param_types: Vec = params + .iter() + .enumerate() + .map(|(index, _)| { + declared_param_types + .get(index) + .cloned() + .flatten() + .or_else(|| expected_params.get(index).cloned()) + .unwrap_or_else(|| self.inference_engine.fresh_type_var()) + }) + .collect(); + + // The parameters are in scope for the body — which is the point of + // knowing their types. + self.push_scope(); + for (name, ty) in params.iter().zip(param_types.iter()) { + self.add_local_type(name.clone(), ty.clone()); + } + // Body type is inferred by checking the body expression. Its own + // return frame: a `return` inside a closure body belongs to the + // closure, and must not be collected as a return of the enclosing + // function (whose declared type it would then have to satisfy). + self.push_return_frame(declared_return.cloned()); + // Like a named function's body: a closure runs when it is called, + // which is after the top level has finished, so it may read a binding + // declared below it. + let pending = self.suspend_pending_top_level(); + let ret_type = self.check_expr(body); + self.restore_pending_top_level(pending); + let collected_returns = self.pop_return_frame(); + self.pop_scope(); + let body_type = ret_type?; + // A `return` inside the body is what the closure returns. Discarding + // the frame (which is what this did) left every block-bodied closure + // typed `… -> Any`, and `Any` satisfies any annotation: `let s: String + // = (|x| { return x + 1; })(1);` type-checked. A named `fn` has always + // joined its collected returns — this is the same rule, not a new one. + // + // The body's own type joins in only when it says something: a block + // ending in a `return` statement types `Any` (there is no tail + // expression), and letting that in would swallow the union. + let inferred_return = if collected_returns.is_empty() { + body_type.clone() + } else { + let mut alternatives = collected_returns.clone(); + if body_type != Type::Any { + alternatives.push(body_type.clone()); + } + crate::typ::union_of(alternatives) + }; + // A declared return type is checked against, then used: an annotation + // that merely renamed the inferred type would state nothing. + let ret_type = match declared_return { + Some(declared) => { + let declared = self.resolve_aliases(declared); + let mut actual = collected_returns; + if body_type != Type::Any { + actual.push(body_type); + } + for ty in &actual { + if !self.is_assignable(ty, &declared) { + return Err(anyhow!( + "Return type mismatch in closure: expected {}, got {}", + declared.display(), + ty.display() + )); + } + } + declared + } + None => inferred_return, + }; + Ok(Type::Function { + params: param_types, + named_params: Vec::new(), + return_type: Box::new(ret_type), + }) + } + + /// Method typing that the declarative table (`BUILTIN_METHODS`) cannot + /// express, for receivers it is not keyed by — a `Tuple`, or a type + /// variable inference has not resolved yet. + /// + /// The table is consulted *first*, so an arm here that only handles + /// `List`/`Map`/`Set` never runs. There were four such arms (`add`, `push`, + /// `keys`/`values`, `clear`), and two of them disagreed with the table about + /// the return type — `push` and `clear` said `Nil` where the table says + /// `Self`. Dead code that contradicts the live rule is a trap set for + /// whoever adds the next receiver kind, so they are gone. fn check_builtin_container_method( &mut self, receiver_ty: &Type, @@ -1575,91 +3126,6 @@ impl TypeChecker { _ => Ok(None), } } - "add" => { - let resolved_receiver = self.resolve_aliases(receiver_ty); - match resolved_receiver { - Type::Set(elem_type) => { - if args.len() != 1 { - return Err(Self::type_err( - "Method add expects 1 argument", - None, - None, - Some(func.clone()), - )); - } - let arg_ty = self.check_expr(&args[0])?; - self.inference_engine.add_constraint((*elem_type).clone(), arg_ty); - Ok(Some(Type::Bool)) - } - Type::Variable(_) => Ok(None), - _ => Ok(None), - } - } - "push" => { - let resolved_receiver = self.resolve_aliases(receiver_ty); - match resolved_receiver { - Type::List(elem_type) => { - if args.len() != 1 { - return Err(Self::type_err( - "Method push expects 1 argument", - None, - None, - Some(func.clone()), - )); - } - let arg_ty = self.check_expr(&args[0])?; - self.inference_engine.add_constraint((*elem_type).clone(), arg_ty); - Ok(Some(Type::Nil)) - } - Type::Variable(_) => Ok(None), - _ => Ok(None), - } - } - "keys" | "values" => { - let resolved_receiver = self.resolve_aliases(receiver_ty); - match resolved_receiver { - Type::Map(key_type, value_type) => { - if !args.is_empty() { - return Err(Self::type_err( - &format!("Method {method} expects 0 arguments"), - None, - None, - Some(func.clone()), - )); - } - let elem = if method == "keys" { *key_type } else { *value_type }; - Ok(Some(Type::List(Box::new(elem)))) - } - Type::Set(elem_type) if method == "values" => { - if !args.is_empty() { - return Err(Self::type_err( - "Method values expects 0 arguments", - None, - None, - Some(func.clone()), - )); - } - Ok(Some(Type::List(elem_type))) - } - Type::Variable(_) => Ok(None), - _ => Ok(None), - } - } - "clear" => { - let resolved_receiver = self.resolve_aliases(receiver_ty); - if matches!(&resolved_receiver, Type::Map(_, _) | Type::Set(_) | Type::List(_)) { - if !args.is_empty() { - return Err(Self::type_err( - "Method clear expects 0 arguments", - None, - None, - Some(func.clone()), - )); - } - return Ok(Some(Type::Nil)); - } - Ok(None) - } _ => Ok(None), } } @@ -1704,11 +3170,18 @@ impl TypeChecker { Ok(Type::Optional(elem_type)) } Type::Map(key_type, value_type) => { + // A miss, for the reason the read arm gives. let field_ty = self.check_expr(field)?; - self.inference_engine.add_constraint((*key_type).clone(), field_ty); + if !self.definitely_not_key(&field_ty, key_type.as_ref()) { + self.inference_engine.add_constraint((*key_type).clone(), field_ty); + } Ok(Type::Optional(value_type)) } Type::Tuple(elems) => { + // A slice, for the reason the read arm gives. + if matches!(&field, Expr::Range { .. }) { + return Ok(Type::List(Box::new(Type::Union(elems.to_vec())))); + } let field_ty = self.check_expr(field)?; if !self.is_assignable(&field_ty, &Type::Int) { return Err(Self::type_err( @@ -1792,6 +3265,24 @@ fn parse_port_builtin(name: &str) -> Option<(bool, lk_values::IntKind)> { } /// Splits a `volatile_{read,write}_uN` name into its direction and width. +/// The machine width a builtin's *name* declares, for the ones whose result is +/// a machine int. +/// +/// Exposed for the bytecode compiler, which needs the same answer to know when +/// arithmetic on the result has to wrap. Derived from the name by the same +/// parsers the checks above use, rather than a second table — a second table is +/// one entry away from a value that wraps in the type system and not in the +/// program. +pub fn builtin_machine_result(name: &str) -> Option { + if let Some((is_write, kind)) = parse_volatile_builtin(name) { + return (!is_write).then_some(kind); + } + if let Some((is_write, kind)) = parse_port_builtin(name) { + return (!is_write).then_some(kind); + } + None +} + fn parse_volatile_builtin(name: &str) -> Option<(bool, lk_values::IntKind)> { let (is_write, rest) = match name.strip_prefix("volatile_read_") { Some(rest) => (false, rest), @@ -1808,3 +3299,240 @@ fn parse_volatile_builtin(name: &str) -> Option<(bool, lk_values::IntKind)> { }; Some((is_write, kind)) } + +/// Read a call's instance variables off its arguments. +/// +/// `param` is a *pattern*: a variable in it simply takes whatever the argument +/// has at that position. The map that comes out belongs to one call and is +/// thrown away with it, which is what makes it an instantiation — two calls to +/// the same function read the same variables to different answers without +/// either one deciding anything for the other. +/// +/// Deliberately structural and partial: a shape it does not recognise binds +/// nothing, and the return type keeps the unresolved variable it had before, +/// which is the answer this whole path used to give for every call. +pub(super) fn bind_instance_variables(param: &Type, arg: &Type, out: &mut HashMap) { + match (param, arg) { + (Type::Variable(name), _) => { + if !matches!(arg, Type::Variable(_)) { + out.entry(name.clone()).or_insert_with(|| arg.clone()); + } + } + (Type::List(p), Type::List(a)) + | (Type::Set(p), Type::Set(a)) + | (Type::Optional(p), Type::Optional(a)) + | (Type::Task(p), Type::Task(a)) + | (Type::Channel(p), Type::Channel(a)) + | (Type::Boxed(p), Type::Boxed(a)) => bind_instance_variables(p, a, out), + // A heterogeneous literal is a `Tuple`, and `[1, 2]` is what a + // `List<'a>` parameter is most often handed. Its element type is the + // one every position agrees on, or nothing. + (Type::List(p), Type::Tuple(elems)) => { + if let Some(first) = elems.first() + && elems.iter().all(|elem| elem == first) + { + bind_instance_variables(p, first, out); + } + } + (Type::Map(pk, pv), Type::Map(ak, av)) => { + bind_instance_variables(pk, ak, out); + bind_instance_variables(pv, av, out); + } + (Type::Tuple(ps), Type::Tuple(as_)) if ps.len() == as_.len() => { + for (p, a) in ps.iter().zip(as_.iter()) { + bind_instance_variables(p, a, out); + } + } + ( + Type::Function { + params: ps, + return_type: pr, + .. + }, + Type::Function { + params: as_, + return_type: ar, + .. + }, + ) => { + for (p, a) in ps.iter().zip(as_.iter()) { + bind_instance_variables(p, a, out); + } + bind_instance_variables(pr, ar, out); + } + (Type::Generic { name: pn, params: ps }, Type::Generic { name: an, params: as_ }) + if pn == an && ps.len() == as_.len() => + { + for (p, a) in ps.iter().zip(as_.iter()) { + bind_instance_variables(p, a, out); + } + } + _ => {} + } +} + +/// [`Type::substitute`], except that a union is left exactly as it was. +/// +/// A map literal's value type is the union of *every key's* value — +/// `{"name": name, "score": 95}` is `Map` — so the union +/// describes several keys at once and no single read is decided by it. Pinning +/// the parameter's arm there does not make `u.score` any more knowable; it just +/// turns a vague answer into a confident wrong one, and `u.score + 5` (which +/// runs fine) starts reporting "left side must be numeric, got String | Int". +/// +/// The honest fix is for a map literal to keep a type per key rather than one +/// union across all of them, which the type system has no shape for yet. Until +/// it does, this is the line between "instantiation tells you more" and +/// "instantiation tells you something wrong". +pub(super) fn substitute_outside_unions(ty: &Type, bindings: &HashMap) -> Type { + match ty { + Type::Union(_) => ty.clone(), + Type::List(inner) => Type::List(Box::new(substitute_outside_unions(inner, bindings))), + Type::Set(inner) => Type::Set(Box::new(substitute_outside_unions(inner, bindings))), + Type::Optional(inner) => Type::Optional(Box::new(substitute_outside_unions(inner, bindings))), + Type::Boxed(inner) => Type::Boxed(Box::new(substitute_outside_unions(inner, bindings))), + Type::Task(inner) => Type::Task(Box::new(substitute_outside_unions(inner, bindings))), + Type::Channel(inner) => Type::Channel(Box::new(substitute_outside_unions(inner, bindings))), + Type::Map(k, v) => Type::Map( + Box::new(substitute_outside_unions(k, bindings)), + Box::new(substitute_outside_unions(v, bindings)), + ), + Type::Tuple(elems) => Type::Tuple(elems.iter().map(|e| substitute_outside_unions(e, bindings)).collect()), + Type::Generic { name, params } => Type::Generic { + name: name.clone(), + params: params.iter().map(|p| substitute_outside_unions(p, bindings)).collect(), + }, + Type::Variable(name) => bindings.get(name).cloned().unwrap_or_else(|| ty.clone()), + other => other.clone(), + } +} + +/// The body of the first arm that an earlier unguarded catch-all shadows. +/// +/// Shares its notion of "catch-all" with [`matches_every_value`], through +/// [`crate::expr::Pattern::is_unguarded_catch_all`]. +fn first_arm_after_catch_all(arms: &[crate::expr::MatchArm]) -> Option<&Expr> { + let mut seen_catch_all = false; + for arm in arms { + if seen_catch_all { + return Some(arm.body.as_ref()); + } + if arm.pattern.is_unguarded_catch_all() { + seen_catch_all = true; + } + } + None +} + +/// Does some arm of `arms` match every value of `value_type`? +/// +/// Deliberately an under-approximation: it answers `true` only for the two +/// shapes a reader would call obviously total — a catch-all arm, and a `Bool` +/// whose two literals both appear. Anything else is treated as able to miss, +/// which makes the match's type `T?`. Being wrong in that direction costs a +/// `?` at the call site; being wrong the other way is what let a `String` +/// binding hold nil. +/// +/// A guard makes an arm conditional, so a guarded catch-all is not one. +fn matches_every_value(arms: &[crate::expr::MatchArm], value_type: &Type) -> bool { + use crate::expr::Pattern; + + fn is_bool_literal(pattern: &Pattern, wanted: bool) -> bool { + matches!(pattern, Pattern::Literal(LiteralVal::Bool(value)) if *value == wanted) + } + + if arms.iter().any(|arm| arm.pattern.is_unguarded_catch_all()) { + return true; + } + if *value_type == Type::Bool { + let covers = |wanted: bool| arms.iter().any(|arm| is_bool_literal(&arm.pattern, wanted)); + return covers(true) && covers(false); + } + false +} + +/// The struct a callee constructs, when the callee is the hidden constructor +/// `module.Type { … }` desugars to (`stmt::struct_ctors`). +/// +/// The desugar is meant to be invisible, so this is what lets its diagnostics +/// speak the source's words: fields of a struct, not named arguments of a +/// function nobody wrote. +fn constructed_struct_name(callee: &Expr) -> Option { + let Expr::Access(_, field) = callee else { + return None; + }; + let Expr::Literal(name) = field.as_ref() else { + return None; + }; + crate::stmt::struct_ctors::constructed_struct_name(name.as_str()?).map(alloc::string::ToString::to_string) +} + +fn missing_named_message(constructed: Option<&str>, name: &str) -> String { + match constructed { + Some(ty) => format!("Missing required field '{name}' for struct '{ty}'"), + None => format!("Missing required named argument: {name}"), + } +} + +fn unknown_named_message(constructed: Option<&str>, name: &str) -> String { + match constructed { + Some(ty) => format!("Unknown field '{name}' for struct '{ty}'"), + None => format!("Unknown named argument: {name}"), + } +} + +/// Whether inference is still running inside this type. +/// +/// A branch whose type is a variable has not been decided yet; a union of "the +/// answer" and "we do not know" would freeze the unknown half in place. +fn has_type_variable(ty: &Type) -> bool { + match ty { + Type::Variable(_) => true, + Type::Optional(inner) | Type::List(inner) | Type::Set(inner) | Type::Task(inner) | Type::Channel(inner) => { + has_type_variable(inner) + } + Type::Ptr { pointee, .. } => has_type_variable(pointee), + Type::Map(key, value) => has_type_variable(key) || has_type_variable(value), + Type::Tuple(items) | Type::Union(items) => items.iter().any(has_type_variable), + Type::Function { + params, + named_params, + return_type, + } => { + params.iter().any(has_type_variable) + || named_params.iter().any(|param| has_type_variable(¶m.ty)) + || has_type_variable(return_type) + } + _ => false, + } +} + +/// The two branch types as one, flattened and deduplicated. +/// +/// `Optional(T)` stays `Optional` rather than becoming `T | Nil`: they are the +/// same type, and `T?` is the spelling every diagnostic and every annotation +/// uses. +fn union_of(first: Type, second: Type) -> Type { + // `Any` absorbs: a branch the checker knows nothing about says nothing + // about the value, and `Any | String` would be a *narrower* claim than the + // truth. This is the case that matters in practice — `xs[i]!` desugars to a + // nil check whose raising half is `Any`, so without this every unwrap in a + // mixed list became `Any | Elem` and then failed arithmetic. + if first == Type::Any || second == Type::Any { + return Type::Any; + } + let mut items: Vec = Vec::new(); + for ty in [first, second] { + match ty { + Type::Union(inner) => items.extend(inner), + other => items.push(other), + } + } + let mut seen = alloc::collections::BTreeSet::new(); + items.retain(|ty| seen.insert(ty.display())); + if items.len() == 1 { + items.remove(0) + } else { + Type::Union(items) + } +} diff --git a/core/src/typ/type_checker/expressions/calls.rs b/core/src/typ/type_checker/expressions/calls.rs index 0eb1968b..33dd2e76 100644 --- a/core/src/typ/type_checker/expressions/calls.rs +++ b/core/src/typ/type_checker/expressions/calls.rs @@ -2,16 +2,89 @@ use crate::compat::prelude::*; use crate::expr::Expr; use crate::typ::type_checker::TypeChecker; +use crate::typ::type_checker::expressions::bind_instance_variables; use crate::val::Type; use anyhow::Result; +use hashbrown::HashMap; impl TypeChecker { /// Check function call type pub(super) fn check_function_call(&mut self, func: &Expr, args: &[Box]) -> Result { + // `chan(1)` after `use chan;` — the import bound that name to the + // module, so this calls a *map*. Reported here because the two engines + // disagreed about it: the VM raised "this value is not a function: it + // is a Map" and the native backend called the builtin constructor + // anyway, answering a channel. A local of the same name is an ordinary + // value and not this mistake. + if let Expr::Var(name) = func + && self.is_imported_stdlib_module(name) + && !self.has_local_binding(name) + { + return Err(Self::type_err( + &format!( + "`{name}` names the imported module here, and a module is not a function — call one of its \ + members (`{name}.new(…)`), or import it under another name (`use {name} as m;`)" + ), + None, + None, + Some(func.clone()), + )); + } if let Some(return_type) = self.check_stdlib_function_call(func, args)? { return Ok(return_type); } + // `m.f(..)` where `m` is a namespace bound by an import. Checked before + // the method path below, which asks what methods the *receiver's type* + // has — a namespace is not a value with methods, and its own type says + // nothing about what it exports. + if let Expr::Access(base, field) = func + && let Expr::Var(namespace) = base.as_ref() + && let Some(member) = super::stdlib::segment_name(field) + && let Some(Type::Function { + params, + named_params: _, + return_type, + }) = self.imported_member_type(namespace, member) + { + if params.len() != args.len() { + return Err(Self::type_err( + &format!("Function expects {} arguments", params.len()), + None, + None, + Some(func.clone()), + )); + } + for (index, (param_type, arg)) in params.iter().zip(args.iter()).enumerate() { + let arg_type = self.check_expr_against(arg, Some(param_type))?; + self.check_argument(param_type, &arg_type, index, arg)?; + } + return Ok(*return_type); + } + + // The same namespace, a name it does not export. A namespace knows + // everything it has, so this is a mistake here rather than "nil is not a + // function" at run time — the sentence `lk check` already gives for a + // standard library module's missing member, now for the other kind of + // module too. + // + // Only when the namespace exists *and* nothing shadows it: a local bound + // to that name is an ordinary value with fields. + if let Expr::Access(base, field) = func + && let Expr::Var(namespace) = base.as_ref() + && let Some(member) = super::stdlib::segment_name(field) + && self.is_imported_namespace(namespace) + && !self.has_local_binding(namespace) + && self.imported_member_type(namespace, member).is_none() + { + return Err(Self::type_err( + &format!("`{namespace}` has no member `{member}`"), + None, + None, + Some(func.clone()), + )); + } + if let Expr::Access(obj_expr, field_expr) = func { let receiver_ty = self.check_expr(obj_expr)?; if let Expr::Literal(field_val) = field_expr.as_ref() @@ -45,7 +118,7 @@ impl TypeChecker { )); } for (index, (param_type, arg)) in remaining_params.iter().zip(args.iter()).enumerate() { - let arg_type = self.check_expr(arg)?; + let arg_type = self.check_expr_against(arg, Some(param_type))?; // +1: the receiver occupies position 0 of the // signature, so the caller's first argument is the // second parameter. @@ -63,6 +136,10 @@ impl TypeChecker { } } return Ok(*return_type); + } else if let Some(return_type) = + self.check_declared_builtin_method(&receiver_ty, name.as_ref(), args)? + { + return Ok(return_type); } else if let Some(return_type) = self.check_builtin_container_method(&receiver_ty, name.as_ref(), args, func)? { @@ -77,6 +154,44 @@ impl TypeChecker { } if let Expr::Var(name) = func { + // Argument count for a builtin global, from the one place that + // knows it: the standard library states it when it registers the + // global. Before this the same fact lived in the registry, in the + // native's own body, and in a hand-written arm here — and only + // three globals had an arm, so `lk check` passed + // `assert(true, "a", "b")`. + // + // Skipped when the program owns the name: its own function, or a + // local holding a callable, answers to its own signature. + if !self.has_local_binding(name) + && !self.has_user_function(name) + && self.registry.get_struct(name).is_none() + && let Some(arity) = crate::typ::stdlib_global_arity(name) + { + let count = args.len(); + let too_few = count < arity.min as usize; + let too_many = arity.max.is_some_and(|max| count > max as usize); + if too_few || too_many { + // The runtime's wording, including "1 argument" — + // `expects exactly 1 arguments` is the kind of thing a + // generated message says and a hand-written one did not. + let plural = |count: u16| if count == 1 { "argument" } else { "arguments" }; + return Err(Self::type_err( + &match arity.max { + Some(max) if max == arity.min => { + alloc::format!("{name}() expects exactly {} {}", arity.min, plural(arity.min)) + } + Some(max) => alloc::format!("{name}() expects {} or {max} {}", arity.min, plural(max)), + None => { + alloc::format!("{name}() expects at least {} {}", arity.min, plural(arity.min)) + } + }, + None, + None, + Some(func.clone()), + )); + } + } match name.as_str() { "Set" => { if args.len() > 1 { @@ -86,9 +201,48 @@ impl TypeChecker { return Ok(Type::Set(Box::new(Type::Any))); }; let arg_ty = self.check_expr(arg)?; + // A tuple's elements are known one by one, so *any* of them + // being unusable settles it; a `List` only has `T` to go + // on. That is why `[1, 1.0]` is caught here even though its + // union type (`Int | Float`) is deliberately let through + // everywhere else — a union may be the Int at run time, a + // tuple element cannot be anything but what it is. + let offender = match self.resolve_aliases(&arg_ty) { + Type::List(elem) => { + let elem = self.resolve_aliases(&elem); + crate::typ::type_checker::type_is_certainly_not_a_key(&elem).then_some(elem) + } + Type::Tuple(elems) => elems + .into_iter() + .map(|elem| self.resolve_aliases(&elem)) + .find(crate::typ::type_checker::type_is_certainly_not_a_key), + _ => None, + }; + if let Some(member) = offender { + return Err(Self::type_err( + &format!( + "{} cannot be a set member — a set is a map's key set, and only nil, Bool, Int and \ + String can be a key", + member.display() + ), + None, + Some(member), + Some(arg.as_ref().clone()), + )); + } return match self.resolve_aliases(&arg_ty) { Type::List(elem) => Ok(Type::Set(elem)), Type::Set(elem) => Ok(Type::Set(elem)), + // A tuple *is* a list — `HeapValue` has no tuple, and both + // `is_assignable_to` and the unifier say so in as many + // words. Only this hand-written match forgot, so + // `Set([1, "a"])` was refused while `[1, "a"].len()`, + // `.contains()`, `.map()` and the rest all worked and the + // runtime built the set happily. A list is spelled + // `Tuple<…>` exactly when its elements differ, so the + // element type is their union — the same `union_of` that + // collapses pattern alternatives and closure returns. + Type::Tuple(elems) => Ok(Type::Set(Box::new(crate::typ::type_checker::union_of(elems)))), Type::Any | Type::Variable(_) => Ok(Type::Set(Box::new(Type::Any))), other => Err(Self::type_err( "Set(value) expects List or Set", @@ -146,9 +300,9 @@ impl TypeChecker { } } "recv" => { - if args.len() != 1 { - return Err(Self::type_err("recv() expects exactly 1 argument", None, None, None)); - } + // Arity is checked generically from the registry — see + // `check_builtin_global_arity`. What is left here is the + // part that is `recv`'s own: the argument must be a channel. let channel_ty = self.check_expr(&args[0])?; return match self.resolve_aliases(&channel_ty) { Type::Channel(inner) => Ok((*inner).clone()), @@ -167,9 +321,7 @@ impl TypeChecker { }; } "spawn" => { - if args.len() != 1 { - return Err(Self::type_err("spawn() expects exactly 1 argument", None, None, None)); - } + // As above: arity generically, "must be callable" here. let callable_ty = self.check_expr(&args[0])?; match self.resolve_aliases(&callable_ty) { Type::Function { .. } => {} @@ -234,8 +386,33 @@ impl TypeChecker { Expr::Var(name) => self.get_function_sig(name).map(|sig| sig.annotated.clone()), _ => None, }; + // This call's own answer for the callee's type variables. + // + // `fn id(x) { return x; }` has the principal type `'a -> 'a` once + // its own constraints are solved, and every call site constrains + // that same `'a` — so `id(1)` and `id("s")` in one program fight + // over it, and what the solver makes of the disagreement is a type + // nothing can be checked against. That is why `let s: String = + // id(1);` passed. + // + // The map below is *local to this call*: it reads the variables off + // the arguments and substitutes them into the return type, which is + // instantiation in effect. Doing it by renaming the signature into + // fresh variables instead would also work, and would additionally + // stop the call sites from constraining the original — but that is + // what makes `id` resolve to a concrete type at all, and without it + // the strict-Any check reads a generic function as an unannotated + // one and demands annotations for it. Keeping the constraints where + // they were leaves that judgement exactly as it was. + // + // The map is also why the constraint alone is not enough: checking + // is one pass, and the solver does not run again until the + // enclosing function ends — long after the `let` that reads this + // call has been checked. + let mut bindings: HashMap = HashMap::new(); for (index, (param_type, arg)) in params.iter().zip(args.iter()).enumerate() { - let arg_type = self.check_expr(arg)?; + let arg_type = self.check_expr_against(arg, Some(param_type))?; + bind_instance_variables(param_type, &self.resolve_aliases(&arg_type), &mut bindings); let declared = annotated .as_ref() .is_some_and(|mask| mask.get(index).copied().unwrap_or(false)); @@ -250,7 +427,7 @@ impl TypeChecker { for (index, decl) in named_params.iter().enumerate() { if index < supplied_named { let arg = &args[params.len() + index]; - let arg_type = self.check_expr(arg)?; + let arg_type = self.check_expr_against(arg, Some(&decl.ty))?; // A named parameter's declaration always carries a type or // a default, so unlike a positional one there is nothing // inferred to mistake for a claim. @@ -268,7 +445,7 @@ impl TypeChecker { } } - return Ok(*return_type); + return Ok(super::substitute_outside_unions(&return_type, &bindings)); } match resolved { @@ -318,6 +495,288 @@ impl TypeChecker { } impl TypeChecker { + /// Check a call against the built-in method's declared signature + /// (`typ::builtin_method_sig`), if it has one. + /// + /// `Ok(None)` means the table says nothing — an unknown method, or a + /// receiver that is not (yet) a known container — and the caller falls + /// through to the hand-written arms and then to `Any`. A receiver still + /// typed as a variable lands here, which is deliberate: constraining a call + /// on it would decide its type from the method name. + /// Whether `m.name(…)` on this map could be a **field** call rather than a + /// method call. + /// + /// The question is only about the *key* type. A map keyed by anything but a + /// string holds no key spelled like a name, so no name can be a field of + /// it. The value type says nothing: `m.name()` does not require a callable + /// there — `{"score": 40}.score()` is `40`, which + /// `compiler_dynamic_method_helper_reads_runtime_properties` pins — so a + /// `Map` can answer any name its keys happen to include, and + /// its type does not say which those are. + /// + /// `Any`, `Unknown` and an unresolved variable are all "could be a string", + /// so an unannotated map keeps accepting any name. + fn map_field_call_is_possible(&mut self, receiver: &Type) -> bool { + let Type::Map(key, _value) = receiver else { + return true; + }; + let erased = |ty: &Type| matches!(ty, Type::Any | Type::Unknown | Type::Variable(_)); + let key = self.resolve_aliases(key); + erased(&key) + || matches!(key, Type::String) + || matches!(&key, Type::Union(arms) if arms.iter().any(|arm| erased(arm) || matches!(arm, Type::String))) + } + + fn check_declared_builtin_method( + &mut self, + receiver_ty: &Type, + method: &str, + args: &[Box], + ) -> Result> { + let resolved_receiver = self.resolve_aliases(receiver_ty); + let Some(sig) = crate::typ::builtin_method_signature(&resolved_receiver, method) else { + // A *known* container with no such method is an error, not a + // shrug. User and trait methods were already resolved above, so + // nothing else can answer this call — the VM will say "List has no + // method 'clear'" when it runs, and there is no reason to wait. + // (`xs.clear()` type-checked for exactly that long.) + // + // Except on a map, where `m.f(x)` need not be a method at all: a + // map's entries *are* its fields, so `m.score` may hold a function + // and calling it is an ordinary property call. Nothing in the map's + // type says which *keys* it has — but it does say two things that + // can rule the field call out, and the exemption used to be wider + // than its own reason: + // + // - the key type. A `Map` cannot hold the string + // `"score"` at all, so no name can be a field of it, and + // `m.contains(k)` on one always raises. `lk check` is the same + // check the executors run, so it should not wait for the + // program to start. + // + // The *value* type is not one of them, though it reads like it + // should be: a field call does not need a callable. `{"score": + // 40}.score()` answers `40`, which + // `compiler_dynamic_method_helper_reads_runtime_properties` pins — + // so a `Map` can answer any name its keys include, and + // its type does not say which those are. + if let Some(kind) = crate::typ::receiver_kind(&resolved_receiver) + && (kind != crate::typ::BuiltinReceiverKind::Map + || !self.map_field_call_is_possible(&resolved_receiver)) + { + return Err(Self::type_err( + &format!("{} has no method '{method}'", receiver_kind_name(kind)), + None, + Some(resolved_receiver), + None, + )); + } + // A scalar is knowable for the opposite reason: its built-in + // method surface is *empty*. `1.abs()`, `1.5.round()`, `x.len()` + // on an Int, `v.to_string()` on anything — every one of them is + // "no method", always. So the only way a name on a scalar can + // resolve is a user `impl Int { … }` or `impl Trait for Float`, + // and both were tried above (they resolve regardless of where the + // `impl` sits relative to the call). Reaching here means nothing + // answers it. + // + // `receiver_kind` covers List/Bytes/Slice/Map/Set/Str only, so + // these four fell straight through to `Any`: `lk check` passed + // `let v = 1; v.nope();` and the VM said "Int has no method + // 'nope'" — the same mistake, caught at check time on a String and + // at run time on an Int. + // + // A channel and a task are the same case, and were the two left + // out: their operations are *module functions* (`send`, `recv`, + // `task.await`), so neither has a method surface at all, and + // neither has fields a name could resolve to instead. `c.close()` + // — the spelling another language would have — type-checked and + // then raised `Channel has no method 'close'` when the program ran. + if matches!( + resolved_receiver, + Type::Int | Type::Float | Type::Bool | Type::Nil | Type::Channel(_) | Type::Task(_) + ) { + return Err(Self::type_err( + &format!("{} has no method '{method}'", resolved_receiver.display()), + None, + Some(resolved_receiver), + None, + )); + } + // A declared struct is as knowable as a container, and it was the + // one shape left unchecked: `p.nonexistent()` type-checked and then + // raised at run time. The receiver has to be a struct the registry + // *has* — `Type::Named` also spells `Bytes`, `Error`, `Task` and + // every resource kind, none of which declare their method surface + // here, so treating "named" as "checkable" would reject working + // programs. + // + // A field is not a method but is callable when it holds a function + // (`H { cb: (Int) -> Int }` makes `h.cb(21)` an ordinary property + // call), so the name has to miss both tables before this fires. + if let Type::Named(type_name) = &resolved_receiver + && let Some(declared) = self.registry.get_struct(type_name) + && !declared.fields.contains_key(method) + { + return Err(Self::type_err( + &format!("{type_name} has no method '{method}'"), + None, + Some(resolved_receiver.clone()), + None, + )); + } + // A trait-typed receiver's method surface is exactly what the trait + // declares — a value typed `Show` is known for that and nothing + // else. Without this, `v.nosuch()` on a `Show` parameter reached + // the runtime, where the interpreter said "P has no method + // 'nosuch'" and the compiled build said something else entirely, + // because the two find out in different ways. + if let Type::Named(trait_name) = &resolved_receiver + && let Some(declared) = self.registry.get_trait(trait_name) + && !declared.methods.contains_key(method) + { + return Err(Self::type_err( + &format!("trait {trait_name} declares no method '{method}'"), + None, + Some(resolved_receiver.clone()), + None, + )); + } + return Ok(None); + }; + // Two reductions want something of the *element*, and say so when they + // run: `sum` adds numbers and `to_bytes` wants bytes. A `List` + // can never answer either, so this is the `Set + Set` case — the + // runtime raises for every value of that type, and the checker is right + // to say so first. + // + // Only for an element type that settles it. `Any`, a union and a type + // variable stay out: those lists may hold numbers when they run. + // A heterogeneous literal is a `Tuple`, and its element types are + // written out one by one — so `[1, "a"].sum()` is refused by the + // `String` in it, which collapsing to `List` would have lost. + let elements: Option> = match &resolved_receiver { + Type::List(elem) => Some(vec![self.resolve_aliases(elem.as_ref())]), + Type::Tuple(elems) => Some(elems.iter().map(|e| self.resolve_aliases(e)).collect()), + _ => None, + }; + if let Some(elements) = elements { + let refuses = |elem: &Type| match method { + "sum" => matches!( + elem, + Type::String | Type::Bool | Type::Nil | Type::List(_) | Type::Map(_, _) | Type::Set(_) + ), + "to_bytes" => matches!( + elem, + Type::String + | Type::Float + | Type::Bool + | Type::Nil + | Type::List(_) + | Type::Map(_, _) + | Type::Set(_) + ), + _ => false, + }; + if elements.iter().any(refuses) { + let (wanted, needs) = if method == "sum" { + (Type::Float, "numbers") + } else { + (Type::Int, "Int items") + }; + return Err(Self::type_err( + &format!("list.{method}() needs {needs}"), + Some(Type::List(Box::new(wanted))), + Some(resolved_receiver), + None, + )); + } + } + // A variadic method has no upper bound: `"{} {}".format(a, b)` passes + // two arguments to one declared parameter, and that is the shape. + if args.len() < sig.required || (args.len() > sig.params.len() && !sig.variadic) { + let expected = if sig.variadic { + format!("at least {}", sig.required) + } else if sig.required == sig.params.len() { + format!("{}", sig.params.len()) + } else { + format!("{} to {}", sig.required, sig.params.len()) + }; + return Err(Self::type_err( + &format!("Method {method} expects {expected} argument(s), got {}", args.len()), + None, + None, + None, + )); + } + let mut callback_result: Option = None; + for (index, ((_, param_type), arg)) in sig.params.iter().zip(args.iter()).enumerate() { + // A callback applied to each element: its first parameter *is* the + // element type. Handed to the closure before its body is read, so + // the body is checked against it — `["a"].map(|s| s.bogus())` is a + // missing method rather than an unknown one. + let arg_type = match (sig.elementwise_callback == Some(index), arg.as_ref()) { + ( + true, + Expr::Closure { + params, + param_types, + return_type, + body, + }, + ) => self.check_closure( + params, + param_types, + return_type.as_deref(), + body, + core::slice::from_ref(&sig.elem), + )?, + _ => self.check_expr(arg)?, + }; + if sig.elementwise_callback == Some(index) + && let Type::Function { + params, return_type, .. + } = &self.resolve_aliases(&arg_type) + { + if let Some(first) = params.first() { + self.inference_engine.add_constraint(first.clone(), sig.elem.clone()); + } + callback_result = Some(self.resolve_aliases(return_type)); + } + self.check_argument(param_type, &arg_type, index, arg)?; + // A set's member is a map's key, so the key rule applies to the one + // value these three take. The ordinary argument check cannot see it: + // on a `Set` the declared parameter is `Elem` = `Any`, which + // accepts anything — so `Set().add(1.5)` passed and raised at run + // time. See `type_is_certainly_not_a_key`. + if matches!(resolved_receiver, Type::Set(_)) && matches!(method, "add" | "contains" | "delete") { + let resolved_arg = self.resolve_aliases(&arg_type); + if crate::typ::type_checker::type_is_certainly_not_a_key(&resolved_arg) { + return Err(Self::type_err( + &format!( + "{} cannot be a set member — a set is a map's key set, and only nil, Bool, Int and \ + String can be a key", + resolved_arg.display() + ), + None, + Some(resolved_arg), + Some(arg.as_ref().clone()), + )); + } + } + } + // `map`'s element type is the callback's return type, instantiated + // here rather than declared in the table — the table cannot name it, + // and `List` is what it said until a call site could. + if callback_result.is_some() + && let Some(instantiated) = + crate::typ::builtin_method_signature_with(&resolved_receiver, method, callback_result) + { + return Ok(Some(instantiated.return_type)); + } + Ok(Some(sig.return_type)) + } + /// Checks one positional argument against the parameter it fills. /// /// A *concrete* parameter type is checked; an unannotated one (a fresh @@ -339,7 +798,7 @@ impl TypeChecker { .add_constraint(param_type.clone(), arg_type.clone()); return Ok(()); } - if self.is_assignable(arg_type, param_type) || literal_fits_machine_int(param_type, arg) { + if self.value_fits(arg, arg_type, param_type) { return Ok(()); } Err(Self::type_err( @@ -363,7 +822,7 @@ impl TypeChecker { .add_constraint(param_type.clone(), arg_type.clone()); return Ok(()); } - if self.is_assignable(arg_type, param_type) || literal_fits_machine_int(param_type, arg) { + if self.value_fits(arg, arg_type, param_type) { return Ok(()); } Err(Self::type_err( @@ -379,25 +838,39 @@ impl TypeChecker { /// `Any` and type variables do not: the first accepts everything by /// definition, and the second is what an unannotated parameter gets, so /// rejecting against it would reject on an invented type. + /// Whether a parameter's type is settled enough to *check* an argument + /// against, rather than to learn from it. + /// + /// "Contains no variable", not "is not a variable". The two differ exactly + /// where a generic method takes a container: `xs.chain(ys)` has parameter + /// `List<'T>`, which is not a variable and was therefore checked — so + /// passing a `List` reported "expected List<'T0>, got List" + /// instead of binding `'T0` to `Int`. `xs.push(y)` took the other path and + /// worked, because *its* parameter is the bare `'T`. + /// + /// What that cost is visible in `bare-metal-x86/program.lk`, which had to + /// write `let line = [0]; line = [];` — build a list with a placeholder + /// element so the element type is known, then throw it away — because + /// `let line = []; line = line.chain(…)` did not type-check. fn is_concrete_parameter(&self, param_type: &Type) -> bool { - !matches!(self.resolve_aliases(param_type), Type::Any | Type::Variable(_)) + let resolved = self.resolve_aliases(param_type); + !matches!(resolved, Type::Any) && !resolved.contains_variables() } } /// Whether `arg` is an integer *literal* that fits a machine-integer /// parameter. /// -/// Machine integers do not convert implicitly — that is the rule that makes -/// `u8 + Int` an error rather than a silent widening — but a literal has no -/// type of its own to preserve. `f(0x3f8)` for `fn f(port: u16)` is the -/// ordinary way to call a driver, and requiring `0x3f8 as u16` there would be -/// ceremony without a reader. -fn literal_fits_machine_int(param_type: &Type, arg: &Expr) -> bool { - let Type::MachineInt(kind) = param_type else { - return false; - }; - let Expr::Literal(crate::val::LiteralVal::Int(value)) = arg else { - return false; - }; - kind.accepts_literal(i128::from(*value)) +/// How a receiver kind is named in a diagnostic — the same words the VM uses +/// when the call reaches it. +fn receiver_kind_name(kind: crate::typ::BuiltinReceiverKind) -> &'static str { + use crate::typ::BuiltinReceiverKind::*; + match kind { + List => "List", + Bytes => "Bytes", + Slice => "Slice", + Map => "Map", + Set => "Set", + Str => "String", + } } diff --git a/core/src/typ/type_checker/expressions/literals.rs b/core/src/typ/type_checker/expressions/literals.rs index 9e42c94a..f0202ea7 100644 --- a/core/src/typ/type_checker/expressions/literals.rs +++ b/core/src/typ/type_checker/expressions/literals.rs @@ -7,8 +7,24 @@ impl TypeChecker { pub(super) fn check_template_string(&mut self, parts: &[TemplateStringPart]) -> Result { for part in parts { if let TemplateStringPart::Expr(expr) = part { - let expr_type = self.check_expr(expr)?; - self.coerce_to_string(&expr_type); + // Checked, and its type deliberately *not* used. + // + // Interpolation renders whatever it is given, so an operand + // whose type is still an unresolved variable must not be pinned + // to `String` by appearing here. It used to be, and the effect + // reached a long way: in + // + // fn h(p0) { m["k${p0}"] = 1; return p0; } + // + // the map's key type made the whole interpolation a `String`, + // the constraint travelled back through it onto `p0`, and the + // function was inferred to *return* a String — so an `Int` + // caller was rejected for a program that is fine. A fuzz run on + // a fresh seed produced it at case 651. + // + // String `+` still constrains, and that is a different question: + // `+` is overloaded, so which one it is has to be decided. + let _ = self.check_expr(expr)?; } } Ok(Type::String) diff --git a/core/src/typ/type_checker/expressions/stdlib.rs b/core/src/typ/type_checker/expressions/stdlib.rs index ba30d199..1d0e4389 100644 --- a/core/src/typ/type_checker/expressions/stdlib.rs +++ b/core/src/typ/type_checker/expressions/stdlib.rs @@ -11,21 +11,100 @@ impl TypeChecker { let Some(path) = access_segments(func) else { return Ok(None); }; + // A `use math as m;` alias names the module it was bound to, so the + // member check below has something to look up. + let path: Vec<&str> = match path.split_first() { + Some((root, rest)) => { + let mut resolved = vec![self.resolve_stdlib_alias(root)]; + resolved.extend_from_slice(rest); + resolved + } + None => path, + }; let Some((module, field)) = canonical_stdlib_path(&path) else { return Ok(None); }; - if module == "math" && field == "clamp" { - self.check_math_clamp_args(args, &[])?; - return Ok(Some(Type::Int)); + if let Some(declared) = crate::typ::stdlib_signature(&format!("{module}.{field}")) { + let required = declared.required_params(); + if args.len() < required || args.len() > declared.params.len() { + return Err(Self::type_err( + &format!("Function expects {}", describe_arity(required, declared.params.len())), + None, + None, + Some(func.clone()), + )); + } + for (param, arg) in declared.params.iter().zip(args.iter()) { + let arg_type = self.check_expr(arg)?; + // An optional parameter is left unconstrained: the declaration + // says what it accepts when present, not that the argument in + // that position *is* one — several exports let a named + // parameter be passed positionally too. + if param.optional || param.ty == Type::Any { + continue; + } + // Checked here rather than handed to the solver. `unify` ends in + // a rule that accepts any two concrete types that disagree, on + // the grounds that an *inferred* type may legitimately differ + // between call sites in a gradually-typed language. That reason + // does not reach this call: the parameter's type was not + // inferred, it was declared by whoever wrote the export. Left to + // the solver, `string.len(5)` passed. + if !arg_type.contains_variables() && !self.is_assignable(&arg_type, ¶m.ty) { + // The types themselves go in `expected`/`actual`, which + // `TypeError`'s Display already renders. + return Err(Self::type_err( + &format!("Argument '{}' of {module}.{field}", param.name), + Some(param.ty.clone()), + Some(arg_type), + Some(arg.as_ref().clone()), + )); + } + self.inference_engine.add_constraint(param.ty.clone(), arg_type); + } + return Ok(Some(declared.return_type)); + } + + // A member the module does not have is a mistake here, not something to + // discover at run time. `math.sqrtt(2.0)`, `os.name()`, `hash.md5(s)` + // and `datetime.year(t)` all type-checked and then died with "nil is not + // a function" — a sentence naming neither the module nor the member, + // which is how the last three ended up on a list of "missing native + // lowerings" until someone tried to run them. + // + // Three conditions, because a dotted call is `a.b.c()` for *any* `a`: + // some standard library must be linked at all (`core`'s own tests and + // bare metal have none), the root must be a module that declares + // something, and the root must not be shadowed by a binding — a local + // named `math` is a value with fields, not the module. + if crate::typ::has_stdlib_signatures() + && crate::typ::stdlib_module_is_declared(&module) + && !crate::typ::stdlib_path_is_declared(&format!("{module}.{field}")) + && path.first().is_some_and(|root| self.lookup_binding(root).is_none()) + { + return Err(Self::type_err( + &format!("`{module}` has no member `{field}`"), + None, + None, + Some(func.clone()), + )); } let Some((params, named_params, return_type)) = stdlib_function_signature(&module, &field) else { return Ok(None); }; - if !named_params.is_empty() || params.len() != args.len() { + // A parameter this table lists as *named* may still be passed + // positionally — that is what the export wrapper does at runtime — so + // the accepted count is a range, not a number. This table is only + // reached when no signature has been registered, which is the case in + // `lk-core`'s own tests: the stdlib crate is not linked there. + if args.len() < params.len() || args.len() > params.len() + named_params.len() { return Err(Self::type_err( - &format!("Function expects {} arguments", params.len()), + &format!( + "Function expects {}", + describe_arity(params.len(), params.len() + named_params.len()) + ), None, None, Some(func.clone()), @@ -51,54 +130,51 @@ impl TypeChecker { return Ok(None); }; - if module == "math" && field == "clamp" { - self.check_math_clamp_args(pos_args, named_args)?; - return Ok(Some(Type::Int)); - } - - Ok(None) - } - - pub(super) fn stdlib_access_function_type(&self, expr: &Expr, field: &Expr) -> Option { - let mut path = access_segments(expr)?; - path.push(segment_name(field)?); - let (module, field) = canonical_stdlib_path(&path)?; - self.stdlib_function_type(&module, &field) - } - - fn stdlib_function_type(&self, module: &str, field: &str) -> Option { - let (params, named_params, return_type) = stdlib_function_signature(module, field)?; - Some(Type::Function { - params, - named_params, - return_type: Box::new(return_type), - }) - } + let Some(declared) = crate::typ::stdlib_signature(&format!("{module}.{field}")) else { + return Ok(None); + }; - fn check_math_clamp_args(&mut self, pos_args: &[Box], named_args: &[(String, Box)]) -> Result<()> { - if pos_args.is_empty() || pos_args.len() > 3 { + // The rules are the export wrapper's, which is what actually runs: a + // parameter listed in `named(...)` may be given positionally *or* by + // name, never both. `math.clamp` used to be the only function checked + // this way — by a hand-written rule naming it — and every other export + // fell through to the generic `Type::Function` path, whose parameter + // list has the named-eligible ones *removed*. So a call that mixed the + // two spellings, `bytes.slice(b, 0, end: 2)`, was rejected as taking + // "1 positional arguments" while `bytes.slice(b, 0, 2)` was fine. + if pos_args.len() > declared.params.len() { return Err(Self::type_err( - "clamp() expects 1..3 positional arguments", - None, + &format!( + "Function expects {}", + describe_arity(declared.required_params(), declared.params.len()) + ), None, None, + Some(callee.clone()), )); } - for arg in pos_args { + + let mut filled = vec![false; declared.params.len()]; + for (index, arg) in pos_args.iter().enumerate() { + filled[index] = true; let arg_type = self.check_expr(arg)?; - self.inference_engine.add_constraint(Type::Int, arg_type); + self.constrain_stdlib_argument(&declared.params[index], arg_type, arg, &module, &field)?; } - let mut seen = HashSet::with_capacity(named_args.len()); + let mut seen: HashSet<&str> = HashSet::with_capacity(named_args.len()); for (name, expr) in named_args { - if name != "min" && name != "max" { + let Some(index) = declared + .params + .iter() + .position(|param| param.named && param.name == *name) + else { return Err(Self::type_err( &format!("Unknown named argument: {}", name), None, None, Some(expr.as_ref().clone()), )); - } + }; if !seen.insert(name.as_str()) { return Err(Self::type_err( &format!("Duplicate named argument: {}", name), @@ -107,13 +183,109 @@ impl TypeChecker { Some(expr.as_ref().clone()), )); } + if filled[index] { + return Err(Self::type_err( + &format!("Argument '{name}' given both positionally and by name"), + None, + None, + Some(expr.as_ref().clone()), + )); + } + filled[index] = true; let arg_type = self.check_expr(expr)?; - self.inference_engine.add_constraint(Type::Int, arg_type); + self.constrain_stdlib_argument(&declared.params[index], arg_type, expr, &module, &field)?; + } + + for (param, filled) in declared.params.iter().zip(filled.iter()) { + if !filled && !param.optional && !param.has_default { + return Err(Self::type_err( + &format!("Missing required named argument: {}", param.name), + None, + None, + Some(callee.clone()), + )); + } + } + + Ok(Some(declared.return_type)) + } + + /// One argument against one declared parameter. + /// + /// Shared by the positional and the named paths so that naming an argument + /// cannot type-check differently from passing it in that position. + fn constrain_stdlib_argument( + &mut self, + param: &crate::typ::ResolvedStdlibParam, + arg_type: Type, + arg: &Expr, + module: &str, + field: &str, + ) -> Result<()> { + // An optional parameter is left unconstrained: the declaration says + // what it accepts when present, not that the argument *is* one. + if param.optional || param.ty == Type::Any { + return Ok(()); + } + // Checked here rather than handed to the solver — see the positional + // path for why `unify` is too permissive for a *declared* type. + if !arg_type.contains_variables() && !self.is_assignable(&arg_type, ¶m.ty) { + return Err(Self::type_err( + &format!("Argument '{}' of {module}.{field}", param.name), + Some(param.ty.clone()), + Some(arg_type), + Some(arg.clone()), + )); } + self.inference_engine.add_constraint(param.ty.clone(), arg_type); Ok(()) } + + pub(super) fn stdlib_access_function_type(&self, expr: &Expr, field: &Expr) -> Option { + let mut path = access_segments(expr)?; + path.push(segment_name(field)?); + let (module, field) = canonical_stdlib_path(&path)?; + self.stdlib_function_type(&module, &field) + } + + fn stdlib_function_type(&self, module: &str, field: &str) -> Option { + if let Some(declared) = crate::typ::stdlib_signature(&format!("{module}.{field}")) { + return Some(Type::Function { + params: declared + .params + .iter() + .filter(|param| !param.named) + .map(|param| param.ty.clone()) + .collect(), + named_params: declared.named_params(), + return_type: Box::new(declared.return_type), + }); + } + let (params, named_params, return_type) = stdlib_function_signature(module, field)?; + Some(Type::Function { + params, + named_params, + return_type: Box::new(return_type), + }) + } +} + +fn describe_arity(required: usize, max: usize) -> String { + if required == max { + format!("exactly {required} argument{}", if required == 1 { "" } else { "s" }) + } else { + format!("{required} to {max} arguments") + } } +/// What the checker knows about the standard library when no standard library +/// is linked in: `core`'s own tests, and targets that ship a different module +/// set (`stdlib/bare`, `stdlib/web`). +/// +/// The declarations in `stdlib/crates` are the real source — they cover all 23 +/// modules and reach the checker through `register_stdlib_signatures`, which +/// takes priority over everything here. This table only has to keep `core` +/// standing on its own. fn stdlib_function_signature(module: &str, field: &str) -> Option<(Vec, Vec, Type)> { let any = || Type::Any; let unary_any = || vec![Type::Any]; @@ -181,9 +353,12 @@ fn access_segments(expr: &Expr) -> Option> { } } -fn segment_name(expr: &Expr) -> Option<&str> { +/// The name a path *segment* spells. Every caller passes the member half of an +/// `Expr::Access`, and a member is a string literal — `math.floor` is +/// `Access(Var("math"), Literal("floor"))`. A bare `Var` there is a bracket +/// index, so `m[floor]` is not the path `m.floor`. +pub(super) fn segment_name(expr: &Expr) -> Option<&str> { match expr { - Expr::Var(name) => Some(name.as_str()), Expr::Literal(value) => value.as_str(), _ => None, } diff --git a/core/src/typ/type_checker/patterns.rs b/core/src/typ/type_checker/patterns.rs index 603421c5..7e395006 100644 --- a/core/src/typ/type_checker/patterns.rs +++ b/core/src/typ/type_checker/patterns.rs @@ -8,6 +8,7 @@ use anyhow::Result; impl TypeChecker { /// Public helper: add variable types introduced by a pattern given the value type pub fn add_bindings_for_pattern(&mut self, pattern: &Pattern, value_type: &Type) -> Result<()> { + self.reject_duplicate_bindings(pattern)?; let bindings = self.collect_bindings_for_pattern(pattern, value_type)?; for (name, ty) in bindings { self.add_local_type(name, ty); @@ -17,8 +18,40 @@ impl TypeChecker { Ok(()) } + /// Refuse a pattern that binds one name twice. + /// + /// Separate from [`Self::add_bindings_for_pattern`] because `if let` and + /// `while let` deliberately discard *that* result — a pattern the matched + /// type cannot produce is what those constructs test at run time — and a + /// repeated name is not that: no value makes it right. + pub fn reject_duplicate_bindings(&self, pattern: &Pattern) -> Result<()> { + if let Some(name) = crate::expr::duplicate_binding(pattern) { + return Err(anyhow::anyhow!(alloc::format!( + "`{name}` is bound twice by one pattern — the second binding shadows the first before \ + anything can read it, so a repeated name matches any value rather than an equal one" + ))); + } + Ok(()) + } + /// Ensure a pattern is compatible with a given value type, adding constraints where possible pub(super) fn check_pattern_against_type(&mut self, pattern: &Pattern, value_type: &Type) -> Result<()> { + // A union scrutinee is the *reason* to write a match: the pattern + // discriminates it, so it has to agree with only the member it selects. + // Constraining it against the whole union reported a conflict between + // two arms of the same match — `[0, 0]` against + // `List | Tuple` — which is a conflict the program + // does not have. + if let Type::Union(members) = self.resolve_aliases(value_type) { + let mut checkpoint = Ok(()); + for member in &members { + match self.check_pattern_against_type(pattern, member) { + Ok(()) => return Ok(()), + Err(err) => checkpoint = Err(err), + } + } + return checkpoint; + } match pattern { Pattern::Literal(v) => { // Unify with literal type @@ -32,10 +65,30 @@ impl TypeChecker { } Pattern::Wildcard => Ok(()), Pattern::List { patterns, rest: _ } => { + // A tuple knows each position's type, and that is exactly what + // a list pattern asks about. Falling through to the shared + // element type below constrained every position to one type, + // so `match ["a", 2] { ["b", n] => … }` reported "Cannot unify + // Int with String" — a conflict manufactured by the check, not + // present in the program. + if let Type::Tuple(elems) = value_type { + for (index, p) in patterns.iter().enumerate() { + let at = elems.get(index).cloned().unwrap_or(Type::Any); + self.check_pattern_against_type(p, &at)?; + } + return Ok(()); + } // Expect a list; if element type is unknown, introduce a fresh var let elem_ty = match value_type { Type::List(inner) => (**inner).clone(), Type::String => Type::String, + // A scrutinee whose type is still open is *not* constrained + // to this pattern's shape. A match arm asks a question; the + // other arms are there because the answer can be no. The + // constraint made every arm a requirement, so a function + // that matched three shapes reported them as a conflict + // with each other — a conflict the program does not have. + Type::Variable(_) | Type::Union(_) => self.inference_engine.fresh_type_var(), other => { // Constrain to List let t = self.inference_engine.fresh_type_var(); @@ -53,6 +106,8 @@ impl TypeChecker { // Expect a map; keys are strings, values have a (possibly inferred) type let val_ty = match value_type { Type::Map(_, v) => (**v).clone(), + // As above: an open scrutinee is asked, not required. + Type::Variable(_) | Type::Union(_) => self.inference_engine.fresh_type_var(), other => { let t = self.inference_engine.fresh_type_var(); self.inference_engine @@ -75,12 +130,13 @@ impl TypeChecker { self.check_pattern_against_type(pattern, value_type)?; // Then validate the guard with temporary bindings from inner pattern let temp_bindings = self.collect_bindings_for_pattern(pattern, value_type)?; - let snapshot = self.local_types.clone(); + self.push_scope(); for (n, ty) in temp_bindings { self.add_local_type(n, ty); } - let gty = self.check_expr(guard)?; - self.local_types = snapshot; + let gty = self.check_expr(guard); + self.pop_scope(); + let gty = gty?; if gty != Type::Bool { return Err(Self::type_err( "Match guard must be Bool", @@ -119,15 +175,59 @@ impl TypeChecker { /// Collect variable bindings and their types from a pattern fn collect_bindings_for_pattern(&mut self, pattern: &Pattern, value_type: &Type) -> Result> { let mut out = Vec::new(); + // A union scrutinee, as in `check_pattern_against_type`: a destructuring + // pattern selects the member it fits, so the bindings come from that + // member rather than from a constraint against the whole union. A + // binding pattern keeps the union — a name binds whatever arrives. + if let Type::Union(members) = self.resolve_aliases(value_type) + && !matches!(pattern, Pattern::Variable(_) | Pattern::Wildcard) + { + let mut last = Ok(out); + for member in &members { + match self.collect_bindings_for_pattern(pattern, member) { + Ok(bindings) => return Ok(bindings), + Err(err) => last = Err(err), + } + } + return last; + } match pattern { Pattern::Variable(name) => { out.push((name.clone(), value_type.clone())); } Pattern::Wildcard | Pattern::Literal(_) | Pattern::Range { .. } => {} Pattern::List { patterns, rest } => { + // Per position, for a tuple — see the note in + // `check_pattern_against_type`. The rest binding keeps the + // remaining positions' types, so `[a, ..rest]` over a + // `Tuple` binds `rest` as `List` rather + // than as a list of the conflict. + if let Type::Tuple(elems) = value_type { + for (index, p) in patterns.iter().enumerate() { + let at = elems.get(index).cloned().unwrap_or(Type::Any); + out.extend(self.collect_bindings_for_pattern(p, &at)?); + } + if let Some(rest_name) = rest { + let remaining: Vec = elems.iter().skip(patterns.len()).cloned().collect(); + let rest_ty = match remaining.split_first() { + None => Type::List(Box::new(Type::Any)), + Some((first, others)) if others.iter().all(|t| t == first) => { + Type::List(Box::new(first.clone())) + } + Some(_) => Type::Tuple(remaining), + }; + out.push((rest_name.clone(), rest_ty)); + } + return Ok(out); + } let (elem_ty, rest_ty) = match value_type { Type::List(inner) => ((**inner).clone(), Type::List(inner.clone())), Type::String => (Type::String, Type::List(Box::new(Type::String))), + Type::Variable(_) | Type::Union(_) => { + let t = self.inference_engine.fresh_type_var(); + let rest_t = Type::List(Box::new(t.clone())); + (t, rest_t) + } other => { let t = self.inference_engine.fresh_type_var(); self.inference_engine @@ -146,6 +246,7 @@ impl TypeChecker { Pattern::Map { patterns, rest } => { let vty = match value_type { Type::Map(_, v) => (**v).clone(), + Type::Variable(_) | Type::Union(_) => self.inference_engine.fresh_type_var(), other => { let t = self.inference_engine.fresh_type_var(); self.inference_engine @@ -215,12 +316,13 @@ impl TypeChecker { Pattern::Guard { pattern, guard } => { // Bind variables from inner pattern temporarily, then type-check guard let bindings = self.collect_bindings_for_pattern(pattern, value_type)?; - let snapshot = self.local_types.clone(); + self.push_scope(); for (n, ty) in bindings { self.add_local_type(n, ty); } - let gty = self.check_expr(guard)?; - self.local_types = snapshot; + let gty = self.check_expr(guard); + self.pop_scope(); + let gty = gty?; if gty != Type::Bool { return Err(Self::type_err( "Match guard must be Bool", diff --git a/core/src/typ/type_checker/tests.rs b/core/src/typ/type_checker/tests.rs index f9cbd2d8..8fbd937b 100644 --- a/core/src/typ/type_checker/tests.rs +++ b/core/src/typ/type_checker/tests.rs @@ -72,16 +72,30 @@ fn test_numeric_auto_promotion() { assert_eq!(result_type, Type::Float); } +/// `/` yields a `Float`, whatever it divides. +/// +/// The checker always said this; the executors did not, and the constant +/// folder said it only when the literals did *not* divide evenly. All four +/// paths agree now, which is what this test is for. #[test] fn test_division_promotes_float() { let mut checker = TypeChecker::new(); - let div_expr = Expr::Bin( - Box::new(Expr::Literal(LiteralVal::Int(3))), - BinOp::Div, - Box::new(Expr::Literal(LiteralVal::Int(2))), - ); - let result_type = checker.check_expr(&div_expr).unwrap(); - assert_eq!(result_type, Type::Float); + for (lhs, rhs) in [ + (LiteralVal::Int(3), LiteralVal::Int(2)), + (LiteralVal::Int(20), LiteralVal::Int(4)), + (LiteralVal::Int(3), LiteralVal::Float(2.0)), + ] { + let div_expr = Expr::Bin( + Box::new(Expr::Literal(lhs.clone())), + BinOp::Div, + Box::new(Expr::Literal(rhs.clone())), + ); + assert_eq!( + checker.check_expr(&div_expr).unwrap(), + Type::Float, + "{lhs:?} / {rhs:?} should be a Float" + ); + } } #[test] @@ -93,7 +107,7 @@ fn test_numeric_type_error_message() { Box::new(Expr::Literal(LiteralVal::Bool(true))), ); let err = checker.check_expr(&bad_expr).unwrap_err(); - assert!(err.to_string().contains("must by numeric types")); + assert!(err.to_string().contains("must be numeric types")); } #[test] @@ -311,14 +325,14 @@ fn test_while_statement_type_checking() { // Test while statement with boolean condition let while_stmt = Stmt::While { condition: Box::new(Expr::Literal(LiteralVal::Bool(true))), - body: Box::new(Stmt::Expr(Box::new(Expr::Literal(LiteralVal::Int(42))))), + body: Box::new(Stmt::expr(Box::new(Expr::Literal(LiteralVal::Int(42))))), }; assert!(while_stmt.type_check(&mut checker).is_ok()); // Test while statement with non-boolean condition let while_stmt_invalid = Stmt::While { condition: Box::new(Expr::Literal(LiteralVal::Int(42))), // Int instead of Bool - body: Box::new(Stmt::Expr(Box::new(Expr::Literal(LiteralVal::Int(42))))), + body: Box::new(Stmt::expr(Box::new(Expr::Literal(LiteralVal::Int(42))))), }; assert!(while_stmt_invalid.type_check(&mut checker).is_ok()); } @@ -334,7 +348,7 @@ fn test_for_statement_type_checking() { Box::new(Expr::Literal(LiteralVal::Int(1))), Box::new(Expr::Literal(LiteralVal::Int(2))), ])), - body: Box::new(Stmt::Expr(Box::new(Expr::Literal(LiteralVal::Nil)))), + body: Box::new(Stmt::expr(Box::new(Expr::Literal(LiteralVal::Nil)))), }; assert!(for_stmt.type_check(&mut checker).is_ok()); @@ -342,7 +356,7 @@ fn test_for_statement_type_checking() { let for_stmt_invalid = Stmt::For { pattern: ForPattern::Variable("item".to_string()), iterable: Box::new(Expr::Literal(LiteralVal::Int(42))), // Int is not iterable - body: Box::new(Stmt::Expr(Box::new(Expr::Literal(LiteralVal::Nil)))), + body: Box::new(Stmt::expr(Box::new(Expr::Literal(LiteralVal::Nil)))), }; let result = for_stmt_invalid.type_check(&mut checker); assert!(result.is_err()); @@ -350,6 +364,138 @@ fn test_for_statement_type_checking() { result .unwrap_err() .to_string() - .contains("For loop iterable must be List, String, Map, or Set") + .contains("For loop iterable must be List, String, Map, Set, Bytes, Slice or Tuple") ); } + +/// The three ways a machine integer refuses to mix, and why each is a rule +/// rather than an oversight. +/// +/// A `u32` register write has to be exactly 32 bits wide and has to wrap rather +/// than promote — promoting to `Int` would silently give the operation 64-bit +/// semantics, which is the opposite of what asking for a width was for. So the +/// checker refuses three things, and the messages are what a driver author +/// reads when a width is wrong. +/// +/// What is *not* refused, and used to be: an integer literal beside a machine +/// An empty list learns its element type from a *container* argument too. +/// +/// `xs.push(y)` has parameter `'T` — a bare variable — so the argument was used +/// to bind it. `xs.chain(ys)` has parameter `List<'T>`, which is not a variable, +/// so it was *checked* instead: passing a `List` reported "expected +/// List<'T0>, got List" rather than binding `'T0` to `Int`. +/// +/// What that cost is a workaround in real code. `bare-metal-x86/program.lk` +/// wrote `let line = [0]; line = [];` — build a list with a placeholder element +/// so the element type is known, then throw the element away — because +/// `let line = []; line = line.chain(…)` did not type-check. +#[test] +fn an_empty_list_learns_its_element_type_from_a_container_argument() { + for source in [ + // The shape the kernel had to work around. + "let a = []; +a = a.chain([1]); +", + // The one that always worked, so a change here cannot have broken it. + "let a = []; +a.push(1); +", + // Learned from the far side, and then used: the binding has to reach + // the reads, not merely silence the argument check. + "let a = []; +a = a.chain([1]); +let b: Int = a.len(); +", + // Nested one deeper. + "let a = []; +a = a.chain([[1]]); +", + ] { + let program = crate::syntax::parse_program_source(source, Default::default()) + .unwrap_or_else(|e| panic!("should parse: {source}: {e}")); + let mut checker = TypeChecker::new(); + program + .type_check(&mut checker) + .unwrap_or_else(|e| panic!("should type-check: {source:?}: {e}")); + } +} + +/// integer takes its width. `reg + 1` is what driver code is made of. Relaxing +/// the checker alone was a miscompile for one round — the compiler went on +/// materialising the literal as an ordinary `Int`, so `255u8 + 1` answered 256 +/// with the type still claiming `u8` — so the literal is now normalised to the +/// width first, in `adopt_machine_width_for_literal`. +#[test] +fn machine_integers_refuse_to_mix() { + for (source, expected) in [ + // A variable of another numeric type: a width mistake. + ( + "let a: u8 = 5;\nlet n = 3;\nlet c = a + n;\n", + "machine integers do not mix", + ), + // A literal that does not fit the width it is used with. The literal + // itself is fine — `a + 1` compiles now, at the width — and this is the + // range check that comes with having a width at all. + ( + "let a: u8 = 5;\nlet c = a + 300;\n", + "out of range for the machine integer", + ), + // Two machine integers of different widths. + ( + "let a: u8 = 5;\nlet b: u16 = 3;\nlet c = a + b;\n", + "machine integer operands must have the same type", + ), + // A literal that does not fit the width it was given. + ("let a: u8 = 300;\n", "out of range"), + ] { + // Parsed and *type-checked*, which is the path `lk FILE` takes. + // `execute_source` skips the checker and simply runs, so a program that + // should be refused executes and the test passes for the wrong reason — + // this one did, answering 8 for `u8 + Int`. + let program = crate::syntax::parse_program_source(source, Default::default()) + .unwrap_or_else(|e| panic!("should parse: {source}: {e}")); + let mut checker = TypeChecker::new(); + let error = program + .type_check(&mut checker) + .expect_err(&alloc::format!("should be refused: {source}")) + .to_string(); + assert!( + error.contains(expected), + "expected {expected:?} for {source:?}, got {error}" + ); + } +} + +/// Strings order lexicographically. `list.sort()` has always put them in that +/// order and the executor has always had a string arm, but this rule refused +/// `<` on them — so the one way to ask which string came first was to sort a +/// two-element list. +#[test] +fn ordering_accepts_two_strings_and_still_rejects_mixed_operands() { + let text = |value: &str| Expr::Literal(LiteralVal::from_str(value)); + let compare = |left, op, right| Expr::Bin(Box::new(left), op, Box::new(right)); + + let mut checker = TypeChecker::new(); + checker + .check_expr(&compare(text("a"), BinOp::Lt, text("b"))) + .expect("two strings order"); + checker + .check_expr(&compare(text("a"), BinOp::Ge, text("b"))) + .expect("two strings order"); + + // Mixed still refuses — and says what the rule is. It used to report "the + // left operand must be numeric types", which blames the wrong thing twice: + // the left operand here *is* a number, and a String would have been fine. + let err = checker + .check_expr(&compare(Expr::Literal(LiteralVal::Int(1)), BinOp::Lt, text("a"))) + .expect_err("Int against String"); + assert!(err.to_string().contains("compares two of a kind"), "{err}"); + + // A type with no ordering at all is told that, rather than being told it is + // not a number — and the expected set no longer omits String. + let list = Expr::List(vec![Box::new(Expr::Literal(LiteralVal::Int(1)))]); + let err = checker + .check_expr(&compare(list.clone(), BinOp::Lt, list)) + .expect_err("lists have no ordering"); + assert!(err.to_string().contains("has no ordering"), "{err}"); +} diff --git a/core/src/typ/type_checker_test.rs b/core/src/typ/type_checker_test.rs index f569d780..f862daab 100644 --- a/core/src/typ/type_checker_test.rs +++ b/core/src/typ/type_checker_test.rs @@ -18,6 +18,35 @@ mod tests { tc.infer_resolved_type(&expr).expect("infer") } + /// A `catch` that renders the error is the ordinary shape, and it was the + /// one shape `try` rejected. + /// + /// The two branches of a value were unified — the same type or an error — + /// while a function with two `return`s of different types has always been a + /// *union*. So `try { xs.take(1) } catch e { "${e}" }` was "Cannot unify + /// List with String", and the caught value renders as text, so that is + /// what a `catch` most often evaluates to. + /// + /// The union is the answer, not a shrug: an annotation still refuses it, + /// naming both halves. + #[test] + fn a_try_whose_branches_differ_is_a_union() { + check_program("let xs = [1, 2, 3];\nprintln(try { xs.take(1) } catch e { \"${e}\" });\n") + .expect("a rendered catch is the ordinary shape"); + + let error = check_program("let xs = [1, 2, 3];\nlet a: Int = try { xs } catch e { \"${e}\" };\n") + .expect_err("the union does not fit an Int"); + let message = format!("{error:#}"); + assert!( + message.contains("List | String"), + "the annotation should be told both halves: {message}" + ); + + // Same type on both sides stays that type, and a branch that answers + // nothing still makes the value optional. + assert_eq!(infer("try { 1 } catch e { 2 }"), Type::Int); + } + #[test] fn test_string_add_concatenation_rules() { // String + String => String @@ -107,4 +136,1572 @@ mod tests { let err = check_program("const A = A + 1;\n").expect_err("must be reported"); assert!(err.to_string().contains("`A` is used before it is defined"), "{err}"); } + + /// `map`'s element type is the callback's, decided at the call site. + /// + /// The table cannot name it — there is no type there to name until someone + /// passes a function — so it declared `List` and every `map` result + /// was unchecked from then on. + #[test] + fn map_takes_its_element_type_from_the_callback() { + assert!(check_program("let xs = [1, 2]; let bad: String = xs.map(|x| x * 2)[0];").is_err()); + assert!(check_program("let xs = [1, 2]; let ok: Int = xs.map(|x| x * 2)[0];").is_ok()); + // Including when the callback changes the type. + assert!(check_program(r#"let xs = [1, 2]; let bad: Int = xs.map(|x| "n=${x}")[0];"#).is_err()); + assert!(check_program(r#"let xs = [1, 2]; let ok: String = xs.map(|x| "n=${x}")[0];"#).is_ok()); + // And when it is a named function rather than a literal. + assert!( + check_program("fn twice(x: Int) -> Int { return x * 2; } let bad: String = [1].map(twice)[0];").is_err() + ); + } + + /// A callback's parameter is the receiver's element type, known *before* + /// its body is checked. + /// + /// Adding it afterwards as a constraint types the result but checks + /// nothing: the body was already read with the parameter still free, so a + /// method that does not exist on the element type went unnoticed. + #[test] + fn a_callback_parameter_is_the_element_type_while_its_body_is_checked() { + assert!(check_program(r#"let ws = ["a"]; let n: Int = ws.map(|s| s.len())[0];"#).is_ok()); + let error = + check_program(r#"let ws = ["a"]; let bad = ws.map(|s| s.bogus());"#).expect_err("a String has no `bogus`"); + assert!( + format!("{error:#}").contains("String has no method 'bogus'"), + "unexpected error: {error:#}" + ); + // `filter` keeps the element type, so its predicate sees it too. + assert!(check_program(r#"let ws = ["a"]; let kept: List = ws.filter(|s| s.len() > 0);"#).is_ok()); + } + + /// A template string is a string, closure body included. + /// + /// `|x| "n=${x}"` was a syntax error while `|x| "n"` parsed: the token that + /// starts an interpolated string was missing from the set a closure body + /// may begin with. + #[test] + fn a_closure_body_may_be_a_template_string() { + assert!(check_program(r#"let f = |x| "n=${x}"; let s: String = f(1);"#).is_ok()); + } + + /// A call to an unannotated function gets *this call's* answer for the + /// callee's type variables. + /// + /// `fn id(x) { return x; }` has the principal type `'a -> 'a`, and every + /// call site used to constrain that same `'a` — so two calls with different + /// types fought over it and the result was a type nothing could be checked + /// against. `let s: String = id(1);` passed. + #[test] + fn a_call_reads_the_callees_type_variables_for_itself() { + assert!(check_program("fn id(x) { return x; } let bad: String = id(1);").is_err()); + assert!(check_program("fn id(x) { return x; } let ok: Int = id(1);").is_ok()); + // Two calls at different types, both right, neither deciding for the + // other. + assert!(check_program(r#"fn id(x) { return x; } let a: Int = id(1); let b: String = id("s");"#).is_ok()); + // A parameter that appears in the return type carries through it. + assert!(check_program(r#"fn pair(x) { return [x, x]; } let bad: Int = pair("s")[0];"#).is_err()); + assert!(check_program(r#"fn pair(x) { return [x, x]; } let ok: String = pair("s")[0];"#).is_ok()); + } + + /// The limit of the above, written down because it is not obvious from + /// either side. + /// + /// `fn first(xs) { return xs[0]; }` is `List<'a> -> 'a`, and a call to it + /// still learns nothing: what a call site is handed in program mode is the + /// *placeholder* signature registered before the body was checked — `'b -> + /// 'c`, with the relation between them living only in the solver. Binding + /// `'b` to `List` there says nothing about `'c`. + /// + /// Registering the solved signature instead was tried and is worse: it + /// resolves the placeholders against the body alone, which loses the cases + /// that do work today (`id`, `pair`) and costs a corpus example besides. + /// Getting this one needs the call site to reach the solver, which is a + /// different design than the single pass this checker is. + #[test] + fn a_parameter_that_only_shapes_the_return_type_is_not_carried_through_yet() { + assert!(check_program("fn first(xs) { return xs[0]; } let bad: String = first([1, 2]);").is_ok()); + } + + /// …but not into a union, which describes several map keys at once. + /// + /// `{"name": name, "score": 95}` is `Map`: the union is + /// every key's value type run together, so no single read is decided by it. + /// Pinning `'a` to `String` there does not make `u.score` more knowable, it + /// makes `u.score + 5` — which runs fine — report "left side must be + /// numeric, got String | Int". + #[test] + fn instantiation_stops_at_a_union() { + assert!( + check_program( + r#"fn user(name) { return {"name": name, "score": 95}; } + let u = user("Alice"); + let n = u.score + 5;"# + ) + .is_ok() + ); + } + + /// A builtin container dispatches with its element type erased — a + /// `TypedList::Mixed` has nothing else to report — so `List` and + /// `List` reach the same entry. Naming one is refused rather than + /// registered under a key nothing looks up, which is what used to happen: + /// the call then failed with "List has no method", true and unhelpful. + #[test] + fn an_impl_target_cannot_name_an_element_type() { + assert!(check_program("impl List { fn second(self) -> Any { return self.get(1); } }").is_ok()); + assert!(check_program("impl Map { fn size(self) -> Int { return self.len(); } }").is_ok()); + + let error = check_program("impl List { fn total(self) -> Int { return 0; } }") + .expect_err("`List` is not a dispatchable target"); + assert!( + format!("{error:#}").contains("cannot name an element type"), + "unexpected error: {error:#}" + ); + } + + /// The other half: a method registered on `List` is found on a list of any + /// element type. It was registered under `List` and looked up under + /// `List`, so it existed and could not be found. + #[test] + fn a_method_on_a_builtin_container_is_found_whatever_its_elements_are() { + assert!( + check_program( + "impl List { fn second(self) -> Any { return self.get(1); } }\n\ + let a = [1, 2].second();\n\ + let b = [\"x\", \"y\"].second();" + ) + .is_ok() + ); + } + + /// A nullable value does not pass for a non-nullable one — including for + /// numbers, where the promotion rule used to erase the `?`. + /// + /// `index_of` answers `Int?` because a miss is nil. Assigning that to a + /// declared `Int` was accepted, so the nil arrived at whatever read the + /// variable next and failed there — a report about the wrong line, for a + /// mistake the annotation was written to catch. + #[test] + fn an_optional_number_is_not_a_number() { + let error = check_program("let n: Int = [1, 2].index_of(2);").expect_err("`Int?` is not an `Int`"); + assert!( + format!("{error:#}").contains("Int?"), + "the error should name the nullable type: {error:#}" + ); + // The same rule the non-numeric types always had. + assert!(check_program("let s: String = [\"a\"].index_of(\"a\");").is_err()); + // And the ways to say "I have handled the nil" still work. + assert!(check_program("let n: Int = [1, 2].index_of(2)!;").is_ok()); + assert!(check_program("let n: Int = [1, 2].index_of(2) ?? 0;").is_ok()); + assert!(check_program("let n: Int? = [1, 2].index_of(2);").is_ok()); + assert!(check_program("let n = [1, 2].index_of(2);").is_ok()); + } + + /// A function-type annotation flows **into** a lambda. + /// + /// Checked in isolation a lambda types as `('T0) -> Any`, which does not + /// unify with the annotation written for it — so a lambda could not be + /// annotated at all, while a named `fn` assigned to the same binding was + /// accepted. + #[test] + fn a_lambda_can_be_annotated_with_a_function_type() { + assert!(check_program("let f: (Int) -> Int = |x| { return x + 1; };").is_ok()); + assert!(check_program("let g: (Int, Int) -> Int = |a, b| a * b;").is_ok()); + assert!(check_program("let h: (String) -> Int = |s| { return s.len(); };").is_ok()); + assert!(check_program("let n: () -> String = || { return \"hi\"; };").is_ok()); + // A named function was always accepted; it must stay so. + assert!(check_program("fn inc(x: Int) -> Int { return x + 1; }\nlet f: (Int) -> Int = inc;").is_ok()); + // The annotation still has to be satisfied. + assert!(check_program("let bad: (Int) -> String = |x| { return x + 1; };").is_err()); + assert!(check_program("let bad: (Int, Int) -> Int = |x| { return x; };").is_err()); + } + + /// A lambda's own parameter and return types are writable. + /// + /// It was the one callable in the language whose types could not be written + /// down — `Type::Function` has always had both halves, so a lambda's + /// parameter type could only be *guessed* from a call site. + #[test] + fn a_lambda_can_declare_its_own_types() { + assert!(check_program("let f = |x: Int| { return x + 1; };").is_ok()); + assert!(check_program("let g = |a: Int, b: Int| -> Int { return a * b; };").is_ok()); + assert!(check_program("let h = |s: String| -> Int { return s.len(); };").is_ok()); + assert!(check_program("let k = |x| -> Int { return x + 1; };").is_ok()); + assert!(check_program("let m = |xs: List| -> Int { return xs.len(); };").is_ok()); + // A comma inside the type does not end the parameter. + assert!(check_program("let n = |m: Map, k: String| -> Int { return m.len(); };").is_ok()); + // The declared return type is checked against, not merely recorded. + assert!(check_program("let bad = |x: Int| -> String { return x + 1; };").is_err()); + // And a declared parameter type is what the body is checked with. + assert!(check_program("let bad = |s: String| { return s + 1; };\nlet n: Int = bad(\"a\");").is_err()); + } + + /// Every context that declares a function type accepts a lambda for it. + /// + /// A `let` was taught; a struct field was not, so `Handler { run: |x| … }` + /// against `run: (Int) -> Int` was rejected with "got `('T0) -> Any`". + /// One helper now answers for all of them. + #[test] + fn a_declared_function_type_accepts_a_lambda_anywhere() { + assert!(check_program("let f: (Int) -> Int = |x| { return x + 1; };").is_ok()); + assert!( + check_program( + "struct Handler { run: (Int) -> Int }\nlet h = Handler { run: |x| { return x * 2; } };\nlet n: Int = h.run(4);" + ) + .is_ok() + ); + // And the declaration is still enforced there. + assert!( + check_program("struct Handler { run: (Int) -> String }\nlet h = Handler { run: |x| { return x * 2; } };") + .is_err() + ); + } + + /// Every position that declares a function type — not just the two that + /// were taught first. + /// + /// Each context had to be taught separately, so each that was not silently + /// rejected the lambda written for it: a `fn` parameter said "got `('T1) -> + /// Int`", a declared return type said "Return type mismatch", and a + /// `List<(Int) -> Int>` annotation refused a list of lambdas. + #[test] + fn a_lambda_reaches_every_position_that_declares_its_type() { + // A parameter. + assert!( + check_program("fn apply(g: (Int) -> Int, v: Int) -> Int { return g(v); }\nlet n: Int = apply(|x| { return x + 1; }, 5);") + .is_ok() + ); + // A declared return type. + assert!(check_program("fn make() -> (Int) -> Int { return |x| { return x + 1; }; }").is_ok()); + // An aggregate's element or value type. + assert!(check_program("let fs: List<(Int) -> Int> = [|x| { return x + 1; }];").is_ok()); + assert!(check_program("let m: Map Int> = {\"a\": |x| { return x + 1; }};").is_ok()); + // Assignment to an already-declared binding. + assert!(check_program("let f: (Int) -> Int = |x| { return x + 1; };\nf = |x| { return x * 2; };").is_ok()); + + // Every one of them still *checks*: the expectation flowing in is half + // of it, the answer is checked against the declaration too. + assert!(check_program("fn apply(g: (Int) -> String, v: Int) -> String { return g(v); }\nlet s = apply(|x| { return x + 1; }, 5);").is_err()); + assert!(check_program("fn make() -> (Int) -> String { return |x| { return x + 1; }; }").is_err()); + assert!(check_program("let fs: List<(Int) -> String> = [|x| { return x + 1; }];").is_err()); + assert!(check_program("let m: Map String> = {\"a\": |x| { return x + 1; }};").is_err()); + // A mismatched arity is not a lambda this expectation applies to. + assert!(check_program("fn apply(g: (Int, Int) -> Int, v: Int) -> Int { return g(v, v); }\nlet n = apply(|x| { return x + 1; }, 5);").is_err()); + } + + /// One name, one meaning: an `impl` may not redefine a method, nor take a + /// field's name. + /// + /// Both were resolved by taking the last declaration, silently. The field + /// case was worse than that: which one `p.get(…)` meant depended on the + /// *argument count* — `p.get()` read the field (the method unreachable), + /// while `p.f(3)` called the method (the field's closure unreachable). + #[test] + fn a_method_name_is_declared_once() { + // The same method twice for one type, whether through one trait… + assert!( + check_program( + "trait Show { fn show(self) -> String; }\nstruct P { x: Int }\nimpl Show for P { fn show(self) -> String { return \"a\"; } }\nimpl Show for P { fn show(self) -> String { return \"b\"; } }" + ) + .is_err() + ); + // …two different traits (there is no `Trait::method(x)` to disambiguate + // with, so `p.run()` would have no answer)… + assert!( + check_program( + "trait A { fn run(self) -> Int; }\ntrait B { fn run(self) -> Int; }\nstruct P { x: Int }\nimpl A for P { fn run(self) -> Int { return 1; } }\nimpl B for P { fn run(self) -> Int { return 2; } }" + ) + .is_err() + ); + // …or two inherent blocks. + assert!( + check_program("struct P { x: Int }\nimpl P { fn get(self) -> Int { return 1; } }\nimpl P { fn get(self) -> Int { return 2; } }") + .is_err() + ); + // A method named like a field, in both arities. + assert!(check_program("struct P { get: Int }\nimpl P { fn get(self) -> Int { return 9; } }").is_err()); + assert!(check_program("struct P { f: (Int) -> Int }\nimpl P { fn f(self) -> Int { return 9; } }").is_err()); + + // Distinct names on one type, and one name on distinct types, are fine. + assert!( + check_program( + "struct P { x: Int }\nimpl P { fn get(self) -> Int { return 1; } fn set(self) -> Int { return 2; } }" + ) + .is_ok() + ); + assert!( + check_program("struct P { x: Int }\nstruct Q { x: Int }\nimpl P { fn get(self) -> Int { return 1; } }\nimpl Q { fn get(self) -> Int { return 2; } }") + .is_ok() + ); + } + + /// A field written twice in a struct literal is a mistake, not a choice. + /// + /// The literal builds an ordered map and a repeat updates in place, so the + /// first value is one nothing can read — the same argument that refuses a + /// repeated parameter name and a repeated binding in a pattern. + #[test] + fn a_repeated_struct_literal_field_is_a_check_error() { + let error = check_program("struct P { x: Int, y: Int }\nlet p = P { x: 1, x: 2, y: 3 };") + .expect_err("`x` is written twice"); + assert!(format!("{error:#}").contains("written twice"), "{error:#}"); + assert!(check_program("struct P { x: Int, y: Int }\nlet p = P { x: 1, y: 3 };").is_ok()); + } + + /// A `..base` spread needs a base with fields. + /// + /// The spread desugars to `__lk_merge_fields(base, overlay)` and nothing + /// looked at the base, so `P { ..5 }` checked clean and died at run time + /// with a sentence naming the desugaring rather than what was written. + #[test] + fn a_spread_base_must_have_fields() { + let error = check_program("struct P { x: Int }\nlet p = P { ..5 };").expect_err("5 has no fields to spread"); + assert!(format!("{error:#}").contains("has to be a struct"), "{error:#}"); + assert!(check_program("struct P { x: Int }\nlet base = P { x: 1 };\nlet q = P { ..base, x: 2 };").is_ok()); + // A map is a base too, and so is `nil` (the empty one). + assert!(check_program("struct P { x: Int }\nlet q = P { ..{\"x\": 1} };").is_ok()); + } + + /// A method the trait never declared is a check error too — the other half + /// of the same rule, and it had the same hole. + /// + /// `TypeRegistry::validate_trait_impl` refuses it, and that runs when the + /// *VM registers impls*. So `lk check` — documented as the same check the + /// executors run — passed a program that stopped on its first line. + #[test] + fn an_undeclared_trait_method_is_a_check_error() { + let error = check_program( + "trait T { fn a(self) -> Int; }\nstruct P { x: Int }\nimpl T for P { fn a(self) -> Int { return 1; } fn b(self) -> Int { return 2; } }", + ) + .expect_err("`b` is not declared by `T`"); + let message = format!("{error:#}"); + assert!(message.contains("is not declared by trait"), "{message}"); + // The fix is named, and named on one line. + assert!(message.contains("`impl P { … }`"), "{message}"); + + // An *inherent* impl is where such a method belongs, so the same + // method there is fine. + assert!( + check_program( + "trait T { fn a(self) -> Int; }\nstruct P { x: Int }\nimpl T for P { fn a(self) -> Int { return 1; } }\nimpl P { fn b(self) -> Int { return 2; } }" + ) + .is_ok() + ); + } + + /// A trait's required methods are checked where `lk check` can see them. + /// + /// The check existed and only ran when the *VM* registered impls, so the + /// pre-flight command passed a program that could not run. + #[test] + fn a_missing_trait_method_is_a_check_error() { + let error = check_program( + "trait Show { fn show(self) -> String; fn tag(self) -> Int; }\nstruct P { x: Int }\nimpl Show for P { fn show(self) -> String { return \"a\"; } }", + ) + .expect_err("`tag` is missing"); + assert!(format!("{error:#}").contains("required by trait"), "{error:#}"); + + // A trait *default* is copied into the impl before this runs, so + // omitting a defaulted method is not an omission. + assert!( + check_program( + "trait Greet { fn hi(self) -> String { return \"hi\"; } }\nstruct P { x: Int }\nimpl Greet for P {}" + ) + .is_ok() + ); + } + + /// An arm an earlier catch-all shadows can never run. + /// + /// You wrote a case you believe happens, and it does not — silently, with + /// nothing ever saying the branch is dead. Refused rather than warned about + /// because the checker has no warning channel, and a loud refusal is what + /// the language does elsewhere for the same shape of mistake. + #[test] + fn a_match_arm_after_a_catch_all_is_refused() { + for source in [ + "let n = 1;\nlet r = match n { _ => \"any\", 1 => \"one\" };", + // A *binding* is a catch-all too — this is the one that was sitting + // in `examples/syntax/unsupported.lk`. + "let r = match 99 { n => n, _ => 0 };", + // So is an or-pattern with a total alternative. + "let n = 1;\nlet r = match n { 1 | _ => \"a\", 2 => \"b\" };", + ] { + let error = check_program(source).expect_err(&alloc::format!("dead arm accepted:\n{source}")); + assert!(format!("{error:#}").contains("can never run"), "{error:#}"); + } + + // A *guarded* catch-all is conditional, so it dominates nothing — the + // same distinction the fall-through detection draws. + assert!(check_program("let n = 1;\nlet r = match n { x if x > 0 => \"pos\", _ => \"other\" };").is_ok()); + // And a catch-all as the last arm is the ordinary shape. + assert!(check_program("let n = 1;\nlet r = match n { 1 => \"one\", _ => \"other\" };").is_ok()); + // A destructuring pattern matches only some values. + assert!(check_program("let pt = [10, 20];\nlet r = match pt { [x, y] => x + y, _ => 0 };").is_ok()); + } + + /// A top-level `let` may not take a name a declaration already binds. + /// + /// A `fn` and a type declaration are hoisted — mutual recursion works, so a + /// `fn` is visible before its line — and the `let` won *in either order*, + /// silently. That is the mistake that put a dead `fn apply` beside a live + /// `let apply` in `examples/syntax/closure.lk`. + #[test] + fn a_top_level_let_cannot_take_a_declared_name() { + for source in [ + "fn pick() -> String { return \"fn\"; }\nlet pick = || { return \"let\"; };", + // Also the other way round: order does not make it coherent. + "let pick = || { return \"let\"; };\nfn pick() -> String { return \"fn\"; }", + "fn pick() -> String { return \"fn\"; }\nlet pick = 42;", + "struct P { x: Int }\nlet P = 1;", + "type Alias = Int;\nlet Alias = 1;", + ] { + let error = check_program(source).expect_err(&alloc::format!("collision accepted:\n{source}")); + let text = alloc::format!("{error:#}"); + assert!(text.contains("is already declared as a"), "{text}"); + } + + // Two `let`s *are* coherent shadowing: both are order-sensitive. + assert!(check_program("let x = 1;\nlet x = 2;").is_ok()); + // And inside a callable body it is ordinary shadowing — the local is + // order-sensitive within its scope, the declaration is outside it. + assert!( + check_program("fn pick() -> Int { return 1; }\nfn use_it() -> Int { let pick = 2; return pick; }").is_ok() + ); + assert!(check_program("fn pick() -> Int { return 1; }\nlet f = || { let pick = 2; return pick; };").is_ok()); + } + + /// A `fn` inside another callable is parsed and then not found by the + /// compiler (function indices come from top-level statements only), so it + /// used to fail with "Compiler undefined function" — the backend's words for + /// a construct the grammar accepted. + #[test] + fn a_function_cannot_be_declared_inside_another() { + let error = + check_program("fn outer() -> Int {\n fn helper(n: Int) -> Int { return n + 1; }\n return helper(5);\n}") + .expect_err("a nested fn is refused"); + let text = format!("{error:#}"); + assert!(text.contains("cannot be declared inside another"), "{text}"); + // The message names both ways to say it instead. + assert!(text.contains("top level") && text.contains("closure"), "{text}"); + // A closure body is a callable body too. + assert!(check_program("let f = || { fn helper() -> Int { return 1; } return helper(); };").is_err()); + // Top level, including mutual recursion, is unaffected — and so are + // `impl` methods, which are `fn` declarations at top level. + assert!( + check_program( + "fn is_even(n: Int) -> Bool { if (n == 0) { return true; } return is_odd(n - 1); }\nfn is_odd(n: Int) -> Bool { if (n == 0) { return false; } return is_even(n - 1); }" + ) + .is_ok() + ); + assert!(check_program("struct P { x: Int }\nimpl P {\n fn get(self) -> Int { return self.x; }\n}").is_ok()); + } + + /// A block-bodied closure's `return` is what the closure returns. + /// + /// The frame collecting them was popped and discarded, so every such + /// closure typed `… -> Any` — and `Any` satisfies anything. + #[test] + fn a_closure_returns_what_its_body_returns() { + assert!(check_program("let f = |x| { return x + 1; };\nlet s: String = f(1);").is_err()); + assert!(check_program("let f = |x| { return x + 1; };\nlet n: Int = f(1);").is_ok()); + } + + /// Statements inside a block are type-checked. + /// + /// `Expr::Block` used to answer `Any` without looking inside, on the grounds + /// that blocks mostly come from desugars checked before they are built. But + /// a closure body is a block too, so a whole class of code was invisible: + /// this exact `let` is rejected at top level and was accepted here. + #[test] + fn a_block_body_is_type_checked() { + assert!(check_program("let s: String = 1;").is_err()); + assert!(check_program("let f = |x| { let s: String = 1; return x; };").is_err()); + assert!(check_program("let f = |x| { let s: String = \"ok\"; return x; };").is_ok()); + // A block is a scope: the inner binding is not visible afterwards. + assert!(check_program("let f = || { let inner = 1; return inner; };\nlet n: Int = f();").is_ok()); + } + /// A key that can never be a key is refused where it is written. + /// + /// Only nil, Bool, Int and String can be a map key or a set member — Float + /// because `0.0 == -0.0` while their bits differ and NaN is not equal to + /// itself, containers because a key you can mutate is a record you can no + /// longer find (`docs/semantics.md`). The runtime enforced it from one + /// place; the checker enforced it from none, so `Set([1.5])` and + /// `{1.5: "a"}` type-checked and raised at run time with the offending type + /// sitting in the literal. + /// + /// Four sites ask, because there are four ways to write a key: `Set(xs)`, + /// a map literal, `s.add(v)`, and `m[k] = v` — the last of which the checker + /// could not even see, since the parser turns it into `__lk_set_index` and + /// only the bytecode compiler knew that name. + #[test] + fn a_type_that_can_never_be_a_key_is_refused_at_check_time() { + for source in [ + "let s = Set([1.5]);", + "let s = Set([[1]]);", + // A tuple names its elements one by one, so one bad element settles it. + "let s = Set([1, 1.0]);", + r#"let m = {1.5: "a"};"#, + r#"let m = {1: "a", 2.5: "b"};"#, + "let s = Set();\ns.add(1.5);", + "let s = Set();\ns.contains([1]);", + r#"let m = {"k": 1}; + m[1.5] = 2;"#, + ] { + let error = check_program(source).expect_err(source); + let message = format!("{error:#}"); + assert!( + message.contains("cannot be a map key") || message.contains("cannot be a set member"), + "{source} → {message}" + ); + } + } + + /// …and nothing else is refused. + /// + /// The rule answers "certainly not a key", not "not obviously a key": a + /// union may be the Int at run time, a list index is an ordinary position, + /// and a receiver of unknown type is nobody's business to refuse. Rejecting + /// a working program is the failure mode that matters here. + #[test] + fn the_key_rule_refuses_nothing_that_might_work() { + for source in [ + "let s = Set([1, 2]);", + r#"let s = Set([nil, true, "x"]);"#, + r#"let m = {"k": 1};"#, + "let m = {1: 2, true: 3};", + // A list index is a position, not a key. + "let xs = [1, 2];\nxs[0] = 9;", + "let i = 0;\nlet xs = [1];\nxs[i] = 5;", + // Values are unrestricted — only keys are. + r#"let m = {"k": 1.5};"#, + r#"let m = {"k": [1, 2]};"#, + "let s = Set([1]);\nlet v = s.values();", + ] { + assert!(check_program(source).is_ok(), "{source}"); + } + } + + /// An argument's type error says *where*, like every other type error. + /// + /// `TypeError::span` is filled by the enclosing statement on the way out, and a + /// bare call is a `Stmt::Expr` — the one statement variant that carried no span + /// at all. So the same mistake reported `1:1-6` when written as a `let` and + /// nothing when written as a call, which in a four-thousand-line program is the + /// difference between a diagnostic and a riddle. + /// + /// Pinned on the *fifth* statement on purpose: an expression carries no position + /// of its own, so the tempting repair is to search the token stream for + /// something that looks like it — which finds the first match in the file, not + /// this one. + #[test] + fn an_argument_type_error_names_the_line_it_is_on() { + let error = check_program( + r#"fn f(a: Int) -> Int { return a; } + f(1); + f(2); + f(3); + f("wrong");"#, + ) + .expect_err("a String is not an Int"); + + let message = format!("{error:#}"); + assert!(message.contains("Argument 1 has the wrong type"), "{message}"); + assert!( + message.contains("(5:"), + "the error must point at the fifth line: {message}" + ); + } + + /// `Tuple` describes a list, and a list satisfies it — in both + /// directions. + /// + /// `Tuple` used to be a type nothing could inhabit: `[1, 2]` is + /// `List` (its elements do not differ, so no tuple is inferred) and + /// `is_assignable_to` had only the Tuple→List half. `Tuple` + /// hid it, because a heterogeneous literal infers `Tuple` directly and + /// never needed the conversion. + /// + /// The unifier had both directions in one arm all along, so this was also + /// the two of them disagreeing. + /// A struct literal names a type, and a name nothing declares is refused. + /// + /// It used to be accepted "as a named type": `Nope { a: 1 }` answered + /// `Nope{a:1}` — a typo that produced a value. The checker's own comment + /// said so ("otherwise, accept as named type"), which is the whole of the + /// rule it was following. + #[test] + fn a_struct_literal_names_a_declared_type() { + check_program("let p = Nope { a: 1 };").expect_err("nothing declares Nope"); + check_program("struct P { x: Int }\nlet p = P { x: 1 };").expect("declared here"); + // Order does not matter: a top-level declaration is visible before the + // line it is written on, the same rule `let` shadowing follows. + check_program("let p = P { x: 1 };\nstruct P { x: Int }").expect("declared later"); + // The message says where a type from another module is reached. + let message = check_program("let p = Nope { a: 1 };") + .expect_err("refused") + .to_string(); + assert!(message.contains("no type named `Nope` is declared here"), "{message}"); + assert!(message.contains("m.Nope"), "{message}"); + } + + /// A type another module declares is not constructible by its bare name. + /// + /// It used to build a value that renders `P{x:4}` and answers `typeof` `P` + /// while carrying none of `P`'s methods: the runtime stamps the + /// *constructing* module's `TypeScope` and the method table is keyed by the + /// declaring one, so the two are different identities that share a name. + /// The failure surfaced at the call site — "P has no method 'norm'" — far + /// from the construction. The spellings that do carry the declaring + /// module's identity (`m.P { … }`, or a constructor it exports) both work. + #[test] + fn a_type_from_another_module_is_not_constructible_by_its_bare_name() { + let imported = || crate::typ::StructDef { + name: "P".to_string(), + fields: [("x".to_string(), Type::Int)].into_iter().collect(), + }; + + let mut checker = TypeChecker::new(); + checker.registry_mut().register_imported_struct(imported()); + let program = + crate::syntax::parse_program_source("let p = P { x: 4 };", Default::default()).expect("parse program"); + let message = program + .type_check(&mut checker) + .expect_err("a bare imported name is refused") + .to_string(); + assert!(message.contains("is declared in another module"), "{message}"); + assert!(message.contains("m.P"), "{message}"); + + // A local declaration of the same name wins — imports are seeded first, + // and registering the local one un-marks the entry. + let mut checker = TypeChecker::new(); + checker.registry_mut().register_imported_struct(imported()); + let program = + crate::syntax::parse_program_source("struct P { x: Int }\nlet p = P { x: 4 };", Default::default()) + .expect("parse program"); + program + .type_check(&mut checker) + .expect("declared here, so it is this module's"); + + // Imported *by name*, the literal is accepted: the import binds the + // constructor the declaring module generates, so the object it builds + // carries that module's identity. Its schema is still the declaring + // module's — an undeclared field is refused. + let mut checker = TypeChecker::new(); + checker.registry_mut().register_imported_struct(imported()); + checker.registry_mut().mark_constructible_import("P", "P"); + let program = + crate::syntax::parse_program_source("let p = P { x: 4 };", Default::default()).expect("parse program"); + program.type_check(&mut checker).expect("imported by name, so bound"); + + let mut checker = TypeChecker::new(); + checker.registry_mut().register_imported_struct(imported()); + checker.registry_mut().mark_constructible_import("P", "P"); + let program = + crate::syntax::parse_program_source("let p = P { y: 4 };", Default::default()).expect("parse program"); + let message = program + .type_check(&mut checker) + .expect_err("the declaring module's schema still applies") + .to_string(); + assert!(message.contains("Unknown field 'y'"), "{message}"); + + // Under an alias the two names differ: the literal is written `Q`, and + // everything about it — schema, error wording, result type — is `P`'s. + let mut checker = TypeChecker::new(); + checker.registry_mut().register_imported_struct(imported()); + checker.registry_mut().mark_constructible_import("Q", "P"); + let program = + crate::syntax::parse_program_source("let q: P = Q { x: 4 };", Default::default()).expect("parse program"); + program.type_check(&mut checker).expect("an alias for an imported type"); + + let mut checker = TypeChecker::new(); + checker.registry_mut().register_imported_struct(imported()); + checker.registry_mut().mark_constructible_import("Q", "P"); + let program = + crate::syntax::parse_program_source("let q = Q { y: 4 };", Default::default()).expect("parse program"); + let message = program + .type_check(&mut checker) + .expect_err("the declaring module's schema still applies") + .to_string(); + assert!(message.contains("struct 'P'"), "{message}"); + } + + /// A top-level statement whose call reaches a binding declared below it. + /// + /// The top level runs in order, so `f()` above `const LATER` read nil — + /// and `typeof(f())` answered `Nil` for a function declared `-> Int`. + /// Whatever touched the nil next reported its own complaint; nothing ever + /// named the ordering. Python raises `NameError` here and JavaScript raises + /// out of the temporal dead zone. + /// + /// The other two cases were already settled: a direct top-level read is + /// refused, and a *body* reading a later binding is ordinary because bodies + /// run after the whole top level. + #[test] + fn a_top_level_call_may_not_reach_a_binding_declared_below_it() { + let message = check_program("fn f() -> Int { return LATER; }\nprintln(f());\nconst LATER = 7;\n") + .expect_err("`f` runs before line 3 does") + .to_string(); + assert!(message.contains("`f` reads `LATER`"), "{message}"); + assert!(message.contains("the top level runs in order"), "{message}"); + + // Transitively, through a second function. + let message = check_program( + "fn inner() -> Int { return LATER; }\n\ + fn outer() -> Int { return inner(); }\n\ + println(outer());\n\ + const LATER = 7;\n", + ) + .expect_err("the read is one call deeper") + .to_string(); + assert!(message.contains("`LATER`"), "{message}"); + + // Below the declaration it is ordinary, and so is a body that reads a + // later binding without being called yet. + check_program("fn f() -> Int { return LATER; }\nconst LATER = 7;\nprintln(f());\n") + .expect("the declaration runs first"); + check_program("fn f() -> Int { return LATER; }\nconst LATER = 7;\n").expect("never called above it"); + + // A local of the same name is not the global one (shadowing is + // subtracted wholesale — see `stmt::init_order`). + check_program("fn f() -> Int { let LATER = 1; return LATER; }\nprintln(f());\nconst LATER = 7;\n") + .expect("the read is the local"); + } + + /// A method a scalar does not have is a *check-time* error, like it already + /// was on a String or a List. + /// + /// Int, Float, Bool and Nil have **no** built-in methods at all — `abs`, + /// `sqrt`, `round`, `len`, `to_string`, every one of them answers "no + /// method" at run time. But `receiver_kind` only knows the six container + /// kinds, so a scalar receiver fell through to `Any` and `lk check` passed + /// `let v = 1; v.nope();` — the same mistake, caught at check time on a + /// `String` and at run time on an `Int`. + /// + /// A Map stays exempt on purpose: its entries *are* its fields, so + /// `m.score(1)` may be an ordinary property call and nothing in the type + /// says which keys exist. + #[test] + fn a_method_a_scalar_does_not_have_is_refused_at_check_time() { + for (receiver, name) in [("1", "Int"), ("1.5", "Float"), ("true", "Bool"), ("nil", "Nil")] { + let message = check_program(&alloc::format!("let v = {receiver};\nprintln(v.nope());\n")) + .expect_err("a scalar has no methods of its own") + .to_string(); + assert!( + message.contains(&alloc::format!("{name} has no method 'nope'")), + "{message}" + ); + } + + // A user `impl` is what makes the name resolvable, and it does so + // wherever it sits — including below the call. + check_program("impl Int { fn double(self) -> Int { return self * 2; } }\nprintln((5).double());\n") + .expect("an impl above the call"); + check_program("println((5).double());\nimpl Int { fn double(self) -> Int { return self * 2; } }\n") + .expect("an impl below the call"); + + // A map keeps answering: its keys are not in its type. + check_program("let m = {\"a\": 1};\nlet f = m.whatever();\n").expect("a map's entries are its fields"); + } + + /// `impl` is hoisted like `fn` and `struct`, so a method call may stand + /// above the block that declares it. + /// + /// It was the one declaration form the checker read in source order: a + /// method becomes known by being type-*checked*, and that walk is ordered. + /// The imported twin was already pre-scanned (`typ::imports`), which is how + /// the asymmetry hid — an impl one `use` away worked, one three lines down + /// did not. + #[test] + fn an_impl_is_visible_above_the_block_that_declares_it() { + check_program( + "struct P { x: Int }\n\ + let p = P { x: 1 };\n\ + println(p.m());\n\ + impl P { fn m(self) -> Int { return self.x; } }\n", + ) + .expect("an impl below its call site"); + + // Hoisting makes it *visible*, not unchecked: the declared arity still + // applies from above. + let message = check_program( + "struct P { x: Int }\n\ + let p = P { x: 1 };\n\ + println(p.m(1, 2));\n\ + impl P { fn m(self) -> Int { return self.x; } }\n", + ) + .expect_err("the declared arity applies from above too") + .to_string(); + assert!(message.contains("Method expects 0 arguments"), "{message}"); + } + + /// A store into a container is checked against what the container's type + /// declares it holds — through every spelling of a store. + /// + /// The parser desugars each of them into a different hidden call + /// (`list.set` for a literal index, `__lk_set_index` otherwise, + /// `__lk_set_field` for a field), and none of the three checked the value. + /// `l.set(0, "a")` on a `List` was refused while `l[0] = "a"` — the + /// same operation, the other spelling — was accepted, and + /// `let n: Int = l[0]` then type-checked and held a String. + #[test] + fn a_store_is_checked_against_what_the_container_declares() { + let refused = [ + ("list element, literal index", "let l: List = [1];\nl[0] = \"a\";"), + ( + "list element, variable index", + "let l: List = [1];\nlet i = 0;\nl[i] = \"a\";", + ), + ( + "nested list element", + "let l: List> = [[1]];\nl[0] = [\"a\"];", + ), + ("map value", "let m: Map = {\"k\": 1};\nm[\"k\"] = \"a\";"), + ("map key", "let m: Map = {\"k\": 1};\nm[7] = 2;"), + ("struct field", "struct S { x: Int }\nlet s = S { x: 1 };\ns.x = \"a\";"), + ( + "the method spelling, which always was", + "let l: List = [1];\nl.set(0, \"a\");", + ), + // A heterogeneous literal infers `Tuple`, whose positions have + // different types — the carrier the first version of this check + // did not cover. + ("tuple position, literal index", "let l = [1, \"a\"];\nl[0] = 2.5;"), + ( + "tuple, index not a literal", + "let l = [1, \"a\"];\nlet i = 0;\nl[i] = 2.5;", + ), + ( + "annotated tuple position", + "let l: Tuple = [1, \"a\"];\nl[0] = \"z\";", + ), + ]; + for (what, source) in refused { + check_program(source).expect_err(what); + } + // A field the struct does not declare, which is the same error reading + // it gives — and a store is where it has to be said, because a store + // has no field-access expression for the read path to see. `p.z = 3` + // desugars straight to `__lk_set_field(p, "z", 3)`, so reading `p.z` + // was refused while writing it was accepted, and the two back ends then + // disagreed: the interpreter grew the field, the compiled build dropped + // it, and neither said anything. + let undeclared = [ + ("plain store", "struct S { x: Int }\nlet s = S { x: 1 };\ns.z = 2;"), + ("compound store", "struct S { x: Int }\nlet s = S { x: 1 };\ns.z += 2;"), + ]; + for (what, source) in undeclared { + let message = check_program(source).expect_err(what).to_string(); + assert!(message.contains("has no field 'z'"), "{what}: {message}"); + } + + let accepted = [ + ("a store of the declared type", "let l: List = [1];\nl[0] = 2;"), + ("compound assignment", "let l: List = [1];\nl[0] += 1;"), + ( + "a map store of the declared types", + "let m: Map = {\"k\": 1};\nm[\"j\"] = 2;", + ), + ( + "a struct field of its declared type", + "struct S { x: Int }\nlet s = S { x: 1 };\ns.x = 2;", + ), + ( + "an untyped container still takes anything", + "let l = [];\nl.push(1);\nl[0] = \"a\";", + ), + ("a tuple position of its own type", "let l = [1, \"a\"];\nl[1] = \"z\";"), + ]; + for (what, source) in accepted { + check_program(source).expect(what); + } + } + + /// A container cannot be widened at its element type, in any of the five + /// positions that could do it. + /// + /// These containers are mutable and a widening is an *alias*, so the wide + /// name can write an element the narrow name's type forbids: + /// + /// ```lk + /// let a: List = [1, 2]; + /// let b: List = a; + /// b.push("s"); + /// let c: Int = a[2]; // type-checked, and held "s" + /// ``` + /// + /// Closing only the parameter position would have left the other four. + #[test] + fn a_container_cannot_be_widened_at_its_element_type() { + let widenings = [ + ("bare let", "let a: List = [1, 2];\nlet b: List = a;"), + ("Any let", "let a: List = [1, 2];\nlet b: List = a;"), + ( + "parameter", + "fn take(xs: List) -> Int { return 0; }\nlet a: List = [1, 2];\nreturn take(a);", + ), + ( + "struct field", + "struct Box { xs: List }\nlet a: List = [1, 2];\nlet b = Box { xs: a };", + ), + ( + "container element", + "let a: List = [1, 2];\nlet holder: List = [a];", + ), + ("return type", "fn widen(xs: List) -> List { return xs; }"), + ( + "map value", + "let m: Map = {\"k\": 1};\nlet w: Map = m;", + ), + ]; + for (what, source) in widenings { + check_program(source).expect_err(what); + } + + // A literal is a fresh container, so there is nothing to alias and the + // annotation is simply what it is checked against. + check_program("let a: List = [1, 2];").expect("a literal takes the declared element type"); + check_program("let a: List = [1, 2, 3];").expect("and so does a bare one"); + check_program("let m: Map = {\"k\": 1};").expect("map literals too"); + + // `List<_>` — the read-only view — accepts every list, and writing + // through it is refused because nothing is assignable to `_`. + check_program("let a: List = [1, 2];\nlet n = a.zip([3, 4]);") + .expect("a builtin that only reads its list argument takes any list"); + check_program("let a: List<_> = [1, 2];\na.push(3);").expect_err("a read-only view cannot be written"); + } + + /// …and the literal half of that rule holds in every position too. + /// + /// It used to hold in two: the `let` statement and a call argument each + /// carried their own copy of it, and a struct field and a `return` had + /// neither. So `let f: List = [1];` was accepted while + /// `S { f: [1] }` and `fn f() -> List { return [1]; }` were refused — + /// the same written value, three answers. + #[test] + fn a_container_literal_takes_the_declared_element_type_in_every_position() { + let positions = [ + ("let", "let xs: List = {LIT};"), + ( + "parameter", + "fn take(xs: List) -> Int { return xs.len(); }\nlet n = take({LIT});", + ), + ("struct field", "struct S { f: List }\nlet s = S { f: {LIT} };"), + ("return", "fn make() -> List { return {LIT}; }"), + ]; + // A homogeneous literal, a heterogeneous one (which infers as a + // `Tuple`), and an empty one. + for literal in ["[1]", "[1, \"a\"]", "[]"] { + for (what, shape) in positions { + let source = shape.replace("{LIT}", literal); + check_program(&source).unwrap_or_else(|e| panic!("{what} with {literal}: {e}")); + } + } + // The same positions still refuse a *variable*, which is what + // invariance is about — the literal is not a loophole in it. + for (what, shape) in positions { + let source = alloc::format!("let a: List = [1];\n{}", shape.replace("{LIT}", "a")); + check_program(&source).expect_err(what); + } + // And a literal whose elements do not fit is still refused. + check_program("struct S { f: List }\nlet s = S { f: [1.5] };") + .expect_err("a literal is checked element by element, not waved through"); + check_program("fn make() -> Map { return {\"k\": \"v\"}; }").expect_err("map literals too"); + + // The machine-int literal rule was split the same way, across the same + // two positions: `let x: u8 = 5;` and `f(5)` were accepted while + // `S { f: 5 }` and `fn f() -> u8 { return 5; }` were not. + let machine_int = [ + ("let", "let x: u8 = {LIT};"), + ("parameter", "fn take(v: u8) -> Int { return 0; }\nlet n = take({LIT});"), + ("struct field", "struct S { f: u8 }\nlet s = S { f: {LIT} };"), + ("return", "fn make() -> u8 { return {LIT}; }"), + ]; + // Nested: a literal inside a literal is fresh too. The rule compared + // types one level down and stopped there, so `[[1]]` for a + // `List>` and `[5]` for a `List` were refused — both + // true of a *variable* and neither of a literal. + for source in [ + "let xs: List> = [[1]];", + "let xs: List>> = [[[1]]];", + "let xs: List = [5];", + "let m: Map = {\"k\": 5};", + "let m: Map> = {\"k\": [1]};", + "struct S { f: List> }\nlet s = S { f: [[1]] };", + ] { + check_program(source).unwrap_or_else(|e| panic!("{source}: {e}")); + } + for source in [ + // A named container among the elements is still an alias. + "let a: List = [1];\nlet xs: List> = [a];", + "let xs: List = [300];", + "let xs: List> = [[1.5]];", + "let m: Map = {\"k\": 300};", + "let m: Map = {\"k\": 5};", + ] { + check_program(source).expect_err(source); + } + + // A store is a position a value is written at, so the same two rules + // reach a reassignment and a field write. + for source in [ + "let xs: List = [];\nxs = [1];", + "let x: u8 = 1;\nx = 5;", + "struct S { f: List }\nlet s = S { f: [] };\ns.f = [1];", + "struct S { f: u8 }\nlet s = S { f: 1 };\ns.f = 5;", + ] { + check_program(source).unwrap_or_else(|e| panic!("{source}: {e}")); + } + for source in [ + "let xs: List = [];\nlet ys: List = [1];\nxs = ys;", + "struct S { f: List }\nlet s = S { f: [] };\nlet ys: List = [1];\ns.f = ys;", + "struct S { f: u8 }\nlet s = S { f: 1 };\ns.f = 300;", + "struct S { f: List }\nlet s = S { f: [] };\ns.f = [1.5];", + ] { + check_program(source).expect_err(source); + } + + for (what, shape) in machine_int { + check_program(&shape.replace("{LIT}", "5")).unwrap_or_else(|e| panic!("{what}: {e}")); + // Having a range is the point of a fixed width, so out of range is + // a refusal in every position too. + check_program(&shape.replace("{LIT}", "300")).expect_err(what); + } + } + + #[test] + fn a_list_satisfies_a_tuple_annotation_of_the_same_element_types() { + check_program("let t: Tuple = [1, 2];").expect("a two-Int list is a Tuple"); + check_program("fn f() -> Tuple { return [1, 2]; }").expect("and so is a returned one"); + check_program("let t: Tuple = [1, \"a\"];").expect("the heterogeneous case still works"); + check_program("let xs: List = [1, 2];\nlet t: Tuple = xs;") + .expect("through a variable too — the type is what is checked, not the literal"); + check_program("fn f(t: Tuple) -> Int { return t[0]; }\nreturn f([1, 2]);") + .expect("Tuple -> List still holds"); + + check_program("let t: Tuple = [\"a\", \"b\"];").expect_err("element types are still checked"); + } + + /// A constant condition does not delete the branch the checker has not + /// seen. + /// + /// `fold_constants` runs in the parser, so anything it drops is dropped + /// before name resolution and type checking ever run. `if false { + /// undefined_fn() } else { 1 }`, `false && undefined_fn()` and `1 ?? + /// undefined_fn()` all passed `lk check` because the call was gone by the + /// time anyone looked — the probe here uses a *type* error rather than an + /// undefined name so it lands in this file's checker rather than in + /// resolution. `-true` was worse than unchecked: it folded to `false` and + /// printed it, while `-b` on a `Bool` variable is rejected. + #[test] + fn a_constant_condition_does_not_hide_the_branch_it_does_not_take() { + check_program("let x = if false { -true } else { 1 };").expect_err("the untaken arm is still code"); + check_program("let x = if true { 1 } else { -true };").expect_err("and so is the other one"); + check_program("let x = false && -true;").expect_err("`&&` does not short-circuit the checker"); + check_program("let x = true || -true;").expect_err("nor does `||`"); + check_program("let x = 1 ?? -true;").expect_err("nor does `??`"); + check_program("let x = -true;").expect_err("negating a Bool is a type error, constant or not"); + + // The folds that discard nothing but a literal still happen, and the + // ordinary shapes still check. + check_program("let x: Bool = false && true;").expect("both operands literal"); + check_program("let x: String = nil ?? \"a\";").expect("`nil ?? e` discards only the nil"); + check_program("let x: Int = -3;").expect("negating a literal"); + check_program("let x = if false { 1 } else { 2 };").expect("both arms literal"); + } + + /// Both arms of a constant `if` type, so the diagnostic names the union — + /// the same thing a function body reports. + /// + /// It used to name whichever arm survived folding, which meant the + /// top-level `let` and the identical `let` inside a function disagreed + /// about what the expression's type even was. + #[test] + fn a_constant_conditional_reports_the_union_of_both_arms() { + let message = check_program("let x: Int = if false { 9.5 } else { \"x\" };") + .expect_err("Int accepts neither arm") + .to_string(); + assert!( + message.contains("Float | String"), + "expected the union of both arms, got: {message}" + ); + } + + /// A declared return type is a promise about every path. + /// + /// `fn g(c: Bool) -> Int { if c { return 1; } }` answered `nil` when `c` was + /// false, and `lk check` said nothing: the failure surfaced at the caller as + /// "Add expected numbers or strings, got Nil and Int", naming the operator + /// rather than the function that promised an `Int`. + /// + /// Only annotations that exclude nil are held to it — `Nil`, `Any` and `T?` + /// all admit the fall-through value, and an unannotated function's return + /// type is inferred from what it returns, so there is no promise to break. + #[test] + fn a_declared_return_type_is_a_promise_about_every_path() { + check_program("fn f(c: Bool) -> Int { if c { return 1; } }").expect_err("the false path falls through"); + check_program("fn f() -> Int { }").expect_err("so does an empty body"); + check_program("fn f() -> String { let x = 1; }").expect_err("and one that only computes"); + check_program("fn f(c: Bool) -> Int { if c { return 1; } else { let x = 2; } }") + .expect_err("an else that does not return is still a path"); + + // Every shape that does leave on every path. + check_program("fn f(c: Bool) -> Int { if c { return 1; } else { return 2; } }").expect("both arms return"); + check_program("fn f(c: Bool) -> Int { if c { return 1; } return 2; }").expect("a trailing return"); + check_program("fn f() -> Int { while true { return 1; } }").expect("a loop with no way out"); + check_program("fn f(x: Int) -> Int { match x { 1 => { return 1; }, _ => { return 2; } } }") + .expect("a match with a catch-all, every arm returning"); + check_program("fn f() -> Int { error(\"no\"); }").expect("a raise leaves too"); + check_program("fn f() -> Int { panic(\"x\"); }").expect("and so does a panic"); + check_program("fn f(c: Bool) -> Int { if c { return 1; } panic(\"x\"); }").expect("mixed"); + + // And the annotations that admit the fall-through value. + check_program("fn f() -> Nil { }").expect("Nil is what falling through answers"); + check_program("fn f() -> Int? { }").expect("an optional says it may answer nothing"); + check_program("fn f() -> Any { }").expect("Any admits it"); + check_program("fn f(c: Bool) { if c { return 1; } }").expect("no annotation, no promise"); + } + /// `m + n` merges, and the checker had to be told. + /// + /// Both executors have implemented map merge all along — the VM's `Add` + /// has a map arm and so does `lkrt_dyn_add` — and only the checker refused, + /// so `a + b` ran when the types were erased to `Any` and was "the left + /// operand must be numeric types" when they were not. `lk check` answers + /// the executors' question; a rule it enforces that neither executor has is + /// the same defect as a rule it misses. + #[test] + fn two_maps_merge_and_the_answer_widens_to_hold_both() { + check_program("let a = {\"a\": 1};\nlet b = {\"b\": 2};\nlet c = a + b;\nprintln(c);\n") + .expect("two maps merge"); + assert_eq!( + infer("{\"a\": 1} + {\"b\": 2}"), + Type::Map(Box::new(Type::String), Box::new(Type::Int)), + "two `Map` merge into one" + ); + assert_eq!( + infer("{\"a\": 1} + {\"b\": \"s\"}"), + Type::Map(Box::new(Type::String), Box::new(Type::Any)), + "values that subsume neither widen to Any" + ); + + // A non-map on either side is still an error, and says what it expected + // rather than borrowing the numeric operator's message. + let err = check_program("let a = {\"a\": 1};\nlet c = a + 1;\nprintln(c);\n") + .expect_err("a map plus a number is an error"); + assert!( + format!("{err}").contains("map merge requires both operands to be maps"), + "unexpected message: {err}" + ); + } + /// A list operand absorbs the other one, and the checker had to be told. + /// + /// The same defect as map merge, one operator over: both executors put the + /// other operand into the list — `"p=" + [1, 2]` is `["p=", 1, 2]`, which + /// `lkrt_dyn_add` states as the rule — and only the checker refused, and + /// only when it could see the types. + /// + /// The heterogeneous literal is the case that made it worse than a + /// refusal. `[1, "a"]` infers to a `Tuple`, which is a list everywhere else + /// but was not routed to this rule, so `"" + [1, "a"]` fell through to the + /// string path and was typed `String` — a wrong type, which propagates, + /// where a refusal would only have stopped. + #[test] + fn a_list_operand_absorbs_the_other_one() { + check_program("println(\"p=\" + [1, 2]);\n").expect("a string joins a list"); + check_program("println([1, 2] + \"x\");\n").expect("…from the other side too"); + check_program("println(1 + [2, 3]);\n").expect("so does a number"); + check_program("println(nil + [1]);\n").expect("and nil"); + check_program("println([1] + {\"k\": 2});\n").expect("and a map"); + + assert_eq!( + infer("[1, 2] + 3"), + Type::List(Box::new(Type::Int)), + "an Int joining a `List` keeps the element type" + ); + assert_eq!( + infer("[1, 2] + \"x\""), + Type::List(Box::new(Type::Any)), + "an element type that subsumes neither widens to Any" + ); + assert_eq!( + infer("\"\" + [1, \"a\"]"), + Type::List(Box::new(Type::Any)), + "a heterogeneous literal is a list here, not a string" + ); + } + /// A concatenation's element type is the *wider* side. + /// + /// `wider_of`'s doc has said all along that it is "the rule + /// `check_list_addition` uses", and `check_list_addition` spelled out the + /// opposite tie-break: it picked whichever side was assignable *to* the + /// other, which is the narrower one. `Int` is assignable to `Float`, so a + /// list holding `1.5` was typed `List` and the annotation was + /// accepted: + /// + /// let v: List = [1] + [1.5]; // accepted + /// let n: Int = v[1]; // accepted + /// typeof(v[1]) // Float + /// + /// The map merge, which does call `wider_of`, answered `Map` + /// for the same pair — one rule, two answers, and the doc pointing at the + /// wrong one. + /// + /// `wider_of` had its own hole under it: `is_assignable(Any, Int)` is true, + /// because an `Any` may be passed where an `Int` is expected, so asking it + /// the subsumption question answered "`Int` is wider". `Any` is answered + /// before asking now. + #[test] + fn a_concatenation_takes_the_wider_element_type() { + assert_eq!( + infer("[1] + [1.5]"), + Type::List(Box::new(Type::Float)), + "an Int list joined to a Float list holds Floats" + ); + assert_eq!( + infer("[1] + [1]"), + Type::List(Box::new(Type::Int)), + "…and one type on both sides is unchanged" + ); + assert_eq!( + infer("[\"a\"] + [1]"), + Type::List(Box::new(Type::Any)), + "neither subsumes the other" + ); + check_program("let v: List = [1] + [1.5];\nprintln(v);\n") + .expect_err("a list holding 1.5 is not a List"); + + // The same rule, and the same `Any`, through the map merge. + assert_eq!( + infer("{\"a\": 1} + {\"b\": 1.5}"), + Type::Map(Box::new(Type::String), Box::new(Type::Float)), + "the merge already took the wider side" + ); + check_program( + "fn f(m: Map) -> Int {\n let v: Map = m + {\"a\": 1};\n println(v);\n return 0;\n}\nf({});\n", + ) + .expect_err("merging into an erased map does not narrow it"); + } + /// A map beside a string is a concatenation, not a failed merge. + /// + /// `"v=" + {"k": 1}` was "map merge requires both operands to be maps" — + /// a message naming an operation the program had not written — while both + /// executors render the map, the way `"${m}"` does. The other containers + /// were never refused here, so the checker rejected one of the five kinds + /// that behave identically. + /// + /// A non-string, non-map operand is still an error, and the runtime agrees: + /// `1 + {"k": 1}` raises there. + #[test] + fn a_map_beside_a_string_concatenates() { + assert_eq!(infer("\"v=\" + {\"a\": 1}"), Type::String, "the map renders"); + assert_eq!(infer("{\"a\": 1} + \"v=\""), Type::String, "from either side"); + check_program("let m = {\"a\": 1};\nprintln(\"v=\" + m);\n").expect("a map joins a string"); + check_program("let m = {\"a\": 1};\nlet c = 1 + m;\nprintln(c);\n") + .expect_err("a number and a map is neither a merge nor a concatenation"); + } + /// `-` removes one value as well as a whole container. + /// + /// The VM has four subtraction arms — list minus list, list minus value, + /// map minus map, map minus key — and the checker accepted two. So + /// `[1, 2, 1] - 1` and `{"a": 1} - "a"` were "requires both operands to be + /// lists"/"maps", naming a rule neither executor has. + /// + /// The map side is narrower than the list side and has to be: a map's + /// members are keyed by nil, Bool, Int and String, and anything else raises + /// when the key is built. + #[test] + fn removal_takes_a_single_value_too() { + assert_eq!( + infer("[1, 2, 1] - 1"), + Type::List(Box::new(Type::Int)), + "removal never introduces an element, so the type is unchanged" + ); + assert_eq!( + infer("[1, \"a\"] - 1"), + Type::List(Box::new(Type::Any)), + "a heterogeneous literal is a list here too" + ); + assert_eq!( + infer("{\"a\": 1} - \"a\""), + Type::Map(Box::new(Type::String), Box::new(Type::Int)), + "…and the map keeps both of its types" + ); + // Removing something that cannot be a key removes nothing, the way + // `m.delete(k)` does — removal looks a key up rather than building one. + check_program("let m = {\"a\": 1};\nlet c = m - 1.5;\nprintln(c);\n") + .expect("a Float is not a key the map holds"); + check_program("let m = {\"a\": 1};\nlet c = m - [1];\nprintln(c);\n") + .expect("neither is a list — and the map on the left decides, not the list on the right"); + check_program("let c = 1 - [1];\nprintln(c);\n").expect_err("a number minus a list is not a removal"); + } + /// The predicates take any value; the reductions need the right elements. + /// + /// Two halves of one audit. `xs.contains(v)`, `index_of`, `count`, + /// `m.has(k)`, `m.delete(k)`, `s.contains(v)` and `s.delete(v)` were + /// declared to take the container's own element or key type, while their + /// *operator* spellings — `v in xs`, `k in m`, `m - k` — take anything and + /// answer "absent". One question, two rules, chosen by which spelling the + /// program used. + /// + /// The other way for `sum` and `to_bytes`: they need something of the + /// element and say so when they run, so a `List` can never answer + /// either. That is the `Set + Set` case — the runtime raises for every + /// value of that type, and the checker is right to say so first. + #[test] + fn predicates_take_any_value_and_reductions_do_not() { + for source in [ + "println([\"a\", \"b\"].contains(1));", + "println([\"a\", \"b\"].index_of(1));", + "println([\"a\", \"b\"].count(1));", + "println([1, 2].contains(1.5));", + "println(\"abc\".contains(1));", + "println(\"ab\".bytes().contains(\"a\"));", + "println({1: 2}.has(\"k\"));", + "println({1: 2}.delete(\"k\"));", + "println(Set([1]).contains(\"a\"));", + "println(Set([1]).delete(\"a\"));", + ] { + check_program(source).unwrap_or_else(|e| panic!("a predicate takes any value: {source}: {e}")); + } + + // Inserting is the other side of the line and still refuses: a + // `List` that accepted an Int would make its own type a lie. + check_program("let xs: List = [\"a\"];\nxs.push(1);\n") + .expect_err("pushing an Int into a List"); + + for source in [ + "println([\"a\"].sum());", + "println([1, \"a\"].sum());", + "println([1.5].to_bytes());", + "println([1, 2.5].to_bytes());", + ] { + assert!(check_program(source).is_err(), "must be refused: {source}"); + } + check_program("println([1, 2.5].sum());").expect("numbers add"); + check_program("println([1, 2].to_bytes());").expect("Ints are bytes"); + } + /// Reading a key of another type is a miss; writing one is still refused. + /// + /// `{"k": 1}[0]` was "Cannot unify String with Int" — the checker's own + /// machinery talking, for a lookup the interpreter answers with nil. The + /// constraint that produced it is still added wherever it does real + /// inference; it is skipped only when both types are concrete and unrelated, + /// which is when the lookup can only miss. + /// + /// A write is the other side of the line: `m[0] = 9` would put a key in the + /// map that its own type says is not there. + #[test] + fn reading_a_key_of_another_type_is_a_miss() { + check_program("let m = {\"k\": 1};\nprintln(m[0]);\n").expect("an Int key misses a string-keyed map"); + check_program("let m = {1: 2};\nprintln(m[\"k\"]);\n").expect("and the other way"); + check_program("let m = {\"k\": 1};\nm[0] = 9;\nprintln(m);\n").expect_err("writing one is refused"); + + // A `Tuple` slices like the list it is — every other container arm + // carries this guard. + check_program("println([1, \"a\"][0..2]);\n").expect("a heterogeneous literal slices"); + assert_eq!( + infer("[1, \"a\"][0..2]"), + Type::List(Box::new(Type::Union(vec![Type::Int, Type::String]))), + "the slice keeps what the tuple held" + ); + } + /// A tree at the bound is walked; past it is refused rather than aborting. + /// + /// The parser bounds its own recursion, and a chain is not recursion: it is + /// a loop that builds a tree as deep as the chain is long. So + /// `a + a + a…` and `x.f().f()…` parsed clean at any length and then + /// overflowed the stack in a later walk — `SIGABRT`, not a diagnostic, on + /// input the parser had accepted. The interpreter, the LSP and the browser + /// playground all read text they did not write. + /// + /// The accepting half is `one_expression_reuses_its_scratch_registers`, + /// which sums 300 terms through the real binary — that is what sets the + /// floor under [`MAX_TREE_DEPTH`]. This is the other end: past the bound is + /// an error message, at eight times the bound as much as at the bound, and + /// on a thread the size the front end runs on. + #[test] + fn a_tree_at_the_bound_is_walked_and_past_it_refused() { + std::thread::Builder::new() + .stack_size(8 * 1024 * 1024) + .spawn(|| { + // `check_program` panics on a parse error rather than + // returning it, so a refusal has to be read from the parser. + let parse = |src: &str| crate::syntax::parse_program_source(src, Default::default()).map(|_| ()); + let bound = crate::ast::parser::MAX_TREE_DEPTH; + + // Inside the bound is walked, not refused — the whole front + // end, on the stack it runs on. + let terms = "a".to_string() + &" + a".repeat(bound / 4); + check_program(&format!("let a = 1;\nlet v = {terms};\nprintln(v);\n")) + .expect("a sum well inside the bound is checked"); + let chain = ".chain([1])".repeat(bound / 8); + check_program(&format!("let xs = [1]{chain};\nprintln(xs.len());\n")) + .expect("a chain well inside the bound is checked"); + + for over in [bound + 8, bound * 8] { + // A sum, which every precedence level builds the same way. + let terms = "a".to_string() + &" + a".repeat(over); + let error = parse(&format!("let a = 1;\nlet v = {terms};\nprintln(v);\n")) + .expect_err("past the bound is refused, not aborted") + .to_string(); + assert!(error.contains("nesting too deep"), "{over} terms: {error}"); + + // A postfix chain, which is the other loop — and the one + // that took a 1700-link program down. + let chain = ".chain([1])".repeat(over); + let error = parse(&format!("let xs = [1]{chain};\nprintln(xs.len());\n")) + .expect_err("past the bound is refused, not aborted") + .to_string(); + assert!(error.contains("nesting too deep"), "{over} links: {error}"); + } + }) + .expect("spawn") + .join() + .expect("the front end must refuse rather than overflow"); + } + + /// A string joined to an erased operand is not promised a `String`. + /// + /// A list operand wins over a string one — `"a" + [1, 2]` is + /// `["a", 1, 2]` — so an operand that *might* be a list makes the answer + /// one too. This promised `String` regardless, and the promise was a lie + /// the native build then acted on: it unboxed the result as a string and + /// raised where the interpreter answered a list. + /// + /// A type *variable* is not erased in the same way: `coerce_to_string` + /// binds it, so by the time the answer is given the operand really is a + /// string. `Any` cannot be bound, and that is the whole difference. + #[test] + fn a_string_joined_to_an_erased_operand_is_not_a_string() { + assert_eq!(infer("\"a\" + \"b\""), Type::String, "two strings"); + assert_eq!(infer("\"a\" + 1"), Type::String, "a number renders"); + assert_eq!( + infer("x + \"!\""), + Type::String, + "a type variable is bound by the coercion" + ); + + check_program("fn f(v: Any) -> Any { return \"a\" + v; }\nprintln(f([1, 2]));\n") + .expect("an erased operand joins a string"); + // A list operand still wins outright, erased or not. + assert_eq!( + infer("\"a\" + [1, 2]"), + Type::List(Box::new(Type::Any)), + "a list operand wins and the answer is a list" + ); + } + /// Every position that binds a name refuses to bind one twice. + /// + /// A construct that binds one name twice can never read the first + /// binding, so `fn f(a: Int, a: Int)` ignores the argument passed for its + /// first parameter and `[a, a]` matches *any* two elements rather than two + /// equal ones — the reading somebody arrives with from a language whose + /// patterns are non-linear. All seven positions accepted it and let the + /// later binding win in silence. + /// + /// The negative half is asserted with it, because the rule is per binder, + /// not per scope: re-binding a name in a *later* statement is ordinary, + /// and an `Or` pattern binds the same name in each of its alternatives by + /// design. + #[test] + fn no_binder_declares_one_name_twice() { + for src in [ + "fn f(a: Int, a: Int) -> Int { return a; }\nprintln(f(1, 2));\n", + "struct P { x: Int, x: Int }\n", + "let f = |a: Int, a: Int| a;\nprintln(f(1, 2));\n", + "let [a, a] = [1, 2];\nprintln(a);\n", + "let t = [1, 2];\nmatch t { [a, a] => { println(a); } _ => { println(0); } }\n", + "let ps = [[1, 2]];\nfor [a, a] in ps { println(a); }\n", + "struct Q { x: Int }\nimpl Q { fn g(self, a: Int, a: Int) -> Int { return a; } }\n", + "let m = {\"k\": 1};\nif let {\"k\": v, \"j\": v} = m { println(v); }\n", + ] { + assert!(check_program(src).is_err(), "should be refused: {src}"); + } + + for src in [ + "let a = 1;\nlet a = 2;\nprintln(a);\n", + "fn f(a: Int, b: Int) -> Int { return a + b; }\nprintln(f(1, 2));\n", + "let t = 1;\nmatch t { 1 | 2 => { println(t); } _ => { println(0); } }\n", + "let ps = [[1, 2]];\nfor [a, b] in ps { println(a + b); }\n", + ] { + check_program(src).unwrap_or_else(|e| panic!("should be accepted: {src}: {e}")); + } + } + /// An `impl` method's signature has to stand in for the trait's, and + /// `lk check` is where that is said. + /// + /// The rule existed and ran only when the VM registered the impl, so a + /// method with the wrong arity, the wrong parameter type or the wrong + /// return type passed the pre-flight command and failed the moment the + /// program ran. #99 moved the *presence* half here and recorded that + /// presence was "the whole question" — it was not. + #[test] + fn an_impl_method_must_match_the_trait_signature() { + for src in [ + // Arity. + "trait T { fn m(self, a: Int) -> Int; }\nstruct S { x: Int }\nimpl T for S { fn m(self) -> Int { return 1; } }\n", + // Parameter type. + "trait T { fn m(self, a: Int) -> Int; }\nstruct S { x: Int }\nimpl T for S { fn m(self, a: String) -> Int { return 1; } }\n", + // Return type. + "trait T { fn m(self) -> Int; }\nstruct S { x: Int }\nimpl T for S { fn m(self) -> String { return \"s\"; } }\n", + ] { + assert!(check_program(src).is_err(), "should be refused: {src}"); + } + + check_program( + "trait T { fn m(self, a: Int) -> Int; }\nstruct S { x: Int }\nimpl T for S { fn m(self, a: Int) -> Int { return a; } }\nprintln(S { x: 1 }.m(2));\n", + ) + .expect("a matching signature is accepted"); + } + /// A positional parameter passed by name says so. + /// + /// `f(a: 1, b: 2)` is what somebody arrives with from Python, Swift or + /// Kotlin. LK has named parameters — declared in a trailing `{ … }` block + /// — and positional ones are positional, so the call is an error; it was + /// reported as "expects 2 positional args, got 0", which is true and no + /// help at all. + #[test] + fn a_positional_parameter_passed_by_name_is_named_as_such() { + let error = check_program("fn f(a: Int, b: Int) -> Int { return a - b; }\nprintln(f(b: 1, a: 5));\n") + .expect_err("positional parameters are not passed by name") + .to_string(); + assert!(error.contains("no named parameter"), "{error}"); + assert!(error.contains("trailing"), "{error}"); + + // The count message stays for the shape it was written for, and a real + // named parameter still works. + let error = check_program("fn f(a: Int, b: Int) -> Int { return a - b; }\nprintln(f(1));\n") + .expect_err("one argument is not two") + .to_string(); + assert!(!error.contains("no named parameter"), "{error}"); + check_program("fn g(a: Int, { b: Int? = 1 }) -> Int { return a; }\nprintln(g(1, b: 2));\n") + .expect("a declared named parameter is accepted"); + } + /// The three declaration-level rules a `trait` was missing. + /// + /// `fn` and `struct` are both refused when declared twice, and an impl's + /// *target type* is checked for existence — the trait half of the same + /// line was not, because it sat behind a `let Some(…)` that skipped the + /// whole conformance check when the lookup missed. All three failed only + /// at run time, or (the repeated method) not at all. + #[test] + fn a_trait_declaration_is_checked_like_the_others() { + for src in [ + // The trait an impl names has to exist. + "struct S { x: Int }\nimpl Nope for S { fn m(self) -> Int { return 1; } }\n", + // Declared twice: the second would replace the first, and every + // impl written against it. + "trait T { fn m(self) -> Int; }\ntrait T { fn n(self) -> Int; }\n", + // One method declared twice inside it. + "trait T { fn m(self) -> Int; fn m(self) -> Int; }\n", + ] { + assert!(check_program(src).is_err(), "should be refused: {src}"); + } + + check_program( + "trait T { fn m(self) -> Int; }\nstruct S { x: Int }\nimpl T for S { fn m(self) -> Int { return 1; } }\nprintln(S { x: 1 }.m());\n", + ) + .expect("an ordinary trait and impl are accepted"); + } } diff --git a/core/src/typ/type_system.rs b/core/src/typ/type_system.rs index 0fd2f6b8..fe5402c2 100644 --- a/core/src/typ/type_system.rs +++ b/core/src/typ/type_system.rs @@ -50,6 +50,35 @@ pub struct TypeRegistry { /// Struct definitions structs: HashMap, + /// Names among `structs` that came from **another module**, not from the + /// program being checked. + /// + /// The two are registered into one table on purpose — an imported type's + /// fields have to be known to check `m.P { x: 1 }` — but a *bare* `P { … }` + /// is a different question: it names a type declared here, and building one + /// for a name that is only imported produced a value that renders `P{x:4}` + /// and reports `typeof` `P` while carrying none of `P`'s methods (the + /// runtime stamps the *constructing* module's `TypeScope`, and the method + /// table is keyed by the declaring one). So the table says which is which. + imported_structs: crate::compat::collections::HashSet, + + /// Imported structs a bare `P { … }` may still build: the ones brought in + /// **by name** (`use { P } from "m"`), keyed by the name they are bound + /// under and holding the name the declaring module gave them. + /// + /// That import binds `P` to the constructor the declaring module generates + /// beside the type (`stmt::struct_ctors`), so the literal has something to + /// call and the object is built by `m` — identity, field order and dispatch + /// all right. A type merely *visible* through a namespace import + /// (`use "m"`) binds no such name, so there the literal has to be written + /// `m.P { … }`. + /// + /// The two names differ under `use { P as Q } from "m"`: the schema, the + /// methods and the value's own `typeof` are all `P`'s, and only the + /// spelling at the construction site is `Q` — so a literal written `Q` is + /// checked, and answers, as a `P`. + constructible_imports: HashMap, + /// Trait definitions traits: HashMap, @@ -60,6 +89,73 @@ pub struct TypeRegistry { type_var_counter: u32, } +/// Whether an `impl`'s method signature satisfies the trait's declaration. +/// +/// One rule, two callers: [`TypeRegistry::validate_trait_impl`], which the VM +/// runs when it registers an impl, and the `impl` statement's own check, which +/// `lk check` runs. The *presence* half was moved to the checker on its own +/// and left this behind — so a method that took the wrong number of arguments, +/// or returned the wrong type, passed `lk check` and failed the moment the +/// program ran, with the pre-flight command saying nothing. +/// +/// Parameters are contravariant and the return type covariant, which is the +/// ordinary rule for a signature that has to stand in for another. +pub fn trait_method_conformance(method_name: &str, trait_name: &str, expected: &Type, actual: &Type) -> Result<()> { + let ( + Type::Function { + params: exp_params, + named_params: exp_named, + return_type: exp_ret, + }, + Type::Function { + params: act_params, + named_params: act_named, + return_type: act_ret, + }, + ) = (expected, actual) + else { + return Ok(()); + }; + if exp_params.len() != act_params.len() { + return Err(anyhow!( + "Method '{}' arity mismatch for trait '{}': expected {}, got {}", + method_name, + trait_name, + exp_params.len(), + act_params.len() + )); + } + if exp_named.len() != act_named.len() { + return Err(anyhow!( + "Method '{}' named parameter count mismatch for trait '{}': expected {}, got {}", + method_name, + trait_name, + exp_named.len(), + act_named.len() + )); + } + let params_ok = exp_params + .iter() + .zip(act_params.iter()) + .all(|(e, a)| a.is_assignable_to(e)); + let named_ok = exp_named.iter().all(|exp_np| { + act_named + .iter() + .find(|act_np| act_np.name == exp_np.name) + .map(|act_np| act_np.has_default == exp_np.has_default && act_np.ty.is_assignable_to(&exp_np.ty)) + .unwrap_or(false) + }); + let ret_ok = act_ret.is_assignable_to(exp_ret); + if !params_ok || !named_ok || !ret_ok { + return Err(anyhow!( + "Method '{}' signature mismatch for trait '{}'", + method_name, + trait_name + )); + } + Ok(()) +} + impl TypeRegistry { pub fn new() -> Self { Self::default() @@ -75,17 +171,54 @@ impl TypeRegistry { self.type_aliases.get(name) } - /// Register a struct definition + /// Register a struct the program being checked declares. pub fn register_struct(&mut self, s: StructDef) { + // A local declaration wins over an imported name of the same spelling: + // imports are seeded first, and this is what un-marks the entry. Both + // tables, and for the same reason — under an alias the constructible + // entry is keyed by the *bound* name, so `use { P as Q }` beside a + // local `struct Q` left `Q { … }` checked against `P`'s schema while + // the compiler (which prefers the local `Q$new`) built the local one. + self.imported_structs.remove(&s.name); + self.constructible_imports.remove(&s.name); + self.structs.insert(s.name.clone(), s); + } + + /// Register a struct **another module** declares. + pub fn register_imported_struct(&mut self, s: StructDef) { + self.imported_structs.insert(s.name.clone()); self.structs.insert(s.name.clone(), s); } + /// Whether `name` is known only because another module declares it. + pub fn is_imported_struct(&self, name: &str) -> bool { + self.imported_structs.contains(name) + } + + /// Marks an imported struct as brought in by name, so a bare literal builds + /// it through the declaring module's constructor. `bound` is the name this + /// file writes; `declared` is the name its module gave it. + pub fn mark_constructible_import(&mut self, bound: &str, declared: &str) { + self.constructible_imports + .insert(bound.to_string(), declared.to_string()); + } + + /// The declaring module's name for a type a bare `bound { … }` may build. + pub fn constructible_import_target(&self, bound: &str) -> Option<&str> { + self.constructible_imports.get(bound).map(String::as_str) + } + /// Get struct definition by name pub fn get_struct(&self, name: &str) -> Option<&StructDef> { self.structs.get(name) } /// Register a trait definition + /// A declared trait, by name. + pub fn get_trait(&self, name: &str) -> Option<&TraitDef> { + self.traits.get(name) + } + pub fn register_trait(&mut self, trait_def: TraitDef) { self.traits.insert(trait_def.name.clone(), trait_def); } @@ -110,6 +243,19 @@ impl TypeRegistry { } } + /// Every type name this program declares — structs, traits, aliases. + /// + /// Used by the unknown-type diagnostic to suggest a near miss, so a typo in + /// a *user's* type name is caught the same way one in a builtin's is. + pub fn declared_type_names(&self) -> Vec { + self.type_aliases + .keys() + .chain(self.structs.keys()) + .chain(self.traits.keys()) + .cloned() + .collect() + } + /// Resolve a named type to its concrete type pub fn resolve_type(&self, name: &str) -> Option { // Check if it's a type alias @@ -141,12 +287,39 @@ impl TypeRegistry { /// Check if a type implements a trait pub fn implements_trait(&self, typ: &Type, trait_name: &str) -> bool { - let type_name = Self::type_to_string(typ); - if let Some(impls) = self.implementations.get(&type_name) { - impls.iter().any(|impl_def| impl_def.trait_name == trait_name) - } else { - false + self.impls_for(typ) + .is_some_and(|impls| impls.iter().any(|impl_def| impl_def.trait_name == trait_name)) + } + + /// The impls registered against `typ`, by its own name and then by its type + /// *constructor*. + /// + /// A container's registered name is written out — `impl List` is stored as + /// `List` — so `List` finds nothing by its own name. The base is + /// the right key rather than a guess, because the language refuses an impl + /// that names an element type ("`List` is not distinguishable from + /// another element type at run time — write `List`"), so one constructor + /// has one entry and every list is that entry's type. + /// + /// Without the second lookup `impl Describe for List` compiled and then + /// `fn tell(v: Describe)` refused every list, so the impl could not be + /// called. Scalars were unaffected — `Int` is its own name — which is why + /// only the containers were broken. + fn impls_for(&self, typ: &Type) -> Option<&Vec> { + let name = Self::type_to_string(typ); + if let Some(found) = self.implementations.get(&name) { + return Some(found); } + // Neither side need agree with the other on the argument: the lookup is + // `List` and the registration is `List`, and it also runs the + // other way — `stream.range(…)` is declared to return a bare `Stream` + // while `impl D for Stream` registers as `Stream`. The + // *constructor* is what matches, in either direction. + let base = name.split('<').next()?; + self.implementations + .iter() + .find(|(key, _)| key.split('<').next() == Some(base) && key.as_str() != name) + .map(|(_, impls)| impls) } /// Get the method implementation for a type and method name @@ -155,8 +328,7 @@ impl TypeRegistry { /// against the module that compiled it; the runtime dispatch table /// (`VmContext::methods`) is what carries that module alongside it. pub fn get_method(&self, typ: &Type, method_name: &str) -> Option { - let type_name = Self::type_to_string(typ); - let impls = self.implementations.get(&type_name)?; + let impls = self.impls_for(typ)?; impls .iter() .find_map(|impl_def| impl_def.methods.get(method_name).map(|(function, _sig)| *function)) @@ -173,6 +345,7 @@ impl TypeRegistry { fn type_to_string(typ: &Type) -> String { match typ { Type::Named(name) => name.clone(), + Type::Unknown => "_".to_string(), Type::Int => "Int".to_string(), Type::MachineInt(kind) => kind.name().to_string(), Type::Ptr { pointee, mutable } => { @@ -255,58 +428,9 @@ impl TypeRegistry { } // If expected is a function, check arity - if let Type::Function { - params: exp_params, - named_params: exp_named, - return_type: exp_ret, - } = expected_ty - { - if let Type::Function { - params: act_params, - named_params: act_named, - return_type: act_ret, - } = &actual_ty - { - if exp_params.len() != act_params.len() { - return Err(anyhow!( - "Method '{}' arity mismatch for trait '{}': expected {}, got {}", - method_name, - impl_def.trait_name, - exp_params.len(), - act_params.len() - )); - } - if exp_named.len() != act_named.len() { - return Err(anyhow!( - "Method '{}' named parameter count mismatch for trait '{}': expected {}, got {}", - method_name, - impl_def.trait_name, - exp_named.len(), - act_named.len() - )); - } - // When signatures are concrete, ensure contravariant params and covariant return - let params_ok = exp_params - .iter() - .zip(act_params.iter()) - .all(|(e, a)| a.is_assignable_to(e)); - let named_ok = exp_named.iter().all(|exp_np| { - act_named - .iter() - .find(|act_np| act_np.name == exp_np.name) - .map(|act_np| { - act_np.has_default == exp_np.has_default && act_np.ty.is_assignable_to(&exp_np.ty) - }) - .unwrap_or(false) - }); - let ret_ok = act_ret.is_assignable_to(exp_ret); - if !params_ok || !named_ok || !ret_ok { - return Err(anyhow!( - "Method '{}' signature mismatch for trait '{}'", - method_name, - impl_def.trait_name - )); - } + if let Type::Function { .. } = expected_ty { + if let Type::Function { .. } = &actual_ty { + trait_method_conformance(method_name, &impl_def.trait_name, expected_ty, &actual_ty)?; } else { // Should not happen given construction above return Err(anyhow!( @@ -318,6 +442,25 @@ impl TypeRegistry { } } + // A method the trait never declared does not belong here. It used to be + // accepted, and it had to be: `impl Type { … }` was a syntax error and + // there is no UFCS, so a trait impl was the only place a method could + // live — programs declared an empty trait and hung everything off it. + // Now that a type can carry its own methods, an undeclared one in a + // *trait* impl is a mistake with an obvious fix, and saying so is what + // keeps the trait's method list meaning something. + for method_name in impl_def.methods.keys() { + if !trait_def.methods.iter().any(|(declared, _)| declared == method_name) { + return Err(anyhow!( + "Method '{}' is not declared by trait '{}' — put it in `impl {} {{ … }}`, \ + which is where a type's own methods go", + method_name, + impl_def.trait_name, + Self::type_to_string(&impl_def.target_type) + )); + } + } + Ok(()) } } @@ -331,33 +474,66 @@ pub struct TypeInferenceEngine { /// Constraints to be solved constraints: Vec<(Type, Type)>, - /// Registry for custom types - registry: TypeRegistry, + /// Next `T{n}` this engine hands out. + /// + /// It used to own a whole [`TypeRegistry`] for this counter — a *clone* + /// taken when the checker was built, so every declaration made afterwards + /// was invisible to unification. The two facts unification needs about + /// declarations (which names are traits, and which types implement them) + /// therefore could not be asked at all; they arrive as a parameter now, and + /// the copy is gone. + type_var_counter: u32, +} + +impl Default for TypeInferenceEngine { + fn default() -> Self { + Self::new() + } } impl TypeInferenceEngine { - pub fn new(registry: TypeRegistry) -> Self { + pub fn new() -> Self { Self { substitutions: HashMap::new(), constraints: Vec::new(), - registry, + type_var_counter: 0, } } /// Generate a fresh type variable pub fn fresh_type_var(&mut self) -> Type { - self.registry.fresh_type_var() + let var_name = format!("T{}", self.type_var_counter); + self.type_var_counter += 1; + Type::Variable(var_name) } /// Add a constraint that two types must be equal + /// Drop everything learned so far, keeping the variable counter. + /// + /// For a caller that checks a *sequence* of independent programs against one + /// checker — the REPL. Each input is its own program, and a name declared in + /// an earlier one is carried in `function_sigs` with the type variables it + /// was given then. Without clearing, a call in the second input binds those + /// variables for good: `fn f(x) { return x; }` then `f(1)` then `f("a")` + /// answered "Cannot unify Int with String", where the same three lines in a + /// file are fine, because there `f` is predeclared fresh for the one program + /// being checked. + /// + /// The counter is *not* reset, so a variable minted later never collides + /// with one still named in a carried signature. + pub fn forget_inferences(&mut self) { + self.substitutions.clear(); + self.constraints.clear(); + } + pub fn add_constraint(&mut self, t1: Type, t2: Type) { self.constraints.push((t1, t2)); } /// Solve all constraints using unification - pub fn solve_constraints(&mut self) -> Result> { + pub fn solve_constraints(&mut self, registry: &TypeRegistry) -> Result> { while let Some((t1, t2)) = self.constraints.pop() { - self.unify(t1, t2)?; + self.unify(t1, t2, registry)?; } Ok(self.substitutions.clone()) } @@ -398,7 +574,21 @@ impl TypeInferenceEngine { } } - fn unify(&mut self, t1: Type, t2: Type) -> Result<()> { + fn unify(&mut self, t1: Type, t2: Type, registry: &TypeRegistry) -> Result<()> { + // Before substitution, because substitution is what hides this case: a + // variable already bound to one type, now required to be another. + // + // `let l = []; l.push(1); l.push("a");` is the shape. The element type + // starts as a variable, the first push binds it to `Int`, and by the + // second push substitution has already turned the variable into `Int` + // — so what reaches the match below is `Int` against `String`, with no + // sign that a *variable* is what disagrees. LK's lists are + // heterogeneous, so the answer is not a conflict: the variable is both, + // and widening it to `Int | String` says so. + if let Some(()) = self.widen_rebound_variable(&t1, &t2) { + return Ok(()); + } + let t1 = Self::normalize_union(self.apply_substitution(&t1)); let t2 = Self::normalize_union(self.apply_substitution(&t2)); @@ -406,6 +596,13 @@ impl TypeInferenceEngine { // Same types unify (a, b) if a == b => Ok(()), + // `_` — the element type of a read-only container view — says + // nothing about what it stands for, so it constrains nothing. It + // reaches the unifier from a declared parameter (`List<_>`) meeting + // an argument (`List`), which is exactly the case it exists to + // accept. + (Type::Unknown, _) | (_, Type::Unknown) => Ok(()), + // `Any` is a weak gradual-typing constraint. It must not bind an // otherwise fresh type variable, because later concrete call-site // constraints should still be able to refine that variable. @@ -437,18 +634,29 @@ impl TypeInferenceEngine { } // Structural unification - (Type::List(a), Type::List(b)) => self.unify(*a, *b), - (Type::Set(a), Type::Set(b)) => self.unify(*a, *b), + (Type::List(a), Type::List(b)) => self.unify(*a, *b, registry), + // A tuple is a list whose element types are known one by one — + // there is no tuple at runtime, `HeapValue` has only `List`. This + // mirrors the rule in `is_assignable_to`; without it the two + // disagreed, and the disagreement was invisible only because the + // concrete-concrete rule below swallows whatever reaches it. + (Type::Tuple(elems), Type::List(elem)) | (Type::List(elem), Type::Tuple(elems)) => { + for tuple_elem in elems { + self.unify(tuple_elem, (*elem).clone(), registry)?; + } + Ok(()) + } + (Type::Set(a), Type::Set(b)) => self.unify(*a, *b, registry), (Type::Map(ak, av), Type::Map(bk, bv)) => { - self.unify(*ak, *bk)?; - self.unify(*av, *bv) + self.unify(*ak, *bk, registry)?; + self.unify(*av, *bv, registry) } (Type::Tuple(a), Type::Tuple(b)) => { if a.len() != b.len() { return Err(anyhow!("Tuple arity mismatch")); } for (x, y) in a.into_iter().zip(b) { - self.unify(x, y)?; + self.unify(x, y, registry)?; } Ok(()) } @@ -468,7 +676,7 @@ impl TypeInferenceEngine { return Err(anyhow!("Function arity mismatch")); } for (a_param, b_param) in a_params.into_iter().zip(b_params) { - self.unify(a_param, b_param)?; + self.unify(a_param, b_param, registry)?; } if a_named.len() != b_named.len() { return Err(anyhow!("Function named parameter count mismatch")); @@ -488,15 +696,15 @@ impl TypeInferenceEngine { if a_default != b_default { return Err(anyhow!("Function named parameter '{}' default mismatch", name)); } - self.unify(a_ty, b_ty)?; + self.unify(a_ty, b_ty, registry)?; } - self.unify(*a_ret, *b_ret) + self.unify(*a_ret, *b_ret, registry) } - (Type::Optional(a), Type::Optional(b)) => self.unify(*a, *b), - (Type::Task(a), Type::Task(b)) => self.unify(*a, *b), - (Type::Channel(a), Type::Channel(b)) => self.unify(*a, *b), - (Type::Boxed(a), Type::Boxed(b)) => self.unify(*a, *b), - (Type::Boxed(inner), other) | (other, Type::Boxed(inner)) => self.unify(*inner, other), + (Type::Optional(a), Type::Optional(b)) => self.unify(*a, *b, registry), + (Type::Task(a), Type::Task(b)) => self.unify(*a, *b, registry), + (Type::Channel(a), Type::Channel(b)) => self.unify(*a, *b, registry), + (Type::Boxed(a), Type::Boxed(b)) => self.unify(*a, *b, registry), + (Type::Boxed(inner), other) | (other, Type::Boxed(inner)) => self.unify(*inner, other, registry), // Union type unification (Type::Union(a_types), Type::Union(b_types)) => { @@ -567,7 +775,7 @@ impl TypeInferenceEngine { return Err(anyhow!("Generic type mismatch")); } for (a_param, b_param) in a_params.iter().zip(b_params.iter()) { - self.unify(a_param.clone(), b_param.clone())?; + self.unify(a_param.clone(), b_param.clone(), registry)?; } Ok(()) } @@ -576,17 +784,93 @@ impl TypeInferenceEngine { // This handles cases where arithmetic on typed variables creates subtype constraints. (ref lhs, ref rhs) if lhs.numeric_class().is_some() && rhs.numeric_class().is_some() => Ok(()), - // Concrete-concrete mismatch with no type variables on either side. - // In a gradually-typed language, the same context can hold different concrete types - // at different call sites; the runtime handles dispatch. Silently accept to avoid - // false-positive type errors in unannotated code. - (ref lhs, ref rhs) if !lhs.contains_variables() && !rhs.contains_variables() => Ok(()), - - // Type mismatch + // A machine int meets a plain `Int` wherever a literal appears in a + // machine-width context — `match ALL_ONES { 0xFFFFFFFFFFFFFFFF => … }`, + // or `reg + 1`. The literal takes the width, which is the rule + // `Stmt::Let` already applies to `let x: u8 = 5`. + // + // `NumericHierarchy::classify` deliberately does not rank machine + // ints (they convert only explicitly, and *assignability* still + // refuses both directions). That is a question about values; + // unification is asking a different one, about which type a literal + // takes. Two machine widths still do not unify with each other. + (Type::MachineInt(_), Type::Int) | (Type::Int, Type::MachineInt(_)) => Ok(()), + + // Type mismatch. + // + // Two disagreeing concrete types used to be accepted here, on the + // grounds that a gradually-typed language legitimately holds + // different concrete types in one context at different call sites. + // Measured, that described four fixable gaps rather than the + // language, and each is now closed: + // + // - a binding that started `nil` kept the type `Nil` after being + // assigned (`Stmt::Assign` widens it), + // - `==` constrained its operands to be the *same* type, so + // `x == nil` was a conflict (`check_binary_op` no longer does), + // - a type variable bound once could not be bound again, so + // `l.push(1); l.push("a")` conflicted on a heterogeneous list + // (`widen_rebound_variable`), + // - constraints were solved at the end of every function against + // a global pool, so one function's leftovers met the next one's + // (`Program::type_check` defers in both modes now). + // A trait meets an implementor. Assignability already says an + // implementor may stand where the trait is expected; a declared + // return type is checked by *unification* instead, so + // `fn pick() -> Show { return P { … }; }` was rejected while + // `fn render(v: Show)` was accepted — the same question answered + // two ways. + (ref t, Type::Named(ref name)) | (Type::Named(ref name), ref t) + if registry.get_trait(name).is_some() && registry.implements_trait(t, name) => + { + Ok(()) + } _ => Err(anyhow!("Cannot unify {} with {}", t1.display(), t2.display())), } } + /// A variable bound to one concrete type and now required to be another: + /// rebind it to both. Returns `Some(())` when it did. + /// + /// Only for a variable against a concrete type. Two variables, or anything + /// still undetermined, is ordinary unification's business — widening there + /// would decide a type that inference has not finished deciding. + fn widen_rebound_variable(&mut self, t1: &Type, t2: &Type) -> Option<()> { + let (var, incoming) = match (t1, t2) { + (Type::Variable(var), other) | (other, Type::Variable(var)) => (var, other), + _ => return None, + }; + let incoming = Self::normalize_union(self.apply_substitution(incoming)); + let bound = Self::normalize_union(self.apply_substitution(&self.substitutions.get(var)?.clone())); + if bound == incoming { + return None; + } + // A *bare* variable is what "inference has not finished deciding" + // means: it has no shape yet, so widening against it would decide + // something ordinary unification is still entitled to decide. + // + // A **constructed** type that merely contains variables is a different + // thing, and both sides used to be refused for it. `List<'T2>` — what an + // empty literal gives — has its shape settled: it is a list, and a list + // never unifies with an `Int` however `'T2` turns out. So refusing did + // not defer a decision, it reported a conflict. `f([]); f(5)` failed + // with `Cannot unify Int with List<'T2>` while `f([1]); f(5)` was + // accepted — the same program with one element in it, and the + // difference decided by which of the two constraints the solver + // happened to pop first. The inner variable survives into the union and + // is substituted later like any other. + if matches!(bound, Type::Variable(_)) || matches!(incoming, Type::Variable(_)) { + return None; + } + // `Any` already accepts everything; widening it says nothing new. + if bound == Type::Any || incoming == Type::Any { + return None; + } + let widened = Self::normalize_union(Type::Union(vec![bound, incoming])); + self.substitutions.insert(var.clone(), widened); + Some(()) + } + /// Apply current substitutions to a type fn apply_substitution(&self, typ: &Type) -> Type { typ.substitute(&self.substitutions) @@ -616,6 +900,18 @@ impl TypeInferenceEngine { } } +/// The registry is the answer to the assignability walk's trait question +/// ([`crate::val::TraitOracle`]). +/// +/// Only a *declared* trait counts: `Type::Named` covers struct names too, and +/// `implements_trait` would answer `false` for those anyway — asking `get_trait` +/// first says why, and keeps a struct name from being read as a bound. +impl crate::val::TraitOracle for TypeRegistry { + fn implements(&self, ty: &Type, trait_name: &str) -> bool { + self.get_trait(trait_name).is_some() && self.implements_trait(ty, trait_name) + } +} + #[cfg(test)] mod tests { use super::*; @@ -662,7 +958,7 @@ mod tests { #[test] fn test_type_inference() { let registry = TypeRegistry::new(); - let mut engine = TypeInferenceEngine::new(registry); + let mut engine = TypeInferenceEngine::new(); let var1 = engine.fresh_type_var(); let var2 = engine.fresh_type_var(); @@ -672,7 +968,7 @@ mod tests { // Add constraint: T1 = T0 engine.add_constraint(var2.clone(), var1.clone()); - let substitutions = engine.solve_constraints().unwrap(); + let substitutions = engine.solve_constraints(®istry).unwrap(); // Both variables should resolve to Int if let Type::Variable(name1) = &var1 { diff --git a/core/src/typ/type_system_test.rs b/core/src/typ/type_system_test.rs index 1dee4397..3e0a532f 100644 --- a/core/src/typ/type_system_test.rs +++ b/core/src/typ/type_system_test.rs @@ -217,6 +217,19 @@ mod tests { assert!(Type::Int.is_assignable_to(&Type::Float)); assert!(!Type::Float.is_assignable_to(&Type::Int)); + // …and promotion does not erase nullability. `numeric_class` looks + // through `Optional` on purpose, which used to make `Int?` and `Int` + // the same class here — so a nil flowed into a declared `Int` and + // failed wherever it was next used. `String?` was rejected all along. + let optional_int = Type::Optional(Box::new(Type::Int)); + assert!(!optional_int.is_assignable_to(&Type::Int)); + assert!(!optional_int.is_assignable_to(&Type::Float)); + assert!(!Type::Union(vec![Type::Int, Type::Nil]).is_assignable_to(&Type::Int)); + // Still assignable where nil is allowed, or where the target is Any. + assert!(optional_int.is_assignable_to(&optional_int)); + assert!(optional_int.is_assignable_to(&Type::Optional(Box::new(Type::Float)))); + assert!(optional_int.is_assignable_to(&Type::Any)); + // Boxed behaviour let boxed_any = Type::Boxed(Box::new(Type::Any)); assert!(Type::Float.is_assignable_to(&boxed_any)); @@ -225,10 +238,21 @@ mod tests { assert!(boxed_float.is_assignable_to(&Type::Float)); assert!(!Type::Bool.is_assignable_to(&int_or_string)); - // Container types (covariant) + // Containers are invariant: they are mutable and a widening is an + // alias, so `List` used as a `List` would let a String be + // pushed through the wide name and read back as an Int through the + // narrow one. let list_int = Type::List(Box::new(Type::Int)); let list_any = Type::List(Box::new(Type::Any)); - assert!(list_int.is_assignable_to(&list_any)); + assert!(!list_int.is_assignable_to(&list_any)); + assert!(!list_any.is_assignable_to(&list_int)); + assert!(list_int.is_assignable_to(&list_int)); + // `List<_>` is the read-only view every list fits, and nothing fits + // into `_` itself — which is what makes it read-only. + let list_unknown = Type::List(Box::new(Type::Unknown)); + assert!(list_int.is_assignable_to(&list_unknown)); + assert!(list_any.is_assignable_to(&list_unknown)); + assert!(!Type::Int.is_assignable_to(&Type::Unknown)); } #[test] @@ -298,13 +322,13 @@ mod tests { #[test] fn test_type_inference() { let registry = TypeRegistry::new(); - let mut engine = TypeInferenceEngine::new(registry); + let mut engine = TypeInferenceEngine::new(); // Test basic unification let var1 = engine.fresh_type_var(); engine.add_constraint(var1.clone(), Type::Int); - let substitutions = engine.solve_constraints().unwrap(); + let substitutions = engine.solve_constraints(®istry).unwrap(); if let Type::Variable(name) = var1 { assert_eq!(substitutions.get(&name), Some(&Type::Int)); @@ -314,11 +338,11 @@ mod tests { #[test] fn test_type_inference_any_does_not_block_later_concrete_constraint() { let registry = TypeRegistry::new(); - let mut engine = TypeInferenceEngine::new(registry); + let mut engine = TypeInferenceEngine::new(); let var = engine.fresh_type_var(); engine.add_constraint(var.clone(), Type::Any); - let substitutions = engine.solve_constraints().unwrap(); + let substitutions = engine.solve_constraints(®istry).unwrap(); if let Type::Variable(name) = &var { assert_eq!(substitutions.get(name), None); } else { @@ -326,7 +350,7 @@ mod tests { } engine.add_constraint(var.clone(), Type::String); - let substitutions = engine.solve_constraints().unwrap(); + let substitutions = engine.solve_constraints(®istry).unwrap(); if let Type::Variable(name) = var { assert_eq!(substitutions.get(&name), Some(&Type::String)); } else { diff --git a/core/src/type_syntax.rs b/core/src/type_syntax.rs new file mode 100644 index 00000000..af1e5063 --- /dev/null +++ b/core/src/type_syntax.rs @@ -0,0 +1,234 @@ +//! How a type is *spelled* in tokens. +//! +//! [`Type::parse`] takes a string, and the parsers hold tokens, so somebody has +//! to decide where a type annotation ends and render what it collected. That was +//! written once inside the statement parser — and then a lambda needed the same +//! thing (`|x: Int| -> Int { … }`), in a position where the statement parser +//! cannot be reached. +//! +//! It lives here rather than in `token` because it names [`Type`], and `token` +//! must not reach into `val` — that edge would drag `token` transitively into +//! the `val` ↔ `vm` cycle (see `docs/module-cycles.md`). +//! +//! What differs between the positions is only *where the type ends*, and that +//! is what [`StopAt`] says. The interesting one is `|`: at statement level it +//! separates the members of a union type, and between a lambda's `|`s it closes +//! the parameter list. So a union cannot be written directly in a lambda +//! parameter — parentheses do not help, because a parenthesised type is not part +//! of the grammar — and it goes through a `type` alias instead, which is a +//! second spelling of the same type. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; + +use crate::token::{Token, token_lexeme}; +use crate::val::Type; + +/// What ends the annotation, beyond the tokens a type can never contain. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum StopAt { + /// Statement level (`let x: T = …`, a parameter list, a field): `|` is a + /// union separator and belongs to the type; `=` ends it. + Union, + /// A lambda parameter (`|x: T, y: U|`): a top-level `|` closes the list and + /// a top-level `,` starts the next parameter. + ClosureParam, + /// A lambda's return type (`|x| -> T { … }`): a top-level `{` starts the + /// body. + ClosureReturn, +} + +impl StopAt { + /// Whether `token`, seen at nesting depth zero, ends the annotation. + fn ends_here(self, token: &Token) -> bool { + match self { + StopAt::Union => matches!(token, Token::Assign), + StopAt::ClosureParam => matches!(token, Token::Pipe | Token::Comma), + StopAt::ClosureReturn => matches!(token, Token::LBrace), + } + } +} + +/// Parses the type starting at `tokens[from]`, returning it and the index just +/// past it. +/// +/// `None` when nothing type-shaped is there or the collected spelling is not a +/// type — the caller words the error, because "after `:`" and "after `->`" want +/// different ones. +pub(crate) fn parse_type_at(tokens: &[Token], from: usize, stop: StopAt) -> Option<(Type, usize)> { + let (collected, end) = collect(tokens, from, stop); + if collected.is_empty() { + return None; + } + Type::parse(&spelling(&collected)).map(|ty| (ty, end)) +} + +/// How the annotation at `from` is spelled, for an error message. +/// +/// The parse and the report have to agree on *what* was read, so the report +/// comes from the same collector rather than from a second guess at where the +/// type ended. +pub(crate) fn spelling_at(tokens: &[Token], from: usize, stop: StopAt) -> String { + let (collected, _) = collect(tokens, from, stop); + spelling(&collected) +} + +/// The one mis-spelling worth naming: a Rust-shaped function type. +/// +/// `fn(Int) -> Int` is what somebody coming from Rust writes, and it is not a +/// type here — `fn` introduces a *declaration*, and the type is `(Int) -> Int`. +/// Without this the two type positions answered differently and neither said +/// the rule: a parameter reported `Invalid type: Fn ( Int) -> Int` (a spelling +/// the program does not contain, from a collector that took the `fn` and then +/// could not parse it) and a `let` reported `Expected type annotation (found +/// Fn)` (from the collector that stops at `fn`, leaving nothing to name). +/// +/// Deliberately *not* accepting `fn(…)` as a second spelling: one type, one +/// way to write it. Two spellings is the shape this codebase keeps removing. +pub(crate) fn function_type_hint(tokens: &[Token], from: usize) -> Option<&'static str> { + matches!(tokens.get(from), Some(Token::Fn)).then_some( + "a function type is written without `fn` — `(Int) -> Int`, not `fn(Int) -> Int`. \ + In this language `fn` introduces a declaration, never a type", + ) +} + +/// The tokens making up a type annotation, and where it ends. +/// +/// Nesting is tracked so a delimiter *inside* the type does not end it: +/// `Map>` holds commas, and `(Int) -> Int` holds parentheses. +fn collect(tokens: &[Token], from: usize, stop: StopAt) -> (Vec<&Token>, usize) { + let mut out: Vec<&Token> = Vec::new(); + let mut pos = from; + let (mut paren, mut bracket, mut brace, mut angle) = (0i32, 0i32, 0i32, 0i32); + while pos < tokens.len() { + let nested = paren > 0 || bracket > 0 || brace > 0 || angle > 0; + if !nested && stop.ends_here(&tokens[pos]) { + break; + } + match &tokens[pos] { + Token::LParen => paren += 1, + Token::RParen if paren > 0 => paren -= 1, + Token::LBracket => bracket += 1, + Token::RBracket if bracket > 0 => bracket -= 1, + Token::LBrace => brace += 1, + Token::RBrace if brace > 0 => brace -= 1, + Token::Lt => angle += 1, + Token::Gt => angle = angle.saturating_sub(1), + // `*` starts a pointer type (`*u8`, `*mut u32`). It is the same + // token as multiplication, but a type position never contains one, + // so there is nothing to disambiguate. + Token::Id(_) + | Token::Comma + | Token::Colon + | Token::ColonColon + | Token::Assign + | Token::FnArrow + | Token::Question + | Token::Mul + | Token::Pipe => {} + _ => break, + } + out.push(&tokens[pos]); + pos += 1; + } + (out, pos) +} + +/// Renders collected tokens as the string [`Type::parse`] reads. +/// +/// Spacing matters only where it separates identifiers; `<`, `>`, `,` and the +/// closers attach to what precedes them, and `|` gets spaces because that is +/// how a union prints. +/// The one renderer for a type's written form. `pub(crate)` because the +/// statement parser's three token-collecting positions render with it too — +/// they used to have their own copy, whose token table was missing every +/// keyword, so a type spelling holding one came out as the *Debug* name: +/// `fn(Int) -> Int` read `Fn(Int) -> Int`, `nil` read `Nil`. +pub(crate) fn spelling(tokens: &[&Token]) -> String { + let mut out = String::new(); + for (i, token) in tokens.iter().enumerate() { + if i == 0 { + out.push_str(&token_lexeme(token)); + continue; + } + match token { + Token::Pipe => out.push_str(" | "), + Token::Lt => out.push('<'), + Token::Gt | Token::Comma | Token::RParen | Token::RBracket | Token::RBrace => { + out.push_str(&token_lexeme(token)); + } + _ => { + if !matches!(tokens.get(i - 1), Some(Token::Lt)) { + out.push(' '); + } + out.push_str(&token_lexeme(token)); + } + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::token::Tokenizer; + + fn ty(src: &str, stop: StopAt) -> Option<(Type, usize)> { + let tokens = Tokenizer::tokenize(src).expect("tokenize"); + parse_type_at(&tokens, 0, stop) + } + + #[test] + fn nesting_does_not_end_the_annotation() { + let (parsed, end) = ty("Map>", StopAt::Union).expect("a nested type"); + assert_eq!(parsed.display(), "Map>"); + assert_eq!(end, 9, "the whole spelling is consumed"); + } + + /// The one interesting difference between the positions. + #[test] + fn pipe_is_a_union_at_statement_level_and_a_delimiter_in_a_lambda() { + let (union, _) = ty("Int | String", StopAt::Union).expect("a union"); + assert_eq!(union.display(), "Int | String"); + // Between a lambda's `|`s the same token closes the parameter list, so + // the type is just `Int` and the caller resumes at the `|`. + let (single, end) = ty("Int | String", StopAt::ClosureParam).expect("a single type"); + assert_eq!(single.display(), "Int"); + assert_eq!(end, 1); + // Which is why a union in a lambda parameter goes through a `type` + // alias: parentheses are not a way to group a type here. + assert!(ty("(Int | String)", StopAt::ClosureParam).is_none()); + } + + /// Each position ends where its grammar says, and nesting is not the end. + #[test] + fn each_position_ends_where_its_grammar_says() { + // A parameter ends at the comma starting the next one… + let (first, end) = ty("Int, b: String", StopAt::ClosureParam).expect("the first parameter"); + assert_eq!(first.display(), "Int"); + assert_eq!(end, 1); + // …but not at a comma *inside* the type. + let (nested, _) = ty("Map, b: String", StopAt::ClosureParam).expect("a nested comma"); + assert_eq!(nested.display(), "Map"); + // A return type ends where the body opens. + let (ret, end) = ty("Int { return 1; }", StopAt::ClosureReturn).expect("a return type"); + assert_eq!(ret.display(), "Int"); + assert_eq!(end, 1); + } + + #[test] + fn a_function_type_is_a_type() { + let (parsed, _) = ty("(Int, Int) -> String", StopAt::Union).expect("a function type"); + assert_eq!(parsed.display(), "(Int, Int) -> String"); + } + + #[test] + fn nothing_type_shaped_is_none() { + assert!(ty("{", StopAt::Union).is_none()); + assert!(ty("+", StopAt::Union).is_none()); + // An unknown *name*, on the other hand, is a type: user-declared + // structs and traits arrive here as plain identifiers. + let (named, _) = ty("Nonesuch", StopAt::Union).expect("a named type"); + assert_eq!(named.display(), "Nonesuch"); + } +} diff --git a/core/src/util.rs b/core/src/util.rs index 924b1e8a..d226af6f 100644 --- a/core/src/util.rs +++ b/core/src/util.rs @@ -1 +1,3 @@ pub mod fast_map; +pub mod text; +pub mod value_map; diff --git a/core/src/util/text.rs b/core/src/util/text.rs new file mode 100644 index 00000000..cddb9579 --- /dev/null +++ b/core/src/util/text.rs @@ -0,0 +1,126 @@ +//! Character-oriented string operations. +//! +//! One implementation, used by both spellings of every string operation: the +//! method form (`s.substring(…)`, dispatched in the VM) and the module form +//! (`string.substring(s, …)`, exported by the standard library). They used to +//! be written twice, and had drifted — `"héllo wörld".len()` answered 11 while +//! `string.len(…)` answered 13. +//! +//! **Positions are characters, not bytes.** LK's strings are UTF-8 and its +//! `len` counts characters, so every position that meets a length has to count +//! the same thing; `s.substring(0, s.len())` is the shape that decides it. +//! Byte-oriented work belongs to `bytes`. +//! +//! Slicing by byte offset is also what made these operations *panic* rather +//! than fail: `&s[2..5]` on `"héllo"` lands inside `é`, and inside the VM a +//! panic is not an error the program can see — it takes the process down. + +/// The number of characters in `text`. +/// +/// The ASCII fast path matters: this is on the hot path for every `s.len()`, +/// and for ASCII the byte length already *is* the character count. +pub fn char_len(text: &str) -> usize { + if text.is_ascii() { + text.len() + } else { + text.chars().count() + } +} + +/// The byte offset of character `index`, or the end of the string when `index` +/// is past the last character. +fn byte_offset(text: &str, index: usize) -> usize { + text.char_indices() + .nth(index) + .map(|(offset, _)| offset) + .unwrap_or(text.len()) +} + +/// `length` characters of `text` starting at character `start`. +/// +/// Out of range is an empty result rather than an error, matching what both +/// implementations did with byte offsets before. +pub fn substring(text: &str, start: usize, length: usize) -> &str { + let start_offset = byte_offset(text, start); + let end_offset = byte_offset(text, start.saturating_add(length)); + // `saturating_add` because `usize` is 32-bit on the bare-metal targets, + // where two large `Int` arguments overflow it; wrapping would produce + // `end < start`. + if end_offset <= start_offset { + return ""; + } + &text[start_offset..end_offset] +} + +/// The character index at which `needle` first occurs. +/// +/// `None` for "not found" — the method form used to answer `-1`, which is a +/// valid index and so goes wrong quietly when it is used as one. +pub fn find_char_index(text: &str, needle: &str) -> Option { + let byte = text.find(needle)?; + Some(text[..byte].chars().count()) +} + +/// Like [`find_char_index`], starting the search at character `start`. +pub fn find_char_index_from(text: &str, needle: &str, start: usize) -> Option { + let start_offset = byte_offset(text, start); + let byte = text[start_offset..].find(needle)? + start_offset; + Some(text[..byte].chars().count()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn counts_characters_not_bytes() { + assert_eq!(char_len("héllo wörld"), 11); + assert_eq!(char_len(""), 0); + assert_eq!(char_len("abc"), 3); + } + + #[test] + fn substring_takes_character_positions() { + // The case that used to panic: byte 2 is inside `é`. + assert_eq!(substring("héllo", 2, 3), "llo"); + assert_eq!(substring("héllo", 0, 2), "hé"); + assert_eq!(substring("abc", 1, 1), "b"); + } + + #[test] + fn substring_out_of_range_is_empty_not_a_panic() { + assert_eq!(substring("abc", 5, 2), ""); + assert_eq!(substring("abc", 1, 0), ""); + assert_eq!(substring("abc", 0, 99), "abc"); + assert_eq!(substring("héllo", 3, usize::MAX), "lo"); + } + + #[test] + fn substring_composes_with_len() { + // The shape that forces positions and lengths to count the same thing. + let text = "héllo wörld"; + assert_eq!(substring(text, 0, char_len(text)), text); + } + + #[test] + fn find_answers_in_characters() { + // Byte offset would be 3 — `é` is two bytes. + assert_eq!(find_char_index("héllo", "llo"), Some(2)); + assert_eq!(find_char_index("héllo", "h"), Some(0)); + assert_eq!(find_char_index("héllo", "zz"), None); + } + + #[test] + fn find_composes_with_substring() { + let text = "héllo wörld"; + let at = find_char_index(text, "wörld").expect("present"); + assert_eq!(substring(text, at, 5), "wörld"); + } + + #[test] + fn find_from_skips_earlier_matches() { + assert_eq!(find_char_index_from("ababa", "a", 1), Some(2)); + assert_eq!(find_char_index_from("héllo", "l", 3), Some(3)); + assert_eq!(find_char_index_from("abc", "a", 9), None); + } +} diff --git a/core/src/util/value_map.rs b/core/src/util/value_map.rs new file mode 100644 index 00000000..5b6efca8 --- /dev/null +++ b/core/src/util/value_map.rs @@ -0,0 +1,44 @@ +//! The map a *program* sees. +//! +//! Distinct from [`FastHashMap`](super::fast_map::FastHashMap), which is the +//! compiler's and the runtime's own bookkeeping: those tables are asked +//! questions about keys and never iterated for the user, so hash order costs +//! nothing. A `Map` in the language is different — it is printed, iterated, +//! and compared — and hash order there is a property of *how the map was +//! built* rather than of what it holds: +//! +//! ```lk +//! let a = {"zebra": 1, "apple": 2, "mango": 3, "kiwi": 4}; +//! let b = {}; +//! b["zebra"] = 1; b["apple"] = 2; b["mango"] = 3; b["kiwi"] = 4; +//! // a == b, and the two printed their fields in different orders. +//! ``` +//! +//! Insertion order fixes that, and it is what every scripting language a +//! reader is likely to come from does. It also removes a load-bearing +//! coincidence: the native runtime used to reproduce the interpreter's *hash +//! layout* to keep `for k in m` iterating the same way in both back ends, and +//! that argument rested on both linking the same `hashbrown`, the same rustc +//! deriving the same `Hash` discriminants, and one fixed seed. Appending to a +//! vector needs no such argument. +//! +//! `shift_remove`, not `swap_remove`: a delete keeps the surviving entries in +//! order, which is the whole point. It is `O(n)` in the tail, which is the +//! price of the guarantee. + +pub type ValueMap = indexmap::IndexMap; + +#[inline] +pub fn value_map_new() -> ValueMap { + ValueMap::default() +} + +#[inline] +pub fn value_map_with_capacity(capacity: usize) -> ValueMap { + ValueMap::with_capacity_and_hasher(capacity, Default::default()) +} + +#[inline] +pub fn value_map_from_iter>(iter: I) -> ValueMap { + iter.into_iter().collect() +} diff --git a/core/src/val.rs b/core/src/val.rs index 08a1f214..9a790905 100644 --- a/core/src/val.rs +++ b/core/src/val.rs @@ -1,6 +1,12 @@ pub mod de; +pub mod position; +pub mod ser; mod runtime_model; +// A value's type identity — the declaring module plus the name — is a property +// of the value, not of the executor. It lived under `vm/` and was the reason +// `val` named `vm` for anything other than a callable payload. +mod type_info; #[cfg(test)] mod de_test; @@ -10,6 +16,8 @@ mod val_test; // Front-end value/type model (LiteralVal/Type/ShortStr/numeric) lives in the L0 // `lk-values` crate; re-exported here so `crate::val::Type` etc. are unchanged. pub use lk_values::{ - FunctionNamedParamType, IntKind, LiteralVal, NumericClass, NumericHierarchy, ShortStr, ShortStrOrStr, Type, + CONTAINER_TYPE_NAMES, FunctionNamedParamType, IntKind, LiteralVal, NUMBER_TYPE_NAME, NoTraits, NumericClass, + NumericHierarchy, PRIMITIVE_TYPES, ShortStr, ShortStrOrStr, TYPE_SPELLINGS, TraitOracle, Type, }; pub use runtime_model::*; +pub use type_info::*; diff --git a/core/src/val/de.rs b/core/src/val/de.rs index 72f21813..81d3543e 100644 --- a/core/src/val/de.rs +++ b/core/src/val/de.rs @@ -1,6 +1,19 @@ +//! A document's keys arrive in **document order**, for every format. +//! +//! An LK map's iteration and display order is the order a key was first written +//! (`docs/semantics.md`), and for a parsed document that is the order it appears +//! in. JSON used to sort them — `serde_json::Value` is a `BTreeMap` — and so did +//! TOML; neither was a decision, only the default of the intermediate each went +//! through. JSON is fixed by not using that intermediate ([`OrderedJson`]); +//! TOML by its crate's `preserve_order`, which is free here because TOML +//! decoding is `std`-only. YAML was already ordered. +//! +//! Writing is deliberately the other way (see [`super::ser`]): output is sorted +//! so a config written twice is byte-identical. + #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::fast_hash_map_new; +use crate::util::value_map::value_map_new; use crate::val::{HeapStore, HeapValue, RuntimeMapKey, RuntimeVal, ShortStr, TypedList}; use alloc::sync::Arc; @@ -178,7 +191,7 @@ pub fn parse_runtime_with_format_into_heap( ) -> anyhow::Result { match format { Format::Json => { - let value = serde_json::from_str::(input).map_err(|e| anyhow::anyhow!(e))?; + let value: OrderedJson = serde_json::from_str(input).map_err(|e| anyhow::anyhow!(e))?; json_to_runtime(value, heap) } #[cfg(feature = "std")] @@ -200,21 +213,100 @@ pub fn parse_runtime_with_format_into_heap( } } -fn json_to_runtime(value: serde_json::Value, heap: &mut HeapStore) -> anyhow::Result { +/// A JSON document with its objects still in **document order**. +/// +/// `serde_json::Value` cannot be used here: its object is a `BTreeMap` unless +/// the `preserve_order` feature is on, and that feature explicitly enables +/// `std` — which this crate must build without (the bare-metal target keeps +/// JSON). So a document round-tripped through it came back alphabetised, and an +/// LK map's order is a *contract*: iteration and display follow the order a key +/// was first written (`docs/semantics.md`). +/// +/// Deserializing straight into a `Vec` of pairs keeps what serde already hands +/// over in order — `MapAccess` yields entries as they appear — and costs +/// nothing else: the pairs go into the LK map in the same walk the old code +/// used. +enum OrderedJson { + Null, + Bool(bool), + Int(i64), + Float(f64), + Str(String), + Array(Vec), + Object(Vec<(String, OrderedJson)>), +} + +impl<'de> serde::Deserialize<'de> for OrderedJson { + fn deserialize>(deserializer: D) -> Result { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = OrderedJson; + + fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.write_str("any JSON value") + } + + fn visit_unit(self) -> Result { + Ok(OrderedJson::Null) + } + fn visit_none(self) -> Result { + Ok(OrderedJson::Null) + } + fn visit_bool(self, v: bool) -> Result { + Ok(OrderedJson::Bool(v)) + } + fn visit_i64(self, v: i64) -> Result { + Ok(OrderedJson::Int(v)) + } + fn visit_u64(self, v: u64) -> Result { + Ok(i64::try_from(v).map_or(OrderedJson::Float(v as f64), OrderedJson::Int)) + } + fn visit_f64(self, v: f64) -> Result { + Ok(OrderedJson::Float(v)) + } + fn visit_str(self, v: &str) -> Result { + Ok(OrderedJson::Str(v.to_string())) + } + fn visit_string(self, v: String) -> Result { + Ok(OrderedJson::Str(v)) + } + fn visit_seq>(self, mut seq: A) -> Result { + let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + while let Some(item) = seq.next_element()? { + out.push(item); + } + Ok(OrderedJson::Array(out)) + } + fn visit_map>(self, mut map: A) -> Result { + let mut out = Vec::with_capacity(map.size_hint().unwrap_or(0)); + while let Some((key, value)) = map.next_entry::()? { + out.push((key, value)); + } + Ok(OrderedJson::Object(out)) + } + } + + deserializer.deserialize_any(Visitor) + } +} + +fn json_to_runtime(value: OrderedJson, heap: &mut HeapStore) -> anyhow::Result { Ok(match value { - serde_json::Value::Null => RuntimeVal::Nil, - serde_json::Value::Bool(value) => RuntimeVal::Bool(value), - serde_json::Value::Number(value) => number_to_runtime(value.as_i64(), value.as_f64()), - serde_json::Value::String(value) => runtime_string_value(&value, heap), - serde_json::Value::Array(values) => { + OrderedJson::Null => RuntimeVal::Nil, + OrderedJson::Bool(value) => RuntimeVal::Bool(value), + OrderedJson::Int(value) => number_to_runtime(Some(value), None), + OrderedJson::Float(value) => number_to_runtime(None, Some(value)), + OrderedJson::Str(value) => runtime_string_value(&value, heap), + OrderedJson::Array(values) => { let mut out = Vec::with_capacity(values.len()); for value in values { out.push(json_to_runtime(value, heap)?); } RuntimeVal::Obj(heap.alloc(HeapValue::List(decoded_values_to_typed_list(out, heap)))) } - serde_json::Value::Object(values) => { - let mut entries = fast_hash_map_new(); + OrderedJson::Object(values) => { + let mut entries = value_map_new(); for (key, value) in values { entries.insert(runtime_string_key(&key), json_to_runtime(value, heap)?); } @@ -238,7 +330,7 @@ fn yaml_to_runtime(value: serde_yaml::Value, heap: &mut HeapStore) -> anyhow::Re RuntimeVal::Obj(heap.alloc(HeapValue::List(decoded_values_to_typed_list(out, heap)))) } serde_yaml::Value::Mapping(values) => { - let mut entries = fast_hash_map_new(); + let mut entries = value_map_new(); for (key, value) in values { entries.insert(yaml_key_to_runtime(key)?, yaml_to_runtime(value, heap)?); } @@ -264,7 +356,7 @@ fn toml_to_runtime(value: toml::Value, heap: &mut HeapStore) -> anyhow::Result { - let mut entries = fast_hash_map_new(); + let mut entries = value_map_new(); for (key, value) in values { entries.insert(runtime_string_key(&key), toml_to_runtime(value, heap)?); } @@ -360,7 +452,7 @@ fn runtime_string_key(value: &str) -> RuntimeMapKey { if let Some(value) = ShortStr::new(value) { RuntimeMapKey::ShortStr(value) } else { - RuntimeMapKey::String(Arc::::from(value)) + RuntimeMapKey::from_text(value) } } diff --git a/core/src/val/position.rs b/core/src/val/position.rs new file mode 100644 index 00000000..0ee0c447 --- /dev/null +++ b/core/src/val/position.rs @@ -0,0 +1,105 @@ +//! What a position means against a container of a given length. +//! +//! One rule, one place. It was written inside the VM's method dispatch as +//! `pub(super)` helpers, which put it out of reach of the **stdlib module +//! crates** — and `bytes` is implemented there. So `b.slice(1, -1)` (the method, +//! through the VM's dispatch) answered `Bytes([98,99,100])` while +//! `bytes.slice(b, 1, -1)` (the module, through its own `usize_arg`) raised +//! "expects a non-negative integer": the same operation, two spellings, two +//! answers. `bytes.get` likewise. +//! +//! `docs/semantics.md` had already ruled that a negative position counts from +//! the end everywhere, and the comment on the read helper even said "List and +//! Bytes raised" in the past tense — a ruling whose fourth site never received +//! it, because it could not see the code that implemented it. +//! +//! The native side keeps its own mirror (`lkrt::lkslice::resolve_position`), +//! which is the documented pattern: lkrt must not depend on the front end. + +use crate::val::RuntimeVal; +use anyhow::{Result, bail}; + +/// A *read* position: negative counts from the end, and the result is clamped +/// into `0..=len`. +/// +/// Clamping rather than raising is the read side's rule throughout the language: +/// reading past the end is nil (or an empty window), which is a meaning a program +/// can have. +pub fn read_position(value: &RuntimeVal, len: usize, context: &str) -> Result { + let RuntimeVal::Int(index) = value else { + bail!("{context} must be Int"); + }; + let len = len as i64; + let resolved = if *index < 0 { len + *index } else { *index }; + Ok(resolved.clamp(0, len) as usize) +} + +/// A read position that may miss: negative counts from the end, and out of range +/// is `None` rather than a clamp. +/// +/// `xs[9]` and `bytes.get(b, 9)` are nil, not the last element — clamping would +/// invent an answer. The distinction from [`read_position`] is exactly that a +/// *window* has a meaningful clamp and an *element* does not. +pub fn element_position(value: &RuntimeVal, len: usize, context: &str) -> Result> { + let RuntimeVal::Int(index) = value else { + bail!("{context} must be Int"); + }; + let resolved = if *index < 0 { len as i64 + *index } else { *index }; + if resolved < 0 || resolved >= len as i64 { + return Ok(None); + } + Ok(Some(resolved as usize)) +} + +/// A *write* position: negative counts from the end, and still out of range is an +/// error. +/// +/// Reading past the end is nil; writing past it is not something a program can +/// mean. The caller does the upper-bound check, because `insert` accepts `len` +/// and the others do not. +pub fn write_position(value: &RuntimeVal, len: usize, context: &str) -> Result { + let RuntimeVal::Int(index) = value else { + bail!("{context} must be Int"); + }; + let resolved = if *index < 0 { len as i64 + *index } else { *index }; + if resolved < 0 { + bail!("{context} {index} is before the start of a list of {len}"); + } + Ok(resolved as usize) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_negative_position_counts_from_the_end() { + let five = 5; + assert_eq!(read_position(&RuntimeVal::Int(-1), five, "ctx").expect("ok"), 4); + assert_eq!( + element_position(&RuntimeVal::Int(-1), five, "ctx").expect("ok"), + Some(4) + ); + assert_eq!(write_position(&RuntimeVal::Int(-1), five, "ctx").expect("ok"), 4); + } + + /// A window clamps, an element misses. Both are read-side rules, and the + /// difference is whether there is an answer to invent. + #[test] + fn out_of_range_clamps_for_a_window_and_misses_for_an_element() { + assert_eq!(read_position(&RuntimeVal::Int(99), 5, "ctx").expect("ok"), 5); + assert_eq!(read_position(&RuntimeVal::Int(-99), 5, "ctx").expect("ok"), 0); + assert_eq!(element_position(&RuntimeVal::Int(99), 5, "ctx").expect("ok"), None); + assert_eq!(element_position(&RuntimeVal::Int(-99), 5, "ctx").expect("ok"), None); + // A write that is still before the start is an error, not a clamp. + assert!(write_position(&RuntimeVal::Int(-99), 5, "ctx").is_err()); + } + + #[test] + fn a_non_integer_position_is_refused_by_every_rule() { + let text = RuntimeVal::Bool(true); + assert!(read_position(&text, 5, "ctx").is_err()); + assert!(element_position(&text, 5, "ctx").is_err()); + assert!(write_position(&text, 5, "ctx").is_err()); + } +} diff --git a/core/src/val/runtime_model.rs b/core/src/val/runtime_model.rs index 0359cfbd..834205f2 100644 --- a/core/src/val/runtime_model.rs +++ b/core/src/val/runtime_model.rs @@ -5,17 +5,60 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::{FastHashMap, FastHashSet, fast_hash_map_from_iter, fast_hash_map_new, fast_hash_set_new}; +use crate::util::fast_map::{FastHashSet, fast_hash_set_new}; +use crate::util::value_map::{ValueMap, value_map_from_iter, value_map_new}; use alloc::sync::Arc; +use crate::val::DeclaredType; use crate::val::{ShortStr, Type}; -use crate::vm::DeclaredType; +mod equality; mod heap; -pub use heap::{HeapRef, HeapStore}; - -#[derive(Clone, Copy, Debug, PartialEq)] +pub use equality::{runtime_value_equals_str, runtime_values_equal}; + +/// How far the runtime will walk into a value before giving up. +/// +/// Comparing and rendering both recurse on the *shape* of a value, so a chain a +/// loop can build — +/// +/// ```lk +/// let node: Any = [1]; +/// for i in 0..200000 { node = [node]; } +/// ``` +/// +/// — put 200000 frames on the Rust stack and aborted the process with +/// `fatal runtime error: stack overflow`. A script must not be able to do that. +/// Past this depth those walks raise an ordinary catchable error instead, which +/// is what Python and Lua do with the same problem. +/// +/// Generous for data — JSON nests single digits deep, a hand-written tree tens +/// — and far below the number of Rust frames the real stack would take. +pub const MAX_VALUE_DEPTH: u32 = 512; +pub use heap::{CollectedModules, HeapRef, HeapStore}; + +/// A value, 16 bytes and `Copy`. +/// +/// **No `PartialEq`, deliberately.** A derived one means two different things +/// for two of these variants: structural for a `ShortStr`, and *handle +/// identity* for an `Obj`. Every place that reached for `==` got identity +/// without noticing, and `ShortStr`'s seven-byte inline limit made half the +/// cases accidentally right — so the bug looked like "strings longer than +/// seven characters", which is not a thing any reader would suspect: +/// +/// ```text +/// ["ab", "cd"].contains("ab") → true +/// ["abcdefghij", …].contains("abcdefghij") → false +/// assert_eq("abcdefghij", "abcdefghij") → failed (in the playground) +/// ``` +/// +/// Equality needs the heap, so it cannot be a `PartialEq` impl at all: it is +/// [`runtime_values_equal`], which takes the heap. Asking for it is a decision; +/// `==` was not. +/// +/// [`RuntimeVal::same_value_or_handle`] is the escape hatch for the places that +/// genuinely mean "the same nil/bool/int, or literally the same object". +#[derive(Clone, Copy, Debug)] pub enum RuntimeVal { Nil, Bool(bool), @@ -25,6 +68,24 @@ pub enum RuntimeVal { Obj(HeapRef), } +/// Equality for tests only. +/// +/// Production code must not have this: `==` on a `RuntimeVal` can only compare +/// handles, and the whole point of removing the derive is that reaching for it +/// stops being possible by accident. A test, though, is written against known +/// values and says what it means — `assert_eq!(returned, RuntimeVal::Int(55))` +/// is about that integer, not about which handle it arrived on. +/// +/// Gated on `cfg(test)`, so it exists while `lk-core`'s own tests compile and +/// nowhere else. A downstream crate that needs it enables the `testing` +/// feature. +#[cfg(any(test, feature = "testing"))] +impl PartialEq for RuntimeVal { + fn eq(&self, other: &Self) -> bool { + self.same_value_or_handle(other) + } +} + impl Default for RuntimeVal { #[inline] fn default() -> Self { @@ -45,6 +106,55 @@ impl RuntimeVal { } } + /// The **language type** name of this value: the struct's own name for an + /// instance, `List` / `Map` / `Set` / `Bytes` / `String` for the other + /// handles, the scalar's own name otherwise. + /// + /// Takes the heap because that is what makes the question answerable — a + /// handle's type lives there. That is the point of the signature: an error + /// message that has the heap cannot accidentally print `Object`, and one + /// that does not have it cannot call this at all. + /// + /// The return borrows the heap for the same reason. It was `&'static str`, + /// and a struct's name is not static — so this function, the one written to + /// stop messages saying `Object`, said `Object` for every struct instance: + /// `p.nonexistent()` reported "Object has no method 'nonexistent'" while the + /// dispatch two lines away already had `Point` in hand. A rule's own carrier + /// had exactly the hole the rule exists to close. + pub fn type_name_in<'heap>(&self, heap: &'heap HeapStore) -> &'heap str { + match self { + Self::Obj(handle) => match heap.get(*handle) { + Some(HeapValue::Object(object)) => object.type_name(), + Some(other) => other.type_name(), + None => "Object", + }, + other => other.kind().scalar_type_name(), + } + } + + /// The same scalar, or literally the same heap object. + /// + /// This is what the derived `PartialEq` used to provide silently. It is + /// still the right question in a few places — deduplicating a constant + /// pool, telling whether two registers hold the same object — and wrong in + /// every place that means "equal". Having to name it is the point: the + /// language's `==` is [`runtime_values_equal`], which needs the heap and + /// answers by value. + #[inline] + pub fn same_value_or_handle(&self, other: &Self) -> bool { + match (self, other) { + (Self::Nil, Self::Nil) => true, + (Self::Bool(left), Self::Bool(right)) => left == right, + (Self::Int(left), Self::Int(right)) => left == right, + // Bit equality, so that two `NaN`s from the same source dedup and + // `0.0`/`-0.0` stay distinct — this is identity, not arithmetic. + (Self::Float(left), Self::Float(right)) => left.to_bits() == right.to_bits(), + (Self::ShortStr(left), Self::ShortStr(right)) => left.as_str() == right.as_str(), + (Self::Obj(left), Self::Obj(right)) => left == right, + _ => false, + } + } + #[inline] pub fn as_int(&self) -> Option { match self { @@ -62,7 +172,22 @@ impl RuntimeVal { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// What a [`RuntimeVal`] is, as a program would say it. +/// +/// The variants are named after the *representation* — `ShortStr` is a string +/// that fits inline, `Obj` is a handle — and that is a distinction no program +/// can see. It reached users anyway: some forty error messages are written +/// `bail!("… got {:?}", value.kind())`, so `-x` on a string answered +/// +/// ```text +/// Neg expected Int or Float, got ShortStr +/// ``` +/// +/// naming a type the language does not have. `Debug` is written by hand for +/// that reason: it is what those messages print, so it prints `String` and +/// `Object`. [`RuntimeValKind::repr_name`] is still there for anyone debugging +/// the representation itself. +#[derive(Clone, Copy, PartialEq, Eq)] pub enum RuntimeValKind { Nil, Bool, @@ -72,6 +197,56 @@ pub enum RuntimeValKind { Obj, } +impl RuntimeValKind { + /// The type name a program would use — **for a scalar**. A handle answers + /// `Object`, which is not a type the language has. + /// + /// Named for that limit on purpose. It used to be `type_name`, and 40-odd + /// error messages reached for it and printed `Object` where they meant + /// `List`, `Map`, `Set`, or `String`-that-did-not-fit-in-seven-bytes. The + /// doc said "a caller that has one should reach for `HeapValue::type_name` + /// instead" and nothing did, because the wrong function had the right name. + /// + /// [`RuntimeVal::type_name_in`] is the one to use: it takes the heap, so + /// forgetting it is a compile error rather than a wrong string. + pub const fn scalar_type_name(self) -> &'static str { + match self { + Self::Nil => "Nil", + Self::Bool => "Bool", + Self::Int => "Int", + Self::Float => "Float", + Self::ShortStr => "String", + // A handle; which kind of object needs the heap, so a caller that + // has one should reach for `HeapValue::type_name` instead. + Self::Obj => "Object", + } + } + + /// The variant's own name — the representation, not the language's type. + pub const fn repr_name(self) -> &'static str { + match self { + Self::Nil => "Nil", + Self::Bool => "Bool", + Self::Int => "Int", + Self::Float => "Float", + Self::ShortStr => "ShortStr", + Self::Obj => "Obj", + } + } +} + +impl core::fmt::Debug for RuntimeValKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.scalar_type_name()) + } +} + +impl core::fmt::Display for RuntimeValKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.scalar_type_name()) + } +} + #[derive(Clone, Debug)] pub enum HeapValue { String(Arc), @@ -92,8 +267,32 @@ pub enum HeapValue { } impl HeapValue { + /// The type name a program would use, including a struct instance's + /// *declared* name. + /// + /// This is the function [`RuntimeValKind::scalar_type_name`] tells callers + /// to reach for instead of saying `Object` — and it said `Object` itself, + /// for every struct instance, at all thirty-odd `bail!` sites that took the + /// advice. Third time the same rule has been fixed one layer further down + /// (`scalar_type_name`, then [`RuntimeVal::type_name_in`], now here), so: + /// **the language's name for a struct instance is what the `struct` was + /// called**, and the variant's own spelling lives in + /// [`Self::representation_name`] under a name that says so. + #[inline] + pub fn type_name(&self) -> &str { + match self { + Self::Object(object) => object.type_name(), + other => other.representation_name(), + } + } + + /// The variant's own spelling — the representation, not the language's + /// type. `Object` for every struct instance, whatever it was declared as. + /// + /// Only [`Self::type_name`] and code genuinely talking about the + /// representation (a heap dump, a GC statistic) should want this. #[inline] - pub fn type_name(&self) -> &'static str { + pub fn representation_name(&self) -> &'static str { match self { Self::String(_) => "String", Self::Bytes(_) => "Bytes", @@ -190,26 +389,24 @@ pub struct RuntimeObject { /// This object's type identity: the declaring module *and* the name. The /// name alone is only unique within one module, so dispatching on it made /// two modules' identically-named structs the same type (see - /// [`crate::vm::TypeScope`]). + /// [`crate::val::TypeScope`]). /// /// Shared by `Arc` rather than stored inline — see [`DeclaredType`] for why /// this struct's width is worth caring about. pub ty: Arc, - pub fields: FastHashMap, RuntimeVal>, - pub field_slots: Vec>, + /// Insertion-ordered, so it is also the field *slot* table: slot `i` is the + /// `i`th key. There used to be a parallel `Vec>` for that, kept in + /// step by hand, because the carrier was a hash map and had no `i`th + /// anything. It cost 24 bytes on **every heap cell** — `RuntimeObject` is + /// the widest `HeapValue` variant, so its width is every list's and every + /// string's too — which is the budget `layout::heap_cells_stay_narrow` + /// guards. + pub fields: ValueMap, RuntimeVal>, } impl RuntimeObject { - pub fn new(ty: Arc, fields: FastHashMap, RuntimeVal>) -> Self { - let mut field_slots = Vec::with_capacity(fields.len()); - for key in fields.keys() { - field_slots.push(Arc::clone(key)); - } - Self { - ty, - fields, - field_slots, - } + pub fn new(ty: Arc, fields: ValueMap, RuntimeVal>) -> Self { + Self { ty, fields } } #[inline] @@ -218,12 +415,12 @@ impl RuntimeObject { } #[inline] - pub fn type_scope(&self) -> &crate::vm::TypeScope { + pub fn type_scope(&self) -> &crate::val::TypeScope { &self.ty.scope } pub fn field_slot(&self, key: &str) -> Option { - self.field_slots.iter().position(|candidate| candidate.as_ref() == key) + self.fields.get_index_of(key) } pub fn get_field(&self, key: &str) -> Option { @@ -231,28 +428,82 @@ impl RuntimeObject { } pub fn get_field_slot(&self, slot: usize, key: &str) -> Option { - let slot_key = self.field_slots.get(slot)?; - if slot_key.as_ref() == key { - self.fields.get(slot_key).cloned() - } else { - None - } + let (slot_key, value) = self.fields.get_index(slot)?; + (slot_key.as_ref() == key).then_some(*value) + } + + /// The fields in declaration order, for a reader that needs all of them — + /// the hybrid bridge marshalling a struct back to the native side. + pub fn fields_iter(&self) -> impl Iterator { + self.fields.iter().map(|(key, value)| (key.as_ref(), *value)) } pub fn set_field(&mut self, key: Arc, value: RuntimeVal) { - if !self.fields.contains_key(key.as_ref()) { - self.field_slots.push(key.clone()); - } + // A new key lands at the end, an existing one keeps its slot — which is + // `IndexMap::insert`'s own behaviour, and used to need a second write to + // the slot table beside it. self.fields.insert(key, value); } } -#[derive(Clone, Debug, PartialEq)] +/// A raised error: its message, and the values along the way. +/// +/// No `PartialEq`: two errors are compared by their *message*, which +/// `same_message` says, and never by their traces — those are `RuntimeVal`s, +/// and comparing them without the heap would compare handles. +#[derive(Clone, Debug)] pub struct ErrorVal { pub message: Arc, pub trace: Vec, } +impl ErrorVal { + #[inline] + pub fn same_message(&self, other: &Self) -> bool { + self.message == other.message + } +} + +/// The one shape a whole list of runtime values shares, if any. +/// +/// Companion to [`TypedList::from_runtime_values`]; lives beside it so the +/// narrowing rule and the shapes it can produce cannot drift apart. +pub(crate) enum RuntimeListShape { + Mixed, + Int, + Float, + Bool, + String, +} + +pub(crate) fn runtime_value_list_shape(values: &[RuntimeVal], heap: &HeapStore) -> RuntimeListShape { + if values.is_empty() { + return RuntimeListShape::Mixed; + } + let mut shape: Option = None; + for value in values { + let next = match value { + RuntimeVal::Int(_) => RuntimeListShape::Int, + RuntimeVal::Float(_) => RuntimeListShape::Float, + RuntimeVal::Bool(_) => RuntimeListShape::Bool, + RuntimeVal::ShortStr(_) => RuntimeListShape::String, + RuntimeVal::Obj(handle) if matches!(heap.get(*handle), Some(HeapValue::String(_))) => { + RuntimeListShape::String + } + _ => return RuntimeListShape::Mixed, + }; + match (&shape, next) { + (None, next) => shape = Some(next), + (Some(RuntimeListShape::Int), RuntimeListShape::Int) + | (Some(RuntimeListShape::Float), RuntimeListShape::Float) + | (Some(RuntimeListShape::Bool), RuntimeListShape::Bool) + | (Some(RuntimeListShape::String), RuntimeListShape::String) => {} + _ => return RuntimeListShape::Mixed, + } + } + shape.unwrap_or(RuntimeListShape::Mixed) +} + #[derive(Clone, Debug)] pub enum TypedList { Mixed(Vec), @@ -263,6 +514,61 @@ pub enum TypedList { } impl TypedList { + /// Build a list from runtime values, keeping the compact representation + /// when they all share one shape. + /// + /// A list should look the same whether it came from a literal, a `push` + /// loop, or a projection like `map` / `keys` / `values`. Producers that + /// reach for `Mixed` directly are invisible in the *answer* but not in the + /// cost: `Mixed` holds 16 bytes per element instead of 8, and every typed + /// fast path downstream (arithmetic, `sort`, index reads) drops to the + /// generic one. The scan is O(n) over a vector the caller just built. + pub fn from_runtime_values(values: &[RuntimeVal], heap: &HeapStore) -> Self { + match runtime_value_list_shape(values, heap) { + RuntimeListShape::Mixed => Self::Mixed(values.to_vec()), + RuntimeListShape::Int => Self::Int( + values + .iter() + .map(|value| match value { + RuntimeVal::Int(value) => *value, + _ => unreachable!("shape scan only returns Int for int values"), + }) + .collect(), + ), + RuntimeListShape::Float => Self::Float( + values + .iter() + .map(|value| match value { + RuntimeVal::Float(value) => *value, + _ => unreachable!("shape scan only returns Float for float values"), + }) + .collect(), + ), + RuntimeListShape::Bool => Self::Bool( + values + .iter() + .map(|value| match value { + RuntimeVal::Bool(value) => *value, + _ => unreachable!("shape scan only returns Bool for bool values"), + }) + .collect(), + ), + RuntimeListShape::String => Self::String( + values + .iter() + .map(|value| match value { + RuntimeVal::ShortStr(value) => Arc::::from(value.as_str()), + RuntimeVal::Obj(handle) => match heap.get(*handle) { + Some(HeapValue::String(value)) => Arc::clone(value), + _ => unreachable!("shape scan only returns String for string values"), + }, + _ => unreachable!("shape scan only returns String for string values"), + }) + .collect(), + ), + } + } + #[inline] pub fn len(&self) -> usize { match self { @@ -279,6 +585,152 @@ impl TypedList { self.len() == 0 } + /// Append `value`, widening the representation only when it has to. + /// + /// `string_value` is the value's text when it is a string — a + /// `TypedList::String` holds `Arc`, which a `RuntimeVal` cannot carry + /// past seven bytes, so the caller reads it out of the heap *before* taking + /// the mutable borrow. + /// + /// This lived in the executor, so `vm::context`'s list methods — the path a + /// native/host caller takes — could not reach it and copied the whole list + /// through `from_runtime_values` instead. Same operation, two answers to + /// "does pushing change this list". + pub fn push(&mut self, value: RuntimeVal, string_value: Option>) -> anyhow::Result<()> { + let list = self; + match list { + TypedList::Mixed(values) if values.is_empty() => match (value, string_value) { + (RuntimeVal::Int(value), _) => *list = TypedList::Int(vec![value]), + (RuntimeVal::Float(value), _) => *list = TypedList::Float(vec![value]), + (RuntimeVal::Bool(value), _) => *list = TypedList::Bool(vec![value]), + (RuntimeVal::ShortStr(_) | RuntimeVal::Obj(_), Some(string_value)) => { + *list = TypedList::String(vec![string_value]); + } + (value, _) => values.push(value), + }, + TypedList::Mixed(values) => values.push(value), + TypedList::Int(values) => match value { + RuntimeVal::Int(value) => values.push(value), + value => { + let mut mixed = copy_numeric_list(values, RuntimeVal::Int); + mixed.push(value); + *list = TypedList::Mixed(mixed); + } + }, + TypedList::Float(values) => match value { + RuntimeVal::Float(value) => values.push(value), + // An `Int` into a `Float` list is the checker's numeric + // promotion, and it has already been *accepted*: + // `let xs = [1.5]; xs.push(9)` type-checks because `Int` is + // assignable to `Float`. Widening to `Mixed` here stores the + // `Int` unchanged, so `typeof(xs[1])` answered `Int` — the + // list stopped being the `List` the checker had just + // promised, and the native carrier (which does hold `9.0`) + // read back a different type for the same program. + // + // Materializing the promotion is what the acceptance meant. + // The reverse is not symmetric and is left alone: `Float` into + // an `Int` list is a narrowing the checker rejects, so it is + // reachable only through an erased type, where widening is the + // dynamic behaviour. + RuntimeVal::Int(value) => values.push(value as f64), + value => { + let mut mixed = copy_numeric_list(values, RuntimeVal::Float); + mixed.push(value); + *list = TypedList::Mixed(mixed); + } + }, + TypedList::Bool(values) => match value { + RuntimeVal::Bool(value) => values.push(value), + value => { + let mut mixed = copy_numeric_list(values, RuntimeVal::Bool); + mixed.push(value); + *list = TypedList::Mixed(mixed); + } + }, + TypedList::String(values) => match string_value { + Some(value) => values.push(value), + None => { + anyhow::bail!("internal error: typed string list push must be materialized before mutable borrow") + } + }, + } + Ok(()) + } + + /// Drop every element, keeping the representation. + /// + /// `Map` and `Set` have had this; a list did not, though the method table in + /// `docs/stdlib.md` listed it — "one operation, one name, across every + /// container" with one container missing. + pub fn clear(&mut self) { + match self { + Self::Mixed(values) => values.clear(), + Self::Int(values) => values.clear(), + Self::Float(values) => values.clear(), + Self::Bool(values) => values.clear(), + Self::String(values) => values.clear(), + } + } + + /// Drop everything from `at` on, keeping the representation. + /// + /// What `pop` and `remove_at` need: a list is mutable in LK (`xs[0] = 9` + /// and `push` both change it in place), so the methods that take an element + /// *out* have to change it too. `pop` used to read the last element and + /// leave it there. + pub fn truncate(&mut self, at: usize) { + match self { + Self::Mixed(values) => values.truncate(at), + Self::Int(values) => values.truncate(at), + Self::Float(values) => values.truncate(at), + Self::Bool(values) => values.truncate(at), + Self::String(values) => values.truncate(at), + } + } + + /// Remove the element at `index`, keeping the representation and the order + /// of the rest. + pub fn remove_at(&mut self, index: usize) { + match self { + Self::Mixed(values) => { + values.remove(index); + } + Self::Int(values) => { + values.remove(index); + } + Self::Float(values) => { + values.remove(index); + } + Self::Bool(values) => { + values.remove(index); + } + Self::String(values) => { + values.remove(index); + } + } + } + + /// A copy of `[start, start + len)`, clamped to what is actually there. + /// + /// This is what materializing a [`SliceValue`] costs — the operation the + /// window exists to avoid, so callers should be the ones that genuinely + /// need every element at once (`to_list`, display). + pub fn window(&self, start: usize, len: usize) -> Self { + let start = start.min(self.len()); + let end = (start + len).min(self.len()); + fn copy(values: &[T], start: usize, end: usize) -> Vec { + values[start..end].to_vec() + } + match self { + Self::Mixed(values) => Self::Mixed(copy(values, start, end)), + Self::Int(values) => Self::Int(copy(values, start, end)), + Self::Float(values) => Self::Float(copy(values, start, end)), + Self::Bool(values) => Self::Bool(copy(values, start, end)), + Self::String(values) => Self::String(copy(values, start, end)), + } + } + pub fn slice_from(&self, start: usize) -> Self { match self { Self::Mixed(values) => Self::Mixed(copy_slice_tail(values, start)), @@ -325,30 +777,35 @@ impl TypedList { } } - /// Collect all elements into an owned Vec. - pub fn collect_owned(&self) -> Vec { - match self { + /// Every element as an owned `Vec`, without allocating. + /// + /// `None` when an element cannot be produced without a heap — a string + /// past `ShortStr`'s inline limit. Callers that can allocate should read + /// elements through `Executor::typed_list_element_allocating` instead. + /// + /// This used to answer such an element with `ShortStr::new(..).unwrap()`, + /// in the branch reached exactly when that returns `None`. The comment + /// beside it admitted the hazard — "longer will fail here. In practice, + /// iter/unique strings in examples are short" — and `xs[0..2]` over a list + /// of long strings duly panicked. + pub fn collect_owned(&self) -> Option> { + Some(match self { Self::Mixed(values) => values.clone(), Self::Int(values) => values.iter().copied().map(RuntimeVal::Int).collect(), Self::Float(values) => values.iter().copied().map(RuntimeVal::Float).collect(), Self::Bool(values) => values.iter().copied().map(RuntimeVal::Bool).collect(), Self::String(values) => { let mut out = Vec::with_capacity(values.len()); - for s in values { - if let Some(short) = ShortStr::new(s.as_ref()) { - out.push(RuntimeVal::ShortStr(short)); - } else { - // Can't allocate here without &mut HeapStore, use ShortStr or skip - // This path is only used for the core_methods runtime, which will - // re-check ShortStr. Fall back to ShortStr only. - // Short strings up to 11 chars are fine; longer will fail here. - // In practice, iter/unique strings in examples are short. - out.push(RuntimeVal::ShortStr(ShortStr::new(s.as_ref()).unwrap())); - } + for text in values { + // An element past the inline limit needs a heap + // allocation, and this method has no `&mut HeapStore`. + // Nothing sensible can be produced for it here, so the + // whole call declines rather than inventing a value. + out.push(RuntimeVal::ShortStr(ShortStr::new(text.as_ref())?)); } out } - } + }) } } @@ -362,7 +819,13 @@ fn copy_slice_tail(values: &[T], start: usize) -> Vec { impl PartialEq for TypedList { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Mixed(left), Self::Mixed(right)) => left == right, + (Self::Mixed(left), Self::Mixed(right)) => { + left.len() == right.len() + && left + .iter() + .zip(right.iter()) + .all(|(left, right)| left.same_value_or_handle(right)) + } (Self::Int(left), Self::Int(right)) => left == right, (Self::Float(left), Self::Float(right)) => left == right, (Self::Bool(left), Self::Bool(right)) => left == right, @@ -378,34 +841,46 @@ fn typed_list_entries_equal_no_heap(left: &TypedList, right: &TypedList) -> bool fn typed_list_item_equal_no_heap(left: &TypedList, left_index: usize, right: &TypedList, right_index: usize) -> bool { match (left, right) { - (TypedList::Mixed(left), TypedList::Mixed(right)) => left[left_index] == right[right_index], + (TypedList::Mixed(left), TypedList::Mixed(right)) => left[left_index].same_value_or_handle(&right[right_index]), (TypedList::Int(left), TypedList::Int(right)) => left[left_index] == right[right_index], (TypedList::Float(left), TypedList::Float(right)) => left[left_index] == right[right_index], (TypedList::Bool(left), TypedList::Bool(right)) => left[left_index] == right[right_index], (TypedList::String(left), TypedList::String(right)) => left[left_index] == right[right_index], - (TypedList::Int(left), TypedList::Mixed(right)) => right[right_index] == RuntimeVal::Int(left[left_index]), - (TypedList::Mixed(left), TypedList::Int(right)) => left[left_index] == RuntimeVal::Int(right[right_index]), - (TypedList::Float(left), TypedList::Mixed(right)) => right[right_index] == RuntimeVal::Float(left[left_index]), - (TypedList::Mixed(left), TypedList::Float(right)) => left[left_index] == RuntimeVal::Float(right[right_index]), - (TypedList::Bool(left), TypedList::Mixed(right)) => right[right_index] == RuntimeVal::Bool(left[left_index]), - (TypedList::Mixed(left), TypedList::Bool(right)) => left[left_index] == RuntimeVal::Bool(right[right_index]), + (TypedList::Int(left), TypedList::Mixed(right)) => { + right[right_index].same_value_or_handle(&RuntimeVal::Int(left[left_index])) + } + (TypedList::Mixed(left), TypedList::Int(right)) => { + left[left_index].same_value_or_handle(&RuntimeVal::Int(right[right_index])) + } + (TypedList::Float(left), TypedList::Mixed(right)) => { + right[right_index].same_value_or_handle(&RuntimeVal::Float(left[left_index])) + } + (TypedList::Mixed(left), TypedList::Float(right)) => { + left[left_index].same_value_or_handle(&RuntimeVal::Float(right[right_index])) + } + (TypedList::Bool(left), TypedList::Mixed(right)) => { + right[right_index].same_value_or_handle(&RuntimeVal::Bool(left[left_index])) + } + (TypedList::Mixed(left), TypedList::Bool(right)) => { + left[left_index].same_value_or_handle(&RuntimeVal::Bool(right[right_index])) + } (TypedList::String(left), TypedList::Mixed(right)) => ShortStr::new(&left[left_index]) .map(RuntimeVal::ShortStr) - .is_some_and(|value| right[right_index] == value), + .is_some_and(|value| right[right_index].same_value_or_handle(&value)), (TypedList::Mixed(left), TypedList::String(right)) => ShortStr::new(&right[right_index]) .map(RuntimeVal::ShortStr) - .is_some_and(|value| left[left_index] == value), + .is_some_and(|value| left[left_index].same_value_or_handle(&value)), _ => false, } } #[derive(Clone, Debug)] pub enum TypedMap { - Mixed(FastHashMap), - StringMixed(FastHashMap, RuntimeVal>), - StringInt(FastHashMap, i64>), - StringFloat(FastHashMap, f64>), - StringBool(FastHashMap, bool>), + Mixed(ValueMap), + StringMixed(ValueMap, RuntimeVal>), + StringInt(ValueMap, i64>), + StringFloat(ValueMap, f64>), + StringBool(ValueMap, bool>), } /// Build a string-keyed [`TypedMap`] from `(key, value)` pairs. Intended for @@ -451,14 +926,12 @@ impl TypedMap { pub fn get_str(&self, key: &str) -> Option { match self { - Self::Mixed(values) => { - if let Some(value) = - ShortStr::new(key).and_then(|key| values.get(&RuntimeMapKey::ShortStr(key)).cloned()) - { - return Some(value); - } - values.get(&RuntimeMapKey::String(Arc::::from(key))).cloned() - } + // One lookup: the text decides the representation, so there is no + // second one to try. This used to probe `ShortStr` and then + // `String`, which papered over the promotion writing the wrong + // variant — and only here, which is why `m["a"]` worked while + // `"a" in m` did not. + Self::Mixed(values) => values.get(&RuntimeMapKey::from_text(key)).cloned(), Self::StringMixed(values) => values.get(key).cloned(), Self::StringInt(values) => values.get(key).copied().map(RuntimeVal::Int), Self::StringFloat(values) => values.get(key).copied().map(RuntimeVal::Float), @@ -477,22 +950,22 @@ impl TypedMap { } Self::StringMixed(entries) => { for (k, v) in entries { - out.push((RuntimeMapKey::String(k.clone()), *v)); + out.push((RuntimeMapKey::from_shared(k.clone()), *v)); } } Self::StringInt(entries) => { for (k, v) in entries { - out.push((RuntimeMapKey::String(k.clone()), RuntimeVal::Int(*v))); + out.push((RuntimeMapKey::from_shared(k.clone()), RuntimeVal::Int(*v))); } } Self::StringFloat(entries) => { for (k, v) in entries { - out.push((RuntimeMapKey::String(k.clone()), RuntimeVal::Float(*v))); + out.push((RuntimeMapKey::from_shared(k.clone()), RuntimeVal::Float(*v))); } } Self::StringBool(entries) => { for (k, v) in entries { - out.push((RuntimeMapKey::String(k.clone()), RuntimeVal::Bool(*v))); + out.push((RuntimeMapKey::from_shared(k.clone()), RuntimeVal::Bool(*v))); } } } @@ -519,10 +992,10 @@ impl TypedMap { { let key = Arc::::from(key_str); *self = match value { - RuntimeVal::Int(value) => Self::StringInt(fast_hash_map_from_iter([(key, value)])), - RuntimeVal::Float(value) => Self::StringFloat(fast_hash_map_from_iter([(key, value)])), - RuntimeVal::Bool(value) => Self::StringBool(fast_hash_map_from_iter([(key, value)])), - value => Self::StringMixed(fast_hash_map_from_iter([(key, value)])), + RuntimeVal::Int(value) => Self::StringInt(value_map_from_iter([(key, value)])), + RuntimeVal::Float(value) => Self::StringFloat(value_map_from_iter([(key, value)])), + RuntimeVal::Bool(value) => Self::StringBool(value_map_from_iter([(key, value)])), + value => Self::StringMixed(value_map_from_iter([(key, value)])), }; return; } @@ -551,7 +1024,7 @@ impl TypedMap { } value => { let key = Arc::::from(key_str); - let mut mixed = fast_hash_map_new(); + let mut mixed = value_map_new(); for (k, v) in values.iter() { mixed.insert(k.clone(), RuntimeVal::Int(*v)); } @@ -575,7 +1048,7 @@ impl TypedMap { } value => { let key = Arc::::from(key_str); - let mut mixed = fast_hash_map_new(); + let mut mixed = value_map_new(); for (k, v) in values.iter() { mixed.insert(k.clone(), RuntimeVal::Float(*v)); } @@ -599,7 +1072,7 @@ impl TypedMap { } value => { let key = Arc::::from(key_str); - let mut mixed = fast_hash_map_new(); + let mut mixed = value_map_new(); for (k, v) in values.iter() { mixed.insert(k.clone(), RuntimeVal::Bool(*v)); } @@ -615,33 +1088,33 @@ impl TypedMap { } fn materialize_string_map_to_mixed(&mut self, key: RuntimeMapKey, value: RuntimeVal) { - let mut mixed = match core::mem::replace(self, Self::Mixed(fast_hash_map_new())) { + let mut mixed = match core::mem::replace(self, Self::Mixed(value_map_new())) { Self::Mixed(values) => values, Self::StringMixed(values) => { - let mut mixed = fast_hash_map_new(); + let mut mixed = value_map_new(); for (key, value) in values { - mixed.insert(RuntimeMapKey::String(key), value); + mixed.insert(RuntimeMapKey::from_shared(key), value); } mixed } Self::StringInt(values) => { - let mut mixed = fast_hash_map_new(); + let mut mixed = value_map_new(); for (key, value) in values { - mixed.insert(RuntimeMapKey::String(key), RuntimeVal::Int(value)); + mixed.insert(RuntimeMapKey::from_shared(key), RuntimeVal::Int(value)); } mixed } Self::StringFloat(values) => { - let mut mixed = fast_hash_map_new(); + let mut mixed = value_map_new(); for (key, value) in values { - mixed.insert(RuntimeMapKey::String(key), RuntimeVal::Float(value)); + mixed.insert(RuntimeMapKey::from_shared(key), RuntimeVal::Float(value)); } mixed } Self::StringBool(values) => { - let mut mixed = fast_hash_map_new(); + let mut mixed = value_map_new(); for (key, value) in values { - mixed.insert(RuntimeMapKey::String(key), RuntimeVal::Bool(value)); + mixed.insert(RuntimeMapKey::from_shared(key), RuntimeVal::Bool(value)); } mixed } @@ -653,24 +1126,28 @@ impl TypedMap { /// Remove a key from the map, returning the removed value if present. /// For typed string maps, if the key type doesn't match (e.g., integer key on StringInt map), /// returns None without modification. + /// + /// `shift_remove`, not `swap_remove`: the survivors keep their order, which + /// is the guarantee the carrier exists for. It costs a memmove of the tail, + /// and a delete that silently reordered the rest would cost the guarantee. pub fn remove(&mut self, key: &RuntimeMapKey) -> Option { match self { - Self::Mixed(entries) => entries.remove(key), + Self::Mixed(entries) => entries.shift_remove(key), Self::StringMixed(entries) => { let key_str = key.as_str()?; - entries.remove(key_str) + entries.shift_remove(key_str) } Self::StringInt(entries) => { let key_str = key.as_str()?; - entries.remove(key_str).map(RuntimeVal::Int) + entries.shift_remove(key_str).map(RuntimeVal::Int) } Self::StringFloat(entries) => { let key_str = key.as_str()?; - entries.remove(key_str).map(RuntimeVal::Float) + entries.shift_remove(key_str).map(RuntimeVal::Float) } Self::StringBool(entries) => { let key_str = key.as_str()?; - entries.remove(key_str).map(RuntimeVal::Bool) + entries.shift_remove(key_str).map(RuntimeVal::Bool) } } } @@ -682,13 +1159,9 @@ impl TypedMap { /// runtime mirrors this construction; the lkrt test compares against this /// function so any drift (hasher, table layout, key shape) fails loudly. pub fn typed_map_iteration_keys<'a>(entries: impl Iterator) -> Vec { - let mut stage1 = fast_hash_map_new(); + let mut stage1 = value_map_new(); for (key, value) in entries { - let key = match ShortStr::new(key) { - Some(short) => RuntimeMapKey::ShortStr(short), - None => RuntimeMapKey::String(Arc::from(key)), - }; - stage1.insert(key, RuntimeVal::Int(value)); + stage1.insert(RuntimeMapKey::from_text(key), RuntimeVal::Int(value)); } match typed_map_from_entries(stage1) { TypedMap::StringInt(map) => map.keys().map(|k| k.to_string()).collect(), @@ -696,7 +1169,61 @@ pub fn typed_map_iteration_keys<'a>(entries: impl Iterator) -> TypedMap { +/// Test-support for lkrt's set-order conformance: the member iteration order +/// of a `Set` built by inserting the given strings in order. +/// +/// A set has no second stage — `RuntimeSet` *is* the `FastHashSet`, so the +/// order is a function of the key hashes and this one insertion sequence. That +/// is only mirrorable if the native side keys its set by the same shape, which +/// is why `lkrt` has exactly one `RtKey`. +pub fn set_iteration_order(members: impl Iterator) -> Vec { + let mut set = fast_hash_set_new(); + for member in members { + set.insert(match &member { + MirrorMember::Int(v) => RuntimeMapKey::Int(*v), + MirrorMember::Str(v) => RuntimeMapKey::from_text(v), + }); + } + set.iter() + .map(|key| match key { + RuntimeMapKey::Int(v) => MirrorMember::Int(*v), + other => MirrorMember::Str(other.as_str().expect("string key").to_string()), + }) + .collect() +} + +/// The member kinds [`set_iteration_order`] round-trips. Deliberately not +/// `RuntimeMapKey` itself: the point of the test is that lkrt does *not* get to +/// see the VM's key type, only the values. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MirrorMember { + Int(i64), + Str(String), +} + +/// The same, for an **int**-keyed literal — where the shaping is different in +/// the way that matters: a non-string key makes [`typed_map_from_entries`] +/// return `Mixed`, which *is* the stage-1 table. There is no stage 2, so a +/// native carrier has to be built by replaying the same insertion sequence +/// rather than by iterating stage 1 into a second table. +pub fn typed_map_iteration_int_keys(entries: impl Iterator) -> Vec { + let mut stage1 = value_map_new(); + for (key, value) in entries { + stage1.insert(RuntimeMapKey::Int(key), RuntimeVal::Int(value)); + } + match typed_map_from_entries(stage1) { + TypedMap::Mixed(map) => map + .keys() + .map(|k| match k { + RuntimeMapKey::Int(i) => *i, + other => unreachable!("int literal keys stay Int, got {other:?}"), + }) + .collect(), + other => unreachable!("an int-keyed literal always shapes to Mixed, got {other:?}"), + } +} + +pub(crate) fn typed_map_from_entries(entries: ValueMap) -> TypedMap { if entries.is_empty() { return TypedMap::Mixed(entries); } @@ -741,19 +1268,17 @@ pub(crate) fn typed_map_from_entries(entries: FastHashMap, -) -> FastHashMap, RuntimeVal> { - let mut out = fast_hash_map_new(); + entries: ValueMap, +) -> ValueMap, RuntimeVal> { + let mut out = value_map_new(); for (key, value) in entries { out.insert(key.as_arc_str().expect("validated string key"), value); } out } -fn string_int_entries_from_runtime_entries( - entries: FastHashMap, -) -> FastHashMap, i64> { - let mut out = fast_hash_map_new(); +fn string_int_entries_from_runtime_entries(entries: ValueMap) -> ValueMap, i64> { + let mut out = value_map_new(); for (key, value) in entries { let RuntimeVal::Int(value) = value else { unreachable!("validated int map value"); @@ -763,10 +1288,8 @@ fn string_int_entries_from_runtime_entries( out } -fn string_float_entries_from_runtime_entries( - entries: FastHashMap, -) -> FastHashMap, f64> { - let mut out = fast_hash_map_new(); +fn string_float_entries_from_runtime_entries(entries: ValueMap) -> ValueMap, f64> { + let mut out = value_map_new(); for (key, value) in entries { let RuntimeVal::Float(value) = value else { unreachable!("validated float map value"); @@ -776,10 +1299,8 @@ fn string_float_entries_from_runtime_entries( out } -fn string_bool_entries_from_runtime_entries( - entries: FastHashMap, -) -> FastHashMap, bool> { - let mut out = fast_hash_map_new(); +fn string_bool_entries_from_runtime_entries(entries: ValueMap) -> ValueMap, bool> { + let mut out = value_map_new(); for (key, value) in entries { let RuntimeVal::Bool(value) = value else { unreachable!("validated bool map value"); @@ -792,8 +1313,18 @@ fn string_bool_entries_from_runtime_entries( impl PartialEq for TypedMap { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Mixed(left), Self::Mixed(right)) => left == right, - (Self::StringMixed(left), Self::StringMixed(right)) => left == right, + (Self::Mixed(left), Self::Mixed(right)) => { + left.len() == right.len() + && left + .iter() + .all(|(key, value)| right.get(key).is_some_and(|other| value.same_value_or_handle(other))) + } + (Self::StringMixed(left), Self::StringMixed(right)) => { + left.len() == right.len() + && left + .iter() + .all(|(key, value)| right.get(key).is_some_and(|other| value.same_value_or_handle(other))) + } (Self::StringInt(left), Self::StringInt(right)) => left == right, (Self::StringFloat(left), Self::StringFloat(right)) => left == right, (Self::StringBool(left), Self::StringBool(right)) => left == right, @@ -805,7 +1336,7 @@ impl PartialEq for TypedMap { fn typed_map_entries_equal(left: &TypedMap, right: &TypedMap) -> bool { left.len() == right.len() && typed_map_entries_all(left, |key, value| { - typed_map_entry_value(right, &key).is_some_and(|right| right == value) + typed_map_entry_value(right, &key).is_some_and(|right| right.same_value_or_handle(&value)) }) } @@ -814,7 +1345,7 @@ fn typed_map_entries_all(map: &TypedMap, mut visit: impl FnMut(RuntimeMapKey, Ru TypedMap::Mixed(entries) => entries.iter().all(|(key, value)| visit(key.clone(), *value)), TypedMap::StringMixed(entries) => entries .iter() - .all(|(key, value)| visit(RuntimeMapKey::String(key.clone()), *value)), + .all(|(key, value)| visit(RuntimeMapKey::from_shared(key.clone()), *value)), TypedMap::StringInt(entries) => entries .iter() .all(|(key, value)| visit(RuntimeMapKey::String(key.clone()), RuntimeVal::Int(*value))), @@ -857,6 +1388,19 @@ fn typed_map_entry_value(map: &TypedMap, key: &RuntimeMapKey) -> Option), - Obj(HeapRef), } impl RuntimeMapKey { + /// The key a piece of text is stored under — **one** representation per + /// text, so a lookup finds what an insert wrote. + /// + /// Short text is a `ShortStr` (inline, no allocation), anything longer a + /// `String`. That this is a function of the text alone is not a detail: + /// the enum derives `Eq` and `Hash`, so the two variants holding the same + /// characters are *different keys*. + /// + /// Promoting a typed string map to `Mixed` used to write `String` for every + /// key, short ones included. `m["a"]`, `m.get("a")`, `m.keys()` and + /// `println(m)` all showed the entry, while `"a" in m` and `m.has("a")` + /// answered `false` and `m.delete("a")` removed nothing — those three build + /// the lookup key from the text and got the other variant. + pub fn from_text(text: &str) -> Self { + match ShortStr::new(text) { + Some(short) => Self::ShortStr(short), + None => Self::String(Arc::from(text)), + } + } + + /// [`Self::from_text`] for text already behind an `Arc` — a typed string + /// carrier's key is one, and long text keeps the allocation it has. + pub fn from_shared(text: Arc) -> Self { + match ShortStr::new(&text) { + Some(short) => Self::ShortStr(short), + None => Self::String(text), + } + } + + /// The key a value is used under — the only conversion. + /// + /// There were two, and they disagreed about the case that matters. The + /// executor's (`m[k] = v`) rejected a list; the container methods' accepted + /// one as `Obj(handle)`, comparing by *handle*. So a set quietly kept + /// members it could never find again: + /// + /// ```text + /// let s = Set([]); + /// s.add([1, 2]); s.has([1, 2]) → false + /// s.add([1, 2]); s.len() → 2 + /// println(s) → Set([,]) + /// ``` + /// + /// A `Set` is a map's key set, so it answers the question the same way: a + /// value whose identity is its handle is not a key. Keying on a mutable + /// container by *value* is not the alternative — mutating the key would + /// lose the entry — which is why maps rejected it in the first place. + pub fn from_value(value: &RuntimeVal, heap: &HeapStore) -> anyhow::Result { + match value { + RuntimeVal::Nil => Ok(Self::Nil), + RuntimeVal::Bool(value) => Ok(Self::Bool(*value)), + RuntimeVal::Int(value) => Ok(Self::Int(*value)), + // `0.0` and `-0.0` are equal but hash differently, and `NaN` is not + // equal to itself: neither can index anything. + RuntimeVal::Float(_) => Err(anyhow::anyhow!("Float cannot be a map key or set member")), + RuntimeVal::ShortStr(value) => Ok(Self::ShortStr(*value)), + RuntimeVal::Obj(handle) => match heap.get(*handle) { + Some(HeapValue::String(value)) => Ok(Self::String(Arc::clone(value))), + Some(other) => Err(anyhow::anyhow!( + "{} cannot be a map key or set member: only nil, Bool, Int and String can", + other.type_name() + )), + None => Err(anyhow::anyhow!("heap object {} out of bounds", handle.index())), + }, + } + } + pub fn as_str(&self) -> Option<&str> { match self { Self::ShortStr(value) => Some(value.as_str()), @@ -883,15 +1493,77 @@ impl RuntimeMapKey { _ => None, } } + + /// The order a `Set` displays its members in: nil, then Bool, then Int by + /// value, then String by content. + /// + /// A stable order is the point — a set's hash iteration order is not + /// portable, so displaying one has to impose something. The display code + /// imposed it on the *rendered text* instead of the members, which made + /// `Set([1, 2, 10, 20, 3])` print `Set([1,10,2,20,3])` and + /// `Set([-1, -2, 5])` print `Set([-1,-2,5])`: an order that is neither + /// insertion, nor value, nor anything a reader can use. + /// + /// Not `derive(Ord)` either, and that is the reason this is a function + /// rather than one: the derive compares *variants*, so a string of 8 bytes + /// (`String`) would sort after every string of 7 (`ShortStr`) — + /// `Set(["ab", "aaaaaaaaaa"])` would come out `"ab"` first. A string's + /// representation is not part of its value anywhere else in the language, + /// and it is not here. + pub fn display_order(&self, other: &Self) -> core::cmp::Ordering { + fn kind(key: &RuntimeMapKey) -> u8 { + match key { + RuntimeMapKey::Nil => 0, + RuntimeMapKey::Bool(_) => 1, + RuntimeMapKey::Int(_) => 2, + RuntimeMapKey::ShortStr(_) | RuntimeMapKey::String(_) => 3, + } + } + kind(self).cmp(&kind(other)).then_with(|| match (self, other) { + (Self::Bool(a), Self::Bool(b)) => a.cmp(b), + (Self::Int(a), Self::Int(b)) => a.cmp(b), + _ => match (self.as_str(), other.as_str()) { + (Some(a), Some(b)) => a.cmp(b), + _ => core::cmp::Ordering::Equal, + }, + }) + } } #[cfg(test)] mod tests { use super::*; + /// The checker's numeric promotion survives into the representation. + /// + /// `xs.push(9)` on a `List` type-checks — `Int` is assignable to + /// `Float` — so the list is still a `List` afterwards. Widening to + /// `Mixed` and storing the `Int` unchanged made `typeof(xs[1])` answer + /// `Int`, which the native carrier (holding `9.0`) contradicts. + /// + /// The reverse stays a widening: `Float` into an `Int` list is a narrowing + /// the checker rejects, so it arrives only through an erased type, where + /// the dynamic answer is the right one. + #[test] + fn an_int_pushed_into_a_float_list_becomes_a_float() { + let mut list = TypedList::Float(vec![1.5]); + list.push(RuntimeVal::Int(9), None).expect("push"); + assert!( + matches!(&list, TypedList::Float(values) if values == &[1.5, 9.0]), + "an accepted promotion must be materialized, not widened away: {list:?}" + ); + + let mut narrowing = TypedList::Int(vec![1]); + narrowing.push(RuntimeVal::Float(1.5), None).expect("push"); + assert!( + matches!(&narrowing, TypedList::Mixed(_)), + "a narrowing arrives only through an erased type and stays dynamic: {narrowing:?}" + ); + } + #[test] fn runtime_entries_materialize_to_typed_string_maps() { - let mut entries = fast_hash_map_new(); + let mut entries = value_map_new(); entries.insert(RuntimeMapKey::String(Arc::::from("answer")), RuntimeVal::Int(42)); assert!(matches!( @@ -899,7 +1571,7 @@ mod tests { TypedMap::StringInt(values) if values.get("answer") == Some(&42) )); - let mut entries = fast_hash_map_new(); + let mut entries = value_map_new(); entries.insert( RuntimeMapKey::ShortStr(ShortStr::new("ok").expect("short")), RuntimeVal::Bool(true), @@ -909,7 +1581,7 @@ mod tests { TypedMap::StringBool(values) if values.get("ok") == Some(&true) )); - let mut entries = fast_hash_map_new(); + let mut entries = value_map_new(); entries.insert(RuntimeMapKey::Int(1), RuntimeVal::Int(42)); assert!(matches!(typed_map_from_entries(entries), TypedMap::Mixed(_))); } @@ -931,7 +1603,7 @@ mod tests { #[test] fn typed_map_get_and_set_preserve_specialized_backing_until_polluted() { - let mut map = TypedMap::StringInt(fast_hash_map_from_iter([(Arc::::from("answer"), 41)])); + let mut map = TypedMap::StringInt(value_map_from_iter([(Arc::::from("answer"), 41)])); assert_eq!( map.get(&RuntimeMapKey::ShortStr(ShortStr::new("answer").expect("short"))), @@ -959,7 +1631,7 @@ mod tests { #[test] fn empty_mixed_map_set_with_string_key_specializes_backing() { - let mut map = TypedMap::Mixed(fast_hash_map_new()); + let mut map = TypedMap::Mixed(value_map_new()); map.set( RuntimeMapKey::ShortStr(ShortStr::new("answer").expect("short")), @@ -975,31 +1647,32 @@ mod tests { #[test] fn typed_map_set_materializes_to_mixed_for_non_string_key() { - let mut map = TypedMap::StringBool(fast_hash_map_from_iter([(Arc::::from("ok"), true)])); + let mut map = TypedMap::StringBool(value_map_from_iter([(Arc::::from("ok"), true)])); map.set(RuntimeMapKey::Int(7), RuntimeVal::Bool(false)); assert!(matches!(map, TypedMap::Mixed(_))); assert_eq!(map.get_str("ok"), Some(RuntimeVal::Bool(true))); assert_eq!(map.get(&RuntimeMapKey::Int(7)), Some(RuntimeVal::Bool(false))); - assert_eq!( - map.get(&RuntimeMapKey::String(Arc::::from("ok"))), - Some(RuntimeVal::Bool(true)) - ); + // The carried-over key takes the representation the text decides, so a + // lookup built the same way finds it. This used to assert the + // `String` variant, which is what the promotion wrote for every key — + // and then `"ok" in m` and `m.has("ok")`, which build the key from the + // text, answered `false` for an entry `m["ok"]` returned. + assert_eq!(map.get(&RuntimeMapKey::from_text("ok")), Some(RuntimeVal::Bool(true))); + assert_eq!(map.get(&RuntimeMapKey::String(Arc::::from("ok"))), None); } #[test] fn typed_map_equality_compares_entries_without_materializing_vector() { - let typed = TypedMap::StringInt(fast_hash_map_from_iter([(Arc::::from("answer"), 42)])); - let string_mixed = TypedMap::StringMixed(fast_hash_map_from_iter([( - Arc::::from("answer"), - RuntimeVal::Int(42), - )])); - let exact_mixed = TypedMap::Mixed(fast_hash_map_from_iter([( + let typed = TypedMap::StringInt(value_map_from_iter([(Arc::::from("answer"), 42)])); + let string_mixed = + TypedMap::StringMixed(value_map_from_iter([(Arc::::from("answer"), RuntimeVal::Int(42))])); + let exact_mixed = TypedMap::Mixed(value_map_from_iter([( RuntimeMapKey::String(Arc::::from("answer")), RuntimeVal::Int(42), )])); - let short_key_mixed = TypedMap::Mixed(fast_hash_map_from_iter([( + let short_key_mixed = TypedMap::Mixed(value_map_from_iter([( RuntimeMapKey::ShortStr(ShortStr::new("answer").expect("short")), RuntimeVal::Int(42), )])); @@ -1044,20 +1717,48 @@ pub struct StreamCursorValue { pub roots: Vec, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SliceKind { - List, - String, -} - +/// A window over a list: `source[start .. start + len]`, without copying it. +/// +/// There used to be a `SliceKind` beside this, distinguishing a list window +/// from a *byte* window over a string. The byte one went with the `slice` +/// module: string positions are characters now, and code that wants bytes says +/// `s.bytes()`. One variant is not a choice, so the field is gone too. #[derive(Debug, Clone)] pub struct SliceValue { pub source: RuntimeVal, - pub kind: SliceKind, pub start: usize, + /// How long the window was when it was taken. Read [`SliceValue::live_len`] + /// instead — the source can shrink underneath it. pub len: usize, } +impl SliceValue { + /// How long the window is *now*, clamped to what the source still holds. + /// + /// A window does not copy, so `xs.pop()` can leave it pointing past the end + /// — and every reader used to answer that differently. For a window of 3 + /// over a list that lost its last element: + /// + /// ```text + /// s.len() → 3 s.to_list() → [1,2,nil] + /// println(s) → [1,2] s.last() → nil + /// s == [1,2] → false s.get(2) → nil + /// ``` + /// + /// Six answers to one question. Clamping is the one the rest of the + /// language already gives — reading past the end is `nil`, not an error — + /// and it makes `len()` agree with what the window will actually hand out. + pub fn live_len(&self, heap: &HeapStore) -> usize { + let RuntimeVal::Obj(handle) = self.source else { + return 0; + }; + let Some(HeapValue::List(list)) = heap.get(handle) else { + return 0; + }; + self.len.min(list.len().saturating_sub(self.start)) + } +} + #[derive(Clone)] pub struct ResourceValue { pub kind: &'static str, @@ -1113,25 +1814,139 @@ pub enum ResourceHandle { #[cfg(test)] mod layout { - /// `RuntimeObject` is the widest `HeapValue` variant, so its size is the - /// size of *every* heap cell — lists, maps and strings included. + /// Every heap cell is one `HeapValue`, so this size is what a list, a + /// string and a map each pay — including programs that declare no structs. /// /// This is a real budget, not a style rule. Adding the declaring module to /// an object's identity as a second `Arc` field pushed `HeapValue` - /// from 72 to 88 bytes and cost ~1.3% geometric mean on the workload suite, - /// on programs that declare no structs at all. Folding both halves behind - /// one `Arc` brought it to 64. + /// from 72 to 88 bytes and cost ~1.3% geometric mean on the workload suite. + /// Folding both halves behind one `Arc` brought it to 64. + /// + /// It is 72 again, and this time deliberately. Insertion-ordered value maps + /// (`util::value_map`) carry an entry vector beside the index table, which + /// is 8 bytes wider than a bare hash table, and `TypedMap`'s own + /// discriminant no longer fits in a niche on top of it. What it buys is in + /// that module's docs; the 24 bytes it *would* have cost were paid back by + /// deleting `RuntimeObject::field_slots`, which an ordered map makes + /// redundant. Measured on the workload suite: ~2% geometric mean, against a + /// 10% gate. + /// + /// The way back to 64, if it is ever wanted, is to flatten `TypedMap`'s + /// five variants into `HeapValue` so the two discriminants become one. #[test] fn heap_cells_stay_narrow() { - assert_eq!( - core::mem::size_of::(), + assert!( + core::mem::size_of::() <= 72, + "HeapValue grew to {} bytes ({} for RuntimeObject, {} for TypedMap) — every heap cell pays for this", core::mem::size_of::(), - "RuntimeObject still sets the heap cell size; re-read the budget below before widening it" + core::mem::size_of::(), + core::mem::size_of::() + ); + } +} + +fn copy_numeric_list(values: &[T], wrap: impl Fn(T) -> RuntimeVal) -> Vec { + let mut mixed = Vec::with_capacity(values.len() + 1); + mixed.extend(values.iter().copied().map(wrap)); + mixed +} + +/// Ascending order over floats that is *total*, which `partial_cmp` is not. +/// +/// `sort_by` may panic — "user-provided comparison function does not correctly +/// implement a total order" — and `partial_cmp(..).unwrap_or(Equal)` earns it: a +/// NaN reads equal to every value while those values stay ordered among +/// themselves, so the relation is not transitive. `xs.sort()` on a float list +/// holding a NaN therefore aborted the interpreter with a Rust panic, which `try` +/// cannot catch. Whether it fired depended on the data: 601 elements went +/// through, 60 did not, which is the worst kind of reachable. +/// +/// So NaN is *ordered* rather than equal-to-everything: all NaNs compare equal to +/// each other and greater than every number, and a sorted list reads as ascending +/// values with the not-a-numbers gathered at the end. +/// +/// `-0.0` and `0.0` stay equal here, where `f64::total_cmp` would separate them — +/// `==` in the language says they are equal, and `sort` disagreeing with `==` +/// about two values would be a second rule to remember for no gain. +/// +/// `lkrt`'s `list_sort!` mirrors this for the native backend. +pub fn compare_floats(left: f64, right: f64) -> core::cmp::Ordering { + match left.partial_cmp(&right) { + Some(ordering) => ordering, + // Unordered, so at least one side is NaN. + None => match (left.is_nan(), right.is_nan()) { + (true, true) => core::cmp::Ordering::Equal, + (true, false) => core::cmp::Ordering::Greater, + (false, true) => core::cmp::Ordering::Less, + // `partial_cmp` answers `None` only for a NaN, so this cannot happen; + // it is spelled out so the relation stays total if that ever changes. + (false, false) => core::cmp::Ordering::Equal, + }, + } +} + +#[cfg(test)] +mod compare_floats_tests { + use super::compare_floats; + // `alloc`, not the std prelude: this crate also builds without an OS. + use alloc::vec::Vec; + use core::cmp::Ordering; + + /// The property `sort_by` needs: transitivity across the NaN. + /// + /// `partial_cmp(..).unwrap_or(Equal)` fails exactly here — NaN == 1.0 and + /// NaN == 2.0 while 1.0 < 2.0 — and Rust's sort notices and panics. + #[test] + fn the_order_is_total_across_nan() { + let nan = f64::NAN; + assert_eq!(compare_floats(nan, nan), Ordering::Equal); + assert_eq!(compare_floats(nan, 1.0), Ordering::Greater); + assert_eq!(compare_floats(1.0, nan), Ordering::Less); + assert_eq!(compare_floats(nan, f64::INFINITY), Ordering::Greater); + // Zeroes stay equal, unlike `total_cmp`. + assert_eq!(compare_floats(-0.0, 0.0), Ordering::Equal); + assert_eq!(compare_floats(1.0, 2.0), Ordering::Less); + + // And a sort over the values that used to abort now completes. + let mut values: Vec = (0..60) + .map(|i| if i % 4 == 0 { f64::NAN } else { f64::from(60 - i) }) + .collect(); + values.sort_by(|left, right| compare_floats(*left, *right)); + assert!( + values[..45].windows(2).all(|pair| pair[0] <= pair[1]), + "the numbers come out ascending: {values:?}" ); assert!( - core::mem::size_of::() <= 64, - "HeapValue grew to {} bytes — every heap cell pays for this", - core::mem::size_of::() + values[45..].iter().all(|value| value.is_nan()), + "and the NaNs are gathered at the end: {values:?}" ); } } + +/// Whether a runtime value may be stored where `declared` is written. +/// +/// Scalars only. A container's declared element type is not something a single +/// value carries — `List` and `List` are the same `HeapValue::List` +/// at run time — so a container-typed field is not checked here, and neither is +/// `Any`, a union, or a named type. What is left is exactly the set a wrong +/// store corrupts silently, and the set the type checker's own assignability +/// rules answer the same way: an `Int` satisfies a `Float` field (the language +/// never coerces at a typed boundary, so it stays an `Int`), and `nil` +/// satisfies a nullable one. +pub fn value_satisfies_declared(value: &RuntimeVal, declared: &Type, heap: &HeapStore) -> bool { + if let Type::Optional(inner) = declared { + return matches!(value, RuntimeVal::Nil) || value_satisfies_declared(value, inner, heap); + } + let is_string = match value { + RuntimeVal::ShortStr(_) => true, + RuntimeVal::Obj(handle) => matches!(heap.get(*handle), Some(HeapValue::String(_))), + _ => false, + }; + match declared { + Type::Int => matches!(value, RuntimeVal::Int(_)), + Type::Float => matches!(value, RuntimeVal::Int(_) | RuntimeVal::Float(_)), + Type::Bool => matches!(value, RuntimeVal::Bool(_)), + Type::String => is_string, + _ => true, + } +} diff --git a/core/src/val/runtime_model/equality.rs b/core/src/val/runtime_model/equality.rs new file mode 100644 index 00000000..a72e084d --- /dev/null +++ b/core/src/val/runtime_model/equality.rs @@ -0,0 +1,440 @@ +//! `==` on runtime values — the only implementation. +//! +//! Equality needs the heap, so it cannot be a `PartialEq` impl (see +//! [`super::RuntimeVal`]). That made it a function, and a function got copied: +//! the executor had one, `vm::context::core_methods` had a second for +//! `contains` / `index_of` / `position` / `unique`, and they disagreed. The +//! second one materialized each list element as a `RuntimeVal` before +//! comparing, and a string element longer than seven bytes has no `RuntimeVal` +//! short of allocating — so it became `Nil`, and two `Nil`s are equal: +//! +//! ```text +//! [["abcdefghij"]].contains(["zzzzzzzzzz"]) → true +//! [["abc"]].contains(["zzz"]) → false +//! ``` +//! +//! The same seven-byte boundary that [`super::RuntimeVal`]'s doc comment +//! describes, reintroduced one layer down. Copies of a relation do not stay +//! equal to each other; there is one here now, and callers pass the heap. +//! +//! Comparison is depth-bounded — see [`super::MAX_VALUE_DEPTH`] for why. + +use alloc::sync::Arc; + +use anyhow::{Result, anyhow}; + +use super::{ + HeapRef, HeapStore, HeapValue, MAX_VALUE_DEPTH, RuntimeMapKey, RuntimeSet, RuntimeVal, TypedList, TypedMap, +}; + +/// `left == right`, by value, through `heap`. +pub fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore) -> Result { + Comparison { heap }.values(left, right, 0) +} + +/// `value == text`, where `text` is a string the caller already holds. +/// +/// The list comparisons need this: a `TypedList::String` element is an +/// `Arc` with no `RuntimeVal` short of a heap allocation. +pub fn runtime_value_equals_str(value: &RuntimeVal, text: &str, heap: &HeapStore) -> Result { + Comparison { heap }.value_equals_str(value, text) +} + +struct Comparison<'a> { + heap: &'a HeapStore, +} + +impl<'a> Comparison<'a> { + fn fetch(&self, handle: HeapRef) -> Result<&'a HeapValue> { + self.heap + .get(handle) + .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index())) + } + + /// The text of a value that is a string, without allocating. + /// + /// A `ShortStr` lives inline in the value, a long one in the heap, so the + /// answer borrows from whichever is shorter-lived. + fn as_str<'v>(&'v self, value: &'v RuntimeVal) -> Result> { + Ok(match value { + RuntimeVal::ShortStr(value) => Some(value.as_str()), + RuntimeVal::Obj(handle) => match self.fetch(*handle)? { + HeapValue::String(text) => Some(text.as_ref()), + _ => None, + }, + _ => None, + }) + } + + fn value_equals_str(&self, value: &RuntimeVal, text: &str) -> Result { + Ok(self.as_str(value)? == Some(text)) + } + + fn values(&self, left: &RuntimeVal, right: &RuntimeVal, depth: u32) -> Result { + Ok(match (left, right) { + (RuntimeVal::Nil, RuntimeVal::Nil) => true, + (RuntimeVal::Bool(left), RuntimeVal::Bool(right)) => left == right, + (RuntimeVal::Int(left), RuntimeVal::Int(right)) => left == right, + // By value, not by bits: `0.0 == -0.0` and `NaN != NaN`, as IEEE + // says and as every other arm here does. + (RuntimeVal::Float(left), RuntimeVal::Float(right)) => left == right, + (RuntimeVal::Int(left), RuntimeVal::Float(right)) => *left as f64 == *right, + (RuntimeVal::Float(left), RuntimeVal::Int(right)) => *left == *right as f64, + (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) if left == right => true, + (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) => { + let left = self.fetch(*left)?; + let right = self.fetch(*right)?; + self.heap_values(left, right, depth)? + } + // The one remaining mixed pair that can be equal: the same text + // reaches `ShortStr` or the heap depending only on its length. + _ => match (self.as_str(left)?, self.as_str(right)?) { + (Some(left), Some(right)) => left == right, + _ => false, + }, + }) + } + + /// One level down. Every recursive step goes through here so the bound is + /// stated once. + fn nested(&self, left: &RuntimeVal, right: &RuntimeVal, depth: u32) -> Result { + if depth >= MAX_VALUE_DEPTH { + return Err(anyhow!( + "comparison nested deeper than {MAX_VALUE_DEPTH} levels; the values are cyclic or too deeply nested to compare" + )); + } + self.values(left, right, depth + 1) + } + + fn heap_values(&self, left: &HeapValue, right: &HeapValue, depth: u32) -> Result { + Ok(match (left, right) { + (HeapValue::String(left), HeapValue::String(right)) => left == right, + (HeapValue::Bytes(left), HeapValue::Bytes(right)) => left == right, + (HeapValue::List(left), HeapValue::List(right)) => self.lists(left, right, depth)?, + // A window compares by its elements, like everything else that has + // elements — including against the list it windows. + // `live_len`, not `len`: the source can have shrunk since the + // window was taken, and a window that outran its source used to + // compare unequal to *everything* while printing its shortened + // contents. + (HeapValue::Slice(left), HeapValue::Slice(right)) => self.slice_ranges( + left.source, + left.start, + left.live_len(self.heap), + right.source, + right.start, + right.live_len(self.heap), + depth, + )?, + (HeapValue::Slice(left), HeapValue::List(right)) => { + self.slice_and_list(left.source, left.start, left.live_len(self.heap), right, depth)? + } + (HeapValue::List(left), HeapValue::Slice(right)) => { + self.slice_and_list(right.source, right.start, right.live_len(self.heap), left, depth)? + } + (HeapValue::Map(left), HeapValue::Map(right)) => self.maps(left, right, depth)?, + (HeapValue::Set(left), HeapValue::Set(right)) => sets_equal(left, right), + // A struct is a product of values, and every other aggregate here + // compares by its contents. This one used to compare by *handle*, + // so `P { x: 1 } == P { x: 1 }` was false — and silently: + // `[p].contains(p_equal)`, `index_of`, `unique` all inherited it. + (HeapValue::Object(left), HeapValue::Object(right)) => self.objects(left, right, depth)?, + // A channel and a task are *identities*, and the identity is the id + // — not the heap object naming it. A value that crosses a channel + // is deep-copied into a fresh object, so comparing by handle said a + // channel sent through a channel was not the one that came out, + // while `send`ing to what came out reached the original. Two + // answers to the same question. + (HeapValue::Channel(left), HeapValue::Channel(right)) => left.id == right.id, + (HeapValue::Task(left), HeapValue::Task(right)) => left.id == right.id, + _ => false, + }) + } + + /// Two structs: the same declared type, and every field equal. + /// + /// The type check is by `DeclaredType` — module *and* name — because a name + /// alone is only unique within one module (see `val::TypeScope`), and two + /// modules' identically-shaped `Point`s are not the same type. + fn objects(&self, left: &super::RuntimeObject, right: &super::RuntimeObject, depth: u32) -> Result { + // Scope and name, not the whole `DeclaredType`: its `fields` list is + // empty when the declaration is out of reach (another module, or a + // host-built object), and comparing it would make the same type unequal + // to itself across that boundary. + if left.ty.scope != right.ty.scope || left.ty.name != right.ty.name || left.fields.len() != right.fields.len() { + return Ok(false); + } + for (name, left_value) in &left.fields { + let Some(right_value) = right.fields.get(name) else { + return Ok(false); + }; + if !self.values(left_value, right_value, depth + 1)? { + return Ok(false); + } + } + Ok(true) + } + + /// The list a window reads through to, or `None` if the source is gone. + fn slice_source_list(&self, source: RuntimeVal) -> Option<&'a TypedList> { + let RuntimeVal::Obj(handle) = source else { + return None; + }; + match self.heap.get(handle) { + Some(HeapValue::List(list)) => Some(list), + _ => None, + } + } + + /// Two windows, compared through their sources. Nothing is materialized: + /// element comparison already works by index, so a window only offsets the + /// index it asks for. + #[allow(clippy::too_many_arguments)] + fn slice_ranges( + &self, + left_source: RuntimeVal, + left_start: usize, + left_len: usize, + right_source: RuntimeVal, + right_start: usize, + right_len: usize, + depth: u32, + ) -> Result { + if left_len != right_len { + return Ok(false); + } + let (Some(left), Some(right)) = ( + self.slice_source_list(left_source), + self.slice_source_list(right_source), + ) else { + return Ok(false); + }; + if left_start + left_len > left.len() || right_start + right_len > right.len() { + return Ok(false); + } + for index in 0..left_len { + if !self.list_items(left, left_start + index, right, right_start + index, depth)? { + return Ok(false); + } + } + Ok(true) + } + + /// A window against a whole list. + fn slice_and_list( + &self, + source: RuntimeVal, + start: usize, + len: usize, + other: &TypedList, + depth: u32, + ) -> Result { + if len != other.len() { + return Ok(false); + } + let Some(list) = self.slice_source_list(source) else { + return Ok(false); + }; + if start + len > list.len() { + return Ok(false); + } + for index in 0..len { + if !self.list_items(list, start + index, other, index, depth)? { + return Ok(false); + } + } + Ok(true) + } + + fn lists(&self, left: &TypedList, right: &TypedList, depth: u32) -> Result { + if left.len() != right.len() { + return Ok(false); + } + // Same representation on both sides: the whole vector at once, and no + // heap lookups at all. + match (left, right) { + (TypedList::Int(left), TypedList::Int(right)) => return Ok(left == right), + (TypedList::Float(left), TypedList::Float(right)) => return Ok(left == right), + (TypedList::Bool(left), TypedList::Bool(right)) => return Ok(left == right), + (TypedList::String(left), TypedList::String(right)) => return Ok(left == right), + _ => {} + } + for index in 0..left.len() { + if !self.list_items(left, index, right, index, depth)? { + return Ok(false); + } + } + Ok(true) + } + + fn list_items( + &self, + left: &TypedList, + left_index: usize, + right: &TypedList, + right_index: usize, + depth: u32, + ) -> Result { + match (left, right) { + (TypedList::Mixed(left), TypedList::Mixed(right)) => { + self.nested(&left[left_index], &right[right_index], depth) + } + (TypedList::Mixed(left), TypedList::String(right)) => { + self.value_equals_str(&left[left_index], &right[right_index]) + } + (TypedList::String(left), TypedList::Mixed(right)) => { + self.value_equals_str(&right[right_index], &left[left_index]) + } + (TypedList::Int(left), _) => { + self.item_against(RuntimeVal::Int(left[left_index]), right, right_index, depth) + } + (TypedList::Float(left), _) => { + self.item_against(RuntimeVal::Float(left[left_index]), right, right_index, depth) + } + (TypedList::Bool(left), _) => { + self.item_against(RuntimeVal::Bool(left[left_index]), right, right_index, depth) + } + (TypedList::String(left), _) => self.string_item_against(&left[left_index], right, right_index), + (TypedList::Mixed(left), _) => self.item_against(left[left_index], right, right_index, depth), + } + } + + fn item_against(&self, left: RuntimeVal, right: &TypedList, right_index: usize, depth: u32) -> Result { + match right { + TypedList::Mixed(right) => self.nested(&left, &right[right_index], depth), + TypedList::Int(right) => self.nested(&left, &RuntimeVal::Int(right[right_index]), depth), + TypedList::Float(right) => self.nested(&left, &RuntimeVal::Float(right[right_index]), depth), + TypedList::Bool(right) => self.nested(&left, &RuntimeVal::Bool(right[right_index]), depth), + TypedList::String(right) => self.value_equals_str(&left, &right[right_index]), + } + } + + fn string_item_against(&self, left: &Arc, right: &TypedList, right_index: usize) -> Result { + match right { + TypedList::Mixed(right) => self.value_equals_str(&right[right_index], left), + TypedList::String(right) => Ok(left == &right[right_index]), + _ => Ok(false), + } + } + + /// Maps compare by key lookup, not by scanning the other side — the same + /// answer as a pairwise search, without its quadratic cost. + fn maps(&self, left: &TypedMap, right: &TypedMap, depth: u32) -> Result { + if left.len() != right.len() { + return Ok(false); + } + match left { + TypedMap::Mixed(entries) => { + for (key, value) in entries { + if !self.map_value(right, key, value, depth)? { + return Ok(false); + } + } + } + TypedMap::StringMixed(entries) => { + for (key, value) in entries { + if !self.map_value(right, &RuntimeMapKey::from_shared(key.clone()), value, depth)? { + return Ok(false); + } + } + } + TypedMap::StringInt(entries) => { + for (key, value) in entries { + let key = RuntimeMapKey::from_shared(key.clone()); + if !self.map_value(right, &key, &RuntimeVal::Int(*value), depth)? { + return Ok(false); + } + } + } + TypedMap::StringFloat(entries) => { + for (key, value) in entries { + let key = RuntimeMapKey::from_shared(key.clone()); + if !self.map_value(right, &key, &RuntimeVal::Float(*value), depth)? { + return Ok(false); + } + } + } + TypedMap::StringBool(entries) => { + for (key, value) in entries { + let key = RuntimeMapKey::from_shared(key.clone()); + if !self.map_value(right, &key, &RuntimeVal::Bool(*value), depth)? { + return Ok(false); + } + } + } + } + Ok(true) + } + + fn map_value(&self, right: &TypedMap, key: &RuntimeMapKey, left_value: &RuntimeVal, depth: u32) -> Result { + let Some(right_value) = right.get(key) else { + return Ok(false); + }; + self.nested(left_value, &right_value, depth) + } +} + +fn sets_equal(left: &RuntimeSet, right: &RuntimeSet) -> bool { + left.len() == right.len() && left.entries().all(|key| right.contains(key)) +} + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(not(feature = "std"))] + use crate::compat::prelude::*; + use crate::val::HeapValue; + + /// The bug that made one implementation two: a list element longer than + /// `ShortStr`'s seven inline bytes had no `RuntimeVal`, became `Nil`, and + /// two `Nil`s compared equal. + #[test] + fn long_string_elements_are_compared_by_text_not_flattened_to_nil() { + let mut heap = HeapStore::new(); + let left = heap.alloc(HeapValue::List(TypedList::String(vec![Arc::from("abcdefghij")]))); + let right = heap.alloc(HeapValue::List(TypedList::String(vec![Arc::from("zzzzzzzzzz")]))); + let same = heap.alloc(HeapValue::List(TypedList::String(vec![Arc::from("abcdefghij")]))); + + let equal = |a, b| runtime_values_equal(&RuntimeVal::Obj(a), &RuntimeVal::Obj(b), &heap).expect("compare"); + assert!(!equal(left, right)); + assert!(equal(left, same)); + } + + /// A chain deeper than the bound raises instead of overflowing the Rust + /// stack — which used to abort the process outright. + #[test] + fn nesting_past_the_bound_raises_instead_of_aborting() { + let mut heap = HeapStore::new(); + let mut build = || { + let mut node = RuntimeVal::Int(1); + for _ in 0..(MAX_VALUE_DEPTH + 8) { + node = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(vec![node])))); + } + node + }; + let left = build(); + let right = build(); + + let error = runtime_values_equal(&left, &right, &heap).expect_err("too deep to compare"); + assert!(error.to_string().contains("nested deeper than"), "{error}"); + } + + /// Shallow nesting still compares all the way down. + #[test] + fn nesting_within_the_bound_still_compares_structurally() { + let mut heap = HeapStore::new(); + let mut build = |leaf: i64| { + let mut node = RuntimeVal::Int(leaf); + for _ in 0..16 { + node = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(vec![node])))); + } + node + }; + let left = build(1); + let same = build(1); + let different = build(2); + + assert!(runtime_values_equal(&left, &same, &heap).expect("compare")); + assert!(!runtime_values_equal(&left, &different, &heap).expect("compare")); + } +} diff --git a/core/src/val/runtime_model/heap.rs b/core/src/val/runtime_model/heap.rs index da26e87e..76972fe3 100644 --- a/core/src/val/runtime_model/heap.rs +++ b/core/src/val/runtime_model/heap.rs @@ -2,7 +2,7 @@ use crate::compat::prelude::*; use alloc::sync::Arc; -use super::{CallableValue, HeapValue, RuntimeMapKey, RuntimeSet, RuntimeVal, TypedList, TypedMap}; +use super::{CallableValue, HeapValue, RuntimeVal, TypedList, TypedMap}; use crate::vm::RuntimeCallable; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -20,6 +20,22 @@ impl HeapRef { } } +/// The imported callables a single collection cycle has already walked. +/// +/// Empty for a collection that reaches no imported function, which is every +/// collection in a single-module program. +#[derive(Debug, Default)] +pub struct CollectedModules { + seen: crate::compat::collections::HashSet, +} + +impl CollectedModules { + /// Records `callable` and answers whether it is new to this cycle. + pub fn first_visit(&mut self, callable: usize) -> bool { + self.seen.insert(callable) + } +} + #[derive(Clone, Debug)] pub struct HeapStore { slots: Vec>, @@ -29,6 +45,10 @@ pub struct HeapStore { live_len: usize, alloc_since_gc: u32, gc_threshold: u32, + /// An embedder asked for a specific threshold, so the live-set scaling in + /// [`Self::should_collect`] is off: a number given by name means that + /// number, not a floor under a policy the caller did not ask for. + pinned_threshold: bool, } impl HeapStore { @@ -46,6 +66,7 @@ impl HeapStore { live_len: 0, alloc_since_gc: 0, gc_threshold: Self::DEFAULT_GC_THRESHOLD, + pinned_threshold: false, } } @@ -61,7 +82,15 @@ impl HeapStore { assert!(u32::try_from(index).is_ok(), "heap object index overflow"); self.slots.push(Some(value)); self.marks.push(Self::WHITE); - self.generations.push(0); + // A slot the table grew back into after `release_dead_tail` cut it + // is a *re-used* slot, not a fresh one: its generation carries on + // from where it left off, so a handle from before the cut still + // fails to match. Only an index the heap has never reached starts + // at zero. + match self.generations.get_mut(index) { + Some(generation) => *generation = generation.wrapping_add(1), + None => self.generations.push(0), + } index as u32 }; self.live_len += 1; @@ -98,14 +127,48 @@ impl HeapStore { self.live_len } + /// How many slots the table holds, live or not — what a sweep walks, and + /// what [`Self::should_collect`] paces itself against. + #[inline] + pub fn slot_capacity(&self) -> usize { + self.slots.len() + } + #[inline] pub fn is_empty(&self) -> bool { self.live_len == 0 } + /// Whether enough has been allocated since the last collection to be worth + /// another one. + /// + /// The bound is the size of the **slot table**, floored at + /// [`Self::gc_threshold`]. A mark and sweep walks every slot whatever + /// triggered it, so collecting every fixed number of allocations makes the + /// collector's share of a program grow with the table: a heap of a hundred + /// thousand slots paid a hundred-thousand-slot walk every thousand + /// allocations. Waiting for it to grow by half its size instead keeps the + /// amortized cost per allocation constant. + /// + /// The table and not the *live set*, because the sweep's cost is the table: + /// one object allocated after a large burst is released pins the whole + /// thing (see [`Self::release_dead_tail`]), and a live-set bound would then + /// collect as if the heap were empty while each collection still walked the + /// peak. Measured: four hundred thousand allocations released and four + /// hundred thousand more made took 0.61s on a fixed threshold, 0.45s on a + /// live-set bound, and 0.07s on this one. + /// + /// `gc_threshold` is the *floor* on how often this can fire, so a small + /// heap keeps the old behaviour exactly. A threshold set by name turns the + /// scaling off entirely (`pinned_threshold`): an embedder that asks to + /// collect every allocation is asking for a policy, not for a floor under + /// one. #[inline] pub fn should_collect(&self) -> bool { - self.alloc_since_gc >= self.gc_threshold + if self.pinned_threshold { + return self.alloc_since_gc >= self.gc_threshold; + } + self.alloc_since_gc as usize >= (self.gc_threshold as usize).max(self.slots.len() / 2) } #[inline] @@ -116,41 +179,80 @@ impl HeapStore { #[inline] pub fn set_gc_threshold(&mut self, threshold: u32) { self.gc_threshold = threshold.max(1); + self.pinned_threshold = true; } + /// Mark and sweep. + /// + /// Marking walks an explicit worklist rather than recursing. Recursion put + /// the object graph's *depth* on the Rust stack, so a chain a program can + /// build in a loop — + /// + /// ```lk + /// let node: Any = [1]; + /// for i in 0..200000 { node = [node]; } + /// ``` + /// + /// — aborted the process with `fatal runtime error: stack overflow` at the + /// next collection, with no way for a script to catch it and no line to + /// blame: the allocation that tripped the threshold, not the one at fault. + /// The worklist also lets edges land straight in it, so marking no longer + /// allocates a fresh `Vec` per object visited. pub fn collect(&mut self, roots: impl IntoIterator) { - for mark in &mut self.marks { - *mark = Self::WHITE; - } - for root in roots { - self.mark_ref(root); - } - self.sweep(); - self.alloc_since_gc = 0; + self.collect_with_visited(roots, &mut CollectedModules::default()); } - fn mark_ref(&mut self, reference: HeapRef) { - let index = reference.index() as usize; - let Some(slot) = self.slots.get(index) else { - return; - }; - if slot.is_none() || self.marks.get(index).copied() == Some(Self::BLACK) { - return; - } - self.marks[index] = Self::BLACK; - let mut refs = Vec::new(); - let mut runtime_callables = Vec::new(); - collect_heap_value_edges( - slot.as_ref().expect("checked live slot"), - &mut refs, - &mut runtime_callables, + /// The same, told which module heaps this collection cycle has already + /// walked. + /// + /// A heap holding an imported function reaches *another* module's heap + /// through it (see the deferred `runtime_callables` below), and that heap + /// reaches further ones the same way. The module graph is a DAG, and + /// without remembering where it has been the walk treats it as a tree: a + /// module reached by K paths is collected K times, each of those repeating + /// the walk beneath it. Measured, with N closures accumulated in a REPL + /// session (each input is its own module, and each holds a callable for + /// every earlier one), cross-module collections went 8 -> ~1_000, + /// 12 -> ~20_000, 16 -> ~327_000 — exponential in N. + pub fn collect_with_visited(&mut self, roots: impl IntoIterator, visited: &mut CollectedModules) { + // No whitening pass. Every mark is already `WHITE` when a collection + // starts, and the three places that could say otherwise all maintain + // it: `sweep` turns each surviving `BLACK` back, a swept slot was never + // marked in the first place, and both `alloc` paths write `WHITE`. The + // loop that used to be here walked every slot the heap had *ever* held + // to write a value each of them already had — the same O(slots) the + // sweep costs, spent twice. + debug_assert!( + self.marks.iter().all(|mark| *mark == Self::WHITE), + "a collection starts from an all-white heap" ); - for reference in refs { - self.mark_ref(reference); + let mut worklist: Vec = roots.into_iter().collect(); + let mut runtime_callables = Vec::new(); + while let Some(reference) = worklist.pop() { + let index = reference.index() as usize; + if index >= self.slots.len() || self.slots[index].is_none() || self.marks[index] == Self::BLACK { + continue; + } + self.marks[index] = Self::BLACK; + let value = self.slots[index].as_ref().expect("checked live slot"); + collect_heap_value_edges(value, &mut worklist, &mut runtime_callables); } + // Deferred to here rather than done mid-walk: each of these collects a + // *different* heap (the callable's own module state), so the order + // relative to this heap's marking cannot matter. for function in runtime_callables { - let _ = function.collect_garbage(); + // Keyed on the *callable*, not on the module it belongs to. Two + // callables into one module carry different captures, and a + // callable's captures live in that module's heap — so skipping the + // second because the first had been there would leave its captures + // unrooted while the heap they live in is swept. Per callable, the + // work skipped is work already done with exactly these roots. + if visited.first_visit(Arc::as_ptr(&function) as *const () as usize) { + let _ = function.collect_garbage_with_visited(visited); + } } + self.sweep(); + self.alloc_since_gc = 0; } } @@ -178,7 +280,11 @@ fn collect_heap_value_edges( HeapValue::Slice(slice) => collect_runtime_value_edge(&slice.source, refs), HeapValue::List(values) => collect_typed_list_edges(values, refs), HeapValue::Map(values) => collect_typed_map_edges(values, refs), - HeapValue::Set(values) => collect_runtime_set_edges(values, refs), + // A set's members are `RuntimeMapKey`s, and none of those is a heap + // handle any more: a long string key is an `Arc` held inline, and + // a container cannot be a key at all (see `RuntimeMapKey::from_value`). + // So a set has no outgoing edges, like a string or a byte buffer. + HeapValue::Set(_) => {} HeapValue::Object(object) => { for value in object.fields.values() { collect_runtime_value_edge(value, refs); @@ -202,12 +308,6 @@ fn collect_heap_value_edges( } } -fn collect_runtime_set_edges(values: &RuntimeSet, refs: &mut Vec) { - for key in values.entries() { - collect_runtime_map_key_edge(key, refs); - } -} - fn collect_typed_list_edges(values: &TypedList, refs: &mut Vec) { if let TypedList::Mixed(values) = values { for value in values { @@ -219,8 +319,8 @@ fn collect_typed_list_edges(values: &TypedList, refs: &mut Vec) { fn collect_typed_map_edges(values: &TypedMap, refs: &mut Vec) { match values { TypedMap::Mixed(values) => { - for (key, value) in values { - collect_runtime_map_key_edge(key, refs); + // Keys hold no handles — see the `Set` arm above. + for value in values.values() { collect_runtime_value_edge(value, refs); } } @@ -233,12 +333,6 @@ fn collect_typed_map_edges(values: &TypedMap, refs: &mut Vec) { } } -fn collect_runtime_map_key_edge(key: &RuntimeMapKey, refs: &mut Vec) { - if let RuntimeMapKey::Obj(reference) = key { - refs.push(*reference); - } -} - fn collect_runtime_value_edge(value: &RuntimeVal, refs: &mut Vec) { if let RuntimeVal::Obj(reference) = value { refs.push(*reference); @@ -249,6 +343,7 @@ impl HeapStore { fn sweep(&mut self) { self.free_list.clear(); let mut live_len = 0; + let mut last_live = 0usize; for (index, slot) in self.slots.iter_mut().enumerate() { if slot.is_none() { self.free_list.push(index as u32); @@ -257,12 +352,44 @@ impl HeapStore { if self.marks[index] == Self::BLACK { self.marks[index] = Self::WHITE; live_len += 1; + last_live = index + 1; } else { *slot = None; self.free_list.push(index as u32); } } self.live_len = live_len; + self.release_dead_tail(last_live); + } + + /// Gives back the empty tail of the slot table. + /// + /// A sweep costs O(slots), not O(live), and `slots` only ever grew — so a + /// program that allocated a lot once and then dropped it kept paying for the + /// peak at every later collection. Four hundred thousand allocations + /// released, then four hundred thousand small ones, spent most of their time + /// walking a table whose live count was near zero. + /// + /// Only the *tail*, because a `HeapRef` is an index: moving a live slot would + /// need every reference to it rewritten, and there is no such list. Cutting + /// the empty end moves nothing. A reference into the cut region is a + /// reference to something already collected, and `get` answers `None` for it + /// exactly as it did when the slot was `None` — the same dangling-ref + /// behaviour, one branch earlier. + /// + /// `generations` is **not** cut with them. That vector is what tells a + /// re-used slot from the one it replaced, so an inline cache holding + /// `(index, generation)` invalidates instead of matching a different object + /// at the same index. Cutting it would restart the counter at zero and let + /// exactly that stale match happen; eight bytes per slot the heap once held + /// is what the invariant costs. + fn release_dead_tail(&mut self, live_end: usize) { + if live_end == self.slots.len() { + return; + } + self.slots.truncate(live_end); + self.marks.truncate(live_end); + self.free_list.retain(|index| (*index as usize) < live_end); } } @@ -275,7 +402,6 @@ impl Default for HeapStore { #[cfg(test)] mod tests { use crate::compat::sync::Mutex; - use crate::util::fast_map::{fast_hash_map_from_iter, fast_hash_map_new}; use alloc::sync::Arc; use super::*; @@ -284,6 +410,84 @@ mod tests { vm::RuntimeModuleState, }; + /// The collector's share of a program must not grow with the data it holds. + /// + /// A mark and sweep costs O(live) whatever triggered it, so a *fixed* + /// allocation threshold makes total GC work O(allocations x live) — a + /// program with a large live set paid a full walk every thousand + /// allocations. Scaling the trigger with the live set makes it O(1) + /// amortized per allocation, which is what this measures: ten times the + /// live set must not mean ten times the collections per allocation. + /// + /// Counted rather than timed, so it says the same thing on any machine. + #[test] + fn collections_do_not_multiply_with_the_heap() { + fn collections_for(live: usize, allocations: usize) -> usize { + let mut heap = HeapStore::new(); + let roots: Vec = (0..live) + .map(|i| heap.alloc(HeapValue::String(Arc::::from(alloc::format!("live{i}"))))) + .collect(); + let mut collections = 0; + for i in 0..allocations { + heap.alloc(HeapValue::String(Arc::::from(alloc::format!("tmp{i}")))); + if heap.should_collect() { + heap.collect(roots.iter().copied()); + collections += 1; + } + } + collections + } + // Ten times the live set, the same number of allocations. With a fixed + // threshold both answers are the same and the *work* is ten times as + // much; scaling the trigger trades that for a tenth of the collections. + let small = collections_for(2_000, 20_000); + let large = collections_for(20_000, 20_000); + assert!(small > 0, "the small heap has to collect at all, got {small}"); + assert!( + large * 5 <= small, + "ten times the live set should collect far less often per allocation, \ + got {large} collections against {small}" + ); + } + + /// A burst that is released gives its slots back, and a handle from before + /// the release still does not match whatever lands there next. + /// + /// The two halves are one test because the second is the price of the + /// first: the tail is cut, so the table can grow back into indices it has + /// used before, and `generations` is what keeps those apart. Cutting the + /// generations with the slots would restart the counter and let a stale + /// `(index, generation)` pair match a different object. + #[test] + fn a_released_burst_gives_its_slots_back_without_reusing_a_generation() { + let mut heap = HeapStore::new(); + let keep = heap.alloc(HeapValue::String(Arc::::from("keep"))); + let doomed: Vec = (0..500) + .map(|i| heap.alloc(HeapValue::String(Arc::::from(alloc::format!("burst{i}"))))) + .collect(); + let stale = doomed[100]; + let stale_generation = heap.shape_generation(stale).expect("live before the collection"); + + heap.collect([keep]); + assert_eq!(heap.len(), 1, "only the kept object survives"); + assert!( + heap.slot_capacity() <= 8, + "the released tail should be given back, table still holds {}", + heap.slot_capacity() + ); + + // Grow back over the same indices; the old handle must not match. + let reborn: Vec = (0..300) + .map(|i| heap.alloc(HeapValue::String(Arc::::from(alloc::format!("again{i}"))))) + .collect(); + assert!(reborn.iter().any(|r| r.index() == stale.index()), "an index came back"); + assert_ne!( + heap.shape_generation(stale), + Some(stale_generation), + "a re-used slot must not answer the generation the old object had" + ); + } + #[test] fn heap_store_returns_stable_refs() { let mut heap = HeapStore::new(); @@ -324,7 +528,9 @@ mod tests { heap.collect([]); assert_eq!(heap.shape_generation(handle), None); - let reused = heap.alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_new()))); + let reused = heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_new(), + ))); assert_eq!(reused.index(), handle.index()); assert_eq!(heap.shape_generation(reused), Some(initial.wrapping_add(2))); } @@ -353,16 +559,15 @@ mod tests { let mut heap = HeapStore::new(); let leaf = heap.alloc(HeapValue::String(Arc::::from("leaf"))); let list = heap.alloc(HeapValue::List(TypedList::Mixed(vec![RuntimeVal::Obj(leaf)]))); - let map = heap.alloc(HeapValue::Map(TypedMap::StringMixed(fast_hash_map_from_iter([( - Arc::::from("list"), - RuntimeVal::Obj(list), - )])))); + let map = heap.alloc(HeapValue::Map(TypedMap::StringMixed( + crate::util::value_map::value_map_from_iter([(Arc::::from("list"), RuntimeVal::Obj(list))]), + ))); let object = heap.alloc(HeapValue::Object(crate::val::RuntimeObject::new( - Arc::new(crate::vm::DeclaredType::new( - crate::vm::TypeScope::anonymous(), + Arc::new(crate::val::DeclaredType::new( + crate::val::TypeScope::anonymous(), Arc::::from("Box"), )), - fast_hash_map_from_iter([(Arc::::from("map"), RuntimeVal::Obj(map))]), + crate::util::value_map::value_map_from_iter([(Arc::::from("map"), RuntimeVal::Obj(map))]), ))); let closure = heap.alloc(HeapValue::Callable(CallableValue::Closure { function_index: 7, @@ -387,21 +592,6 @@ mod tests { assert!(heap.get(garbage).is_none()); } - #[test] - fn heap_store_gc_marks_mixed_map_object_keys() { - let mut heap = HeapStore::new(); - let key_object = heap.alloc(HeapValue::String(Arc::::from("key-object"))); - let map = heap.alloc(HeapValue::Map(TypedMap::Mixed(fast_hash_map_from_iter([( - RuntimeMapKey::Obj(key_object), - RuntimeVal::Int(1), - )])))); - - heap.collect([map]); - - assert!(heap.get(map).is_some()); - assert!(heap.get(key_object).is_some()); - } - #[test] fn heap_store_gc_marks_stream_and_cursor_roots() { let mut heap = HeapStore::new(); @@ -428,6 +618,26 @@ mod tests { assert!(heap.get(garbage).is_none()); } + /// Marking used to recurse, so the *depth* of the object graph sat on the + /// Rust stack and a chain a loop can build aborted the process at the next + /// collection. There is no depth bound here on purpose: a collection cannot + /// be allowed to fail. + #[test] + fn heap_store_gc_marks_a_chain_far_deeper_than_the_rust_stack() { + let mut heap = HeapStore::new(); + let mut node = heap.alloc(HeapValue::String(Arc::::from("leaf"))); + for _ in 0..200_000 { + node = heap.alloc(HeapValue::List(TypedList::Mixed(vec![RuntimeVal::Obj(node)]))); + } + let garbage = heap.alloc(HeapValue::String(Arc::::from("garbage"))); + + heap.collect([node]); + + assert_eq!(heap.len(), 200_001); + assert!(heap.get(node).is_some()); + assert!(heap.get(garbage).is_none()); + } + #[test] fn heap_store_gc_collects_runtime_callable_shared_state_without_marking_dest_heap_captures() { let mut source_heap = HeapStore::new(); diff --git a/core/src/val/ser.rs b/core/src/val/ser.rs new file mode 100644 index 00000000..2566c3ae --- /dev/null +++ b/core/src/val/ser.rs @@ -0,0 +1,175 @@ +//! Runtime values *out* — the sibling [`super::de`] never had. +//! +//! JSON, YAML and TOML could be read and not written, so the most ordinary +//! script there is — read a config, change a field, write it back — could only +//! do the first two thirds. `base64`, `hex` and `url` next door are all pairs; +//! a parser without its serializer is half an operation. +//! +//! Everything goes through `serde_json::Value` first, so escaping, number +//! formatting and object nesting are decided once by a library that has already +//! argued about them, and the YAML and TOML writers get the same input. +//! +//! # What refuses, and why +//! +//! A value with no JSON counterpart is an error rather than a guess: +//! +//! - **A map key that is not a string.** JSON object keys are strings, and both +//! Python and JavaScript quietly stringify an integer key — so `1` and `"1"` +//! land on the same entry and the round trip stops being one. Refusing says +//! so at the point the program can still choose. +//! - **NaN and the infinities.** JSON has no spelling for them; `null` is what +//! JavaScript substitutes, which turns a broken computation into a missing +//! field. +//! - **A set, a byte buffer, a function, a channel, a task.** An array would +//! read back as a list, base64 is the caller's decision, and the rest are not +//! data. +//! +//! A `struct` *is* written, as an object of its fields — it reads back as a +//! map, which is what a JSON object is. +//! +//! Object keys come out **sorted**, because `serde_json::Map` is a `BTreeMap`. +//! That is left alone rather than worked around: a config written twice from +//! the same data is byte-identical, which is what makes the output diffable, +//! and JSON says nothing about key order anyway. It does mean `stringify` and +//! `println` order a struct's fields differently — `println` shows the +//! declaration order, which is what a reader wrote. +//! +//! **Reading is the other way round, on purpose.** [`super::de`] hands back a +//! document's keys in the order the document has them, because an LK map's +//! order is a contract and a parsed document has an order to keep. The two are +//! not in tension: writing imposes an order so the bytes are stable, reading +//! reports the order it was given. What *was* wrong is that reading used to sort +//! too — not by decision, but because `serde_json::Value` is a `BTreeMap` and +//! nobody had looked. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; +use crate::val::{HeapStore, HeapValue, MAX_VALUE_DEPTH, RuntimeMapKey, RuntimeVal, TypedList, TypedMap}; +use alloc::string::{String, ToString}; +use anyhow::{Result, bail}; + +/// `value` as compact JSON text. +pub fn to_json_string(value: &RuntimeVal, heap: &HeapStore) -> Result { + Ok(to_serde_value(value, heap, 0)?.to_string()) +} + +/// `value` as YAML text. +#[cfg(feature = "std")] +pub fn to_yaml_string(value: &RuntimeVal, heap: &HeapStore) -> Result { + let value = to_serde_value(value, heap, 0)?; + serde_yaml::to_string(&value).map_err(|error| anyhow::anyhow!("cannot write YAML: {error}")) +} + +/// `value` as TOML text. +/// +/// TOML has no top-level scalar or array — a document *is* a table — so +/// anything but a map is refused here rather than producing a file no TOML +/// parser will read back. +#[cfg(feature = "std")] +pub fn to_toml_string(value: &RuntimeVal, heap: &HeapStore) -> Result { + let value = to_serde_value(value, heap, 0)?; + if !value.is_object() { + bail!("a TOML document is a table, so the top level must be a map"); + } + toml::to_string(&value).map_err(|error| anyhow::anyhow!("cannot write TOML: {error}")) +} + +fn to_serde_value(value: &RuntimeVal, heap: &HeapStore, depth: u32) -> Result { + if depth >= MAX_VALUE_DEPTH { + bail!("value nested deeper than {MAX_VALUE_DEPTH} levels; it is cyclic or too deeply nested to write"); + } + Ok(match value { + RuntimeVal::Nil => serde_json::Value::Null, + RuntimeVal::Bool(value) => serde_json::Value::Bool(*value), + RuntimeVal::Int(value) => serde_json::Value::from(*value), + RuntimeVal::Float(value) => match serde_json::Number::from_f64(*value) { + Some(number) => serde_json::Value::Number(number), + // `null` is what JavaScript substitutes here, which turns a broken + // computation into a missing field. + None => bail!("{value} has no JSON form (NaN and the infinities do not)"), + }, + RuntimeVal::ShortStr(value) => serde_json::Value::String(value.as_str().to_string()), + RuntimeVal::Obj(handle) => { + let Some(object) = heap.get(*handle) else { + bail!("heap object {} out of bounds", handle.index()); + }; + heap_value_to_serde(object, heap, depth)? + } + }) +} + +fn heap_value_to_serde(value: &HeapValue, heap: &HeapStore, depth: u32) -> Result { + Ok(match value { + HeapValue::String(text) => serde_json::Value::String(text.to_string()), + HeapValue::List(list) => list_to_serde(list, heap, depth)?, + HeapValue::Slice(slice) => { + let RuntimeVal::Obj(source) = slice.source else { + return Ok(serde_json::Value::Array(Vec::new())); + }; + let Some(HeapValue::List(list)) = heap.get(source) else { + return Ok(serde_json::Value::Array(Vec::new())); + }; + list_to_serde(&list.window(slice.start, slice.live_len(heap)), heap, depth)? + } + HeapValue::Map(map) => map_to_serde(map, heap, depth)?, + // A struct is an object of its fields. No ordering effort here: the + // map below is a `BTreeMap`, so whatever order they go in they come out + // sorted — see this module's note. + HeapValue::Object(object) => { + let mut out = serde_json::Map::with_capacity(object.fields.len()); + for (name, value) in &object.fields { + out.insert(name.to_string(), to_serde_value(value, heap, depth + 1)?); + } + serde_json::Value::Object(out) + } + other => bail!("{} has no JSON form", other.type_name()), + }) +} + +fn list_to_serde(list: &TypedList, heap: &HeapStore, depth: u32) -> Result { + let mut out = Vec::with_capacity(list.len()); + match list { + TypedList::Mixed(values) => { + for value in values { + out.push(to_serde_value(value, heap, depth + 1)?); + } + } + TypedList::Int(values) => out.extend(values.iter().map(|value| serde_json::Value::from(*value))), + TypedList::Float(values) => { + for value in values { + out.push(to_serde_value(&RuntimeVal::Float(*value), heap, depth + 1)?); + } + } + TypedList::Bool(values) => out.extend(values.iter().map(|value| serde_json::Value::Bool(*value))), + TypedList::String(values) => { + out.extend(values.iter().map(|value| serde_json::Value::String(value.to_string()))); + } + } + Ok(serde_json::Value::Array(out)) +} + +fn map_to_serde(map: &TypedMap, heap: &HeapStore, depth: u32) -> Result { + let entries = map.entries_iter(); + let mut out = serde_json::Map::with_capacity(entries.len()); + for (key, value) in entries.iter() { + out.insert(object_key(key)?, to_serde_value(value, heap, depth + 1)?); + } + Ok(serde_json::Value::Object(out)) +} + +/// A JSON object key, or a refusal. +/// +/// Python and JavaScript both stringify a non-string key, so `1` and `"1"` land +/// on the same entry and the round trip stops being one. Saying so is the point +/// where the program can still choose. +fn object_key(key: &RuntimeMapKey) -> Result { + match key { + RuntimeMapKey::ShortStr(text) => Ok(text.as_str().to_string()), + RuntimeMapKey::String(text) => Ok(text.to_string()), + RuntimeMapKey::Int(value) => bail!( + "a JSON object key is a String, and `{value}` is an Int — write it as \"{value}\" if that is what you mean" + ), + RuntimeMapKey::Bool(value) => bail!("a JSON object key is a String, and `{value}` is a Bool"), + RuntimeMapKey::Nil => bail!("a JSON object key is a String, and `nil` is not one"), + } +} diff --git a/core/src/val/type_info.rs b/core/src/val/type_info.rs new file mode 100644 index 00000000..7dd39516 --- /dev/null +++ b/core/src/val/type_info.rs @@ -0,0 +1,225 @@ +//! A value's **type identity**: which module declared a named type, and its name. +//! +//! `struct Point` in `a.lk` and `struct Point` in `b.lk` are different types, +//! so a name alone does not identify one — see [`TypeScope`]. +//! +//! This lived under `vm/`, bundled with the compiler's `trait`/`impl` tables +//! (`TypeInfo`, still there) purely because both were "type information". They +//! are not the same thing: those tables are a *module artifact* the compiler +//! hands to a back end, while this is a property of a **value** — +//! `RuntimeObject` embeds an `Arc`. Being in `vm` made `val` +//! name `vm`, which is half of the `val` <-> `vm` cycle recorded in +//! `CLAUDE.md`; the remaining half is the callable payload, and that one is a +//! real redesign rather than a move. + +use alloc::sync::Arc; +use serde::{Deserialize, Serialize}; + +/// Identity of the module that *declares* a named type. +/// +/// # Why a declared type needs more than its name +/// +/// `struct Point` in `a.lk` and `struct Point` in `b.lk` are different types. +/// The runtime used to disagree: an object carried only `"Point"` and the +/// dispatch table was keyed by that bare string, so whichever module registered +/// last owned the name for the whole context — `a.mk(1).tag()` returned `b`'s +/// answer. The same missing half made a *transitive* import fail outright: the +/// importer collected impls one level deep, so a value built by a module its +/// own dependency imported had no reachable methods at all. +/// +/// Both are the same hole: identity lived in a name, and a name is only unique +/// inside one module. +/// +/// # Why the declaring module is the right scope +/// +/// A struct literal can only name a type declared in the same compilation unit +/// — an imported struct is not constructible (`Point { .. }` in the importer is +/// "Unknown struct 'Point'") and not nameable in an annotation. So the module +/// executing the construction *is* the module that declared the type, and +/// stamping the object at construction needs no extra compiler plumbing. +/// +/// # Representation +/// +/// The normalized source path for a file module, so the identity is stable +/// across processes and can ride in a `ModuleArtifact`. Modules with no file +/// behind them (the entry program, `eval`-style sources, tests) get +/// [`TypeScope::anonymous`], which is distinct from every path and from other +/// anonymous scopes only by being the single scope of that run — good enough, +/// because nothing can import them. +#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct TypeScope(Arc); + +impl TypeScope { + /// The scope of a module loaded from `path` (already normalized by the + /// resolver). + pub fn from_path(path: &str) -> Self { + Self(Arc::::from(path)) + } + + /// The scope of a module with no file behind it. + pub fn anonymous() -> Self { + Self(Arc::::from("")) + } + + /// The one scope shared by every `impl` whose target is a **builtin** type + /// (`impl Doubler for Int`). + /// + /// A builtin type is not declared by anybody, so it has no declaring module + /// to be scoped to and every module means the same `Int`. Filing those + /// impls per-module would be wrong in the other direction: the receiver is + /// a bare `5` with no module attached, so the lookup could never find them. + /// + /// A builtin type has no declaring module, so one trait can be implemented + /// for it exactly once in a program. Two modules that both + /// `impl Doubler for Int` are **refused**, naming both files — see + /// `VmContext::note_builtin_impl_owner`. This used to say the second + /// registration silently won, which is what made the answer depend on + /// import order. + pub fn builtin() -> Self { + Self(Arc::::from("")) + } + + /// Whether this is the shared scope for builtin types (see + /// [`Self::builtin`]), which admits only one impl per trait. + pub fn is_builtin(&self) -> bool { + self.0.as_ref() == "" + } + + pub fn as_str(&self) -> &str { + &self.0 + } + + /// Pointer identity — the same scope value, not merely an equal one. + /// + /// Every module hands out clones of one `Arc`, so this answers "still the + /// same module?" without a string compare. The executor asks that on every + /// activation, which is why it is worth not spelling `==` there. + #[inline] + pub fn is_same(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl Default for TypeScope { + fn default() -> Self { + Self::anonymous() + } +} + +/// The full identity of a declared type: which module declared it, and its +/// name. Neither half identifies a type on its own. +/// +/// Kept as one heap-allocated value that instances share by `Arc`, rather than +/// as two fields on every object. `RuntimeObject` is the largest `HeapValue` +/// variant and therefore sets the size of *every* heap cell — list, map, string +/// and all — so widening it by a second fat pointer measurably slowed programs +/// that contain no structs at all (~1.3% on the workload suite). One thin +/// pointer instead of the previous bare `Arc` name makes objects smaller +/// than they were before scoping. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct DeclaredType { + pub scope: TypeScope, + pub name: Arc, + /// The declaration's fields, in the order they were written — empty when + /// the declaration is not in reach (a struct from another module, or an + /// object built by a host). + /// + /// `display` reads the order, to print a value's fields the way its type + /// was written: fields live in a map on the object, so without this the + /// order was the hasher's — `struct Range { start, end }` printed `end` + /// first, and a hasher change would have silently permuted every struct in + /// the language. The *declared type* rides along so a store can be checked + /// against it. It costs nothing per object — instances share one + /// `DeclaredType` by `Arc`. + pub fields: Arc<[DeclaredField]>, + /// Whether any field was written with a type — precomputed because the + /// answer decides whether a store has to look at all, and a store that + /// scanned the field list to find out made construction quadratic in the + /// field count for the (common) type that declares none. + typed_fields: bool, +} + +/// One field of a declared type: the name it was written with, and the type it +/// was written with when it had one. +/// +/// `Eq`/`Hash` are over the **name** alone, which the derive cannot do (a +/// `Type` is neither). That is not a shortcut: a declared type is identified by +/// its scope and name, and within one of those a field name occurs once — two +/// fields of one type that agree on the name are the same field. +#[derive(Clone, Debug)] +pub struct DeclaredField { + pub name: Arc, + pub ty: Option, +} + +impl PartialEq for DeclaredField { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for DeclaredField {} + +impl core::hash::Hash for DeclaredField { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl DeclaredField { + pub fn new(name: Arc, ty: Option) -> Self { + Self { name, ty } + } +} + +impl DeclaredType { + pub fn new(scope: TypeScope, name: Arc) -> Self { + Self { + scope, + name, + fields: Arc::from([] as [DeclaredField; 0]), + typed_fields: false, + } + } + + pub fn with_fields(scope: TypeScope, name: Arc, fields: Arc<[DeclaredField]>) -> Self { + let typed_fields = fields.iter().any(|field| field.ty.is_some()); + Self { + scope, + name, + fields, + typed_fields, + } + } + + /// The declared type of `field`, when the declaration is in reach and the + /// field was written with one. + /// The declared field of this name, when it is declared. + /// + /// Handing out the declaration's own `Arc` is what lets every instance + /// share one allocation for a field name instead of minting one per + /// construction — `Arc::drop_slow` was 9% of a loop building one + /// struct. + pub fn declared_field_name(&self, field: &str) -> Option<&Arc> { + self.fields + .iter() + .find(|declared| &*declared.name == field) + .map(|declared| &declared.name) + } + + pub fn field_type(&self, field: &str) -> Option<&crate::val::Type> { + if !self.typed_fields { + return None; + } + self.fields + .iter() + .find(|declared| &*declared.name == field) + .and_then(|declared| declared.ty.as_ref()) + } +} + +impl core::fmt::Display for TypeScope { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(&self.0) + } +} diff --git a/core/src/val/val_test.rs b/core/src/val/val_test.rs index 01344ea5..8ae91864 100644 --- a/core/src/val/val_test.rs +++ b/core/src/val/val_test.rs @@ -31,7 +31,10 @@ mod tests { expect_expr("1 + 2", "3"); expect_expr("1 - 2", "-1"); expect_expr("2 * 3", "6"); + // `/` yields a Float, so this is 1.5 rather than 1 — and `20 / 4` is + // `5.0`, which prints as `5` because a whole Float drops its fraction. expect_expr("3 / 2", "1.5"); + expect_expr("20 / 4", "5"); } #[test] @@ -131,7 +134,7 @@ mod tests { #[test] fn test_display_formatting() { - expect_expr(r#"[1, "hello", true]"#, "[1, hello, true]"); + expect_expr(r#"[1, "hello", true]"#, "[1,\"hello\",true]"); expect_expr(r#"{"name": "Alice", "age": 30}.name"#, "Alice"); } diff --git a/core/src/vm.rs b/core/src/vm.rs index 946ba7f6..6a9065e9 100644 --- a/core/src/vm.rs +++ b/core/src/vm.rs @@ -2,11 +2,7 @@ //! //! The public surface exposes the canonical `Instr` compiler/executor path. -#[allow(dead_code, unused_imports)] -pub(crate) mod alloc; -#[allow(dead_code, unused_imports)] pub mod analysis; -#[allow(dead_code, unused_imports)] mod analysis_queries; mod artifact; mod cache; @@ -24,8 +20,6 @@ mod migration_guard; mod repl; mod resolver; mod runtime; -#[allow(dead_code)] -pub(crate) mod ssa; mod type_info; pub mod verify; #[cfg(all(test, feature = "std"))] @@ -35,7 +29,7 @@ pub use artifact::*; pub use cache::*; pub use call_window::*; pub use compiler::*; -pub use context::{MethodImpl, VmContext, receiver_type_scope}; +pub use context::{MethodImpl, VmContext, core_call_method_windowed, receiver_type_scope}; #[cfg(test)] pub use exec::test_support; pub use exec::*; diff --git a/core/src/vm/alloc.rs b/core/src/vm/alloc.rs deleted file mode 100644 index fef1460f..00000000 --- a/core/src/vm/alloc.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::compat::prelude::*; -/// Allocation region selected by escape analysis. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum AllocationRegion { - #[default] - ThreadLocal, - Heap, -} - -/// Plan produced for a function describing how SSA values should be allocated. -#[derive(Debug, Clone, Default)] -pub struct RegionPlan { - /// Allocation class per SSA value index. - pub values: Vec, - /// Allocation class for the function return value (by convention index = `values.len()`). - pub return_region: AllocationRegion, -} - -impl RegionPlan { - pub fn region_for(&self, value_index: usize) -> AllocationRegion { - self.values - .get(value_index) - .copied() - .unwrap_or(AllocationRegion::ThreadLocal) - } -} diff --git a/core/src/vm/analysis.rs b/core/src/vm/analysis.rs index 685979b3..a0264272 100644 --- a/core/src/vm/analysis.rs +++ b/core/src/vm/analysis.rs @@ -1,14 +1,11 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use alloc::sync::Arc; #[cfg(all(not(test), feature = "vm-profile"))] use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use serde::{Deserialize, Serialize}; use crate::val::{LiteralVal, Type}; -use crate::vm::alloc::RegionPlan; -use crate::vm::ssa::SsaFunction; /// Classification of how a value escapes during execution. /// @@ -43,22 +40,6 @@ impl EscapeClass { } } -/// Summary of escape behaviour for the current SSA function. -#[derive(Debug, Clone, Default)] -pub struct EscapeSummary { - pub return_class: EscapeClass, - /// SSA values that were classified as escaping. - pub escaping_values: Vec, -} - -impl EscapeSummary { - pub fn mark_escaping(&mut self, value: usize) { - if !self.escaping_values.contains(&value) { - self.escaping_values.push(value); - } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] pub enum PerfValueKind { #[default] @@ -588,15 +569,6 @@ impl PerformanceFacts { } } -/// Aggregated analysis artifacts produced by the SSA pipeline. -#[derive(Debug, Clone, Default)] -pub struct FunctionAnalysis { - pub ssa: Option, - pub escape: EscapeSummary, - pub region_plan: Arc, - pub perf: PerformanceFacts, -} - // --------------------------------------------------------------------------- // Runtime metrics — three-way cfg: // 1. #[cfg(test)] → always-on, thread-local @@ -605,18 +577,25 @@ pub struct FunctionAnalysis { // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The counters the VM keeps when metrics are compiled in. +/// +/// Ten fields were removed here, all of them structurally zero: the copy-policy +/// clone family (`copy_policy_heap_clones` and its seven per-site siblings) plus +/// `return_value_moves`. Every one of them was written only inside +/// `record_copy_policy_clone` / `record_return_value_move`, and **neither had a +/// caller anywhere** — while `lk coverage --runtime` and the one-line profile +/// printed all ten. The one-line form was worse than that: it read +/// `let heap_clones = metrics.copy_policy_heap_clones; let val_clones = +/// heap_clones;`, so one always-zero value was printed under three names. +/// +/// They are not missing recording sites — they are retired. `RuntimeVal` became +/// `Copy`, so a register move copies the value and there is no clone to count; +/// `execute_records_move_heap_clone_as_register_copy_metric` says exactly that +/// in its own body while its name still claims otherwise. A counter that +/// outlives what it measured reports a fact about the program that is not true. pub struct VmRuntimeMetrics { pub opcode_steps: u64, - pub copy_policy_heap_clones: u64, - pub register_copy_heap_clones: u64, - pub local_copy_heap_clones: u64, - pub local_load_heap_clones: u64, - pub local_store_heap_clones: u64, - pub const_load_heap_clones: u64, - pub call_arg_heap_clones: u64, - pub container_copy_heap_clones: u64, pub register_writes: u64, - pub return_value_moves: u64, pub branch_ops: u64, pub typed_branch_ops: u64, pub call_ops: u64, @@ -635,7 +614,7 @@ pub struct VmRuntimeMetrics { } pub const VM_OPCODE_COUNT: usize = 128; -pub const VM_REGISTER_WRITE_SOURCE_COUNT: usize = 10; +pub const VM_REGISTER_WRITE_SOURCE_COUNT: usize = 9; pub const VM_INDEX_KEY_METRIC_COUNT: usize = 12; pub const VM_REGISTER_WRITE_SOURCE_NAMES: [&str; VM_REGISTER_WRITE_SOURCE_COUNT] = [ "move", @@ -647,7 +626,6 @@ pub const VM_REGISTER_WRITE_SOURCE_NAMES: [&str; VM_REGISTER_WRITE_SOURCE_COUNT] "call_return", "global", "string", - "other", ]; pub const VM_INDEX_KEY_METRIC_NAMES: [&str; VM_INDEX_KEY_METRIC_COUNT] = [ "known_string_key", @@ -711,9 +689,15 @@ pub(crate) enum VmRegisterWriteSource { CallReturn, Global, String, - Other, + // No `Other`: a scan of every dispatch arm that writes a register found all + // of them classified, so a catch-all would be a line that can only print + // zero — the shape this pass exists to remove. An opcode added later belongs + // in a *named* bucket. } +// Only the profiling frame indexes a source into its array, and that frame is a +// unit struct with no-op methods unless metrics are compiled in. +#[cfg(any(test, feature = "vm-profile"))] impl VmRegisterWriteSource { #[inline] pub(crate) const fn index(self) -> usize { @@ -727,7 +711,6 @@ impl VmRegisterWriteSource { Self::CallReturn => 6, Self::Global => 7, Self::String => 8, - Self::Other => 9, } } } @@ -736,16 +719,7 @@ impl Default for VmRuntimeMetrics { fn default() -> Self { Self { opcode_steps: 0, - copy_policy_heap_clones: 0, - register_copy_heap_clones: 0, - local_copy_heap_clones: 0, - local_load_heap_clones: 0, - local_store_heap_clones: 0, - const_load_heap_clones: 0, - call_arg_heap_clones: 0, - container_copy_heap_clones: 0, register_writes: 0, - return_value_moves: 0, branch_ops: 0, typed_branch_ops: 0, call_ops: 0, @@ -783,32 +757,12 @@ pub(crate) enum VmContainerMetric { String, } -#[derive(Debug, Clone, Copy)] -pub(crate) enum VmValueCopyMetric { - Generic, - Register, - LocalLoad, - LocalStore, - ConstLoad, - CallArg, - Container, -} - // ==================== test mode ==================== #[cfg(test)] impl VmRuntimeMetrics { const ZERO: Self = Self { opcode_steps: 0, - copy_policy_heap_clones: 0, - register_copy_heap_clones: 0, - local_copy_heap_clones: 0, - local_load_heap_clones: 0, - local_store_heap_clones: 0, - const_load_heap_clones: 0, - call_arg_heap_clones: 0, - container_copy_heap_clones: 0, register_writes: 0, - return_value_moves: 0, branch_ops: 0, typed_branch_ops: 0, call_ops: 0, @@ -874,6 +828,13 @@ pub(crate) fn record_register_write_sources_batch(sources: &[u64; VM_REGISTER_WR for (dst, count) in metrics.register_write_sources.iter_mut().zip(sources.iter()) { *dst += count; } + // The total is *computed from* the breakdown, so the two cannot disagree. + // `register_writes` used to be bumped in `write_stack_index` — one helper + // among several — while the sources are recorded at every opcode that + // writes a register, so the report printed a "total" of 210 above parts + // summing to 763. Neither number was wrong on its own; the pairing was, + // and a reader takes the first line for the sum of the rest. + metrics.register_writes += sources.iter().sum::(); }); } @@ -887,50 +848,6 @@ pub(crate) fn record_index_key_metrics_batch(sources: &[u64; VM_INDEX_KEY_METRIC }); } -#[cfg(test)] -#[inline] -pub(crate) fn record_copy_policy_clone(kind: VmValueCopyMetric, heap_backed: bool) { - if !heap_backed { - return; - } - update_thread_runtime_metrics(|metrics| { - metrics.copy_policy_heap_clones += 1; - match kind { - VmValueCopyMetric::Generic => {} - VmValueCopyMetric::Register => metrics.register_copy_heap_clones += 1, - VmValueCopyMetric::LocalLoad => { - metrics.local_copy_heap_clones += 1; - metrics.local_load_heap_clones += 1; - } - VmValueCopyMetric::LocalStore => { - metrics.local_copy_heap_clones += 1; - metrics.local_store_heap_clones += 1; - } - VmValueCopyMetric::ConstLoad => metrics.const_load_heap_clones += 1, - VmValueCopyMetric::CallArg => metrics.call_arg_heap_clones += 1, - VmValueCopyMetric::Container => metrics.container_copy_heap_clones += 1, - } - }); -} - -#[cfg(test)] -#[inline] -pub(crate) fn record_register_write_known_enabled() { - update_thread_runtime_metrics(|metrics| metrics.register_writes += 1); -} - -#[cfg(test)] -#[inline] -pub(crate) fn record_register_write() { - update_thread_runtime_metrics(|metrics| metrics.register_writes += 1); -} - -#[cfg(test)] -#[inline] -pub(crate) fn record_return_value_move() { - update_thread_runtime_metrics(|metrics| metrics.return_value_moves += 1); -} - #[cfg(test)] #[inline] pub(crate) fn record_branch_op_known_enabled(typed: bool) { @@ -984,25 +901,16 @@ pub fn vm_runtime_metrics_reset() { // ==================== vm-profile mode (atomic counters) ==================== #[cfg(all(not(test), feature = "vm-profile"))] -static COPY_POLICY_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static REGISTER_COPY_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static LOCAL_COPY_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static LOCAL_LOAD_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static LOCAL_STORE_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static CONST_LOAD_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static CALL_ARG_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static CONTAINER_COPY_HEAP_CLONES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] static REGISTER_WRITES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] -static RETURN_VALUE_MOVES: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] static OPCODE_STEPS: AtomicU64 = AtomicU64::new(0); #[cfg(all(not(test), feature = "vm-profile"))] @@ -1052,14 +960,6 @@ pub fn vm_runtime_metrics_enabled() -> bool { runtime_metrics_enabled() } -#[cfg(all(not(test), feature = "vm-profile"))] -#[inline(always)] -fn increment(counter: &AtomicU64) { - if runtime_metrics_enabled() { - counter.fetch_add(1, Ordering::Relaxed); - } -} - #[cfg(all(not(test), feature = "vm-profile"))] #[inline(always)] pub(crate) fn record_opcode_step_known_enabled() { @@ -1079,6 +979,8 @@ pub(crate) fn record_opcode_histogram_batch(histogram: &[u64; VM_OPCODE_COUNT]) #[cfg(all(not(test), feature = "vm-profile"))] #[inline] pub(crate) fn record_register_write_sources_batch(sources: &[u64; VM_REGISTER_WRITE_SOURCE_COUNT]) { + // See the `cfg(test)` twin: the total is the sum of the breakdown. + REGISTER_WRITES.fetch_add(sources.iter().sum::(), Ordering::Relaxed); for (counter, count) in REGISTER_WRITE_SOURCES.iter().zip(sources.iter()) { if *count != 0 { counter.fetch_add(*count, Ordering::Relaxed); @@ -1096,59 +998,9 @@ pub(crate) fn record_index_key_metrics_batch(sources: &[u64; VM_INDEX_KEY_METRIC } } -#[cfg(all(not(test), feature = "vm-profile"))] -#[inline] -pub(crate) fn record_copy_policy_clone(kind: VmValueCopyMetric, heap_backed: bool) { - if !heap_backed || !runtime_metrics_enabled() { - return; - } - COPY_POLICY_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - match kind { - VmValueCopyMetric::Generic => {} - VmValueCopyMetric::Register => { - REGISTER_COPY_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - } - VmValueCopyMetric::LocalLoad => { - LOCAL_COPY_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - LOCAL_LOAD_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - } - VmValueCopyMetric::LocalStore => { - LOCAL_COPY_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - LOCAL_STORE_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - } - VmValueCopyMetric::ConstLoad => { - CONST_LOAD_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - } - VmValueCopyMetric::CallArg => { - CALL_ARG_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - } - VmValueCopyMetric::Container => { - CONTAINER_COPY_HEAP_CLONES.fetch_add(1, Ordering::Relaxed); - } - }; -} - /// Known-enabled variant: caller has already checked `collect_metrics`, /// so this unconditionally increments the counter without reading the /// global metrics gate atomically. -#[cfg(all(not(test), feature = "vm-profile"))] -#[inline(always)] -pub(crate) fn record_register_write_known_enabled() { - REGISTER_WRITES.fetch_add(1, Ordering::Relaxed); -} - -#[cfg(all(not(test), feature = "vm-profile"))] -#[inline] -pub(crate) fn record_register_write() { - increment(®ISTER_WRITES); -} - -#[cfg(all(not(test), feature = "vm-profile"))] -#[inline] -pub(crate) fn record_return_value_move() { - increment(&RETURN_VALUE_MOVES); -} - #[cfg(all(not(test), feature = "vm-profile"))] #[inline] pub(crate) fn record_branch_op_known_enabled(typed: bool) { @@ -1217,16 +1069,7 @@ pub fn vm_runtime_metrics_snapshot() -> VmRuntimeMetrics { VmRuntimeMetrics { opcode_steps: OPCODE_STEPS.load(Ordering::Relaxed), - copy_policy_heap_clones: COPY_POLICY_HEAP_CLONES.load(Ordering::Relaxed), - register_copy_heap_clones: REGISTER_COPY_HEAP_CLONES.load(Ordering::Relaxed), - local_copy_heap_clones: LOCAL_COPY_HEAP_CLONES.load(Ordering::Relaxed), - local_load_heap_clones: LOCAL_LOAD_HEAP_CLONES.load(Ordering::Relaxed), - local_store_heap_clones: LOCAL_STORE_HEAP_CLONES.load(Ordering::Relaxed), - const_load_heap_clones: CONST_LOAD_HEAP_CLONES.load(Ordering::Relaxed), - call_arg_heap_clones: CALL_ARG_HEAP_CLONES.load(Ordering::Relaxed), - container_copy_heap_clones: CONTAINER_COPY_HEAP_CLONES.load(Ordering::Relaxed), register_writes: REGISTER_WRITES.load(Ordering::Relaxed), - return_value_moves: RETURN_VALUE_MOVES.load(Ordering::Relaxed), branch_ops: BRANCH_OPS.load(Ordering::Relaxed), typed_branch_ops: TYPED_BRANCH_OPS.load(Ordering::Relaxed), call_ops: CALL_OPS.load(Ordering::Relaxed), @@ -1258,16 +1101,7 @@ pub fn vm_runtime_metrics_reset() { for counter in &INDEX_KEY_METRICS { counter.store(0, Ordering::Relaxed); } - COPY_POLICY_HEAP_CLONES.store(0, Ordering::Relaxed); - REGISTER_COPY_HEAP_CLONES.store(0, Ordering::Relaxed); - LOCAL_COPY_HEAP_CLONES.store(0, Ordering::Relaxed); - LOCAL_LOAD_HEAP_CLONES.store(0, Ordering::Relaxed); - LOCAL_STORE_HEAP_CLONES.store(0, Ordering::Relaxed); - CONST_LOAD_HEAP_CLONES.store(0, Ordering::Relaxed); - CALL_ARG_HEAP_CLONES.store(0, Ordering::Relaxed); - CONTAINER_COPY_HEAP_CLONES.store(0, Ordering::Relaxed); REGISTER_WRITES.store(0, Ordering::Relaxed); - RETURN_VALUE_MOVES.store(0, Ordering::Relaxed); BRANCH_OPS.store(0, Ordering::Relaxed); TYPED_BRANCH_OPS.store(0, Ordering::Relaxed); CALL_OPS.store(0, Ordering::Relaxed); @@ -1289,38 +1123,6 @@ pub fn vm_runtime_metrics_enabled() -> bool { false } -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_opcode_step_known_enabled() {} - -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_opcode_histogram_batch(_histogram: &[u64; VM_OPCODE_COUNT]) {} - -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_register_write_sources_batch(_sources: &[u64; VM_REGISTER_WRITE_SOURCE_COUNT]) {} - -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_index_key_metrics_batch(_sources: &[u64; VM_INDEX_KEY_METRIC_COUNT]) {} - -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_copy_policy_clone(_kind: VmValueCopyMetric, _heap_backed: bool) {} - -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_register_write_known_enabled() {} - -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_register_write() {} - -#[cfg(all(not(test), not(feature = "vm-profile")))] -#[inline(always)] -pub(crate) fn record_return_value_move() {} - #[cfg(all(not(test), not(feature = "vm-profile")))] #[inline(always)] pub(crate) fn record_branch_op_known_enabled(_typed: bool) {} @@ -1341,6 +1143,79 @@ pub fn vm_runtime_metrics_snapshot() -> VmRuntimeMetrics { #[cfg(all(not(test), not(feature = "vm-profile")))] pub fn vm_runtime_metrics_reset() {} +/// What a function's reachable subtree does with its module's globals. +/// +/// Three answers rather than a bool, because the two ways of failing are +/// different things to tell a program: a body that *writes* a global is asking +/// for something a cross-module call cannot give it, while a body that makes a +/// call this walk cannot follow is merely unproven — the same refusal, but for +/// a reason the author can act on differently. +pub(crate) enum GlobalUse { + /// Reads these global slots (possibly none) and writes nothing. + Reads(alloc::vec::Vec), + /// Contains a `SetGlobal`. + Writes, + /// Makes a call whose target this walk cannot name, so nothing about + /// globals is proven past it. + OpaqueCall, +} + +impl GlobalUse { + /// The `(writes, reads)` shape the cross-module dispatch check wants: an + /// unfollowable call counts as a write, because the reads it hides would + /// otherwise be missing from a list that is supposed to be complete. + pub(crate) fn writes_and_reads(self) -> (bool, alloc::vec::Vec) { + match self { + GlobalUse::Reads(reads) => (false, reads), + GlobalUse::Writes | GlobalUse::OpaqueCall => (true, alloc::vec::Vec::new()), + } + } +} + +/// How a function's reachable subtree uses its module's globals. +/// +/// Reachability follows `CallDirect` and `MakeClosure`, the two opcodes that +/// name a function index statically — the same edges the AOT hybrid prescan +/// walks. An indirect call (a closure through a register, a builtin loaded into +/// one, a method dispatch) cannot be followed, so it is [`GlobalUse::OpaqueCall`]: +/// that keeps the read list complete for every function that answers +/// [`GlobalUse::Reads`], which is what both callers rely on. +/// +/// Two callers, one walk. The compiler records this per impl method so a +/// cross-module trait dispatch can seed exactly the globals the body reads; the +/// runtime asks the same question of an ordinary function when it is passed out +/// of its module as a value. Two walks that disagreed would let a function that +/// writes a global cross a boundary where the write would be lost. +pub(crate) fn function_global_use(module: &crate::vm::Module, root: u32) -> GlobalUse { + use crate::vm::ir::Opcode; + + /// A call this walk cannot follow to a named function index. + fn is_opaque_call(op: Opcode) -> bool { + matches!(op, Opcode::Call | Opcode::CallNamed | Opcode::CallMethodK) + } + + let mut reads: alloc::vec::Vec = alloc::vec::Vec::new(); + let mut seen = alloc::vec![false; module.functions.len()]; + let mut stack = alloc::vec![root as usize]; + while let Some(index) = stack.pop() { + if index >= module.functions.len() || core::mem::replace(&mut seen[index], true) { + continue; + } + for instr in &module.functions[index].code { + match instr.opcode() { + Opcode::SetGlobal => return GlobalUse::Writes, + op if is_opaque_call(op) => return GlobalUse::OpaqueCall, + Opcode::GetGlobal => reads.push(instr.bx()), + Opcode::CallDirect | Opcode::MakeClosure => stack.push(instr.b() as usize), + _ => {} + } + } + } + reads.sort_unstable(); + reads.dedup(); + GlobalUse::Reads(reads) +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/src/vm/artifact.rs b/core/src/vm/artifact.rs index e0998451..f061f290 100644 --- a/core/src/vm/artifact.rs +++ b/core/src/vm/artifact.rs @@ -1,6 +1,5 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::fast_hash_map_new; use alloc::sync::Arc; use anyhow::{Result, anyhow, bail}; @@ -8,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{ stmt::import::ImportStmt, - val::{HeapRef, RuntimeMapKey, ShortStr}, + val::{RuntimeMapKey, ShortStr}, }; use super::{ @@ -38,7 +37,7 @@ use super::{ // consumer with an empty table rather than a wrong one — still a semantic // difference, hence the bump. // Version 11: `ModuleData.type_scope` carries the identity of the module as a -// declarer of types (see `super::TypeScope`). A v10 artifact has no scope, so a +// declarer of types (see `crate::val::TypeScope`). A v10 artifact has no scope, so a // v11 consumer would file every one of its declared types under the anonymous // scope and collide them with the host program's — exactly the wrong-dispatch // bug the scope exists to close, hence a rejection rather than a default. @@ -47,7 +46,25 @@ use super::{ // `false` is the *permissive* answer — a v11 artifact would let a // global-writing method run against a temporary copy of its module's globals // and silently drop the write, so this one cannot degrade quietly either. -pub const MODULE_ARTIFACT_VERSION: u32 = 12; +// Version 15: `TypeInfo.structs` carries each `struct`'s field names in +// declaration order, which is what `display` prints an instance's fields in. It +// decodes to empty, and empty means "fall back to sorting by name" — so a v14 +// artifact would print its structs in a different order than the source it was +// built from. Cosmetic, but a golden-output comparison is not. +// Version 16: `ImplDecl.trait_name` is optional — an inherent `impl Type { … }` +// names no trait. A v15 artifact encodes it as a bare string, which a v16 +// consumer cannot read as an `Option`; a v15 consumer cannot read the `null` a +// v16 producer writes. Neither direction degrades quietly, but the version says +// so first. +// Version 17: `LoadNative` was removed and the opcodes above it were renumbered +// to close the hole — see `opcodes_are_contiguous` for why the hole could not +// simply be left. +/// Bumped to 18 when `StructDecl.fields` became `Vec` — a +/// field's *declared type* travels with its name now, because that is the only +/// thing that says what a field read produces. A v17 artifact encodes the +/// fields as bare strings, which a v18 consumer cannot read as records, and the +/// other direction is the same mismatch. +pub const MODULE_ARTIFACT_VERSION: u32 = 18; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ModuleArtifact { @@ -59,9 +76,6 @@ pub struct ModuleArtifact { impl ModuleArtifact { pub fn new(imports: Vec, module: &Module) -> Result { - if !module.natives.is_empty() { - bail!("Module artifact cannot encode inline native entries"); - } Ok(Self { format: "lk.module".to_string(), version: MODULE_ARTIFACT_VERSION, @@ -118,11 +132,11 @@ pub struct ModuleData { #[serde(default, skip_serializing_if = "super::TypeInfo::is_empty")] pub type_info: super::TypeInfo, /// Identity of this module as a declarer of types; see - /// [`super::TypeScope`]. Not skippable — an absent scope would silently + /// [`crate::val::TypeScope`]. Not skippable — an absent scope would silently /// mean "anonymous", which is a *different* type identity, not a missing /// one. #[serde(default)] - pub type_scope: super::TypeScope, + pub type_scope: crate::val::TypeScope, } impl ModuleData { @@ -177,7 +191,6 @@ impl ModuleData { type_info: self.type_info, type_scope: self.type_scope, functions, - natives: Vec::new(), globals: { let mut globals = Vec::with_capacity(self.globals.len()); for name in self.globals { @@ -253,7 +266,6 @@ impl FunctionData { } code }, - analyses: Vec::new(), performance: self.performance, register_count: self.register_count, param_count: self.param_count, @@ -290,7 +302,7 @@ impl ConstPoolData { Self { ints: pool.ints.clone(), floats: pool.floats.clone(), - strings: pool.strings.clone(), + strings: pool.strings.iter().map(|s| s.to_string()).collect(), heap_values, } } @@ -299,7 +311,7 @@ impl ConstPoolData { Ok(ConstPool { ints: self.ints, floats: self.floats, - strings: self.strings, + strings: self.strings.into_iter().map(Arc::::from).collect(), heap_values: { let mut values = Vec::with_capacity(self.heap_values.len()); for value in self.heap_values { @@ -393,7 +405,7 @@ impl ConstHeapValueData { ConstHeapValue::List(out) } Self::Map(values) => { - let mut map = fast_hash_map_new(); + let mut map = crate::util::value_map::value_map_new(); for (key, value) in values { map.insert(key.into_runtime_key()?, value.into_runtime_value()?); } @@ -411,7 +423,6 @@ pub enum RuntimeMapKeyData { Int(i64), ShortStr(String), String(String), - Obj(u32), } impl RuntimeMapKeyData { @@ -422,7 +433,6 @@ impl RuntimeMapKeyData { RuntimeMapKey::Int(value) => Self::Int(*value), RuntimeMapKey::ShortStr(value) => Self::ShortStr(value.as_str().to_string()), RuntimeMapKey::String(value) => Self::String(value.to_string()), - RuntimeMapKey::Obj(value) => Self::Obj(value.index()), } } @@ -435,7 +445,6 @@ impl RuntimeMapKeyData { ShortStr::new(&value).ok_or_else(|| anyhow!("artifact short string key exceeds inline limit"))?, ), Self::String(value) => RuntimeMapKey::String(Arc::::from(value)), - Self::Obj(value) => RuntimeMapKey::Obj(HeapRef::new(value)), }) } } @@ -490,7 +499,7 @@ return 1;\n"; assert_eq!(info.traits.len(), 1, "the trait declaration is recorded"); assert_eq!(info.traits[0].name, "Show"); assert_eq!(info.impls.len(), 1, "the impl block is recorded"); - assert_eq!(info.impls[0].trait_name, "Show"); + assert_eq!(info.impls[0].trait_name.as_deref(), Some("Show")); assert_eq!(info.impls[0].type_name, "Point"); assert_eq!(info.impls[0].methods.len(), 1); assert_eq!(info.impls[0].methods[0].name, "show"); @@ -513,7 +522,7 @@ return 1;\n"; #[test] fn module_artifact_rejects_previous_version() { - assert_eq!(MODULE_ARTIFACT_VERSION, 12); + assert_eq!(MODULE_ARTIFACT_VERSION, 18); let source = "return 1;\n"; let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); diff --git a/core/src/vm/cache.rs b/core/src/vm/cache.rs index e32726fd..f9a260da 100644 --- a/core/src/vm/cache.rs +++ b/core/src/vm/cache.rs @@ -1,9 +1,6 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::{ - val::HeapRef, - vm::analysis::{PerfCallFact, PerfIndexFact}, -}; +use crate::{val::HeapRef, vm::analysis::PerfIndexFact}; #[derive(Clone, Copy, Debug)] pub struct IndexInlineCache { @@ -15,23 +12,10 @@ pub struct IndexInlineCache { #[derive(Clone, Debug, Default)] pub struct InlineCaches { - pub globals: Vec>, pub indexes: Vec>, - pub calls: Vec>, } impl InlineCaches { - pub fn global(&self, pc: usize) -> Option { - self.globals.get(pc).copied().flatten() - } - - pub fn set_global(&mut self, pc: usize, slot: u16) { - if self.globals.len() <= pc { - self.globals.resize(pc + 1, None); - } - self.globals[pc] = Some(slot); - } - pub fn index(&self, pc: usize, handle: HeapRef, generation: u64) -> Option { self.indexes .get(pc) @@ -66,17 +50,6 @@ impl InlineCaches { pub fn index_cache_for_tests(&self, pc: usize) -> Option { self.indexes.get(pc).copied().flatten() } - - pub fn call(&self, pc: usize) -> Option { - self.calls.get(pc).copied().flatten() - } - - pub fn set_call(&mut self, pc: usize, fact: PerfCallFact) { - if self.calls.len() <= pc { - self.calls.resize(pc + 1, None); - } - self.calls[pc] = Some(fact); - } } #[cfg(test)] diff --git a/core/src/vm/compiler.rs b/core/src/vm/compiler.rs index f4f022fe..71a39bbc 100644 --- a/core/src/vm/compiler.rs +++ b/core/src/vm/compiler.rs @@ -5,6 +5,7 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; +use alloc::rc::Rc; mod assign; mod builder; mod call; @@ -40,11 +41,10 @@ use crate::{ expr::{Expr, Pattern, TemplateStringPart}, operator::{BinOp, UnaryOp}, stmt::{ForPattern, Program, Stmt}, - util::fast_map::FastHashMap, val::{FunctionNamedParamType, LiteralVal, RuntimeMapKey, ShortStr, Type}, }; -use super::{ConstHeapValue, ConstRuntimeValue, Function, GlobalSlot, Instr, Module, NativeEntry, Opcode}; +use super::{ConstHeapValue, ConstRuntimeValue, Function, GlobalSlot, Instr, Module, Opcode}; use crate::vm::analysis::{ PerfCallTargetKind, PerfContainerBuildFact, PerfGlobalFact, PerfKeyFact, PerfRegisterFact, PerfStringIntKeyFact, PerfValueKind, @@ -61,14 +61,41 @@ pub struct Compiler { next_reg: u16, peak_reg: u16, // highest next_reg ever reached — used for register_count locals: HashMap, - function_names: HashMap, - function_signatures: HashMap, - function_bodies: HashMap, - native_names: HashMap, - global_names: HashMap, + /// Which scope each live binding was declared in. + /// + /// `locals` alone cannot answer "is this name bound *here*, or outside?", + /// and `let` needs that: a `let` shadowing an outer binding must take a + /// fresh register, because reusing the outer one overwrites the value the + /// enclosing scope goes back to reading. `if c { let x = 2; }` left `x` at + /// 2 outside the block — in every construct, silently. + local_scopes: HashMap, + /// How many scopes deep the lowering currently is. Bumped wherever + /// `locals` is saved and restored. + scope_depth: u32, + // The ten tables below describe the *program*, not the function being + // compiled: names, signatures, inlinable bodies, widths. Every function gets + // its own `Compiler`, and each one used to receive a deep **clone** of all + // ten — so compiling the n-th function copied everything the n-1 before it + // had declared, and `function_bodies` copies an AST per entry. Quadratic in + // the size of the file, and measurably so: 1000 functions type-checked and + // compiled in 0.55s, 2000 in 2.30s, 4000 in 10.9s. + // + // `Rc` because none of them is ever written after `collect_*` builds it — + // sharing is the whole truth about them, and a clone is now a refcount bump. + // Read sites are unchanged: `Rc` derefs. + function_names: Rc>, + function_signatures: Rc>, + function_bodies: Rc>, + native_names: Rc>, + global_names: Rc>, /// Top-level `let` names visible to callables: user-data globals, not /// module objects — method calls on them dispatch as methods. - user_let_globals: HashSet, + user_let_globals: Rc>, + /// Every top-level name bound to user data (`let` / `const` / `:=`), plus + /// whatever the host declares as data (the REPL's own bindings). Used only + /// to tell a value apart from an imported module object at a method call; + /// unlike [`Self::user_let_globals`] it does not affect register caching. + top_level_data_globals: Rc>, capture_names: HashMap, capture_cells: HashSet, cell_locals: HashSet, @@ -80,7 +107,35 @@ pub struct Compiler { /// driver-ish code annotates its widths — and anything it cannot prove /// simply does not get the wrap, which the type checker has already /// rejected by then. - machine_regs: HashMap, + /// What a register's declared width says, when it says anything. + /// + /// One table with two answers rather than two tables: a producer records + /// what the register *is*, and the compiler cannot record the scalar half + /// while forgetting the element half — which is how seven of the ten + /// boundaries a width can cross came to drop it. + pub(super) machine_regs: HashMap, + /// Top-level functions that declare a machine-int return, by name. Collected + /// once so a `let` bound to a call can learn its width — see + /// [`Compiler::initializer_machine_width`]. + function_machine_returns: Rc>, + /// Machine-int widths of the names this closure captured, learned from the + /// enclosing scope at the moment the closure was built. A capture is read + /// through `LoadCapture` into a fresh register, which carries nothing. + capture_machine_widths: HashMap, + /// Machine-int widths of top-level bindings, by name — see + /// [`support::collect_top_level_machine_widths`]. + global_machine_widths: Rc>, + /// Machine-int field widths, by struct name then field name — see + /// [`support::collect_struct_field_machine_widths`]. + struct_field_machine_widths: Rc>>, + /// Method names any `impl` in this program declares — see + /// [`support::collect_impl_method_names`]. A call to one of these is never + /// lowered to a builtin opcode. + impl_method_names: Rc>, + /// Which struct a local is known to hold, learned from a struct-literal + /// initializer or a declared type. The compiler tracks no other types; this + /// exists only to give `r.field` a width to wrap to. + local_struct_types: HashMap, /// Loop-pattern variables of the enclosing `for` loops: the fused loop /// opcodes own the raw register, so a capture takes a fresh snapshot cell /// per capture site instead of re-binding the register (per-iteration @@ -99,15 +154,54 @@ pub struct Compiler { loops: Vec, loop_const_scopes: Vec>, single_char_string_locals: HashMap, - const_map_locals: HashMap>, + const_map_locals: HashMap>, local_rebind_suppression: u16, + /// The `let` binding whose initializer is being lowered, if any. + /// + /// Only a diagnostic: a binding is not in scope inside its own initializer, + /// so `let fact = |n| … fact(n - 1) …;` cannot resolve `fact` — and the + /// report was "Compiler undefined callable `fact`", a sentence about an + /// operand for a rule about scope. Knowing which binding is being + /// initialized is what lets the message state the rule. + initializing_binding: Option, top_level: bool, + /// The one register every top-level `fn` declaration publishes through. + /// + /// A declaration is `LoadFunction r; SetGlobal r, slot` — the register is + /// dead the instant the store lands, so 236 declarations paying 236 + /// registers is 235 more than the work needs. That is not a rounding error: + /// registers are `u8` in the encoding, so the top level has 256, and + /// `bare-metal-x86/program.lk` with its drivers bundled in declares 236 + /// functions. It ran out on the *constants* that came afterwards, which is + /// nowhere near the cause. + /// + /// One shared register rather than a recycled one, and the difference + /// matters. Recycling — handing the register back so anything may use it + /// next — is wrong here for a reason outside this compiler: the AOT + /// lowering tracks what a register *means* keyed by `(block, register)` + /// with no notion of time, so a register that once held a function value + /// keeps that meaning. A later `SetGlobal` from it is then read as + /// declaration bookkeeping and elided — a global write silently dropped. + /// A register that only ever holds a function value being published cannot + /// have that happen to it, because its meaning never changes. + // TODO: make the AOT lowering's `builtin_regs` time-aware, and this can go + // back to being an ordinary watermark like every other statement's. + fn_publish_reg: Option, emitted_return: bool, } +/// The most arguments one call can pass, and therefore the most parameters a +/// callable can usefully declare. +/// +/// It is the `Call` opcode's positional-count operand — 7 bits — and that is a +/// fact about the encoding, not about the program. Which is exactly why it +/// belongs in *one* named place with the reason written down: it leaked into +/// two different messages as a bare `max 127`, and a third one said `max 255` +/// about the same kind of limit somewhere else. +pub(crate) const MAX_CALL_ARGUMENTS: usize = i8::MAX as usize; + impl Compiler { pub(super) fn lower_expr(&mut self, expr: &Expr) -> Result { - self.record_expr_analysis(expr); match expr { Expr::Paren(inner) => self.lower_expr(inner), Expr::Cast(inner, ty) => self.lower_cast(inner, ty), @@ -122,7 +216,7 @@ impl Compiler { Expr::Call(name, args) => self.lower_named_call(name, args), Expr::CallExpr(callee, args) => self.lower_call_expr(callee, args), Expr::CallNamed(callee, positional, named) => self.lower_named_arg_call(callee, positional, named), - Expr::Closure { params, body } => self.lower_closure(params, body), + Expr::Closure { params, body, .. } => self.lower_closure(params, body), Expr::Unary(op, inner) => self.lower_unary(op, inner), Expr::And(lhs, rhs) => self.lower_short_circuit(lhs, rhs, ShortCircuitKind::And), Expr::Or(lhs, rhs) => self.lower_short_circuit(lhs, rhs, ShortCircuitKind::Or), @@ -130,6 +224,11 @@ impl Compiler { Expr::OptionalAccess(target, key) => self.lower_optional_access(target, key), Expr::TemplateString(parts) => self.lower_template_string(parts), Expr::Block(statements) => self.lower_block_expr(statements), + Expr::Try { + body, + catch_var, + handler, + } => self.lower_try_expr(body, catch_var, handler), Expr::Range { start, end, @@ -141,7 +240,7 @@ impl Compiler { Expr::Conditional(condition, then_expr, else_expr) => { self.lower_conditional(condition, then_expr, else_expr) } // Every `Expr` variant lowers — parse-time-desugared sugar - // (try/catch → pcall, select → select$block) never reaches here + // (select → select$block) never reaches here // as a dedicated node. } } @@ -151,28 +250,369 @@ impl Compiler { /// Anything else clears the note: a register reused for a different value /// must not keep an old width, or arithmetic would wrap to a type the /// value no longer has. + /// The machine width a `let`'s initializer produces, when it has one and + /// nobody wrote it down. + /// + /// Machine-int arithmetic wraps to its width, and the wrap is emitted where + /// the width is *proven*. Proof used to come from exactly two places: an + /// annotation, and an `as` cast. Everything else was left unproven, on the + /// grounds that not wrapping is the safe answer — but not wrapping is a + /// different answer, and the type checker had already decided which one is + /// right: + /// + /// ```lk + /// fn read() -> u32 { return 4000000000 as u32; } + /// let a = read(); let b = read(); println(a + b); // 8000000000 + /// let c: u32 = 4000000000; let d: u32 = 4000000000; + /// println(c + d); // 3705032704 + /// ``` + /// + /// Same types, same values, and the answer turned on whether a width had + /// been typed out. This closes the three ways it can be known without one: + /// a call to a function that declares a machine return, a builtin whose + /// name *is* the width (`volatile_read_u32`, `port_in_u8`), and a read of a + /// local already known to hold one. + /// + /// Deliberately not a general inference pass. Everything it does not + /// recognize stays unproven and unwrapped, exactly as before — this widens + /// what can be proven, it does not change what proof means. + pub(super) fn initializer_machine_width(&self, expr: &Expr) -> Option { + match expr { + Expr::Paren(inner) => self.initializer_machine_width(inner), + // A shift or a bitwise operation keeps the width it is given. + // + // `let mask = flags << 3;` is a `u32` if `flags` is one, and until + // this was here it was nothing: the parser desugars `<<` into a call + // and a call's width came only from a declared return type. That + // mattered beyond tidiness — `(1u64 << 63) >> 63` could not be told + // to shift logically, because by the time the `>>` was lowered its + // operand had no proven width left to consult. + // + // The *type checker* deliberately does not do this, and that + // asymmetry is load-bearing. Teaching it the same rule makes + // `let top = one << 63; top < one;` type-check — and it is then + // compiled wrong: comparison and division on the `i64` carrier are + // signed, so a `u64` with bit 63 set compares as negative and + // divides as negative. Today the checker calls that expression a + // width mismatch and refuses it, which is not helpful but is not + // *wrong*. Closing this properly means unsigned compare, divide and + // modulo — opcodes, in both backends — and the checker's half is the + // last piece of that, not the first. + Expr::Call(name, args) + if matches!( + name.as_str(), + "__lk_shl" | "__lk_shr" | "__lk_shr_u" | "__lk_bit_and" | "__lk_bit_or" | "__lk_bit_xor" + ) && args.len() == 2 => + { + self.expr_machine_width(&args[0]) + } + Expr::Call(name, args) if name.as_str() == "__lk_bit_not" && args.len() == 1 => { + self.expr_machine_width(&args[0]) + } + // Arithmetic keeps the width too, and leaving it out was not merely + // untidy. `println(top + 5)` printed a negative number where + // `let big = top + 5; println(big);` printed the right one, because + // only the second had a *register* to carry the fact. The same hole + // made `(a + b) >> 1` on a `u64` shift arithmetically — a wrong + // value, not just a wrong rendering — since the shift asks this + // question about its left operand and got `None`. + // + // The literal is admitted on either side for the reason + // `lower_unsigned_bin` admits it: the type checker has already + // measured it against this width, so it is that width. + Expr::Bin(lhs, op, rhs) if op.is_arith() => { + match (self.expr_machine_width(lhs), self.expr_machine_width(rhs)) { + (Some(left), Some(right)) => (left == right).then_some(left), + (Some(left), None) => support::is_int_literal(rhs).then_some(left), + (None, Some(right)) => support::is_int_literal(lhs).then_some(right), + (None, None) => None, + } + } + // Both shapes, because name resolution rewrites a plain call: + // `read()` is `Call("read", …)` in the parser's output and + // `CallExpr(Var("read"), …)` by the time the compiler sees it. + // Matching only the first is why the first version of this looked + // correct and changed nothing. + Expr::Call(name, _) => self.call_machine_width(name), + Expr::CallExpr(callee, _) => match callee.as_ref() { + Expr::Var(name) => self.call_machine_width(name), + _ => None, + }, + _ => None, + } + } + + /// The width a call to `name` produces: a user function that declares one, + /// or a builtin whose name *is* one. + pub(in crate::vm::compiler) fn call_machine_width(&self, name: &str) -> Option { + self.call_register_width(name).and_then(RegisterWidth::scalar) + } + + /// The width a call to `name` produces, scalar or elements — a user + /// function that declares one, or a builtin whose name *is* one. + pub(in crate::vm::compiler) fn call_register_width(&self, name: &str) -> Option { + self.function_machine_returns + .get(name) + .copied() + .or_else(|| crate::typ::builtin_machine_result(name).map(RegisterWidth::Scalar)) + } + pub(super) fn note_machine_reg(&mut self, reg: u16, ty: Option<&crate::val::Type>) { - match ty { - Some(crate::val::Type::MachineInt(kind)) => { - self.machine_regs.insert(reg, *kind); + match ty.and_then(register_width_of) { + Some(width) => { + self.machine_regs.insert(reg, width); } - _ => { + None => { self.machine_regs.remove(®); } } } + /// The machine width an expression is known to produce, without lowering it. + /// + /// A local whose slot was recorded, or a call whose declared return type + /// says so. Deliberately narrow: anything it cannot prove stays unproven, + /// which everywhere else in this path means "do the ordinary thing". + pub(in crate::vm::compiler) fn expr_machine_width(&self, expr: &Expr) -> Option { + match expr { + Expr::Paren(inner) => self.expr_machine_width(inner), + Expr::Var(name) => self + .locals + .get(name) + .copied() + .and_then(|reg| self.machine_regs.get(®).copied()) + .and_then(RegisterWidth::scalar), + // `r.value` where `value` is declared `u32`. + // + // The register a field lands in has no width of its own — it came + // out of a container — so without this `r.value + 1` on a `u32` + // field added at 64 bits and answered 4294967296. Narrow on + // purpose: only a local whose struct is known, which is the shape a + // register block is read through. + Expr::Access(target, key) => self + .access_register_width_of(target, key) + .and_then(RegisterWidth::scalar), + other => self.initializer_machine_width(other), + } + } + + /// Records that `name` holds a struct, from a literal or a declared type. + pub(in crate::vm::compiler) fn note_local_struct_type( + &mut self, + name: &str, + type_annotation: Option<&crate::val::Type>, + value: &Expr, + ) { + let declared = match type_annotation { + Some(crate::val::Type::Named(struct_name)) => Some(struct_name.clone()), + _ => None, + }; + let from_literal = match value { + Expr::StructLiteral { name, .. } => Some(name.clone()), + _ => None, + }; + match declared.or(from_literal) { + Some(struct_name) => { + self.local_struct_types.insert(String::from(name), struct_name); + } + // Rebinding the name to something else ends the fact, the same way + // the width fact ends when a register changes hands. + None => { + self.local_struct_types.remove(name); + } + } + } + + /// What a register holding `target.key` — or `target[key]` — is worth. + /// + /// Two answers because the read can produce either: `s.count` where `count` + /// is a `u32` is a `Scalar`, and `s.buf` where `buf` is a `List` is + /// `Elements`, so `s.buf[0] + 1` has a width one step further on. + pub(in crate::vm::compiler) fn access_register_width_of(&self, target: &Expr, key: &Expr) -> Option { + let target = match target { + Expr::Paren(inner) => inner.as_ref(), + other => other, + }; + // Indexing *out of* something whose elements have a width: the register + // holding the container is the fact's carrier, so this reaches every + // container the compiler has a register for — a local, a parameter, a + // capture, a global, a call's result, a field. + if let Some(kind) = self.expr_register_width(target).and_then(RegisterWidth::element) { + return Some(RegisterWidth::Scalar(kind)); + } + let Expr::Var(name) = target else { + return None; + }; + // A member is a string literal; `p[field]` is an index, not `p.field`. + let field = match key { + Expr::Literal(value) => value.as_str()?, + _ => return None, + }; + let struct_name = self.local_struct_types.get(name.as_str())?; + self.struct_field_machine_widths + .get(struct_name.as_str())? + .get(field) + .copied() + } + + /// The width fact a register holding `expr` carries, either half. + /// + /// [`Self::expr_machine_width`] is the scalar half of this; the element half + /// is what an index read consults. + pub(in crate::vm::compiler) fn expr_register_width(&self, expr: &Expr) -> Option { + match expr { + Expr::Paren(inner) => self.expr_register_width(inner), + Expr::Var(name) => self + .locals + .get(name) + .copied() + .and_then(|reg| self.machine_regs.get(®).copied()) + .or_else(|| { + self.capture_machine_widths + .get(name) + .copied() + .map(RegisterWidth::Scalar) + }) + .or_else(|| self.global_machine_widths.get(name).copied()), + Expr::Access(target, key) => self.access_register_width_of(target, key), + Expr::Call(name, _) => self.call_register_width(name), + Expr::CallExpr(callee, _) => match callee.as_ref() { + Expr::Var(name) => self.call_register_width(name), + _ => None, + }, + other => self.initializer_machine_width(other).map(RegisterWidth::Scalar), + } + } + + /// A string concatenation's operands, with a carrier-filling one rendered. + /// + /// The third display site, and the one the first two made easy to miss. + /// `println(top)` and `"${top}"` were fixed by choosing the rendering where + /// the width still exists; `"addr " + top` renders in the *`+`*, which sees + /// two runtime values and an `i64` carrier, so it printed the negative + /// number the other two had stopped printing. + /// + /// Only when the other side is statically a string: `a + b` on two numbers + /// is arithmetic, and the result of that is displayed by whoever displays + /// it — this is about the operator that *is* the rendering. + pub(in crate::vm::compiler) fn rendered_concat_operands( + &self, + lhs: &Expr, + op: &BinOp, + rhs: &Expr, + ) -> Option<(Expr, Expr)> { + if !matches!(op, BinOp::Add) { + return None; + } + let is_string = |expr: &Expr| expr_static_value_kind(expr) == PerfValueKind::String; + if is_string(lhs) + && let Some(rendered) = self.unsigned_rendering_if_carrier_filling(rhs) + { + return Some((lhs.clone(), rendered)); + } + if is_string(rhs) + && let Some(rendered) = self.unsigned_rendering_if_carrier_filling(lhs) + { + return Some((rendered, rhs.clone())); + } + None + } + + /// `__lk_u64_str(expr)` when `expr` is a `u64`/`usize`, otherwise `None`. + /// + /// The narrow question the two display sites — a rendering call's arguments + /// and a template string's parts — both have to ask. Only the widths that + /// *fill* the carrier: below 64 bits the high bits are zero, so the signed + /// reading and the unsigned one are the same digits. + pub(in crate::vm::compiler) fn unsigned_rendering_if_carrier_filling(&self, expr: &Expr) -> Option { + let kind = self.expr_machine_width(expr)?; + matches!(kind, crate::val::IntKind::U64 | crate::val::IntKind::Usize).then(|| call::unsigned_rendering_of(expr)) + } + /// The machine width both operands share, if they have one. /// /// Returns `None` when either side is unproven or the widths differ — the /// type checker rejects mixed widths, so a disagreement here means the /// compiler simply could not prove it, and the safe answer is not to wrap. pub(super) fn shared_machine_width(&self, lhs: u16, rhs: u16) -> Option { - let left = self.machine_regs.get(&lhs).copied()?; - let right = self.machine_regs.get(&rhs).copied()?; + let left = self.machine_regs.get(&lhs).copied().and_then(RegisterWidth::scalar)?; + let right = self.machine_regs.get(&rhs).copied().and_then(RegisterWidth::scalar)?; (left == right).then_some(left) } + /// Carries a container's element width onto the register a read of it + /// lands in. + /// + /// For the reads that go register to register with no access expression to + /// consult — a loop variable, a destructuring bind. `for b in bytes { b + 1 }` + /// and `let [head] = bytes;` are the same read as `bytes[0]`, and they were + /// the two positions left adding at 64 bits after the others were closed. + pub(in crate::vm::compiler) fn carry_element_width(&mut self, container: u16, dst: u16) { + match self.register_element_width(container) { + Some(kind) => { + self.machine_regs.insert(dst, RegisterWidth::Scalar(kind)); + } + None => { + self.machine_regs.remove(&dst); + } + } + } + + /// Copies a container's element width onto another register holding the + /// same elements — a `ToIter` snapshot, which is the same values in a + /// different container. + pub(in crate::vm::compiler) fn copy_element_width(&mut self, src: u16, dst: u16) { + match self.register_element_width(src) { + Some(kind) => { + self.machine_regs.insert(dst, RegisterWidth::Elements(kind)); + } + None => { + self.machine_regs.remove(&dst); + } + } + } + + /// The width of what an index or field read out of `container` produces. + pub(in crate::vm::compiler) fn register_element_width(&self, container: u16) -> Option { + self.machine_regs + .get(&container) + .copied() + .and_then(RegisterWidth::element) + } + + /// Gives an integer literal the machine width of the operand beside it. + /// + /// Only a literal, and only when the other side is *proven*: a variable of + /// another numeric type is a width mistake the type checker rejects, and a + /// register whose width the compiler could not prove stays unwrapped, which + /// is the safe answer everywhere else in this path. + /// + /// The literal's range was already checked — the checker measured it against + /// this very width — so the normalisation here cannot lose anything the + /// program was entitled to. + pub(in crate::vm::compiler) fn adopt_machine_width_for_literal( + &mut self, + lhs: u16, + rhs: u16, + lhs_is_literal: bool, + rhs_is_literal: bool, + ) -> Result<()> { + let left = self.machine_regs.get(&lhs).copied().and_then(RegisterWidth::scalar); + let right = self.machine_regs.get(&rhs).copied().and_then(RegisterWidth::scalar); + match (left, right) { + (Some(kind), None) if rhs_is_literal => { + self.emit_machine_wrap(rhs, kind)?; + self.machine_regs.insert(rhs, RegisterWidth::Scalar(kind)); + } + (None, Some(kind)) if lhs_is_literal => { + self.emit_machine_wrap(lhs, kind)?; + self.machine_regs.insert(lhs, RegisterWidth::Scalar(kind)); + } + _ => {} + } + Ok(()) + } + /// Normalise `reg` to `kind`'s width in place, reusing the `as` path so the /// VM and Cranelift agree by construction rather than by two parallel /// implementations of the same masking. @@ -182,16 +622,10 @@ impl Compiler { }; let encoded = checked_u8("wrap reg", reg)?; self.emit(Instr::abc(super::ir::Opcode::CastTo, encoded, encoded, target as u8)); - self.machine_regs.insert(reg, kind); + self.machine_regs.insert(reg, RegisterWidth::Scalar(kind)); Ok(()) } - pub(super) fn record_expr_analysis(&mut self, expr: &Expr) { - if let Some(analysis) = super::ssa::pipeline::analyze_expr(expr) { - self.function.analyses.push(analysis); - } - } - pub(super) fn lower_template_string(&mut self, parts: &[TemplateStringPart]) -> Result { let parts = parts .iter() @@ -212,10 +646,16 @@ impl Compiler { self.alloc_reg(); } - // Lower each part into its register + // Lower each part into its register, handing back what it needed + // to get there — the window stays, the scratch behind it does not. + // Without this a template's parts each kept every temporary they + // used, so 60 interpolations of `${a.count(b) + i}` reached the + // 256-register ceiling and the program was refused. + let watermark = self.next_reg; for (i, part) in parts.iter().enumerate() { let target_reg = start_reg + i as u16; self.lower_template_string_part_to_register(target_reg, part, force_single_expr_string)?; + self.next_reg = self.live_register_floor().max(watermark); } let dst = self.alloc_reg(); @@ -272,8 +712,11 @@ impl Compiler { for _ in 1..parts.len() { self.alloc_reg(); } + // Per part, as in `lower_template_string`. + let watermark = self.next_reg; for (index, part) in parts.iter().enumerate() { self.lower_template_string_part_to_register(start_reg + index as u16, part, force_single_expr_string)?; + self.next_reg = self.live_register_floor().max(watermark); } self.emit(Instr::abc( Opcode::ConcatN, @@ -313,6 +756,11 @@ impl Compiler { match part { TemplateStringPart::Literal(value) => self.lower_val(&LiteralVal::from_str(value)), TemplateStringPart::Expr(expr) => { + // The other half of the rendering fix in `lower_named_call`: a + // template part is a display site too, and `"${top}"` was + // showing the same negative number `println(top)` did. + let rendered = self.unsigned_rendering_if_carrier_filling(expr); + let expr = rendered.as_ref().unwrap_or(expr.as_ref()); let value = self.lower_readonly_operand(expr)?; if !force_expr_string || self.function.performance.value_kind(value) == PerfValueKind::String { return Ok(value); @@ -339,6 +787,11 @@ impl Compiler { match part { TemplateStringPart::Literal(value) => self.emit_literal_to_register(dst, &LiteralVal::from_str(value)), TemplateStringPart::Expr(expr) => { + // Before the `force_expr_string` split, not after: with several + // parts the flag is *off* because `Concat` stringifies at + // runtime — which is exactly where the width is already gone. + let rendered = self.unsigned_rendering_if_carrier_filling(expr); + let expr = rendered.as_ref().unwrap_or(expr.as_ref()); if !force_expr_string { return self.lower_expr_to_register(dst, expr, "template part"); } @@ -360,10 +813,30 @@ impl Compiler { } pub(super) fn lower_block_expr(&mut self, statements: &[Box]) -> Result { + // A block expression is a scope, like the statement form. Without the + // restore a `let` inside one rebound the name for good — `match x { 1 + // => { let u = 5; } }` left `u` at 5 afterwards, and a match arm body + // *is* a block expression. + // + // The registers are deliberately not rolled back: the block's value + // lives in one of them, and the caller has not read it yet. + let saved_locals = self.locals.clone(); + let saved_cell_locals = self.cell_locals.clone(); + let saved_const_maps = self.const_map_locals.clone(); + let saved_scopes = self.enter_scope(); + let result = self.lower_block_expr_inner(statements); + self.cell_locals = self.scope_restored_cell_locals(&saved_locals, saved_cell_locals); + self.locals = saved_locals; + self.const_map_locals = saved_const_maps; + self.exit_scope(saved_scopes); + result + } + + fn lower_block_expr_inner(&mut self, statements: &[Box]) -> Result { let mut last = None; for stmt in statements { match stmt.as_ref() { - Stmt::Expr(expr) => { + Stmt::Expr { value: expr, .. } => { last = Some(self.lower_expr(expr)?); } Stmt::Return { .. } => { @@ -405,7 +878,13 @@ impl Compiler { let capture_base = self.alloc_regs(captures.len())?; let mut capture_names = HashMap::new(); let mut capture_cells = HashSet::new(); + let mut capture_widths = HashMap::new(); for (index, name) in captures.iter().enumerate() { + // Asked *before* the capture is lowered, while the name still + // resolves to the enclosing scope's register. + if let Some(kind) = self.expr_machine_width(&Expr::Var(name.clone())) { + capture_widths.insert(name.clone(), kind); + } let (value, is_cell) = self.lower_capture_value(name)?; self.emit_move(capture_base + index as u16, value, "closure capture")?; capture_names.insert(name.clone(), index as u16); @@ -414,8 +893,14 @@ impl Compiler { } } - let mut compiled = - self.compile_closure_function(params, body, capture_names, capture_cells, function_index + 1)?; + let mut compiled = self.compile_closure_function( + params, + body, + capture_names, + capture_cells, + capture_widths, + function_index + 1, + )?; let dst = self.alloc_reg(); self.emit(Instr::abc( Opcode::MakeClosure, @@ -434,10 +919,20 @@ impl Compiler { body: &Expr, capture_names: HashMap, capture_cells: HashSet, + capture_widths: HashMap, dynamic_function_base: u32, ) -> Result { - if params.len() > u16::MAX as usize { - bail!("Compiler closure has too many params: {}", params.len()); + // Said at the declaration, because that is where the mistake is: a call + // can pass at most `MAX_CALL_ARGUMENTS`, so a closure with more + // parameters than that could never be called at all. Reported as a + // register overflow before this — advice ("split the body") that cannot + // be followed, about a body that is not the problem. + if params.len() > MAX_CALL_ARGUMENTS { + bail!( + "this closure declares {} parameters, and {MAX_CALL_ARGUMENTS} is the most a call can pass, \ + so it could never be called. Take a list or a map instead", + params.len() + ); } let mut compiler = Self::with_names( self.function_names.clone(), @@ -448,9 +943,22 @@ impl Compiler { false, ); compiler.user_let_globals = self.user_let_globals.clone(); + compiler.top_level_data_globals = self.top_level_data_globals.clone(); compiler.capture_names = capture_names; compiler.capture_cells = capture_cells; + compiler.capture_machine_widths = capture_widths; + // The width facts a closure body needs are the enclosing compiler's, and + // none of them were being inherited: a closure reading a top-level + // `const MASK: u32` computed at 64 bits for the same reason a function + // body did. + compiler.function_machine_returns = self.function_machine_returns.clone(); + compiler.struct_field_machine_widths = self.struct_field_machine_widths.clone(); + compiler.global_machine_widths = self.global_machine_widths.clone(); compiler.dynamic_function_base = dynamic_function_base; + // Inherited so a self-call inside the body can be recognised: the body + // is a compiler of its own, and the binding being initialized is a fact + // about the enclosing `let`. + compiler.initializing_binding = self.initializing_binding.clone(); compiler.function.param_count = params.len() as u16; compiler.function.positional_param_count = params.len() as u16; compiler.function.param_names = Vec::with_capacity(params.len()); @@ -490,24 +998,49 @@ impl Compiler { pub(super) fn lower_conditional(&mut self, condition: &Expr, then_expr: &Expr, else_expr: &Expr) -> Result { let dst = self.alloc_reg(); + let watermark = self.next_reg; let false_jumps = self.emit_condition_false_jumps(condition)?; + // Each branch is its own path, so `emitted_return` is saved and + // restored around it — the same discipline `lower_if` uses. A branch + // whose block returns (`let a = if c { return 1; } else { 2 };`) left + // the flag set, and every statement after the conditional was then + // dropped as dead code: the function fell off its end and answered nil. + self.emitted_return = false; self.lower_expr_to_register(dst, then_expr, "conditional then")?; - let jmp_end = self.emit_jmp_placeholder(); + let then_returns = self.emitted_return; + let jmp_end = (!then_returns).then(|| self.emit_jmp_placeholder()); + self.next_reg = watermark; // recycle the branch's temporaries let else_start = self.function.code.len(); self.patch_condition_false_jumps(false_jumps, else_start)?; + self.emitted_return = false; self.lower_expr_to_register(dst, else_expr, "conditional else")?; + let else_returns = self.emitted_return; + self.next_reg = watermark; - let end = self.function.code.len(); - self.patch_jmp(jmp_end, end)?; + if let Some(jmp_end) = jmp_end { + let end = self.function.code.len(); + self.patch_jmp(jmp_end, end)?; + } + self.emitted_return = then_returns && else_returns; Ok(dst) } pub(super) fn materialize_list(&mut self, values: Vec) -> Result { let len = values.len(); if len > u8::MAX as usize { - bail!("Compiler list literal has {} elements, max {}", len, u8::MAX); + // Not a list literal, whatever the old message said: this packs an + // argument list for the `__lk_call_method` helper, and the values + // are already in registers — so unlike `lower_list` there is no + // build-empty-and-push route available here, because 256 live + // argument registers have already overflowed the same operand. + bail!( + "this method call packs {} arguments, and {} is the most it can: the helper receives them in \ + one register window, and a window is addressed in 8 bits. Pass a list instead", + len, + u8::MAX + ); } let base = self.alloc_regs(len)?; @@ -545,45 +1078,233 @@ impl Compiler { } if let Some(capture) = self.capture_names.get(name).copied() { let cell_or_value = self.emit_load_capture(capture)?; - if self.capture_cells.contains(name) { - return self.emit_load_cell_value(cell_or_value); + let width = self.capture_machine_widths.get(name).copied(); + let dst = if self.capture_cells.contains(name) { + self.emit_load_cell_value(cell_or_value)? + } else { + cell_or_value + }; + if let Some(kind) = width { + self.machine_regs.insert(dst, RegisterWidth::Scalar(kind)); } - return Ok(cell_or_value); + return Ok(dst); } if let Some(slot) = self.global_names.get(name).copied() { - return self.emit_get_global(slot); + return self.emit_get_global_named(slot, Some(name)); + } + Err(anyhow!("undefined name `{name}`{}", self.suggest_known_name(name))) + } + + /// What the writer probably meant, as a trailing ` — did you mean …` or the + /// empty string. + /// + /// The most common mistake in any language used to report `Compiler + /// undefined local/global `nope`` — a sentence naming this compiler and two + /// of its storage classes, for a typo. The reader's question is "what *is* + /// spelled here", and the names in scope are right here to answer it. + /// + /// The measurement is [`crate::typ::edit_distance`], the same one the + /// unknown-*type* hint uses, with the same budget: one edit for a short + /// name, two for a longer one, so `nmae` finds `name` and `x` finds + /// nothing. + pub(super) fn suggest_known_name(&self, name: &str) -> String { + let mut candidates: Vec<&str> = self.locals.keys().map(String::as_str).collect(); + candidates.extend(self.global_names.keys().map(String::as_str)); + candidates.extend(self.function_names.keys().map(String::as_str)); + + // A case difference first: likeliest mistake, surest answer. + if let Some(exact) = candidates.iter().find(|candidate| candidate.eq_ignore_ascii_case(name)) { + return alloc::format!(" — did you mean `{exact}`?"); + } + let budget = if name.len() <= 4 { 1 } else { 2 }; + let mut best: Option<(usize, &str)> = None; + for candidate in candidates { + let distance = crate::typ::edit_distance(name, candidate); + if distance <= budget && best.is_none_or(|(previous, _)| distance < previous) { + best = Some((distance, candidate)); + } + } + match best { + Some((_, candidate)) => alloc::format!(" — did you mean `{candidate}`?"), + None => String::new(), } - Err(anyhow!("Compiler undefined local/global `{name}`")) } pub(super) fn lower_bin(&mut self, lhs: &Expr, op: &BinOp, rhs: &Expr) -> Result { + // `"addr " + top` renders the `u64` unsigned — see + // `rendered_concat_operands`. + if let Some((lhs, rhs)) = self.rendered_concat_operands(lhs, op, rhs) { + return self.lower_bin(&lhs, op, &rhs); + } + // `u64` compares and divides unsigned. + // + // A value with bit 63 set *is* a negative `i64` carrier, so the ordinary + // opcodes put `1u64 << 63` below 1 and divide it to a negative. One + // comparison primitive covers all four orderings — `a > b` is `b < a`, + // and the inclusive forms are those negated — so this rewrite is three + // builtins rather than six. + // + // Rewritten *here*, before anything is lowered, because this is the + // first place with both the operator and a proven width, and because a + // call cannot easily be emitted from inside the opcode path. + if let Some(result) = self.lower_unsigned_bin(lhs, op, rhs)? { + return Ok(result); + } let static_flavor = numeric_flavor(lhs, op, rhs); + // Whether each side is written as an integer literal, before the names + // are shadowed by the registers they lower into. + let lhs_is_literal = support::is_int_literal(lhs); + let rhs_is_literal = support::is_int_literal(rhs); + // `1 + expr`: the immediate form wants the constant on the right, so the + // commuted attempt must lower `expr` to ask whether its value is a proven + // `Int`. When the answer is no, **the register it just produced is the + // operand** — falling through to lower `rhs` a second time left the first + // lowering's instructions in the stream and ran the expression twice. So + // `1 + f(x)` called `f` twice, and `return 1 + f(n - 1)` cost 2^n calls: + // `f(5)` made 63 of them, `f(50)` never finished. The answer stayed right + // for a pure function, which is how it survived. + // + // Lowering `rhs` before `lhs` reorders nothing observable: only an integer + // literal reaches `commuted_int_immediate_operand`. + // Where the operands' scratch registers are handed back. + // + // A register VM needs one temporary for a chain of `+`, not one per + // term: the result may be written over the left operand, which is + // exactly what `x += 1` has always compiled to (`AddIntI r0 r0 …`). It + // did not, and a 300-term chain — or 27 list elements each holding a + // comparison — hit the 256-register ceiling and the program was + // refused. Locals live below `live_register_floor()`, so nothing that + // outlives the expression can be reused here. + let watermark = self.next_reg; + let mut commuted_rhs = None; if static_flavor == NumericFlavor::Int && let Some(immediate) = support::commuted_int_immediate_operand(op, lhs) { let rhs = self.lower_readonly_operand(rhs)?; - if self.function.performance.value_kind(rhs) == PerfValueKind::Int { + // Not for a machine integer: the immediate form skips the width + // normalisation below, so `1 + reg` would run at 64 bits while the + // type says otherwise. + if self.function.performance.value_kind(rhs) == PerfValueKind::Int && !self.machine_regs.contains_key(&rhs) + { + self.next_reg = self.live_register_floor().max(watermark); let dst = self.alloc_reg(); return self.emit_int_immediate_to_register(dst, op, rhs, immediate); } + commuted_rhs = Some(rhs); } let lhs = self.lower_readonly_operand(lhs)?; - if let Some(immediate) = int_immediate_operand(op, rhs) { - let dst = self.alloc_reg(); + if commuted_rhs.is_none() + && let Some(immediate) = int_immediate_operand(op, rhs) + && !self.machine_regs.contains_key(&lhs) + { let flavor = if self.function.performance.value_kind(lhs) == PerfValueKind::Int { Some(NumericFlavor::Int) } else { None }; + // The destination is named only once this path is taken: it used to + // be allocated first, so an attempt that fell through left a + // register nobody would ever write. if flavor == Some(static_flavor) { + self.next_reg = self.live_register_floor().max(watermark); + let dst = self.alloc_reg(); return self.emit_int_immediate_to_register(dst, op, lhs, immediate); } } - let rhs = self.lower_readonly_operand(rhs)?; - let dst = self.alloc_reg(); + let rhs = match commuted_rhs { + Some(reg) => reg, + None => self.lower_readonly_operand(rhs)?, + }; + // A literal beside a machine integer takes its width, so the wrap below + // has two proven operands to agree about. + self.adopt_machine_width_for_literal(lhs, rhs, lhs_is_literal, rhs_is_literal)?; + // Both facts about the operands are read **before** the destination is + // named, because naming it may take one of their registers back — and + // `alloc_reg` ends a register's facts, which is what makes the reuse + // safe in the first place. Read after, `a + 1` in `fn f(a: u8)` lost the + // width it had just proven and answered 256. let flavor = numeric_flavor_from_register_facts(&self.function.performance, op, lhs, rhs).unwrap_or(static_flavor); - self.emit_bin_op_to_register_with_flavor(dst, op, lhs, rhs, flavor) + let machine_width = binary_machine_width(op) + .then(|| self.shared_machine_width(lhs, rhs)) + .flatten(); + self.next_reg = self.live_register_floor().max(watermark); + let dst = self.alloc_reg(); + self.emit_bin_op_with_width(dst, op, lhs, rhs, flavor, machine_width) + } + + /// The unsigned form of an operator, when both operands fill the carrier. + /// + /// `u64` and `usize` only: for every narrower width the high bits are zero, + /// so the signed opcode has no sign to misread and is both correct and + /// faster. Equality is not here either — bit equality is the same question + /// in both signednesses. + fn lower_unsigned_bin(&mut self, lhs: &Expr, op: &BinOp, rhs: &Expr) -> Result> { + let fills_carrier = |kind| matches!(kind, crate::val::IntKind::U64 | crate::val::IntKind::Usize); + // One side proven, and the other proven *or a literal*. + // + // The literal is the case that matters and the one that was missed: the + // type checker gives an integer literal the width of the operand beside + // it, so `top / 2` type-checks as a `u64` division — and then divided + // *signed*, because this asked for two proven operands and a literal is + // never proven. Two correct features composing into a wrong answer. + // + // A literal is safe here because the checker has already measured it + // against this width: a value that fits `u64` has the same bits read + // either way, so the unsigned operation is the right one. + // + // One thing in this class is still signed: `println(top)` on a `u64` + // with bit 63 set shows a negative number. + // + // Not done, and the reason is that it is a different kind of change from + // the five before it. Shift, compare, divide, modulo and `as Float` are + // all *operators* — the compiler chooses an unsigned form where it has + // the width. `println` is a variadic stdlib function that receives + // runtime values, so making it right means rewriting its *arguments* at + // the call site, and the same would go for every other function a `u64` + // is handed to. What is wrong there is the display, not the value; the + // arithmetic above is exact, and printing the halves or the hex is a + // workaround that computes the right thing. + let proven_or_literal = |this: &Self, expr: &Expr| { + this.expr_machine_width(expr).is_some_and(fills_carrier) || support::is_int_literal(expr) + }; + let left_proven = self.expr_machine_width(lhs).is_some_and(fills_carrier); + let right_proven = self.expr_machine_width(rhs).is_some_and(fills_carrier); + if !(left_proven || right_proven) || !proven_or_literal(self, lhs) || !proven_or_literal(self, rhs) { + return Ok(None); + } + let call = |name: &str, a: &Expr, b: &Expr| { + Expr::Call( + alloc::string::String::from(name), + alloc::vec![Box::new(a.clone()), Box::new(b.clone())], + ) + }; + let rewritten = match op { + BinOp::Lt => call("__lk_lt_u", lhs, rhs), + BinOp::Gt => call("__lk_lt_u", rhs, lhs), + BinOp::Div => call("__lk_div_u", lhs, rhs), + BinOp::Mod => call("__lk_mod_u", lhs, rhs), + // `a <= b` is `!(b < a)`, `a >= b` is `!(a < b)`. + BinOp::Le => Expr::Unary(crate::operator::UnaryOp::Not, Box::new(call("__lk_lt_u", rhs, lhs))), + BinOp::Ge => Expr::Unary(crate::operator::UnaryOp::Not, Box::new(call("__lk_lt_u", lhs, rhs))), + _ => return Ok(None), + }; + self.lower_expr(&rewritten).map(Some) + } + + /// The same rewrite, for the lower-into-a-given-register path. + pub(in crate::vm::compiler) fn lower_unsigned_bin_into( + &mut self, + dst: u16, + lhs: &Expr, + op: &BinOp, + rhs: &Expr, + ) -> Result> { + let Some(src) = self.lower_unsigned_bin(lhs, op, rhs)? else { + return Ok(None); + }; + self.emit_move(dst, src, "unsigned bin")?; + Ok(Some(())) } pub(super) fn emit_bin_op_to_register(&mut self, dst: u16, op: &BinOp, lhs: u16, rhs: u16) -> Result { @@ -603,15 +1324,24 @@ impl Compiler { rhs: u16, flavor: NumericFlavor, ) -> Result { - // Only the operators that can *produce* a machine int are wrapped. - // A comparison of two `u8`s is a `Bool`, and running it through the - // width path both emitted a pointless `CastTo` on a 0/1 and recorded - // the destination register as holding a `u8` — a stale width fact that - // a later, unrelated value in the same register would inherit. - let produces_machine_int = matches!(op, BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod); - let machine_width = produces_machine_int + let machine_width = binary_machine_width(op) .then(|| self.shared_machine_width(lhs, rhs)) .flatten(); + self.emit_bin_op_with_width(dst, op, lhs, rhs, flavor, machine_width) + } + + /// [`emit_bin_op_to_register_with_flavor`] with the operands' shared width + /// already read — for the caller that reuses an operand's register as the + /// destination and so must ask before it does. + pub(in crate::vm::compiler) fn emit_bin_op_with_width( + &mut self, + dst: u16, + op: &BinOp, + lhs: u16, + rhs: u16, + flavor: NumericFlavor, + machine_width: Option, + ) -> Result { let dst = self.emit_bin_op_unwrapped(dst, op, lhs, rhs, flavor)?; // Machine-int arithmetic wraps to its width. The operation itself runs // at 64 bits and is normalised afterwards, reusing the `as` path: two @@ -699,6 +1429,73 @@ impl Compiler { } } +/// Whether an operator can *produce* a machine integer, and so needs its result +/// wrapped to the width. +/// +/// A comparison of two `u8`s is a `Bool`, and running it through the width path +/// both emitted a pointless `CastTo` on a 0/1 and recorded the destination +/// register as holding a `u8` — a stale width fact that a later, unrelated value +/// in the same register would inherit. +/// The declared width behind a register. +/// +/// A machine integer's width is a *static* fact — a `RuntimeVal::Int` carries +/// no width, and giving it one would put a tag check on the hottest path in the +/// language — so it travels register to register, from wherever the declaration +/// was read to wherever the arithmetic happens. +/// +/// Two answers because a register holds either the number or the container it +/// comes out of, and both facts have the same producers: a parameter, a `let` +/// annotation, a global, a capture, a declared return, a struct field. Recording +/// only the first is what made `bytes[0] + 10` add at 64 bits for a +/// `List` — the value's own width was right there in the declaration and +/// nothing carried it past the container. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(in crate::vm) enum RegisterWidth { + /// The register holds a machine integer of this width. + Scalar(crate::val::IntKind), + /// The register holds a container whose *elements* are this wide. + Elements(crate::val::IntKind), +} + +impl RegisterWidth { + /// The width when the register holds the number itself. + fn scalar(self) -> Option { + match self { + Self::Scalar(kind) => Some(kind), + Self::Elements(_) => None, + } + } + + /// The width of what comes *out* of this register: the elements of a + /// container, and nothing for a number (indexing one is not a thing). + fn element(self) -> Option { + match self { + Self::Elements(kind) => Some(kind), + Self::Scalar(_) => None, + } + } +} + +/// The width a declared type contributes to the register that holds it. +pub(in crate::vm::compiler) fn register_width_of(ty: &Type) -> Option { + match ty { + Type::MachineInt(kind) => Some(RegisterWidth::Scalar(*kind)), + Type::List(element) | Type::Set(element) => match element.as_ref() { + Type::MachineInt(kind) => Some(RegisterWidth::Elements(*kind)), + _ => None, + }, + Type::Map(_, value) => match value.as_ref() { + Type::MachineInt(kind) => Some(RegisterWidth::Elements(*kind)), + _ => None, + }, + _ => None, + } +} + +fn binary_machine_width(op: &BinOp) -> bool { + matches!(op, BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod) +} + fn impl_method_type( target_type: &Type, params: &[String], @@ -771,6 +1568,19 @@ fn string_int_template_key(expr: &Expr) -> Option<(&str, &Expr)> { } } +/// Whether a `"…${suffix}"` map key's suffix is a proven `Int`. +/// +/// It gates a speculative lowering: `try_lower_string_int_key_for_map` lowers the +/// suffix and *then* checks a register fact, so an answer of `None` after that +/// point leaves the emitted instructions in the stream and the caller lowers the +/// key again — the operand would run twice. What keeps that unreachable is the +/// shape list below: a literal, a name, and arithmetic over them are all free to +/// lower twice (`Compiler::is_free_to_lower_twice` says the same thing for the +/// fused branch forms, which had exactly this bug). +/// +/// So widening this — accepting, say, a call annotated `-> Int` — would +/// reintroduce it. Whatever is added here has to be free to lower twice as well, +/// or the lowering has to stop deciding after it emits. fn string_int_key_suffix_is_int_like( expr: &Expr, locals: &crate::compat::collections::HashMap, @@ -1032,10 +1842,6 @@ pub fn compile_module(program: &Program) -> Result { Compiler::compile_module(program) } -pub fn compile_module_with_natives(program: &Program, natives: Vec) -> Result { - Compiler::compile_module_with_natives(program, natives) -} - pub fn compile_source(source: &str) -> Result { Compiler::compile_source(source) } @@ -1091,7 +1897,3 @@ fn collect_for_pattern_names(pattern: &ForPattern, out: &mut Vec Result { Compiler::compile_source_module(source) } - -pub fn compile_source_module_with_natives(source: &str, natives: Vec) -> Result { - Compiler::compile_source_module_with_natives(source, natives) -} diff --git a/core/src/vm/compiler/assign.rs b/core/src/vm/compiler/assign.rs index 0a29b1c4..7f903cc8 100644 --- a/core/src/vm/compiler/assign.rs +++ b/core/src/vm/compiler/assign.rs @@ -76,21 +76,25 @@ impl Compiler { } let rhs = self.lower_readonly_operand(value)?; - if let Some(dst) = self.locals.get(name).copied() { - let lhs = if self.cell_locals.contains(name) { - self.emit_load_cell_value(dst)? - } else { - dst - }; - let (dst, rebind_dst) = if self.cell_locals.contains(name) { - (dst, false) - } else { - self.local_write_slot(dst) - }; - let result = self.emit_bin_op_to_register(dst, op, lhs, rhs)?; + if let Some(slot) = self.locals.get(name).copied() { if self.cell_locals.contains(name) { - self.emit_store_cell_value(dst, result, "compound assign cell")?; + // The local's register holds the *cell*, so the arithmetic has + // to land somewhere else: computing into it overwrote the cell + // with the number, and the store that followed then found no + // cell to store into — + // + // let n = 1; let f = || n; n += 1; + // → StoreCellVal expected UpvalCell object + // + // The capture-cell branch below has always done it this way; + // `lhs` is the value read out of the cell, which is a fresh + // temporary and therefore a safe destination. + let lhs = self.emit_load_cell_value(slot)?; + let result = self.emit_bin_op_to_register(lhs, op, lhs, rhs)?; + self.emit_store_cell_value(slot, result, "compound assign cell")?; } else { + let (dst, rebind_dst) = self.local_write_slot(slot); + let result = self.emit_bin_op_to_register(dst, op, slot, rhs)?; if result != dst { let move_source = !self.is_current_local_slot(result); self.emit_move_with_policy(dst, result, "compound assign local", move_source)?; @@ -379,15 +383,19 @@ impl Compiler { Ok(false) } - fn emit_set_index_expr(&mut self, target: &Expr, key: &Expr, value: &Expr) -> Result<()> { - self.clear_const_map_target(target); - let target = self.lower_readonly_access_target(target)?; + fn emit_set_index_expr(&mut self, target_expr: &Expr, key: &Expr, value: &Expr) -> Result<()> { + self.clear_const_map_target(target_expr); + let was_plain = self.plain_local_receiver(target_expr); + let target = self.lower_readonly_access_target(target_expr)?; let index_fact = index_fact_from_target(&self.function.performance, target) .filter(|fact| fact.target_kind != PerfIndexTargetKind::String); let move_key = set_index_key_move_preferred(key); let (key, key_fact) = self.lower_index_key_for_target(target, index_fact, key)?; let move_key = move_key && !self.is_current_local_slot(key); let value = self.lower_readonly_operand(value)?; + // The key or the value may have boxed the target's local — see + // `reread_promoted_receiver`. + let target = self.reread_promoted_receiver(target_expr, target, was_plain)?; let move_value = !self.is_current_local_slot(value); let pc = self.function.code.len(); if let Some(const_key) = get_field_key(index_fact, key_fact) { @@ -493,11 +501,22 @@ fn rewritten_object_set_assign<'a>(name: &str, expr: &'a Expr) -> Option<(&'a Ex Some((&args[0], &args[1], &args[2])) } +/// A store written as a bare statement, over a base this compiler evaluates +/// rather than a name it re-binds. +/// +/// Both spellings, because both reach here the same way: an assignment target +/// that is a *chain* (`p.q.n`, `p.m["b"]`, `xs[0][1]`) has no name to re-bind, +/// so the parser desugars it to one of these applied to the chain before its +/// last segment. `__lk_set_index` was already accepted with any base; +/// `__lk_set_field` was not, and its sibling +/// [`rewritten_object_set_assign`] only matches the base being the assigned +/// name — so `p.q.n = 5` compiled to a call that *rebuilds* the object and +/// threw the result away. fn rewritten_map_set_call(expr: &Expr) -> Option<(&Expr, &Expr, &Expr)> { let Expr::CallExpr(callee, args) = expr else { return None; }; - if args.len() != 3 || !is_var(callee, "__lk_set_index") { + if args.len() != 3 || !(is_var(callee, "__lk_set_index") || is_var(callee, "__lk_set_field")) { return None; } Some((&args[0], &args[1], &args[2])) diff --git a/core/src/vm/compiler/builder.rs b/core/src/vm/compiler/builder.rs index eb4bea0c..b188db4a 100644 --- a/core/src/vm/compiler/builder.rs +++ b/core/src/vm/compiler/builder.rs @@ -1,5 +1,6 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; +use crate::expr::Expr; use anyhow::{Result, anyhow, bail}; use crate::vm::analysis::{ @@ -9,7 +10,79 @@ use crate::vm::analysis::{ use super::{Compiler, ConstHeapValue, Function, Instr, Opcode, support::*}; +/// Everything the compiler knows *by name* about the code it is inside. +/// +/// Set aside as a unit by [`Compiler::take_name_environment`] so an expression +/// can be lowered in a scope that is not the surrounding one. The module-level +/// tables are deliberately absent: they are shared by every scope in the +/// module, and an expression lowered elsewhere should still see them. +#[derive(Debug, Default)] +pub(super) struct NameEnvironment { + locals: crate::compat::collections::HashMap, + local_scopes: crate::compat::collections::HashMap, + cell_locals: crate::compat::collections::HashSet, + capture_names: crate::compat::collections::HashMap, + capture_cells: crate::compat::collections::HashSet, + capture_machine_widths: crate::compat::collections::HashMap, + single_char_string_locals: crate::compat::collections::HashMap, + const_map_locals: crate::compat::collections::HashMap< + String, + crate::util::value_map::ValueMap, + >, + local_struct_types: crate::compat::collections::HashMap, +} + +/// How many registers the top level may spend caching its global-backed +/// bindings before it stops. +/// +/// Not a tuning number so much as a division of a fixed budget. Registers are +/// `u8` in the instruction encoding, so a function has 256 of them and the top +/// level is a function; half is more working set than any single statement here +/// has ever needed, and the other half is what a program gets to keep. Below +/// this nothing changes at all. +const TOP_LEVEL_CACHE_LIMIT: u16 = 128; + impl Compiler { + /// Whether a top-level binding may keep its register as a cache of the + /// global slot it was just written to. + /// + /// Worth caching, and it was cached unconditionally right up until a + /// program had more than 255 top-level bindings. A kernel reaches that the + /// ordinary way: `bare-metal-x86/program.lk` plus the drivers bundled into + /// it declare 256 constants between them, none of the files anywhere near + /// unusual, and the failure was `Compiler global dst register 256 exceeds + /// u8 encoding` naming whichever `const` happened to be added last. + /// + /// So the cache gets an eviction rule. Past the limit the binding is *only* + /// a global — reads cost a `GetGlobal` and the register goes back. Nothing + /// about the meaning changes: the value was already in the global slot, + /// which is the one place a *function* could ever see it from. + /// + /// Asked *before* the initializer is lowered, never after. The register + /// file runs out on the temporaries of the statement that follows the last + /// binding, not on the binding itself, so a check that comes afterwards + /// still overflows — which is how the first version of this failed. + pub(super) fn top_level_binding_is_cacheable(&self, name: &str, is_const: bool) -> bool { + // A **mutable** name a callable can see lives in its global slot, and + // the top-level body must read and write *that* — not a register copy. + // + // The copy made one name into two variables that agreed only until the + // first write on either side: `let n = 0; fn bump() { n = n + 1; }` then + // `bump()` left the function's view at 1 and the top level's at 0, and a + // top-level `n = 5` was invisible to the function. Both backends did it, + // so nothing caught it — it is a language bug, not a divergence. + // + // A `const` keeps its cache: nothing can write it, so the register and + // the global cannot come apart. That is not only an optimisation — the + // register is where a machine-integer *width* is recorded, and a + // global-only `const PAGE_NX: u64 = 0x8000000000000000` prints as a + // negative `i64`. + if self.top_level && !is_const && self.user_let_globals.contains(name) { + return false; + } + !self.top_level || self.next_reg < TOP_LEVEL_CACHE_LIMIT || !self.global_names.contains_key(name) + } + #[inline] pub(super) fn alloc_reg(&mut self) -> u16 { let reg = self.next_reg; @@ -17,12 +90,40 @@ impl Compiler { if self.next_reg > self.peak_reg { self.peak_reg = self.next_reg; } + // The same for the performance facts, which are the other table keyed by + // register — and the one that decides which *opcode* is emitted, so a + // fact that outlives its value picks an instruction for a type the + // register no longer holds. + // + // The invariant was already being maintained here, by hand: `call.rs` + // clears the destination at a dozen call sites because a call result's + // type is unknown. Doing it where the register changes hands makes it + // hold by construction instead of by remembering. Measured on the + // example corpus before the change: 28 reads of a fact belonging to a + // register's previous occupant, one of them claiming `List`. + self.function.performance.clear_register(reg); + // A width fact belongs to the value in the register, and handing the + // register to a new value ends it. + // + // Registers are recycled at every statement boundary, and the fact + // outliving its value is a wrong answer rather than a missed + // optimisation: `let z: u8 = 0; println(z - 1)` answered -1 instead of + // 255 when the literal `1` happened to land on a register an `i8` had + // used two statements earlier. The literal takes the *other* operand's + // width only if its own register claims none — and that register was + // still claiming `i8`, so the two disagreed and nothing was wrapped. + // Nothing about the failing program mentioned `i8`. + self.machine_regs.remove(®); reg } pub(super) fn alloc_regs(&mut self, count: usize) -> Result { let count = u16::try_from(count).map_err(|_| anyhow!("Compiler register block too large: {count}"))?; let base = self.next_reg; + // Same reason as `alloc_reg`, for the whole block. + for reg in base..base.saturating_add(count) { + self.machine_regs.remove(®); + } self.next_reg = self .next_reg .checked_add(count) @@ -56,6 +157,24 @@ impl Compiler { if dst == src { return Ok(()); } + // A machine int keeps its width when it is moved, and a register that + // receives a value of no particular width stops claiming one. + // + // Both halves in one place, because a width fact is only ever about the + // value *currently* in a register: the second half is what makes a + // stale width impossible rather than merely absent. Every site that + // wrote a register used to be responsible for remembering, and the + // comment in `emit_bin_op_to_register_with_flavor` says what forgetting + // costs — "a stale width fact that a later, unrelated value in the same + // register would inherit". + match self.machine_regs.get(&src).copied() { + Some(kind) => { + self.machine_regs.insert(dst, kind); + } + None => { + self.machine_regs.remove(&dst); + } + } let pc = self.function.code.len(); self.emit(Instr::abc( Opcode::Move, @@ -75,13 +194,106 @@ impl Compiler { Ok(()) } + /// Whether `target` is a plain local that is *not* yet a capture cell — + /// the one shape whose register a later operand can change underneath a + /// reference already taken to it. + pub(super) fn plain_local_receiver(&self, target: &Expr) -> Option { + let Expr::Var(name) = target else { return None }; + (self.locals.contains_key(name.as_str()) && !self.cell_locals.contains(name.as_str())).then(|| name.to_string()) + } + + /// Re-reads a target when lowering the operands after it promoted it. + /// + /// A target that is a plain local is the local's *register*, not a copy. + /// Capturing that local in a closure boxes it in place + /// (`promote_captured_local` moves the cell over the register), so an + /// operand containing such a closure changes what the already-taken target + /// points at — and the instruction then runs against the cell: + /// + /// ```text + /// xs.map(|x| x + xs.len()) → UpvalCell has no method 'map' + /// xs[0] = || xs.len() → SetIndex target object changed … "UpvalCell" + /// ``` + /// + /// Re-reading is free in every other case (a set lookup) and costs nothing + /// semantically here: the target is a variable, so reading it twice has no + /// effect the first read did not. + pub(super) fn reread_promoted_receiver( + &mut self, + target: &Expr, + receiver: u16, + was_plain: Option, + ) -> Result { + match was_plain { + Some(name) if self.cell_locals.contains(&name) => self.lower_readonly_operand(target), + _ => Ok(receiver), + } + } + + /// Take every name the surrounding code has bound, leaving none. + /// + /// Pairs with [`Self::restore_name_environment`]. Only names go: registers, + /// the function being built and the module's own tables stay, because the + /// expression to be lowered still belongs to this function's code. + pub(super) fn take_name_environment(&mut self) -> NameEnvironment { + NameEnvironment { + locals: core::mem::take(&mut self.locals), + local_scopes: core::mem::take(&mut self.local_scopes), + cell_locals: core::mem::take(&mut self.cell_locals), + capture_names: core::mem::take(&mut self.capture_names), + capture_cells: core::mem::take(&mut self.capture_cells), + capture_machine_widths: core::mem::take(&mut self.capture_machine_widths), + single_char_string_locals: core::mem::take(&mut self.single_char_string_locals), + const_map_locals: core::mem::take(&mut self.const_map_locals), + local_struct_types: core::mem::take(&mut self.local_struct_types), + } + } + + pub(super) fn restore_name_environment(&mut self, saved: NameEnvironment) { + self.locals = saved.locals; + self.local_scopes = saved.local_scopes; + self.cell_locals = saved.cell_locals; + self.capture_names = saved.capture_names; + self.capture_cells = saved.capture_cells; + self.capture_machine_widths = saved.capture_machine_widths; + self.single_char_string_locals = saved.single_char_string_locals; + self.const_map_locals = saved.const_map_locals; + self.local_struct_types = saved.local_struct_types; + } + pub(super) fn insert_local(&mut self, name: impl Into, reg: u16) -> Option { let name = name.into(); self.single_char_string_locals.remove(&name); self.function.performance.mark_local_slot(reg); + self.local_scopes.insert(name.clone(), self.scope_depth); self.locals.insert(name, reg) } + /// Opens a nested scope, answering what [`Self::exit_scope`] needs back. + /// + /// The bindings themselves are saved by the callers (each has its own rule + /// for `cell_locals` and the const-map cache); this pairs with them to keep + /// the *depth* consistent, which is what tells a `let` whether the name it + /// is binding belongs to this scope or an enclosing one. + pub(super) fn enter_scope(&mut self) -> crate::compat::collections::HashMap { + self.scope_depth += 1; + self.local_scopes.clone() + } + + pub(super) fn exit_scope(&mut self, saved: crate::compat::collections::HashMap) { + self.scope_depth -= 1; + self.local_scopes = saved; + } + + /// Whether `name`'s live binding was declared in the scope being lowered. + /// + /// A `let` may reuse the register of a binding it *replaces* in the same + /// scope; shadowing one from an enclosing scope must not, or the value the + /// outer scope resumes reading is the inner one. + pub(super) fn local_declared_in_current_scope(&self, name: &str) -> bool { + self.local_scopes.get(name) == Some(&self.scope_depth) + } + /// Binds a *fresh declaration*: a new binding is a plain value, so any /// stale cell mark from a previous same-named binding is dropped (a /// leftover mark makes reads `LoadCellVal` a non-cell). Restores that @@ -272,8 +484,13 @@ impl Compiler { } pub(super) fn emit_pattern_assert(&mut self, condition: u16) -> Result<()> { + self.emit_assert(condition, "Pattern does not match value") + } + + /// Raise `message` unless `condition` holds. + pub(super) fn emit_assert(&mut self, condition: u16, message: &str) -> Result<()> { let skip_raise = self.emit_test_placeholder(condition)?; - self.emit_raise("Pattern does not match value")?; + self.emit_raise(message)?; let end = self.function.code.len(); self.patch_test_true_jump(skip_raise, end) } diff --git a/core/src/vm/compiler/call.rs b/core/src/vm/compiler/call.rs index bedb850f..ec3c49f3 100644 --- a/core/src/vm/compiler/call.rs +++ b/core/src/vm/compiler/call.rs @@ -17,11 +17,169 @@ use super::{ Compiler, Instr, Opcode, facts::{expr_static_value_kind, index_fact_from_target}, get_field_key, - support::{FunctionSignature, checked_u8, simple_local_expr_name}, + support::{FunctionSignature, access_member_name, checked_u8, simple_local_expr_name}, }; +/// `__lk_u64_str(expr)` — the unsigned decimal rendering of a carrier-filling +/// value, as an expression the caller can lower in the argument's place. +pub(in crate::vm::compiler) fn unsigned_rendering_of(expr: &Expr) -> Expr { + Expr::Call( + alloc::string::String::from("__lk_u64_str"), + alloc::vec![Box::new(expr.clone())], + ) +} + impl Compiler { + /// `__lk_shr` becomes `__lk_shr_u` when the value being shifted is a `u64`. + /// + /// Answering a `String` rather than a `&str` so the caller can rebind the + /// name: the operand has to be *peeked at* to decide, and peeking means + /// lowering it, which cannot happen twice. + fn unsigned_shift_name(&self, name: &str, args: &[Box]) -> Result { + use alloc::string::ToString; + if name != "__lk_shr" || args.len() != 2 { + return Ok(name.to_string()); + } + let Some(kind) = self.expr_machine_width(&args[0]) else { + return Ok(name.to_string()); + }; + let fills_the_carrier = matches!(kind, crate::val::IntKind::U64 | crate::val::IntKind::Usize); + Ok(if fills_the_carrier { "__lk_shr_u" } else { name }.to_string()) + } + + /// The globals that do nothing with an argument but format it. + /// + /// Deliberately short, and the boundary is not "prints" but "*only* prints". + /// `assert_eq` prints its arguments too, and also compares them — turning one + /// into a string there would make the comparison ask whether a string equals + /// a number, which is a wrong answer traded for a right rendering. A call + /// that computes with the value keeps the value. + fn renders_its_arguments(name: &str) -> bool { + matches!(name, "print" | "println" | "panic" | "error") + } + + /// Rewrites carrier-filling arguments into their unsigned rendering. + /// + /// `None` when nothing changed, so the common call pays one width lookup per + /// argument and no allocation. + #[allow(clippy::vec_box, reason = "the AST stores call arguments as `Vec>`")] + fn render_arguments_unsigned(&self, name: &str, args: &[Box]) -> Option>> { + if !Self::renders_its_arguments(name) { + return None; + } + let fills_carrier = |expr: &Expr| { + self.expr_machine_width(expr) + .is_some_and(|kind| matches!(kind, crate::val::IntKind::U64 | crate::val::IntKind::Usize)) + }; + if !args.iter().any(|arg| fills_carrier(arg)) { + return None; + } + Some( + args.iter() + .map(|arg| { + if fills_carrier(arg) { + Box::new(unsigned_rendering_of(arg)) + } else { + arg.clone() + } + }) + .collect(), + ) + } + pub(super) fn lower_named_call(&mut self, name: &str, args: &[Box]) -> Result { + // A `u64` handed to something that only *renders* it prints unsigned. + // + // The value was never wrong: `top + 5` computes the right bits. What was + // wrong is that `println` receives runtime values, where the width is + // gone, and hands the carrier to an `i64` formatter — so a page-table + // entry or a physical address above `i64::MAX` printed as a negative + // number. The width exists only here, at the call site, so this is where + // the rendering has to be chosen. + // + // Only for callees that *purely* render. Rewriting an argument changes + // its type from `Int` to `Str`, which is harmless for something that was + // going to format it and destructive for anything that compares or + // computes: `assert_eq(top, other)` would compare a string with a + // number. See `renders_its_arguments`. + if let Some(rendered) = self.render_arguments_unsigned(name, args) { + return self.lower_named_call(name, &rendered); + } + // `~x` on a machine integer is that width's complement. + // + // Every value rides an `i64` carrier, so complementing a `u32` sets the + // 32 bits above it too: `~(0xff as u32)` answered `0xFFFFFFFFFFFFFF00`, + // which reads back as -256. It stayed unnoticed because the shape people + // write is `a & ~b`, where the `&` masks the strays away — and the one + // that does not, `~mask` on its own, is exactly what a driver writes to + // clear a field. + // + // The arithmetic operators normalise afterwards; this is the same + // normalisation for the one bitwise operation that can leave the width. + // + // A previous round recorded here that this cost two examples their + // native lowering and reverted it. That was a misattribution: neither + // example contains a `~`, so this branch never fired for them, and the + // AOT coverage number is identical with and without it. The drop came + // from a change landing alongside. Measure at your own commit before + // blaming your own diff. + // The shifts are the other two. `<<` and `>>` desugar into named calls + // the same way, and they were not in this branch: at `u8`, `1 << 9` + // answered 512, and at `i32`, `1 << 31` answered 2147483648 where the + // sign bit makes it -2147483648. `&`, `|` and `^` need nothing — two + // operands already inside the width cannot leave it. + // + // The width is argument 0's in all three: a shift count has its own + // type and does not decide the result's. + if matches!(name, "__lk_bit_not" | "__lk_shl" | "__lk_shr") + && !args.is_empty() + && let Some(kind) = self.expr_machine_width(&args[0]) + { + let dst = self.lower_named_call_body(name, args)?; + self.emit_machine_wrap(dst, kind)?; + self.machine_regs.insert(dst, super::RegisterWidth::Scalar(kind)); + return Ok(dst); + } + let dst = self.lower_named_call_body(name, args)?; + // A declared return width is a width the *caller* can rely on, and the + // register the result lands in had none: `fn ret() -> u8 { … }` then + // `ret() + 10` added at 64 bits and answered 260 where `let v = ret(); + // v + 10` answered 4. The fact existed (`function_machine_returns`) and + // only `expr_machine_width` consulted it — and the arithmetic path asks + // the *register*, not the expression. + // The declared return type's width, whichever half it is: a `u8` + // return and a `List` return are the same fact one level apart, and + // recording only the first is what let `ret_buf()[0] + 10` add at 64 + // bits. + match self.call_register_width(name) { + Some(width) => { + self.machine_regs.insert(dst, width); + } + None => { + self.machine_regs.remove(&dst); + } + } + Ok(dst) + } + + fn lower_named_call_body(&mut self, name: &str, args: &[Box]) -> Result { + // `>>` on a `u64` is a *logical* shift. + // + // The parser desugars `a >> b` into `__lk_shr(a, b)` before anything + // knows a type, and the builtin behind that name shifts an `i64` + // arithmetically. Every value in this language rides an `i64` carrier, + // so for a `u8`, `u16` or `u32` the high bits are zero and the sign + // replication has nothing to replicate — it happens to be right. A + // `u64` fills the carrier: bit 63 *is* the sign bit, so + // `(1u64 << 63) >> 63` answered -1 instead of 1, silently and on both + // backends. That value is a physical address, a page-table entry, the + // high half of a 64-bit BAR. + // + // The choice is made here because this is the first place that has both + // the operator and a proven width. `usize` too, for the same reason on a + // 64-bit target. + let shift_name = self.unsigned_shift_name(name, args)?; + let name = shift_name.as_str(); if let Some(signature) = self.function_signatures.get(name).cloned() && !signature.named_params.is_empty() && self.function_names.contains_key(name) @@ -48,7 +206,7 @@ impl Compiler { return self.lower_named_call(name, args); } if let Expr::Access(target, method) = callee - && let Some(method) = method_name(method) + && let Some(method) = access_member_name(method) { if self.is_external_global_access_target(target) { if self.is_stdlib_module_method(target, "map", "get", method) { @@ -73,15 +231,22 @@ impl Compiler { let Expr::Var(name) = target else { return false; }; - // A top-level `let` occupies a global slot but holds user data: + // A top-level `let`/`:=` occupies a global slot but holds user data: // `names.len()` inside a function must dispatch as a method, not as a // module-member property read (which would index the list/map value // with the method name). + // + // This used to consult `user_let_globals`, which is `let`-only and + // additionally filtered to names some function mentions — so `xs := …` + // read from a function body dispatched as a module member and failed + // with "register N expected Int, got String". `top_level_data_globals` + // is the unfiltered set, and the REPL adds its live bindings to it. self.global_names.contains_key(name) && !self.locals.contains_key(name) && !self.function_names.contains_key(name) && !self.native_names.contains_key(name) && !self.user_let_globals.contains(name) + && !self.top_level_data_globals.contains(name) } fn is_stdlib_module_method(&self, target: &Expr, module: &str, method: &str, actual_method: &str) -> bool { @@ -176,6 +341,13 @@ impl Compiler { if self.try_lower_int_midpoint_to_register(dst, &args[0])? { return Ok(dst); } + // `math.floor(a / b)` is the only way to write integer division now + // that `/` yields a `Float`, so it gets one instruction. Reached from + // here as well as from `lower_into`, because an operand position + // (`sub - math.floor(sub / 10)`) never goes through that path. + if self.try_lower_int_floor_div_to_register(dst, &args[0])? { + return Ok(dst); + } self.next_reg = watermark; let arg = self.lower_readonly_operand(&args[0])?; if self.function.performance.value_kind(arg) == PerfValueKind::Int { @@ -242,6 +414,14 @@ impl Compiler { } fn lower_builtin_method_call(&mut self, target: &Expr, method: &str, args: &[Box]) -> Result { + // A name some `impl` in this program declares is not assumed builtin — + // see `collect_impl_method_names`. The dedicated opcodes below are + // chosen from the method name alone, with no type for the receiver, so + // a struct method called `len` answered "Len target object is not + // sized" and one called `push` failed at compile time on arity. + if self.impl_method_names.contains(method) { + return self.lower_dynamic_method_call(target, method, args); + } match method { "len" => { if !args.is_empty() { @@ -277,12 +457,19 @@ impl Compiler { } } + /// `xs.set(i, v)` / `m.set(k, v)` — writes in place and answers the + /// receiver, so writes chain the way `push` and `insert` do. + /// + /// It used to answer `nil`, which made "write one element" the one + /// mutating method you could not chain — an arbitrary split, since + /// `push` beside it has always answered the container. fn lower_set_method_call(&mut self, target: &Expr, key: &Expr, value: &Expr) -> Result { - self.emit_set_method_effect(target, key, value)?; - let dst = self.alloc_reg(); - self.emit(Instr::abc(Opcode::LoadNil, checked_u8("method set result", dst)?, 0, 0)); - self.set_register_kind(dst, PerfValueKind::Nil); - Ok(dst) + // The register the write went through *is* the answer. Lowering the + // receiver a second time here evaluated the expression twice: for a + // local that is the same slot, but `make().set(0, 9)` called `make` + // twice, wrote into the first list and answered the second — so the + // write appeared to do nothing. `push` beside it never had this shape. + self.emit_set_method_effect(target, key, value) } fn lower_len_method_call(&mut self, target: &Expr) -> Result { @@ -322,7 +509,7 @@ impl Compiler { let Expr::Access(target, method) = callee.as_ref() else { return Ok(false); }; - let Some("set") = method_name(method) else { + let Some("set") = access_member_name(method) else { return Ok(false); }; if self.is_external_global_access_target(target) { @@ -335,13 +522,19 @@ impl Compiler { Ok(true) } - fn emit_set_method_effect(&mut self, target: &Expr, key: &Expr, value: &Expr) -> Result<()> { + /// Emits the write and answers the register it wrote through — the + /// receiver, which is what `xs.set(k, v)` evaluates to. + fn emit_set_method_effect(&mut self, target: &Expr, key: &Expr, value: &Expr) -> Result { self.clear_const_map_target(target); + let was_plain = self.plain_local_receiver(target); let target_reg = self.lower_mutable_method_receiver(target)?; let index_fact = index_fact_from_target(&self.function.performance, target_reg) .filter(|fact| fact.target_kind != PerfIndexTargetKind::String); if let Some((suffix, key_fact)) = self.try_lower_string_int_key_for_map(index_fact, key)? { let value_reg = self.lower_readonly_operand(value)?; + // See `reread_promoted_receiver`: the key or the value may have + // boxed the receiver's local. + let target_reg = self.reread_promoted_receiver(target, target_reg, was_plain)?; let move_value = !self.is_current_local_slot(value_reg); let pc = self.function.code.len(); self.emit(Instr::abc( @@ -361,10 +554,11 @@ impl Compiler { if let Some(fact) = index_fact { self.function.performance.set_index_fact(pc, fact); } - return Ok(()); + return Ok(target_reg); } let (key_reg, key_fact) = self.lower_index_key_for_target(target_reg, index_fact, key)?; let value_reg = self.lower_readonly_operand(value)?; + let target_reg = self.reread_promoted_receiver(target, target_reg, was_plain)?; let move_key = set_method_key_move_preferred(key) && !self.is_current_local_slot(key_reg); let move_value = !self.is_current_local_slot(value_reg); let pc = self.function.code.len(); @@ -392,12 +586,16 @@ impl Compiler { if let Some(fact) = index_fact { self.function.performance.set_index_fact(pc, fact); } - Ok(()) + Ok(target_reg) } fn lower_push_method_call(&mut self, target: &Expr, value: &Expr) -> Result { + let was_plain = self.plain_local_receiver(target); let target_reg = self.lower_mutable_method_receiver(target)?; let value_reg = self.lower_readonly_operand(value)?; + // The value may have boxed the receiver's local — see + // `reread_promoted_receiver`. + let target_reg = self.reread_promoted_receiver(target, target_reg, was_plain)?; let move_value = !self.is_current_local_slot(value_reg); let pc = self.function.code.len(); self.emit(Instr::abc( @@ -494,11 +692,13 @@ impl Compiler { // to the generic helper call below. let name_const = self.push_string(method)?; if name_const <= u16::from(u8::MAX) && args.len() <= u8::MAX as usize { + let was_plain = self.plain_local_receiver(target); let receiver = self.lower_readonly_operand(target)?; let mut arg_regs = Vec::with_capacity(args.len()); for arg in args { arg_regs.push(self.lower_readonly_operand(arg)?); } + let receiver = self.reread_promoted_receiver(target, receiver, was_plain)?; let base = self.alloc_regs(args.len() + 1)?; self.emit_call_window_move(base, receiver, "method receiver")?; for (offset, arg) in arg_regs.iter().copied().enumerate() { @@ -514,12 +714,14 @@ impl Compiler { return Ok(base); } let helper = self.load_callable_by_name("__lk_call_method")?; + let was_plain = self.plain_local_receiver(target); let receiver = self.lower_readonly_operand(target)?; let method = self.lower_val(&LiteralVal::from_str(method))?; let mut arg_regs = Vec::with_capacity(args.len()); for arg in args { arg_regs.push(self.lower_readonly_operand(arg)?); } + let receiver = self.reread_promoted_receiver(target, receiver, was_plain)?; let args_list = self.materialize_list(arg_regs)?; self.lower_call_window_regs(helper, &[receiver, method, args_list]) } @@ -541,11 +743,22 @@ impl Compiler { bail!("Compiler named call `{function_name}` is shadowed by a local binding"); } - let signature = self - .function_signatures - .get(function_name) - .cloned() - .ok_or_else(|| anyhow::anyhow!("Compiler missing named-call signature for `{function_name}`"))?; + let Some(signature) = self.function_signatures.get(function_name).cloned() else { + // A name with no signature *here* is normally an imported function: + // signatures are collected from this program's own declarations, and + // an import has none to collect. The call still has everything it + // needs — the names are constants and the callee's own metadata + // says the order — so it goes out as a dynamic `CallNamed`, which is + // the same opcode a call through a variable uses. + // + // Without this, `use { scale } from "m"; scale(value: 3, by: 4)` + // failed to *compile*, with a sentence about the compiler's + // bookkeeping; the identical call to a local function worked. + if self.global_names.contains_key(function_name) { + return self.lower_dynamic_named_arg_call(callee, positional, named); + } + bail!("undefined function `{function_name}` called with named arguments"); + }; if positional.len() != signature.positional_count { bail!( "Compiler named call `{function_name}` expects {} positional args, got {}", @@ -574,7 +787,7 @@ impl Compiler { self.lower_signature_named_call(function_name, &signature, positional, &provided) } - fn lower_dynamic_named_arg_call( + pub(super) fn lower_dynamic_named_arg_call( &mut self, callee: &Expr, positional: &[Box], @@ -592,13 +805,20 @@ impl Compiler { let callee = self.lower_readonly_operand(callee)?; let call_base = self.alloc_regs(1 + positional.len() + named.len() * 2)?; self.emit_call_window_move(call_base, callee, "named call callee")?; + // Each argument's scratch is handed back before the next one is + // lowered: the window stays, what an argument needed to reach it does + // not. Without this a wide call cost two registers per argument on top + // of the window — 60 arguments of `f(a) + g(b) + i` reached 191. + let watermark = self.next_reg; for (offset, arg) in positional.iter().enumerate() { self.lower_expr_to_register(call_base + 1 + offset as u16, arg, "named call positional arg")?; + self.next_reg = self.live_register_floor().max(watermark); } let mut offset = 1 + positional.len() as u16; for (name, value) in named { self.emit_literal_to_register(call_base + offset, &LiteralVal::from_str(name))?; self.lower_expr_to_register(call_base + offset + 1, value, "named call arg value")?; + self.next_reg = self.live_register_floor().max(watermark); offset += 2; } @@ -662,7 +882,7 @@ impl Compiler { let reg = if index < supplied_named { self.lower_readonly_operand(&args[signature.positional_count + index])? } else if let Some(default) = param.default.as_ref() { - self.lower_readonly_operand(default)? + self.lower_named_default(default, &previous)? } else { self.restore_call_params(previous); bail!( @@ -715,7 +935,7 @@ impl Compiler { let reg = if let Some(expr) = provided.get(param.name.as_str()) { self.lower_readonly_operand(expr)? } else if let Some(default) = param.default.as_ref() { - self.lower_readonly_operand(default)? + self.lower_named_default(default, &previous)? } else { self.restore_call_params(previous); bail!( @@ -747,10 +967,15 @@ impl Compiler { let call_base = self.alloc_regs(total_count + 1)?; let mut previous = Vec::with_capacity(total_count); + // Per argument, as in `lower_named_arg_call_window`. A bound parameter + // name points *into* the window, which is below this mark, so binding + // and recycling do not compete. + let watermark = self.next_reg; let result = (|| { for (index, (param_name, arg)) in signature.positional_params.iter().zip(args.iter()).enumerate() { let dst = call_base + 1 + index as u16; self.lower_expr_to_register(dst, arg, "direct signature positional arg")?; + self.next_reg = self.live_register_floor().max(watermark); self.bind_call_param(param_name, dst, &mut previous); } @@ -764,7 +989,7 @@ impl Compiler { "direct signature positional named arg", )?; } else if let Some(default) = param.default.as_ref() { - self.lower_expr_to_register(dst, default, "direct signature default arg")?; + self.lower_named_default_to_register(dst, default, &previous, "direct signature default arg")?; } else { bail!( "Compiler missing required named argument `{}` in call to `{function_name}`", @@ -793,10 +1018,13 @@ impl Compiler { let call_base = self.alloc_regs(total_count + 1)?; let mut previous = Vec::with_capacity(total_count); + // Per argument, as in `lower_named_arg_call_window`. + let watermark = self.next_reg; let result = (|| { for (index, (param_name, arg)) in signature.positional_params.iter().zip(positional.iter()).enumerate() { let dst = call_base + 1 + index as u16; self.lower_expr_to_register(dst, arg, "direct signature named positional arg")?; + self.next_reg = self.live_register_floor().max(watermark); self.bind_call_param(param_name, dst, &mut previous); } @@ -805,7 +1033,12 @@ impl Compiler { if let Some(expr) = provided.get(param.name.as_str()) { self.lower_expr_to_register(dst, expr, "direct signature named arg")?; } else if let Some(default) = param.default.as_ref() { - self.lower_expr_to_register(dst, default, "direct signature named default arg")?; + self.lower_named_default_to_register( + dst, + default, + &previous, + "direct signature named default arg", + )?; } else { bail!( "Compiler missing required named argument `{}` in call to `{function_name}`", @@ -822,6 +1055,62 @@ impl Compiler { self.emit_direct_call_at_window(function_index, call_base, total_count) } + /// Lowers a named parameter's default, in the callee's scope rather than + /// the caller's. + /// + /// A default belongs to the *declaration*. The names it may read are the + /// callee's own parameters — bound above, in declaration order, which is + /// what makes `fn f(x: Int, {y: Int = x + 1})` work — and then module + /// scope. It used to be lowered with the caller's locals still in view, so + /// a caller that happened to have a binding of the same name captured it: + /// + /// ```lk + /// const LIMIT: Int = 7; + /// fn f({n: Int = LIMIT}) -> Int { return n; } + /// fn g() -> Int { let LIMIT = 99; return f(); } + /// ``` + /// + /// `f()` answered 7 from the top level and 99 from `g` — a silent wrong + /// answer that neither `lk check` nor any gate saw, because the default is + /// written in one function and read in another. + fn lower_named_default(&mut self, default: &Expr, bound: &[(String, Option)]) -> Result { + let params = self.bound_parameter_registers(bound); + let saved = self.take_name_environment(); + for (name, reg) in params { + self.insert_local(name, reg); + } + let lowered = self.lower_readonly_operand(default); + self.restore_name_environment(saved); + lowered + } + + /// The same, writing into a fixed destination register (the direct-call + /// path, which places each argument itself). + fn lower_named_default_to_register( + &mut self, + dst: u16, + default: &Expr, + bound: &[(String, Option)], + context: &str, + ) -> Result<()> { + let params = self.bound_parameter_registers(bound); + let saved = self.take_name_environment(); + for (name, reg) in params { + self.insert_local(name, reg); + } + let lowered = self.lower_expr_to_register(dst, default, context); + self.restore_name_environment(saved); + lowered + } + + /// The callee parameters bound so far, by the name each was bound under. + fn bound_parameter_registers(&self, bound: &[(String, Option)]) -> Vec<(String, u16)> { + bound + .iter() + .filter_map(|(name, _)| self.locals.get(name).map(|reg| (name.clone(), *reg))) + .collect() + } + fn bind_call_param(&mut self, name: &str, reg: u16, previous: &mut Vec<(String, Option)>) { previous.push((name.to_string(), self.insert_local(name.to_string(), reg))); } @@ -845,13 +1134,21 @@ impl Compiler { } fn lower_call_window_exprs(&mut self, callee: u16, args: &[&Expr]) -> Result { - if args.len() > i8::MAX as usize { - bail!("Compiler call has {} args, max {}", args.len(), i8::MAX); + if args.len() > crate::vm::compiler::MAX_CALL_ARGUMENTS { + bail!( + "this call passes {} arguments, and {} is the most one call can pass: every call names its \ + argument count in 7 bits of the instruction. Pass a list instead", + args.len(), + crate::vm::compiler::MAX_CALL_ARGUMENTS + ); } let call_base = self.alloc_regs(args.len() + 1)?; self.emit_call_window_move(call_base, callee, "call callee")?; + // Per argument, as in `lower_named_arg_call_window`. + let watermark = self.next_reg; for (offset, arg) in args.iter().copied().enumerate() { self.lower_expr_to_register(call_base + 1 + offset as u16, arg, "call arg")?; + self.next_reg = self.live_register_floor().max(watermark); } let pc = self.function.code.len(); @@ -877,7 +1174,12 @@ impl Compiler { pub(super) fn lower_call_window_regs(&mut self, callee: u16, arg_regs: &[u16]) -> Result { if arg_regs.len() > i8::MAX as usize { - bail!("Compiler call has {} args, max {}", arg_regs.len(), i8::MAX); + bail!( + "this call passes {} arguments, and {} is the most one call can pass: every call names its \ + argument count in 7 bits of the instruction. Pass a list instead", + arg_regs.len(), + crate::vm::compiler::MAX_CALL_ARGUMENTS + ); } let call_base = self.alloc_regs(arg_regs.len() + 1)?; self.emit_call_window_move(call_base, callee, "call callee")?; @@ -955,7 +1257,12 @@ impl Compiler { return Ok(inlined); } if args.len() > i8::MAX as usize { - bail!("Compiler call has {} args, max {}", args.len(), i8::MAX); + bail!( + "this call passes {} arguments, and {} is the most one call can pass: every call names its \ + argument count in 7 bits of the instruction. Pass a list instead", + args.len(), + crate::vm::compiler::MAX_CALL_ARGUMENTS + ); } let function_index = *self .function_names @@ -971,8 +1278,11 @@ impl Compiler { } let call_base = self.alloc_regs(args.len() + 1)?; + // Per argument, as in `lower_named_arg_call_window`. + let watermark = self.next_reg; for (offset, arg) in args.iter().enumerate() { self.lower_expr_to_register(call_base + 1 + offset as u16, arg, "direct call arg")?; + self.next_reg = self.live_register_floor().max(watermark); } let pc = self.function.code.len(); @@ -997,7 +1307,12 @@ impl Compiler { fn lower_direct_function_call_regs(&mut self, function_name: &str, arg_regs: &[u16]) -> Result { if arg_regs.len() > i8::MAX as usize { - bail!("Compiler call has {} args, max {}", arg_regs.len(), i8::MAX); + bail!( + "this call passes {} arguments, and {} is the most one call can pass: every call names its \ + argument count in 7 bits of the instruction. Pass a list instead", + arg_regs.len(), + crate::vm::compiler::MAX_CALL_ARGUMENTS + ); } let function_index = *self .function_names @@ -1034,14 +1349,6 @@ impl Compiler { } } -fn method_name(expr: &Expr) -> Option<&str> { - match expr { - Expr::Var(name) => Some(name.as_str()), - Expr::Literal(value) => value.as_str(), - _ => None, - } -} - pub(super) fn map_get_method_call_args<'a>(callee: &'a Expr, args: &'a [Box]) -> Option<(&'a Expr, &'a Expr)> { if args.len() != 1 { return None; @@ -1049,7 +1356,7 @@ pub(super) fn map_get_method_call_args<'a>(callee: &'a Expr, args: &'a [Box( let Expr::Access(join_target, join_method) = unparen_expr(join_callee) else { return None; }; - if method_name(join_method) != Some("join") { + if access_member_name(join_method) != Some("join") { return None; } let Expr::CallExpr(split_callee, split_args) = unparen_expr(join_target) else { @@ -1093,7 +1400,7 @@ fn split_join_same_separator_string_target<'a>( let Expr::Access(split_target, split_method) = unparen_expr(split_callee) else { return None; }; - if method_name(split_method) != Some("split") || !known_string_expr(split_target, facts, locals) { + if access_member_name(split_method) != Some("split") || !known_string_expr(split_target, facts, locals) { return None; } Some(split_target) diff --git a/core/src/vm/compiler/const_maps.rs b/core/src/vm/compiler/const_maps.rs index 51ba9822..2c420ba0 100644 --- a/core/src/vm/compiler/const_maps.rs +++ b/core/src/vm/compiler/const_maps.rs @@ -4,7 +4,7 @@ use anyhow::Result; use crate::{ expr::Expr, - util::fast_map::FastHashMap, + util::value_map::ValueMap, val::{LiteralVal, RuntimeMapKey}, vm::{ConstHeapValue, ConstRuntimeValue}, }; @@ -85,7 +85,7 @@ impl Compiler { fn const_heap_map_from_expr_literals_from_expr( expr: &Expr, -) -> Result>> { +) -> Result>> { let Expr::Map(entries) = expr else { return Ok(None); }; diff --git a/core/src/vm/compiler/container_lower.rs b/core/src/vm/compiler/container_lower.rs index 0f42d816..63f652ce 100644 --- a/core/src/vm/compiler/container_lower.rs +++ b/core/src/vm/compiler/container_lower.rs @@ -1,11 +1,11 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Result, anyhow}; use crate::{ expr::Expr, val::LiteralVal, - vm::analysis::{PerfContainerBuildFact, PerfContainerFact, PerfValueKind}, + vm::analysis::{PerfContainerBuildFact, PerfContainerFact, PerfContainerMoveFact, PerfValueKind}, }; use super::{ @@ -23,13 +23,29 @@ impl Compiler { self.set_register_list_fact(dst, list_fact_from_exprs(elements)); return Ok(dst); } + // `NewList` names its element window as (u8 base, u8 len), so a longer + // literal is not one instruction — and 256 live registers would not fit + // the same operand anyway. This used to `bail!`, and only when constant + // folding did not apply: an all-constant literal of any length becomes a + // heap constant above, so `[0, …, 399]` compiled and `[0, …, 398, x]` + // did not. 255 was the opcode's operand width, never a rule about lists. + // A literal that fits keeps the window path exactly as it was; a longer + // one builds empty and pushes everything, because the window competes + // with `dst` for the same 256 registers — a 255-element window leaves + // `dst` at 256, which the operand cannot name either. let len = elements.len(); - if len > u8::MAX as usize { - bail!("Compiler list literal has {} elements, max {}", len, u8::MAX); - } - let base = self.alloc_regs(len)?; - for (offset, element) in elements.iter().enumerate() { + let window = if len > u8::MAX as usize { 0 } else { len }; + let base = self.alloc_regs(window)?; + // Each element's *temporaries* are handed back before the next one is + // lowered — the window itself stays, the scratch behind it does not. + // Without this every element's leftovers stacked up, so a literal cost + // one register per element plus one for every temporary any element + // ever used: 27 elements of `f(a) == a.f()` reached the 256 ceiling and + // the program was refused for a list a quarter that size. + let watermark = self.next_reg; + for (offset, element) in elements[..window].iter().enumerate() { self.lower_expr_to_register(base + offset as u16, element, "list element")?; + self.next_reg = self.live_register_floor().max(watermark); } let dst = self.alloc_reg(); let pc = self.function.code.len(); @@ -37,7 +53,7 @@ impl Compiler { Opcode::NewList, checked_u8("list dst", dst)?, checked_u8("list base", base)?, - checked_u8("list len", len as u16)?, + checked_u8("list len", window as u16)?, )); self.function.performance.set_container_build_fact( pc, @@ -46,6 +62,29 @@ impl Compiler { move_values: true, }, ); + // The tail goes in one element at a time, each through a scratch + // register that is handed straight back — holding all of them at once + // is what runs into the register ceiling from the other side. + for element in &elements[window..] { + let watermark = self.next_reg; + let scratch = self.alloc_reg(); + self.lower_expr_to_register(scratch, element, "list element")?; + let push_pc = self.function.code.len(); + self.emit(Instr::abc( + Opcode::ListPush, + checked_u8("list dst", dst)?, + checked_u8("list element", scratch)?, + 0, + )); + self.function.performance.set_container_move_fact( + push_pc, + PerfContainerMoveFact { + move_key: false, + move_value: true, + }, + ); + self.next_reg = self.live_register_floor().max(watermark); + } self.set_register_list_fact(dst, list_fact_from_exprs(elements)); Ok(dst) } @@ -58,18 +97,26 @@ impl Compiler { self.set_register_map_fact(dst, map_fact_from_exprs(entries)); return Ok(dst); } + // Same ceiling as `lower_list`, one bit tighter: `NewMap` names its + // key/value window as (u8 base, u8 len) and each entry costs two + // registers, so a literal past 127 entries builds empty and sets the + // rest. It used to `bail!`, and — like the list — only when constant + // folding did not apply, so `{"a": 1, …}` of any length compiled until + // one value stopped being a literal. let len = entries.len(); - if len > i8::MAX as usize { - bail!("Compiler map literal has {} entries, max {}", len, i8::MAX); - } + let window = if len > i8::MAX as usize { 0 } else { len }; let base = self.alloc_regs( - len.checked_mul(2) + window + .checked_mul(2) .ok_or_else(|| anyhow!("Compiler map entry overflow"))?, )?; - for (offset, (key, value)) in entries.iter().enumerate() { + // Per entry, as in `lower_list`: the window stays, the scratch does not. + let watermark = self.next_reg; + for (offset, (key, value)) in entries[..window].iter().enumerate() { let key_dst = base + (offset as u16 * 2); self.lower_expr_to_register(key_dst, key, "map key")?; self.lower_expr_to_register(key_dst + 1, value, "map value")?; + self.next_reg = self.live_register_floor().max(watermark); } let dst = self.alloc_reg(); let pc = self.function.code.len(); @@ -77,7 +124,7 @@ impl Compiler { Opcode::NewMap, checked_u8("map dst", dst)?, checked_u8("map base", base)?, - checked_u8("map len", len as u16)?, + checked_u8("map len", window as u16)?, )); self.function.performance.set_container_build_fact( pc, @@ -86,25 +133,82 @@ impl Compiler { move_values: true, }, ); + // The tail, one entry at a time through two scratch registers that are + // handed back. A later duplicate key overwrites an earlier one here just + // as it does inside `NewMap`, so the route does not change the answer. + for (key, value) in &entries[window..] { + let watermark = self.next_reg; + let key_reg = self.alloc_reg(); + self.lower_expr_to_register(key_reg, key, "map key")?; + let value_reg = self.alloc_reg(); + self.lower_expr_to_register(value_reg, value, "map value")?; + let set_pc = self.function.code.len(); + self.emit(Instr::abc( + Opcode::SetIndex, + checked_u8("map dst", dst)?, + checked_u8("map key", key_reg)?, + checked_u8("map value", value_reg)?, + )); + self.function.performance.set_container_move_fact( + set_pc, + PerfContainerMoveFact { + move_key: true, + move_value: true, + }, + ); + self.next_reg = self.live_register_floor().max(watermark); + } self.set_register_map_fact(dst, map_fact_from_exprs(entries)); Ok(dst) } + /// `Name { field: value, … }`, however many fields it has. + /// + /// `NewObject` reads its fields from a contiguous window of *two* registers + /// each plus one for the type name, so the window is what runs out first — + /// and the diagnostics disagreed about where. The guard said "max 127", but + /// 127 fields need 255 window registers plus `dst`, so 127 was never + /// reachable: at ~85 fields the surrounding locals already pushed the + /// allocator over and the failure came out as "this function needs more than + /// 256 registers", blaming the function for a limit belonging to one + /// literal. Two messages, one real ceiling, and neither of them named it. + /// + /// So the window is sized against what is actually free, and every field it + /// cannot hold is set afterwards on the finished object, one at a time, + /// through a scratch register that is handed straight back. Same shape as the + /// list and map literals next door. pub(super) fn lower_struct_literal(&mut self, name: &str, fields: &[(String, Box)]) -> Result { - let len = fields.len(); - if len > i8::MAX as usize { - bail!("Compiler object literal has {} fields, max {}", len, i8::MAX); + // A type this module does not declare, imported by name: build it + // through the constructor the *declaring* module generates beside it + // (`stmt::struct_ctors`), which the import bound under this name. The + // call runs there, so the object carries that module's `TypeScope` and + // its methods dispatch — `NewObject` here would stamp *this* module's + // scope and produce a same-named type with no methods. + // + // Recognised by what the compiler already knows: a local `struct S` + // always brings a local `S$new`, so its absence plus a bound global of + // this name is exactly the imported case. The checker has already + // refused every other way to reach here (`is_constructible_import`). + let constructor = crate::stmt::struct_ctors::constructor_name(name); + if !self.function_names.contains_key(&constructor) && self.global_names.contains_key(name) { + let args: Vec<(String, Box)> = fields.to_vec(); + return self.lower_dynamic_named_arg_call(&Expr::Var(name.to_string()), &[], &args); } - let base = self.alloc_regs( - len.checked_mul(2) - .and_then(|slots| slots.checked_add(1)) - .ok_or_else(|| anyhow!("Compiler object field overflow"))?, - )?; + let len = fields.len(); + // `1 + 2 * window` for the window itself, one more for `dst`, and every + // register must still be nameable in 8 bits. + let free = (u8::MAX as usize).saturating_sub(self.next_reg as usize); + let window = len.min(free.saturating_sub(2) / 2).min(i8::MAX as usize); + + let base = self.alloc_regs(1 + window * 2)?; self.emit_literal_to_register(base, &LiteralVal::from_str(name))?; - for (offset, (key, value)) in fields.iter().enumerate() { + // Per field, as in `lower_list`. + let watermark = self.next_reg; + for (offset, (key, value)) in fields[..window].iter().enumerate() { let key_dst = base + 1 + (offset as u16 * 2); self.emit_literal_to_register(key_dst, &LiteralVal::from_str(key))?; self.lower_expr_to_register(key_dst + 1, value, "object value")?; + self.next_reg = self.live_register_floor().max(watermark); } let dst = self.alloc_reg(); @@ -112,9 +216,45 @@ impl Compiler { Opcode::NewObject, checked_u8("object dst", dst)?, checked_u8("object base", base)?, - checked_u8("object len", len as u16)?, + checked_u8("object len", window as u16)?, )); self.set_register_kind(dst, PerfValueKind::Object); + + for (key, value) in &fields[window..] { + let watermark = self.next_reg; + let const_key = self.push_string(key)?; + let scratch = self.alloc_reg(); + self.lower_expr_to_register(scratch, value, "object value")?; + let set_pc = self.function.code.len(); + if const_key <= u8::MAX as u16 { + self.emit(Instr::abc( + Opcode::SetFieldK, + checked_u8("object dst", dst)?, + checked_u8("object value", scratch)?, + const_key as u8, + )); + } else { + // The const pool outgrew the `c` operand; the key goes in a + // register instead. Rare, and the only alternative is refusing a + // program for how many strings it happens to contain. + let key_reg = self.alloc_reg(); + self.emit_literal_to_register(key_reg, &LiteralVal::from_str(key))?; + self.emit(Instr::abc( + Opcode::SetIndex, + checked_u8("object dst", dst)?, + checked_u8("object key", key_reg)?, + checked_u8("object value", scratch)?, + )); + } + self.function.performance.set_container_move_fact( + set_pc, + PerfContainerMoveFact { + move_key: false, + move_value: true, + }, + ); + self.next_reg = self.live_register_floor().max(watermark); + } Ok(dst) } @@ -125,7 +265,15 @@ impl Compiler { inclusive: bool, step: Option<&Expr>, ) -> Result { - let end = end.ok_or_else(|| anyhow!("Compiler open-ended range expression is not supported"))?; + // `let r = 0..;` — a range materializes eagerly here (it *is* a list), + // so an endless one has no value to build. Same rule, said the same way + // as the `for` form. + let end = end.ok_or_else(|| { + anyhow!( + "a range needs an end — `0..n` — because a range is built as a list of its elements, and \ + `0..` has no last element" + ) + })?; let base = self.alloc_regs(3)?; match start { Some(start) => self.lower_expr_to_register(base, start, "range start")?, diff --git a/core/src/vm/compiler/control_flow.rs b/core/src/vm/compiler/control_flow.rs index 8d2948c2..75696273 100644 --- a/core/src/vm/compiler/control_flow.rs +++ b/core/src/vm/compiler/control_flow.rs @@ -60,13 +60,58 @@ impl Compiler { /// the way it returned from the closure, silently — and what removes the /// cell-capture of outer locals that made a top-level `try` writing an outer /// variable fail at runtime. - pub(super) fn lower_try(&mut self, body: &[Box], catch_var: &str, handler: &[Box]) -> Result<()> { + /// `try { body } catch e { handler }` used for effect — no value register, + /// so the region is exactly what it was before `try` became an expression. + pub(super) fn lower_try_stmt(&mut self, body: &[Box], catch_var: &str, handler: &[Box]) -> Result<()> { + self.lower_try_region(body, catch_var, handler, None) + } + + /// `try { body } catch e { handler }`, producing a value: each half's tail + /// expression lands in one shared register, which is what makes + /// `let r = try { … } catch e { … }` work. In statement position the + /// register is simply never read. The register is allocated *before* the region opens, so both the + /// body's writes and the handler's are visible after it closes — and it is + /// the same shape the AOT's `written_registers` scan already looks for, so + /// native lowering needs to know nothing new about it. + pub(super) fn lower_try_expr(&mut self, body: &[Box], catch_var: &str, handler: &[Box]) -> Result { + // Only when a half actually ends in an expression. A `try` used as a + // statement — both halves ending in `;` — then emits exactly the code + // it always did, with no register reserved and nothing written before + // the region opens. + let needs_value = ends_in_expression(body) || ends_in_expression(handler); + let value_reg = if needs_value { + let reg = self.alloc_reg(); + self.emit(Instr::abc(Opcode::LoadNil, checked_u8("try value", reg)?, 0, 0)); + Some(reg) + } else { + None + }; + self.lower_try_region(body, catch_var, handler, value_reg)?; + match value_reg { + Some(reg) => Ok(reg), + // Neither half has a value, so the answer is nil — the same rule an + // `if` branch that ends in a statement follows. + None => { + let reg = self.alloc_reg(); + self.emit(Instr::abc(Opcode::LoadNil, checked_u8("try value", reg)?, 0, 0)); + Ok(reg) + } + } + } + + fn lower_try_region( + &mut self, + body: &[Box], + catch_var: &str, + handler: &[Box], + value_reg: Option, + ) -> Result<()> { // Allocated before the region opens: the handler reads it after the // body's registers have been recycled, so it must sit below them. let catch_reg = self.alloc_reg(); let region = self.emit_try_begin_placeholder(catch_reg)?; - let body_returns = self.lower_scoped_stmt_sequence(body, catch_reg)?; + let body_returns = self.lower_scoped_stmt_sequence_valued(body, catch_reg, value_reg)?; self.emit(Instr::ax(Opcode::TryEnd, 0)); // A body that always returns never reaches the jump over the handler. let jmp_end = (!body_returns).then(|| self.emit_jmp_placeholder()); @@ -81,10 +126,12 @@ impl Compiler { // back afterwards, or the shadowed local reads as the raw cell object. let locals = self.locals.clone(); let cell_locals = self.cell_locals.clone(); + let scopes = self.enter_scope(); self.insert_fresh_local(catch_var.to_string(), catch_reg); - let handler_returns = self.lower_scoped_stmt_sequence(handler, catch_reg)?; + let handler_returns = self.lower_scoped_stmt_sequence_valued(handler, catch_reg, value_reg)?; self.cell_locals = self.scope_restored_cell_locals(&locals, cell_locals); self.locals = locals; + self.exit_scope(scopes); if let Some(jmp_end) = jmp_end { let end = self.function.code.len(); @@ -98,13 +145,44 @@ impl Compiler { /// Lowers `statements` as their own scope, restoring the enclosing bindings /// and register floor afterwards. Returns whether the sequence always /// returned. `keep_reg` stays allocated across the restore. - fn lower_scoped_stmt_sequence(&mut self, statements: &[Box], keep_reg: u16) -> Result { + /// + /// When `value_reg` is given, the sequence's trailing + /// expression is moved into it — the sequence's *value*, by the same rule a + /// block expression uses. A sequence that ends in a statement leaves the + /// register alone, so it keeps the nil it was initialized with; that is what + /// `if` does for a branch that ends in a statement too. + fn lower_scoped_stmt_sequence_valued( + &mut self, + statements: &[Box], + keep_reg: u16, + value_reg: Option, + ) -> Result { + let (statements, tail) = match (value_reg, statements.split_last()) { + (Some(_), Some((last, leading))) => match last.as_ref() { + Stmt::Expr { value: expr, .. } => (leading, Some(expr.as_ref())), + _ => (statements, None), + }, + _ => (statements, None), + }; let locals = self.locals.clone(); let cell_locals = self.cell_locals.clone(); let const_map_locals = self.const_map_locals.clone(); + let scopes = self.enter_scope(); self.emitted_return = false; self.local_rebind_suppression += 1; self.lower_stmt_sequence(statements)?; + if let (Some(value_reg), Some(tail)) = (value_reg, tail) + && !self.emitted_return + { + // Straight into the value register, not through a scratch one. + // A scratch register inside a protected region is a register the + // native back end sees the body write, and registers are recycled + // once the region ends — so the scratch collides with a *later* + // region's body-local and the whole function stops lowering. This + // is also what a hand-written `try { r = …; }` does, and it is why + // that shape lowered when this one did not. + self.lower_expr_to_register(value_reg, tail, "try value")?; + } self.local_rebind_suppression -= 1; let returns = self.emitted_return; // Same restore as `Stmt::Block`: an in-region promotion of an *outer* @@ -112,6 +190,7 @@ impl Compiler { self.cell_locals = self.scope_restored_cell_locals(&locals, cell_locals); self.locals = locals; self.const_map_locals = const_map_locals; + self.exit_scope(scopes); if !returns { self.next_reg = self.live_register_floor().max(keep_reg + 1); } @@ -287,6 +366,11 @@ impl Compiler { 0, )); self.set_register_kind(iterable, PerfValueKind::List); + // The snapshot holds the same elements, so it holds the same + // element width — without this a `for` over anything the compiler + // cannot prove is already a list (a parameter, most of the time) + // lost it at the `ToIter`. + self.copy_element_width(iterable_value, iterable); iterable }; let len = self.alloc_reg(); @@ -324,6 +408,7 @@ impl Compiler { self.function.performance.set_index_fact(pc, fact); } } + self.carry_element_width(iterable, value); let previous_binding = self.bind_for_pattern(pattern, value)?; let previous_single_char_locals = self.single_char_string_locals.clone(); if matches!(iterable_kind, PerfValueKind::String) @@ -373,12 +458,28 @@ impl Compiler { let watermark = self.next_reg; self.begin_loop_scalar_const_scope_for_exprs(&[], body)?; let step_sign = range_step_sign(step); + // A zero step never advances the index, so the loop is either infinite + // or empty depending on which comparison you write. `NewRange` and + // `iter.range` both refuse it; a `for` header is the same absurdity and + // gets the same answer, just earlier because the step is right there. + if matches!(step_sign, RangeStepSign::Zero) { + bail!("Range step cannot be zero"); + } let index = self.alloc_reg(); match start { Some(start) => self.lower_expr_to_register(index, start, "for range initial index")?, None => self.emit_literal_to_register(index, &LiteralVal::Int(0))?, } - let end = end.ok_or_else(|| anyhow!("Compiler open-ended range for loop is not supported"))?; + // `for i in 0.. { … }` — a range with no end. The old report named this + // compiler and called it "not supported", which reads like something + // that might arrive later; a loop over an endless range is a loop that + // never finishes, so the answer is what to write instead. + let end = end.ok_or_else(|| { + anyhow!( + "a `for` needs a range with an end — `for i in 0..n` — and `0..` has none, so this loop \ + would never finish. Use a `while` if that is what you meant" + ) + })?; let body_mutations = mutated_names_in_stmt(body); let end = self.lower_loop_snapshot_operand(end, &body_mutations)?; @@ -393,6 +494,7 @@ impl Compiler { RangeStepSign::Positive => self.lower_for_range_static_loop(index, end, step, inclusive, true, body)?, RangeStepSign::Negative => self.lower_for_range_static_loop(index, end, step, inclusive, false, body)?, RangeStepSign::Dynamic => self.lower_for_range_dynamic_loop(index, end, step, inclusive, body)?, + RangeStepSign::Zero => unreachable!("a zero step is refused above"), } self.restore_for_pattern(previous_binding); @@ -453,12 +555,12 @@ impl Compiler { } ForPattern::Ignore => Ok(()), ForPattern::Tuple(patterns) => { - let condition = self.lower_list_pattern_condition(value, patterns.len())?; + let condition = self.lower_list_pattern_condition(value, patterns.len(), true)?; self.emit_pattern_assert(condition)?; self.bind_for_sequence_pattern(patterns, value, previous) } ForPattern::Array { patterns, rest: None } => { - let condition = self.lower_list_pattern_condition(value, patterns.len())?; + let condition = self.lower_list_pattern_condition(value, patterns.len(), true)?; self.emit_pattern_assert(condition)?; self.bind_for_sequence_pattern(patterns, value, previous) } @@ -466,7 +568,7 @@ impl Compiler { patterns, rest: Some(rest), } => { - let condition = self.lower_list_pattern_condition(value, patterns.len())?; + let condition = self.lower_list_pattern_condition(value, patterns.len(), false)?; self.emit_pattern_assert(condition)?; self.bind_for_sequence_pattern(patterns, value, previous)?; let start = self.lower_val(&LiteralVal::Int(patterns.len() as i64))?; @@ -552,3 +654,9 @@ impl Compiler { Ok(()) } } + +/// Whether a statement sequence ends in an expression — its *value*, by the +/// same rule a block expression uses. +fn ends_in_expression(statements: &[Box]) -> bool { + matches!(statements.last().map(|stmt| stmt.as_ref()), Some(Stmt::Expr { .. })) +} diff --git a/core/src/vm/compiler/decls.rs b/core/src/vm/compiler/decls.rs index 54a5065c..e61ac6cd 100644 --- a/core/src/vm/compiler/decls.rs +++ b/core/src/vm/compiler/decls.rs @@ -2,13 +2,29 @@ use super::*; impl Compiler { pub(super) fn lower_function_decl(&mut self, name: &str) -> Result<()> { - let function = self.load_function_by_name(name)?; + // Publishing a top-level `fn` to its global slot goes through one + // shared register — see `Compiler::fn_publish_reg` for why it is shared + // rather than recycled. Every declaration overwrites it and then stores + // it, so nothing ever reads a stale value out of it. if self.top_level && let Some(slot) = self.global_names.get(name).copied() { - self.emit_set_global(function, slot)?; + let dst = match self.fn_publish_reg { + Some(reg) => reg, + None => { + let reg = self.alloc_reg(); + self.fn_publish_reg = Some(reg); + reg + } + }; + self.load_function_into(dst, name)?; + self.emit_set_global(dst, slot)?; return Ok(()); } + // A declaration that binds a *local* — nested inside a function, or a + // name this module does not export — needs a register of its own, + // because the binding is the register. + let function = self.load_function_by_name(name)?; self.insert_local(name.to_string(), function); Ok(()) } @@ -28,7 +44,12 @@ impl Compiler { Ok(()) } - pub(super) fn lower_impl_decl(&mut self, trait_name: &str, target_type: &Type, methods: &[Stmt]) -> Result<()> { + pub(super) fn lower_impl_decl( + &mut self, + trait_name: Option<&str>, + target_type: &Type, + methods: &[Stmt], + ) -> Result<()> { let target_type_text = target_type.display(); let mut decl_methods = Vec::with_capacity(methods.len()); for method in methods { @@ -45,7 +66,13 @@ impl Compiler { }; // The compiled body's index is the durable identity of this method; // the registration call below only re-encodes it as a runtime value. - let function_index = self.compile_impl_method_function_indexed(params, named_params, body)?; + let function_index = self.compile_impl_method_function_indexed( + params, + param_types, + named_params, + body, + &alloc::format!("{target_type_text}::{name}"), + )?; let method_type = impl_method_type(target_type, params, param_types, named_params, return_type); let method_type_text = method_type.display(); decl_methods.push(crate::vm::ImplMethod { @@ -59,7 +86,7 @@ impl Compiler { }); } self.type_info.impls.push(crate::vm::ImplDecl { - trait_name: trait_name.to_string(), + trait_name: trait_name.map(str::to_string), type_name: target_type_text, methods: decl_methods, }); @@ -75,8 +102,10 @@ impl Compiler { pub(super) fn compile_impl_method_function_indexed( &mut self, params: &[String], + param_types: &[Option], named_params: &[crate::stmt::NamedParamDecl], body: &Stmt, + debug_name: &str, ) -> Result { let function_index = self .dynamic_function_base @@ -84,6 +113,7 @@ impl Compiler { .ok_or_else(|| anyhow!("Compiler dynamic impl method index overflow"))?; let mut compiled = Self::compile_function_body( params, + param_types, named_params, body, self.function_names.clone(), @@ -92,26 +122,45 @@ impl Compiler { self.native_names.clone(), self.global_names.clone(), self.user_let_globals.clone(), + self.top_level_data_globals.clone(), + self.function_machine_returns.clone(), + self.struct_field_machine_widths.clone(), + self.impl_method_names.clone(), + self.global_machine_widths.clone(), HashMap::new(), function_index + 1, )?; + // `Type::method`, so a diagnostic about this function can name it. + // Impl methods carried no name at all, and every AOT blocker inside one + // read as a bare `an operand at pc 1 …` with nothing to look up. + compiled.function.debug_name = Some(alloc::sync::Arc::::from(debug_name)); self.pending_functions.push(compiled.function); self.pending_functions.append(&mut compiled.pending_functions); Ok(function_index) } pub(super) fn load_callable_by_name(&mut self, name: &str) -> Result { - self.try_load_callable_by_name(name)? - .ok_or_else(|| anyhow!("Compiler undefined callable `{name}`")) + if let Some(loaded) = self.try_load_callable_by_name(name)? { + return Ok(loaded); + } + // The one case with a rule behind it rather than a typo: the name being + // called *is* the binding currently being initialized, so a lambda is + // trying to call itself. `let fact = |n| … fact(n - 1) …;` reported + // "undefined callable `fact`" — a sentence about an operand, for a rule + // about scope, with nothing to do about it. + if self.initializing_binding.as_deref() == Some(name) { + bail!( + "`{name}` is not in scope inside its own initializer, so this closure cannot call itself; \ + write a recursive function as a top-level `fn {name}(…)`" + ); + } + bail!("undefined function `{name}`{}", self.suggest_known_name(name)) } pub(super) fn try_load_callable_by_name(&mut self, name: &str) -> Result> { if self.function_names.contains_key(name) { return self.load_function_by_name(name).map(Some); } - if self.native_names.contains_key(name) { - return self.load_native_by_name(name).map(Some); - } if let Some(slot) = self.global_names.get(name).copied() { return self.emit_get_global(slot).map(Some); } @@ -119,11 +168,17 @@ impl Compiler { } pub(super) fn load_function_by_name(&mut self, name: &str) -> Result { + let dst = self.alloc_reg(); + self.load_function_into(dst, name)?; + Ok(dst) + } + + /// As [`Self::load_function_by_name`], into a register the caller chose. + pub(super) fn load_function_into(&mut self, dst: u16, name: &str) -> Result<()> { let function_index = *self .function_names .get(name) .ok_or_else(|| anyhow!("Compiler undefined function `{name}`"))?; - let dst = self.alloc_reg(); let function_index = u16::try_from(function_index) .map_err(|_| anyhow!("Compiler function index {function_index} exceeds u16"))?; self.emit(Instr::abx( @@ -138,33 +193,20 @@ impl Compiler { ..PerfRegisterFact::default() }, ); - Ok(dst) + Ok(()) } - pub(super) fn load_native_by_name(&mut self, name: &str) -> Result { - let native_index = *self - .native_names - .get(name) - .ok_or_else(|| anyhow!("Compiler undefined native `{name}`"))?; - let dst = self.alloc_reg(); - let native_index = - u16::try_from(native_index).map_err(|_| anyhow!("Compiler native index {native_index} exceeds u16"))?; - self.emit(Instr::abx( - Opcode::LoadNative, - checked_u8("native dst", dst)?, - native_index, - )); - self.function.performance.set_register_fact( - dst, - PerfRegisterFact { - callable: PerfCallTargetKind::Native, - ..PerfRegisterFact::default() - }, - ); - Ok(dst) + pub(super) fn emit_get_global(&mut self, slot: u32) -> Result { + self.emit_get_global_named(slot, None) } - pub(super) fn emit_get_global(&mut self, slot: u32) -> Result { + /// As [`Self::emit_get_global`], told which name it is reading. + /// + /// The name is what makes a declared width usable: a top-level + /// `const MASK: u32` reads through `GetGlobal` into a fresh register, and + /// the register is where every machine-integer rule looks. Callers that do + /// not have a name pass `None` and get the old behaviour. + pub(super) fn emit_get_global_named(&mut self, slot: u32, name: Option<&str>) -> Result { let dst = self.alloc_reg(); let slot = u16::try_from(slot).map_err(|_| anyhow!("Compiler global slot {slot} exceeds u16"))?; let pc = self.function.code.len(); @@ -177,6 +219,10 @@ impl Compiler { }, ); self.function.performance.clear_register(dst); + // The declared width of the global, onto the register it landed in. + if let Some(width) = name.and_then(|name| self.global_machine_widths.get(name).copied()) { + self.machine_regs.insert(dst, width); + } Ok(dst) } diff --git a/core/src/vm/compiler/entry.rs b/core/src/vm/compiler/entry.rs index 0e8eb4d9..f93c7c11 100644 --- a/core/src/vm/compiler/entry.rs +++ b/core/src/vm/compiler/entry.rs @@ -1,6 +1,7 @@ use crate::compat::collections::HashMap; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; +use alloc::rc::Rc; use anyhow::{Result, anyhow, bail}; @@ -11,9 +12,10 @@ use crate::{ }; use super::{ - CompiledFunction, Compiler, Function, FunctionSignature, HashSet, Module, NativeEntry, - collect_function_inline_bodies, collect_function_names, collect_function_signatures, - collect_function_visible_let_names, collect_global_names_with_external, collect_native_names, + CompiledFunction, Compiler, Function, FunctionSignature, HashSet, Module, collect_function_inline_bodies, + collect_function_machine_returns, collect_function_names, collect_function_signatures, + collect_function_visible_let_names, collect_global_names_with_external, collect_impl_method_names, + collect_struct_field_machine_widths, collect_top_level_data_global_names, collect_top_level_machine_widths, export_name_from_attributes, extern_name_from_attributes, function_frame_params, global_slots_from_names, item_without_attributes, }; @@ -33,37 +35,62 @@ impl Compiler { } pub fn compile_module(program: &Program) -> Result { - Self::compile_module_with_natives(program, Vec::new()) + Self::compile_module_with_globals(program, core::iter::empty::<&str>()) } - pub fn compile_module_with_natives(program: &Program, natives: Vec) -> Result { - Self::compile_module_with_natives_and_globals(program, natives, core::iter::empty::<&str>()) + pub fn compile_module_with_globals(program: &Program, external_globals: I) -> Result + where + I: IntoIterator, + S: AsRef, + { + Self::compile_module_with_globals_and_data(program, external_globals, core::iter::empty::<&str>()) } - pub fn compile_module_with_natives_and_globals( + /// As [`Self::compile_module_with_globals`], with the subset of + /// `external_globals` that hold *user data* rather than imported module + /// objects. + /// + /// Only a host that keeps bindings alive across compilations knows this — + /// in-tree that is the REPL, whose `xs` from a previous line is an external + /// global indistinguishable from `math` without being told. Getting it + /// wrong is not a missed optimisation: `xs.len()` compiles to an index read + /// keyed by the string `"len"` and fails at run time. + pub fn compile_module_with_globals_and_data( program: &Program, - natives: Vec, external_globals: I, + external_data_globals: D, ) -> Result where I: IntoIterator, S: AsRef, + D: IntoIterator, + T: AsRef, { - let function_names = collect_function_names(program)?; - let function_signatures = collect_function_signatures(program)?; - let function_bodies = collect_function_inline_bodies(program)?; - let native_names = collect_native_names(&natives)?; - let global_names = collect_global_names_with_external(program, external_globals)?; - let user_let_globals = collect_function_visible_let_names(program); + let external_data_globals = external_data_globals + .into_iter() + .map(|name| name.as_ref().to_owned()) + .collect::>(); + let function_names = Rc::new(collect_function_names(program)?); + let function_signatures = Rc::new(collect_function_signatures(program)?); + let function_bodies = Rc::new(collect_function_inline_bodies(program)?); + let native_names = Rc::new(HashMap::new()); + let global_names = Rc::new(collect_global_names_with_external(program, external_globals)?); + let user_let_globals = Rc::new(collect_function_visible_let_names(program)); + let mut data_globals = collect_top_level_data_global_names(program); + data_globals.extend(external_data_globals); + let data_globals = Rc::new(data_globals); + let machine_returns = Rc::new(collect_function_machine_returns(program)); + let struct_widths = Rc::new(collect_struct_field_machine_widths(program)); + let impl_methods = Rc::new(collect_impl_method_names(program)); + let global_widths = Rc::new(collect_top_level_machine_widths(program)); let mut module = Module { functions: vec![Function::default(); function_names.len() + 1], - natives, globals: global_slots_from_names(&global_names), entry: 0, type_info: crate::vm::TypeInfo::default(), // Stamped by the caller, which knows what file this is // (`compile_program_module_with_ctx`); the compiler does not. - type_scope: crate::vm::TypeScope::anonymous(), + type_scope: crate::val::TypeScope::anonymous(), }; let mut entry = Self::with_names( @@ -75,7 +102,18 @@ impl Compiler { true, ); entry.user_let_globals = user_let_globals.clone(); + entry.top_level_data_globals = data_globals.clone(); + entry.function_machine_returns = machine_returns.clone(); + entry.struct_field_machine_widths = struct_widths.clone(); + entry.impl_method_names = impl_methods.clone(); + entry.global_machine_widths = global_widths.clone(); entry.dynamic_function_base = module.functions.len() as u32; + // As in `compile_function_body`: method-name constants first, so a + // `CallMethodK`'s 8-bit name index does not run out on a top level that + // also names structs and fields. + for method in crate::stmt::init_order::method_names_called_at_top_level(program) { + entry.push_string(&method)?; + } entry.lower_program_statements(program)?; module.type_info = core::mem::take(&mut entry.type_info); module.functions[0] = entry.finish()?; @@ -85,6 +123,7 @@ impl Compiler { if let Stmt::Function { name, params, + param_types, named_params, body, .. @@ -95,6 +134,7 @@ impl Compiler { .ok_or_else(|| anyhow!("Compiler missing function index for `{name}`"))?; let mut compiled = Self::compile_function_body( params, + param_types, named_params, body, function_names.clone(), @@ -103,6 +143,11 @@ impl Compiler { native_names.clone(), global_names.clone(), user_let_globals.clone(), + data_globals.clone(), + machine_returns.clone(), + struct_widths.clone(), + impl_methods.clone(), + global_widths.clone(), HashMap::new(), module.functions.len() as u32, )?; @@ -128,76 +173,51 @@ impl Compiler { Ok(module) } - /// Records, per impl method, how its reachable subtree uses module globals - /// (see [`crate::vm::ImplMethod::writes_globals`] and - /// [`reads_globals`](crate::vm::ImplMethod::reads_globals)). + /// Records, per impl method, how its reachable subtree uses module globals. /// - /// Reachability follows `CallDirect` and `MakeClosure`, the two opcodes that - /// name a function index statically — the same edges the AOT hybrid prescan - /// walks. An indirect call (a closure through a register, a builtin loaded - /// into one, a method dispatch) is *not* followed, so it counts as - /// `writes_globals`: that keeps the read list complete for every method the - /// flag clears, which is what a cross-module dispatch relies on. + /// The walk itself is [`crate::vm::analysis::function_global_use`] — one + /// implementation, because the same question is asked at run time when a + /// function value crosses a module boundary, and two walks that disagreed + /// would let a function that writes a global cross anyway. fn record_impl_method_global_use(module: &mut Module) { - use super::super::ir::Opcode; - - /// A call this walk cannot follow to a named function index. - fn is_opaque_call(op: Opcode) -> bool { - matches!(op, Opcode::Call | Opcode::CallNamed | Opcode::CallMethodK) - } - - let walk = |root: u32| -> (bool, Vec) { - let mut reads: Vec = Vec::new(); - let mut seen = vec![false; module.functions.len()]; - let mut stack = vec![root as usize]; - while let Some(index) = stack.pop() { - if index >= module.functions.len() || core::mem::replace(&mut seen[index], true) { - continue; - } - for instr in &module.functions[index].code { - match instr.opcode() { - Opcode::SetGlobal => return (true, Vec::new()), - op if is_opaque_call(op) => return (true, Vec::new()), - Opcode::GetGlobal => reads.push(instr.bx()), - Opcode::CallDirect | Opcode::MakeClosure => stack.push(instr.b() as usize), - _ => {} - } - } - } - reads.sort_unstable(); - reads.dedup(); - (false, reads) - }; - + let facts: Vec<(u32, bool, Vec)> = module + .type_info + .impls + .iter() + .flat_map(|decl| decl.methods.iter()) + .map(|method| { + let (writes, reads) = + crate::vm::analysis::function_global_use(module, method.function).writes_and_reads(); + (method.function, writes, reads) + }) + .collect(); + let mut facts = facts.into_iter(); for decl in &mut module.type_info.impls { for method in &mut decl.methods { - let (writes_globals, reads_globals) = walk(method.function); - method.writes_globals = writes_globals; - method.reads_globals = reads_globals; + let (function, writes, reads) = facts.next().expect("one fact per method, in the same order"); + debug_assert_eq!(function, method.function); + method.writes_globals = writes; + method.reads_globals = reads; } } } pub fn compile_source(source: &str) -> Result { let program = parse_program_source(source, ParseOptions::default())?; - Self::compile_program(&program) + Ok(Self::compile_module(&program)?.functions.swap_remove(0)) } pub fn compile_source_module(source: &str) -> Result { - Self::compile_source_module_with_natives(source, Vec::new()) - } - - pub fn compile_source_module_with_natives(source: &str, natives: Vec) -> Result { let program = parse_program_source(source, ParseOptions::default())?; - Self::compile_module_with_natives(&program, natives) + Self::compile_module(&program) } pub(super) fn with_names( - function_names: HashMap, - function_signatures: HashMap, - function_bodies: HashMap, - native_names: HashMap, - global_names: HashMap, + function_names: Rc>, + function_signatures: Rc>, + function_bodies: Rc>, + native_names: Rc>, + global_names: Rc>, top_level: bool, ) -> Self { Self { @@ -214,14 +234,20 @@ impl Compiler { #[allow(clippy::too_many_arguments)] pub(super) fn compile_function_body( params: &[String], + param_types: &[Option], named_params: &[crate::stmt::NamedParamDecl], body: &Stmt, - function_names: HashMap, - function_signatures: HashMap, - function_bodies: HashMap, - native_names: HashMap, - global_names: HashMap, - user_let_globals: HashSet, + function_names: Rc>, + function_signatures: Rc>, + function_bodies: Rc>, + native_names: Rc>, + global_names: Rc>, + user_let_globals: Rc>, + top_level_data_globals: Rc>, + machine_returns: Rc>, + struct_widths: Rc>>, + impl_methods: Rc>, + global_widths: Rc>, capture_names: HashMap, dynamic_function_base: u32, ) -> Result { @@ -238,8 +264,31 @@ impl Compiler { false, ); compiler.user_let_globals = user_let_globals; + compiler.top_level_data_globals = top_level_data_globals; + compiler.function_machine_returns = machine_returns; + compiler.struct_field_machine_widths = struct_widths; + compiler.impl_method_names = impl_methods; + compiler.global_machine_widths = global_widths; compiler.capture_names = capture_names; compiler.dynamic_function_base = dynamic_function_base; + // Said at the declaration, like the closure form: a call passes at most + // `MAX_CALL_ARGUMENTS`, so more parameters than that means a function + // nothing can call. Reported before this as a register overflow at the + // *call*, which pointed at the wrong line and offered advice about a + // body that was not the problem. + // `params`, not `frame_params`: the limit is the *positional* count, + // which a call names in 7 bits. Named parameters ride a wider field and + // are bounded by the register file instead (`MAX_STRUCT_FIELDS`) — a + // 200-field struct's generated constructor is 200 named parameters, and + // counting those here refused a struct literal that works. + if params.len() > crate::vm::compiler::MAX_CALL_ARGUMENTS { + bail!( + "this function declares {} positional parameters, and {} is the most a call can pass, so it \ + could never be called. Take a list or a map instead", + params.len(), + crate::vm::compiler::MAX_CALL_ARGUMENTS + ); + } compiler.function.param_count = frame_params.len() as u16; compiler.function.positional_param_count = params.len() as u16; compiler.function.param_names = Vec::with_capacity(frame_params.len()); @@ -249,12 +298,51 @@ impl Compiler { .param_names .push(alloc::sync::Arc::::from(name.as_str())); } + // Method-name constants first, before anything in the body can take a + // low index (see `stmt::init_order::method_names_called`). + for method in crate::stmt::init_order::method_names_called(body) { + compiler.push_string(&method)?; + } compiler.function.capture_count = compiler.capture_names.len() as u16; compiler.next_reg = compiler.function.param_count; compiler.peak_reg = compiler.function.param_count; for (index, param) in frame_params.iter().enumerate() { compiler.insert_local(param.clone(), index as u16); } + // A parameter's declared width is a width the body can rely on. + // + // Without this, every machine-integer rule stopped at the function + // boundary: `fn f(a: u8) -> u8 { return a + 1; }` answered 256, and + // `fn f(a: u64, b: u64) { return a > b; }` compared two addresses + // *signed*. All of it — the wrap, the unsigned compare, the logical + // shift, the unsigned divide — is chosen from `machine_regs`, and a + // parameter register was never in it. The rules held for an annotated + // `let` and for an `as` cast, which is why every test and every driver + // that casts on entry looked right. + // + // No differential test could see it: the fact is missing in the + // compiler, so both backends are handed the same wrong instruction. + for (index, declared) in param_types.iter().enumerate() { + if let Some(width) = declared.as_ref().and_then(crate::vm::compiler::register_width_of) + && index < params.len() + { + compiler.machine_regs.insert(index as u16, width); + } + } + // Named parameters carry their own annotations and sit after the + // positional ones in the frame, in `function_frame_params` order. + for (offset, named) in named_params.iter().enumerate() { + if let Some(width) = named + .type_annotation + .as_ref() + .and_then(crate::vm::compiler::register_width_of) + { + let index = params.len() + offset; + if index < frame_params.len() { + compiler.machine_regs.insert(index as u16, width); + } + } + } compiler.lower_stmt(body)?; if !compiler.emitted_return { compiler.emit_empty_return(); diff --git a/core/src/vm/compiler/expr_lower.rs b/core/src/vm/compiler/expr_lower.rs index 1a27db0e..cc092960 100644 --- a/core/src/vm/compiler/expr_lower.rs +++ b/core/src/vm/compiler/expr_lower.rs @@ -8,6 +8,27 @@ impl Compiler { } pub(super) fn lower_access_to_register(&mut self, dst: u16, target: &Expr, key: &Expr) -> Result<()> { + // A field's declared width, onto the register it lands in. + // + // The binary paths ask `machine_regs` about *registers*, not about the + // expression that filled them, so knowing `r.value` is a `u32` is only + // useful once it is written down here. Without it `r.value + 1` on a + // `u32` field added at 64 bits and answered 4294967296. + // + // Recorded before the access lowers rather than after: the lowering + // below has several returns, and one of them is a fused opcode. + match self.access_register_width_of(target, key) { + Some(width) => { + self.machine_regs.insert(dst, width); + } + None => { + self.machine_regs.remove(&dst); + } + } + self.lower_access_to_register_inner(dst, target, key) + } + + fn lower_access_to_register_inner(&mut self, dst: u16, target: &Expr, key: &Expr) -> Result<()> { let target = self.lower_readonly_access_target(target)?; let index_fact = index_fact_from_target(&self.function.performance, target); if let Some((suffix, key_fact)) = self.try_lower_string_int_key_for_map(index_fact, key)? { @@ -198,7 +219,16 @@ impl Compiler { // TODO(32-bit targets): on a 32-bit deployment target a pointer is // narrower than the `i64` carrier, so this will need the same // truncation a `u32` gets. Harmless while both backends are 64-bit, - // and wrong the moment the AOT path cross-compiles to thumb/arm32. + // and wrong the moment the AOT path cross-compiles to thumb/arm32 — + // which it cannot: Cranelift's backend set here has no 32-bit target + // (`no_32_bit_target_is_reachable_yet` in lk-aot-codegen fails when + // that stops being true, and names this site). + // + // Note this is the *compiler*, so it cannot follow the target even in + // principle: bytecode is target-agnostic, and the triple only appears + // at `lk compile object:`. A truncation here would have to + // become one the VM performs at run time, as `truncate_to_width` + // already does for `isize`/`usize`. if matches!(ty, crate::val::Type::Ptr { .. }) { let src = self.lower_readonly_operand(inner)?; // The result is an address, not a machine integer of some width: @@ -206,6 +236,21 @@ impl Compiler { self.machine_regs.remove(&src); return Ok(src); } + // `u64 as Float` reads the carrier as unsigned. + // + // The last conversion in this family. A `u64` with bit 63 set is a + // negative `i64` carrier, and unlike a comparison or a divide the result + // does not *look* wrong until it is compared with zero. + if matches!(ty, crate::val::Type::Float) + && let Some(kind) = self.expr_machine_width(inner) + && matches!(kind, crate::val::IntKind::U64 | crate::val::IntKind::Usize) + { + let call = Expr::Call( + alloc::string::String::from("__lk_u64_to_float"), + alloc::vec![Box::new(inner.clone())], + ); + return self.lower_expr(&call); + } let Some(target) = crate::vm::ir::CastTarget::from_type(ty) else { anyhow::bail!("internal error: cast target {} reached lowering", ty.display()); }; @@ -229,6 +274,7 @@ impl Compiler { let dst = self.alloc_reg(); let opcode = match op { UnaryOp::Not => Opcode::Not, + UnaryOp::Neg => Opcode::Neg, }; self.emit(Instr::abc( opcode, @@ -312,16 +358,38 @@ impl Compiler { Ok(vec![self.emit_branch_placeholder(Opcode::BrNil, value)?]) } Expr::Bin(lhs, op, rhs) if compare_test_opcode(op).is_some() => { - if let Some((opcode, value, immediate)) = self.lower_mod_zero_i4_branch_operands(lhs, op, rhs)? { + // A `u64` comparison is unsigned, and the fused compare-branch + // opcodes below are not. + // + // This is the *third* path the same rewrite has to reach: + // `lower_bin` for a comparison producing a value, the + // lower-into-register path for one feeding a call argument, and + // this one for a condition. Each was found by a test the + // previous fix left failing — `println(a < b)` after + // `let c = a / b`, and `if (a > b)` after both. + if let Some(value) = self.lower_unsigned_bin(lhs, op, rhs)? { + return Ok(vec![self.emit_branch_placeholder(Opcode::BrFalse, value)?]); + } + // Each fused shape below lowers an operand to decide, and leaves + // those instructions behind when it declines — so they are only + // tried over operands that are free to lower twice. See + // [`Self::is_free_to_lower_twice`]: `if (a > b)` and + // `if (x % 2 == 0)` still fuse, `if (1 + f(x) > 0)` no longer + // calls `f` three times. + let speculate = Self::is_free_to_lower_twice(lhs) && Self::is_free_to_lower_twice(rhs); + if speculate + && let Some((opcode, value, immediate)) = self.lower_mod_zero_i4_branch_operands(lhs, op, rhs)? + { return Ok(vec![self.emit_i4_branch_placeholder(opcode, value, immediate)?]); } - if let Some((opcode, value)) = self.lower_zero_branch_operands(lhs, op, rhs)? { + if speculate && let Some((opcode, value)) = self.lower_zero_branch_operands(lhs, op, rhs)? { return Ok(vec![self.emit_branch_placeholder(opcode, value)?]); } - if let Some((opcode, value, immediate)) = self.lower_i4_branch_operands(lhs, op, rhs)? { + if speculate && let Some((opcode, value, immediate)) = self.lower_i4_branch_operands(lhs, op, rhs)? { return Ok(vec![self.emit_i4_branch_placeholder(opcode, value, immediate)?]); } - if ENABLE_COMPARE_TEST_IMMEDIATE_LOWERING + if speculate + && ENABLE_COMPARE_TEST_IMMEDIATE_LOWERING && let Some((opcode, lhs, rhs)) = self.lower_compare_test_immediate_operands(lhs, op, rhs)? { return Ok(vec![ @@ -373,6 +441,36 @@ impl Compiler { .map(Some) } + /// Whether lowering this expression twice is observably the same as once. + /// + /// The fused compare-and-branch shapes below cannot decide without a + /// register fact (`value_kind`), so each lowers its operand and *then* asks — + /// and a helper that declines answers `None` with its instructions already in + /// the stream. The next attempt lowers the expression again, so an operand + /// with a side effect runs once per attempt that looked and declined: + /// `if (1 + f(x) > 0)` called `f` three times. Worse, the attempts do not + /// even agree on *which* subexpression is the operand — the `%`-against-zero + /// form takes `x` out of `x % k` while the next form takes `x % k` whole — so + /// there is no single register to hand along. + /// + /// What makes the speculation sound is this: only speculate over operands + /// that are free to lower twice. A name, a literal, and arithmetic over them + /// re-lower to at most a dead `Move`/`LoadInt` on the path that declines, + /// which is what that path already costs; a call re-lowers to a *call*. + /// + /// Deliberately a whitelist. A new `Expr` variant is not free until someone + /// says it is, and the cost of being wrong here is a program that runs its + /// operand twice — the exact bug this exists to prevent. + fn is_free_to_lower_twice(expr: &Expr) -> bool { + match expr { + Expr::Var(_) | Expr::Literal(_) => true, + Expr::Paren(inner) | Expr::Unsafe(inner) | Expr::Cast(inner, _) => Self::is_free_to_lower_twice(inner), + Expr::Unary(_, inner) => Self::is_free_to_lower_twice(inner), + Expr::Bin(lhs, _, rhs) => Self::is_free_to_lower_twice(lhs) && Self::is_free_to_lower_twice(rhs), + _ => false, + } + } + pub(super) fn lower_compare_test_immediate_operands( &mut self, lhs: &Expr, diff --git a/core/src/vm/compiler/facts.rs b/core/src/vm/compiler/facts.rs index 2e89eea7..a95ce07c 100644 --- a/core/src/vm/compiler/facts.rs +++ b/core/src/vm/compiler/facts.rs @@ -89,6 +89,14 @@ pub(super) fn bin_op_result_kind(op: &BinOp, flavor: NumericFlavor) -> PerfValue if op.is_cmp() || matches!(op, BinOp::In) { return PerfValueKind::Bool; } + // `/` yields a `Float` however it is spelled, so two `Int` operands do not + // make an `Int` result. Saying they did is what let the compiler emit a + // typed fused opcode on the quotient — `let mid = (lo + hi) / 2; mid * 2` + // lowered `mid * 2` to `MulIntI`, which then failed at runtime with + // "MulIntI expected Int lhs, got Float". + if matches!(op, BinOp::Div) && matches!(flavor, NumericFlavor::Int | NumericFlavor::Float) { + return PerfValueKind::Float; + } match flavor { NumericFlavor::Int if op.is_arith() => PerfValueKind::Int, NumericFlavor::Float if op.is_arith() => PerfValueKind::Float, @@ -124,6 +132,10 @@ fn bin_op_static_value_kind(lhs: &Expr, op: &BinOp, rhs: &Expr) -> PerfValueKind if op.is_cmp() || matches!(op, BinOp::In) { return PerfValueKind::Bool; } + // As `bin_op_result_kind`: a quotient is a `Float`, never an `Int`. + if matches!(op, BinOp::Div) && matches!(numeric_flavor(lhs, op, rhs), NumericFlavor::Int | NumericFlavor::Float) { + return PerfValueKind::Float; + } match numeric_flavor(lhs, op, rhs) { NumericFlavor::Int if op.is_arith() => PerfValueKind::Int, NumericFlavor::Float if op.is_arith() => PerfValueKind::Float, diff --git a/core/src/vm/compiler/facts_tests.rs b/core/src/vm/compiler/facts_tests.rs index 05393f2c..340c2163 100644 --- a/core/src/vm/compiler/facts_tests.rs +++ b/core/src/vm/compiler/facts_tests.rs @@ -4,9 +4,8 @@ use crate::{ token::Tokenizer, val::RuntimeVal, vm::analysis::{PerfCallTargetKind, PerfIndexTargetKind, PerfValueKind}, - vm::{NativeArgs, NativeEntry, NativeFunction, NativeRuntime, Opcode, execute, execute_module}, + vm::{Opcode, execute, execute_module}, }; -use anyhow::{Result, bail}; fn compile_source(source: &str) -> Function { let tokens = Tokenizer::tokenize(source).expect("tokenize"); @@ -1196,35 +1195,6 @@ fn compiler_records_dynamic_named_call_shape_fact() { ); } -#[test] -fn compiler_records_native_call_target_shape_fact() { - fn native_id(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { - let [RuntimeVal::Int(value)] = args.as_slice() else { - bail!("native_id expects one int"); - }; - Ok(RuntimeVal::Int(*value)) - } - - let module = compile_source_module_with_natives( - "return native_id(42);", - vec![NativeEntry { - name: "native_id".to_string(), - arity: 1, - function: NativeFunction::Plain(native_id), - }], - ) - .expect("compile module"); - let function = &module.functions[0]; - let call_pc = function - .code - .iter() - .position(|instr| instr.opcode() == Opcode::Call) - .expect("Call"); - let fact = function.performance.call_site(call_pc).expect("call fact"); - - assert_eq!(fact.target_kind, PerfCallTargetKind::Native); -} - #[test] fn compiler_records_global_slot_facts_for_get_and_set() { let module = compile_source_module( diff --git a/core/src/vm/compiler/for_value_usage.rs b/core/src/vm/compiler/for_value_usage.rs index 5b6897ee..4263ceb2 100644 --- a/core/src/vm/compiler/for_value_usage.rs +++ b/core/src/vm/compiler/for_value_usage.rs @@ -7,11 +7,11 @@ use crate::{ pub(super) fn stmt_uses_for_binding_value(stmt: &Stmt, name: &str) -> bool { match stmt { - Stmt::Attributed { item, .. } => stmt_uses_for_binding_value(item, name), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => stmt_uses_for_binding_value(item, name), Stmt::Empty | Stmt::Break | Stmt::Continue | Stmt::Import(_) | Stmt::Struct { .. } | Stmt::TypeAlias { .. } => { false } - Stmt::Expr(expr) | Stmt::Return { value: Some(expr) } => expr_uses_for_binding_value(expr, name), + Stmt::Expr { value: expr, .. } | Stmt::Return { value: Some(expr) } => expr_uses_for_binding_value(expr, name), Stmt::Return { value: None } => false, Stmt::Let { value, .. } => expr_uses_for_binding_value(value, name), Stmt::Define { value, .. } => expr_uses_for_binding_value(value, name), @@ -48,10 +48,6 @@ pub(super) fn stmt_uses_for_binding_value(stmt: &Stmt, name: &str) -> bool { Stmt::For { iterable, body, .. } => { expr_uses_for_binding_value(iterable, name) || stmt_uses_for_binding_value(body, name) } - Stmt::Try { body, handler, .. } => body - .iter() - .chain(handler) - .any(|stmt| stmt_uses_for_binding_value(stmt, name)), Stmt::Block { statements } => { for stmt in statements { if stmt_uses_for_binding_value(stmt, name) { @@ -120,6 +116,14 @@ fn expr_uses_for_binding_value(expr: &Expr, name: &str) -> bool { TemplateStringPart::Expr(expr) => expr_uses_for_binding_value(expr, name), TemplateStringPart::Literal(_) => false, }), + Expr::Try { body, handler, .. } => { + for stmt in body.iter().chain(handler) { + if stmt_uses_for_binding_value(stmt, name) { + return true; + } + } + false + } Expr::Block(statements) => { for stmt in statements { if stmt_uses_for_binding_value(stmt, name) { @@ -139,7 +143,7 @@ fn expr_uses_for_binding_value(expr: &Expr, name: &str) -> bool { expr_uses_for_binding_value(value, name) || arms.iter().any(|arm| expr_uses_for_binding_value(&arm.body, name)) } - Expr::Closure { params, body } => { + Expr::Closure { params, body, .. } => { if params.iter().any(|param| param == name) { return false; } @@ -164,14 +168,10 @@ fn is_single_char_len_call(callee: &Expr, args: &[Box], name: &str) -> boo let Expr::Access(target, method) = callee else { return false; }; + // The member of a dot access is a string literal; a bare `Var` there is a + // bracket *index*, and `xs[len]` is not `xs.len()`. matches!(target.as_ref(), Expr::Var(value) if value == name) - && (matches!( - method.as_ref(), - Expr::Var(value) if value == "len" - ) || matches!( - method.as_ref(), - Expr::Literal(value) if value.as_str() == Some("len") - )) + && matches!(method.as_ref(), Expr::Literal(value) if value.as_str() == Some("len")) } pub(super) fn stmt_shadows_name_deep(stmt: &Stmt, name: &str) -> bool { @@ -179,7 +179,7 @@ pub(super) fn stmt_shadows_name_deep(stmt: &Stmt, name: &str) -> bool { return true; } match stmt { - Stmt::Attributed { item, .. } => stmt_shadows_name_deep(item, name), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => stmt_shadows_name_deep(item, name), Stmt::If { then_stmt, else_stmt, .. } => { @@ -200,22 +200,25 @@ pub(super) fn stmt_shadows_name_deep(stmt: &Stmt, name: &str) -> bool { stmt_shadows_name_deep(body, name) } Stmt::Block { statements } => statements.iter().any(|stmt| stmt_shadows_name_deep(stmt, name)), - // The caught name shadows too, and both sides are searched. - Stmt::Try { - body, - catch_var, - handler, - } => { - catch_var == name - || body - .iter() - .chain(handler) - .any(|stmt| stmt_shadows_name_deep(stmt, name)) - } + // `try { … } catch e { … }` is an expression, so it arrives wrapped — + // and `e` shadows for the length of the handler. + Stmt::Expr { value: expr, .. } => match expr.as_ref() { + Expr::Try { + body, + catch_var, + handler, + } => { + catch_var == name + || body + .iter() + .chain(handler) + .any(|stmt| stmt_shadows_name_deep(stmt, name)) + } + _ => false, + }, Stmt::Impl { methods, .. } => methods.iter().any(|method| stmt_shadows_name_deep(method, name)), Stmt::Function { .. } => false, Stmt::Empty - | Stmt::Expr(_) | Stmt::Return { .. } | Stmt::Let { .. } | Stmt::Define { .. } diff --git a/core/src/vm/compiler/free_vars.rs b/core/src/vm/compiler/free_vars.rs index b2ed1fee..bf92ba93 100644 --- a/core/src/vm/compiler/free_vars.rs +++ b/core/src/vm/compiler/free_vars.rs @@ -101,12 +101,23 @@ pub(super) fn collect_expr_free_vars(expr: &Expr, bound: &mut HashSet, f } } } - Expr::Closure { params, body } => { + Expr::Closure { params, body, .. } => { let mut nested_bound = bound.clone(); nested_bound.extend(params.iter().cloned()); collect_expr_free_vars(body, &mut nested_bound, free); } Expr::Block(statements) => collect_stmt_free_vars(statements, bound, free), + Expr::Try { + body, + catch_var, + handler, + } => { + collect_stmt_free_vars(body, &mut bound.clone(), free); + // The handler's error binding is its own, so it is not free there. + let mut handler_bound = bound.clone(); + handler_bound.insert(catch_var.clone()); + collect_stmt_free_vars(handler, &mut handler_bound, free); + } Expr::Match { value, arms } => { collect_expr_free_vars(value, bound, free); for arm in arms { @@ -122,15 +133,17 @@ pub(super) fn collect_expr_free_vars(expr: &Expr, bound: &mut HashSet, f fn collect_stmt_free_vars(statements: &[Box], bound: &mut HashSet, free: &mut Vec) { for stmt in statements { match stmt.as_ref() { - Stmt::Attributed { item, .. } => collect_single_stmt_free_vars(item, bound, free), - Stmt::Expr(expr) => collect_expr_free_vars(expr, bound, free), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => { + collect_single_stmt_free_vars(item, bound, free) + } + Stmt::Expr { value: expr, .. } => collect_expr_free_vars(expr, bound, free), Stmt::Return { value: Some(value) } => collect_expr_free_vars(value, bound, free), Stmt::Return { value: None } | Stmt::Empty | Stmt::Break | Stmt::Continue => {} Stmt::Let { pattern, value, .. } => { collect_expr_free_vars(value, bound, free); collect_pattern_bound_vars(pattern, bound); } - Stmt::Define { name, value } => { + Stmt::Define { name, value, .. } => { collect_expr_free_vars(value, bound, free); bound.insert(name.clone()); } @@ -185,16 +198,6 @@ fn collect_stmt_free_vars(statements: &[Box], bound: &mut HashSet, collect_for_pattern_bound_vars(pattern, &mut body_bound); collect_single_stmt_free_vars(body, &mut body_bound, free); } - Stmt::Try { - body, - catch_var, - handler, - } => { - collect_stmt_free_vars(body, &mut bound.clone(), free); - let mut handler_bound = bound.clone(); - handler_bound.insert(catch_var.clone()); - collect_stmt_free_vars(handler, &mut handler_bound, free); - } Stmt::Block { statements } => collect_stmt_free_vars(statements, &mut bound.clone(), free), Stmt::Function { name, .. } => { bound.insert(name.clone()); @@ -328,7 +331,7 @@ pub(super) fn collect_stmt_closure_captures(stmt: &Stmt, out: &mut Vec) collect_expr_closure_captures(iterable, out); collect_stmt_closure_captures(body, out); } - Stmt::Expr(expr) => collect_expr_closure_captures(expr, out), + Stmt::Expr { value: expr, .. } => collect_expr_closure_captures(expr, out), Stmt::Return { value: Some(value) } => collect_expr_closure_captures(value, out), Stmt::Return { value: None } => {} // A nested `fn` captures nothing from locals (functions are compiled @@ -408,6 +411,11 @@ pub(super) fn collect_expr_closure_captures(expr: &Expr, out: &mut Vec) collect_stmt_closure_captures(stmt, out); } } + Expr::Try { body, handler, .. } => { + for stmt in body.iter().chain(handler) { + collect_stmt_closure_captures(stmt, out); + } + } Expr::Match { value, arms } => { collect_expr_closure_captures(value, out); for arm in arms { diff --git a/core/src/vm/compiler/inline.rs b/core/src/vm/compiler/inline.rs index b1250698..2893afd3 100644 --- a/core/src/vm/compiler/inline.rs +++ b/core/src/vm/compiler/inline.rs @@ -50,6 +50,7 @@ impl Compiler { fn inline_direct_function_body(&mut self, params: &[String], args: &[Box], body: &Stmt) -> Result { let saved_locals = self.locals.clone(); let saved_cell_locals = self.cell_locals.clone(); + let saved_scopes = self.enter_scope(); let mutated_names = mutated_names_in_stmt(body); let result = (|| { @@ -105,6 +106,7 @@ impl Compiler { // restore; only names the inline shadowed with a fresh binding revert. self.cell_locals = self.scope_restored_cell_locals(&saved_locals, saved_cell_locals); self.locals = saved_locals; + self.exit_scope(saved_scopes); result } @@ -157,24 +159,61 @@ impl Compiler { match stmt { Stmt::Attributed { item, .. } => self.lower_inline_stmt(item, result, returns, tail_position), Stmt::Block { statements } => { + // The same scope save/restore `Stmt::Block` gets outside an + // inline. Without it a `let` in a nested block rebound the + // name **permanently**, so + // `fn f(c) { let y = 1; if c { let y = 2; } let s = 45; return y; }` + // returned 45 once inlined: `y` still pointed at the inner + // binding's register, and `s` was handed that register back. + let watermark = self.next_reg; + let locals = self.locals.clone(); + let cell_locals = self.cell_locals.clone(); + let const_map_locals = self.const_map_locals.clone(); + let scopes = self.enter_scope(); self.local_rebind_suppression += 1; self.lower_inline_stmt_sequence(statements, result, returns)?; self.local_rebind_suppression -= 1; + // In-block promotions of an *outer* local survive the restore — + // see the same note on the non-inline arm. + self.cell_locals = self.scope_restored_cell_locals(&locals, cell_locals); + self.locals = locals; + self.const_map_locals = const_map_locals; + self.exit_scope(scopes); + self.next_reg = self.live_register_floor().max(watermark); Ok(()) } Stmt::Let { pattern: Pattern::Variable(name), + type_annotation, value, .. + } => { + let slot = self.bind_inline_local(name, value)?; + // The same two rules `lower_let` applies, which this arm did + // not: the annotation if there is one, otherwise whatever the + // initializer establishes. + // + // Without them a machine integer lost its width the moment its + // function was inlined — and a small function is exactly the + // one that gets inlined. `fn size(p: u32, m: u32) -> u32 { let + // z: u32 = 0; return z - (p & m); }` is `drivers/pci.lk`'s BAR + // sizing, and inlined it subtracted at 64 bits and answered a + // negative number. + match type_annotation { + Some(_) => self.note_machine_reg(slot, type_annotation.as_ref()), + None => { + if let Some(kind) = self.initializer_machine_width(value) { + self.machine_regs.insert(slot, super::RegisterWidth::Scalar(kind)); + } + } + } + Ok(()) } - | Stmt::Define { name, value } => { - let slot = self.alloc_reg(); - if !self.try_lower_expr_to_register(slot, value)? { - let value = self.lower_expr(value)?; - let move_source = !self.is_current_local_slot(value); - self.emit_move_with_policy(slot, value, "inline local", move_source)?; + Stmt::Define { name, value, .. } => { + let slot = self.bind_inline_local(name, value)?; + if let Some(kind) = self.initializer_machine_width(value) { + self.machine_regs.insert(slot, super::RegisterWidth::Scalar(kind)); } - self.insert_fresh_local(name.clone(), slot); Ok(()) } Stmt::Assign { name, value, .. } => self.lower_assign(name, value), @@ -186,7 +225,7 @@ impl Compiler { } => self.lower_inline_if(condition, then_stmt, else_stmt.as_deref(), result, returns), Stmt::While { condition, body } => self.lower_inline_while(condition, body, result, returns), Stmt::Return { value: Some(value) } => self.lower_inline_return(value, result, returns, tail_position), - Stmt::Expr(expr) if inline_dead_expr_is_supported(expr) => { + Stmt::Expr { value: expr, .. } if inline_dead_expr_is_supported(expr) => { self.lower_expr(expr)?; Ok(()) } @@ -194,6 +233,20 @@ impl Compiler { } } + /// Lowers an inlined `let`/`def` initializer into a fresh register and binds + /// the name to it, answering the register so the caller can record what it + /// knows about the value's width. + fn bind_inline_local(&mut self, name: &str, value: &Expr) -> Result { + let slot = self.alloc_reg(); + if !self.try_lower_expr_to_register(slot, value)? { + let lowered = self.lower_expr(value)?; + let move_source = !self.is_current_local_slot(lowered); + self.emit_move_with_policy(slot, lowered, "inline local", move_source)?; + } + self.insert_fresh_local(alloc::string::String::from(name), slot); + Ok(slot) + } + fn lower_inline_stmt_sequence( &mut self, statements: &[Box], @@ -327,7 +380,7 @@ fn inline_stmt_is_supported(stmt: &Stmt) -> bool { } Stmt::While { condition, body } => inline_expr_is_supported(condition) && inline_stmt_is_supported(body), Stmt::Return { value: Some(value) } => inline_expr_is_supported(value), - Stmt::Expr(expr) => inline_dead_expr_is_supported(expr), + Stmt::Expr { value: expr, .. } => inline_dead_expr_is_supported(expr), _ => false, } } @@ -371,7 +424,13 @@ fn inline_dead_expr_is_supported(expr: &Expr) -> bool { fn inline_expr_is_supported(expr: &Expr) -> bool { match expr { - Expr::Paren(inner) | Expr::Unary(_, inner) | Expr::OptionalAccess(inner, _) => inline_expr_is_supported(inner), + // `Cast` belongs with the other transparent wrappers. Leaving it out + // made any function containing an `as` conversion un-inlinable, which + // is not a property of casts — the two traversals below already treat + // it exactly this way. + Expr::Paren(inner) | Expr::Unary(_, inner) | Expr::Cast(inner, _) | Expr::OptionalAccess(inner, _) => { + inline_expr_is_supported(inner) + } Expr::Literal(_) | Expr::Var(_) => true, Expr::Bin(lhs, _, rhs) | Expr::And(lhs, rhs) @@ -415,11 +474,7 @@ fn inline_call_expr_uses_runtime_method_helper(callee: &Expr) -> bool { pub(super) fn stmt_contains_call_to(stmt: &Stmt, target: &str) -> bool { match stmt { - Stmt::Attributed { item, .. } => stmt_contains_call_to(item, target), - Stmt::Try { body, handler, .. } => body - .iter() - .chain(handler) - .any(|stmt| stmt_contains_call_to(stmt, target)), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => stmt_contains_call_to(item, target), Stmt::If { condition, then_stmt, @@ -459,7 +514,7 @@ pub(super) fn stmt_contains_call_to(stmt: &Stmt, target: &str) -> bool { Stmt::Return { value } => value.as_ref().is_some_and(|value| expr_contains_call_to(value, target)), Stmt::Function { body, .. } => stmt_contains_call_to(body, target), Stmt::Block { statements } => statements.iter().any(|stmt| stmt_contains_call_to(stmt, target)), - Stmt::Expr(expr) => expr_contains_call_to(expr, target), + Stmt::Expr { value: expr, .. } => expr_contains_call_to(expr, target), Stmt::Empty | Stmt::Import(_) | Stmt::Struct { .. } @@ -511,6 +566,10 @@ fn expr_contains_call_to(expr: &Expr, target: &str) -> bool { crate::expr::TemplateStringPart::Expr(expr) => expr_contains_call_to(expr, target), }), Expr::Block(statements) => statements.iter().any(|stmt| stmt_contains_call_to(stmt, target)), + Expr::Try { body, handler, .. } => body + .iter() + .chain(handler) + .any(|stmt| stmt_contains_call_to(stmt, target)), Expr::Range { start, end, step, .. } => [start, end, step] .into_iter() .flatten() @@ -572,7 +631,7 @@ fn collect_assigned_names(stmt: &Stmt, names: &mut HashSet) { value, .. } - | Stmt::Define { name, value } => { + | Stmt::Define { name, value, .. } => { names.insert(name.clone()); collect_assigned_names_in_expr(value, names); } @@ -597,7 +656,7 @@ fn collect_assigned_names(stmt: &Stmt, names: &mut HashSet) { collect_assigned_names(stmt, names); } } - Stmt::Expr(expr) => collect_assigned_names_in_expr(expr, names), + Stmt::Expr { value: expr, .. } => collect_assigned_names_in_expr(expr, names), Stmt::Return { value: Some(value) } => collect_assigned_names_in_expr(value, names), _ => {} } @@ -672,6 +731,11 @@ fn collect_assigned_names_in_expr(expr: &Expr, names: &mut HashSet) { collect_assigned_names(stmt, names); } } + Expr::Try { body, handler, .. } => { + for stmt in body.iter().chain(handler) { + collect_assigned_names(stmt, names); + } + } Expr::Range { start, end, step, .. } => { for expr in [start, end, step].into_iter().flatten() { collect_assigned_names_in_expr(expr, names); diff --git a/core/src/vm/compiler/loop_consts.rs b/core/src/vm/compiler/loop_consts.rs index 69769461..b42137db 100644 --- a/core/src/vm/compiler/loop_consts.rs +++ b/core/src/vm/compiler/loop_consts.rs @@ -7,7 +7,6 @@ use anyhow::Result; use crate::{ expr::{Expr, MatchArm}, stmt::Stmt, - util::fast_map::FastHashMap, val::{LiteralVal, RuntimeMapKey, ShortStr}, vm::ConstRuntimeValue, }; @@ -17,7 +16,7 @@ use super::{ call::map_get_method_call_args, checked_u8, inline::{inline_body_is_supported, stmt_contains_call_to}, - support::{FunctionInlineBody, const_runtime_map_key_from_literal}, + support::{FunctionInlineBody, access_member_name, const_runtime_map_key_from_literal}, }; #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -168,10 +167,10 @@ impl Compiler { fn collect_stmt_scalar_consts(stmt: &Stmt, keys: &mut Vec) { match stmt { - Stmt::Attributed { item, .. } => collect_stmt_scalar_consts(item, keys), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => collect_stmt_scalar_consts(item, keys), Stmt::Empty | Stmt::Break | Stmt::Continue | Stmt::Import(_) | Stmt::Struct { .. } | Stmt::TypeAlias { .. } => { } - Stmt::Expr(expr) | Stmt::Return { value: Some(expr) } => collect_expr_scalar_consts(expr, keys), + Stmt::Expr { value: expr, .. } | Stmt::Return { value: Some(expr) } => collect_expr_scalar_consts(expr, keys), Stmt::Return { value: None } => {} Stmt::Let { value, .. } | Stmt::Define { value, .. } => collect_expr_scalar_consts(value, keys), Stmt::Assign { value, .. } | Stmt::CompoundAssign { value, .. } => collect_expr_scalar_consts(value, keys), @@ -187,11 +186,6 @@ fn collect_stmt_scalar_consts(stmt: &Stmt, keys: &mut Vec) { } } // A try/catch is a two-way branch, so it is walked like `If`. - Stmt::Try { body, handler, .. } => { - for stmt in body.iter().chain(handler) { - collect_stmt_scalar_consts(stmt, keys); - } - } Stmt::IfLet { value, then_stmt, @@ -300,6 +294,11 @@ fn collect_expr_scalar_consts(expr: &Expr, keys: &mut Vec) { collect_stmt_scalar_consts(stmt, keys); } } + Expr::Try { body, handler, .. } => { + for stmt in body.iter().chain(handler) { + collect_stmt_scalar_consts(stmt, keys); + } + } Expr::Range { start, end, step, .. } => { if let Some(start) = start { collect_expr_scalar_consts(start, keys); @@ -328,7 +327,9 @@ fn collect_expr_scalar_consts(expr: &Expr, keys: &mut Vec) { fn collect_stmt_folded_int_consts(stmt: &Stmt, locals: &mut HashMap, keys: &mut Vec) { match stmt { - Stmt::Attributed { item, .. } => collect_stmt_folded_int_consts(item, locals, keys), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => { + collect_stmt_folded_int_consts(item, locals, keys) + } Stmt::Let { pattern, value, .. } => { collect_expr_folded_int_consts(value, locals, keys); if let crate::expr::Pattern::Variable(name) = pattern { @@ -339,7 +340,7 @@ fn collect_stmt_folded_int_consts(stmt: &Stmt, locals: &mut HashMap } } } - Stmt::Define { name, value } => { + Stmt::Define { name, value, .. } => { collect_expr_folded_int_consts(value, locals, keys); if let Some(value) = folded_int_expr_value(value, locals) { locals.insert(name.clone(), value); @@ -351,7 +352,9 @@ fn collect_stmt_folded_int_consts(stmt: &Stmt, locals: &mut HashMap collect_expr_folded_int_consts(value, locals, keys); locals.remove(name); } - Stmt::Expr(expr) | Stmt::Return { value: Some(expr) } => collect_expr_folded_int_consts(expr, locals, keys), + Stmt::Expr { value: expr, .. } | Stmt::Return { value: Some(expr) } => { + collect_expr_folded_int_consts(expr, locals, keys) + } Stmt::Return { value: None } | Stmt::Empty | Stmt::Break @@ -377,16 +380,6 @@ fn collect_stmt_folded_int_consts(stmt: &Stmt, locals: &mut HashMap // assignment still invalidates a later fold within it. Cloning per // statement would have let every statement fold against pre-branch // values. - Stmt::Try { body, handler, .. } => { - let mut body_locals = locals.clone(); - for stmt in body { - collect_stmt_folded_int_consts(stmt, &mut body_locals, keys); - } - let mut handler_locals = locals.clone(); - for stmt in handler { - collect_stmt_folded_int_consts(stmt, &mut handler_locals, keys); - } - } Stmt::IfLet { value, then_stmt, @@ -491,6 +484,12 @@ fn collect_expr_folded_int_consts(expr: &Expr, locals: &HashMap, ke collect_stmt_folded_int_consts(stmt, &mut scoped, keys); } } + Expr::Try { body, handler, .. } => { + let mut scoped = locals.clone(); + for stmt in body.iter().chain(handler) { + collect_stmt_folded_int_consts(stmt, &mut scoped, keys); + } + } Expr::Range { start, end, step, .. } => { for expr in [start, end, step].into_iter().flatten() { collect_expr_folded_int_consts(expr, locals, keys); @@ -555,10 +554,12 @@ fn collect_stmt_inline_call_scalar_consts( keys: &mut Vec, ) { match stmt { - Stmt::Attributed { item, .. } => collect_stmt_inline_call_scalar_consts(item, bodies, visiting, keys), + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => { + collect_stmt_inline_call_scalar_consts(item, bodies, visiting, keys) + } Stmt::Empty | Stmt::Break | Stmt::Continue | Stmt::Import(_) | Stmt::Struct { .. } | Stmt::TypeAlias { .. } => { } - Stmt::Expr(expr) | Stmt::Return { value: Some(expr) } => { + Stmt::Expr { value: expr, .. } | Stmt::Return { value: Some(expr) } => { collect_expr_inline_call_scalar_consts(expr, bodies, visiting, keys); } Stmt::Return { value: None } => {} @@ -579,11 +580,6 @@ fn collect_stmt_inline_call_scalar_consts( collect_stmt_inline_call_scalar_consts(else_stmt, bodies, visiting, keys); } } - Stmt::Try { body, handler, .. } => { - for stmt in body.iter().chain(handler) { - collect_stmt_inline_call_scalar_consts(stmt, bodies, visiting, keys); - } - } Stmt::IfLet { value, then_stmt, @@ -698,6 +694,11 @@ fn collect_expr_inline_call_scalar_consts( collect_stmt_inline_call_scalar_consts(stmt, bodies, visiting, keys); } } + Expr::Try { body, handler, .. } => { + for stmt in body.iter().chain(handler) { + collect_stmt_inline_call_scalar_consts(stmt, bodies, visiting, keys); + } + } Expr::Range { start, end, step, .. } => { for expr in [start, end, step].into_iter().flatten() { collect_expr_inline_call_scalar_consts(expr, bodies, visiting, keys); @@ -755,14 +756,16 @@ fn collect_boxed_exprs_inline_call_scalar_consts( fn collect_stmt_const_map_get_scalar_consts( stmt: &Stmt, - const_maps: &HashMap>, + const_maps: &HashMap>, keys: &mut Vec, ) -> Result<()> { match stmt { - Stmt::Attributed { item, .. } => collect_stmt_const_map_get_scalar_consts(item, const_maps, keys)?, + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => { + collect_stmt_const_map_get_scalar_consts(item, const_maps, keys)? + } Stmt::Empty | Stmt::Break | Stmt::Continue | Stmt::Import(_) | Stmt::Struct { .. } | Stmt::TypeAlias { .. } => { } - Stmt::Expr(expr) | Stmt::Return { value: Some(expr) } => { + Stmt::Expr { value: expr, .. } | Stmt::Return { value: Some(expr) } => { collect_expr_const_map_get_scalar_consts(expr, const_maps, keys)?; } Stmt::Return { value: None } => {} @@ -783,11 +786,6 @@ fn collect_stmt_const_map_get_scalar_consts( collect_stmt_const_map_get_scalar_consts(else_stmt, const_maps, keys)?; } } - Stmt::Try { body, handler, .. } => { - for stmt in body.iter().chain(handler) { - collect_stmt_const_map_get_scalar_consts(stmt, const_maps, keys)?; - } - } Stmt::IfLet { value, then_stmt, @@ -836,7 +834,7 @@ fn collect_stmt_const_map_get_scalar_consts( fn collect_expr_const_map_get_scalar_consts( expr: &Expr, - const_maps: &HashMap>, + const_maps: &HashMap>, keys: &mut Vec, ) -> Result<()> { if let Some(key) = const_map_get_scalar_loop_key(expr, const_maps)? { @@ -894,6 +892,11 @@ fn collect_expr_const_map_get_scalar_consts( collect_stmt_const_map_get_scalar_consts(stmt, const_maps, keys)?; } } + Expr::Try { body, handler, .. } => { + for stmt in body.iter().chain(handler) { + collect_stmt_const_map_get_scalar_consts(stmt, const_maps, keys)?; + } + } Expr::Range { start, end, step, .. } => { for expr in [start, end, step].into_iter().flatten() { collect_expr_const_map_get_scalar_consts(expr, const_maps, keys)?; @@ -916,7 +919,7 @@ fn collect_expr_const_map_get_scalar_consts( fn collect_boxed_exprs_const_map_get_scalar_consts( exprs: &[Box], - const_maps: &HashMap>, + const_maps: &HashMap>, keys: &mut Vec, ) -> Result<()> { for expr in exprs { @@ -927,7 +930,7 @@ fn collect_boxed_exprs_const_map_get_scalar_consts( fn const_map_get_scalar_loop_key( expr: &Expr, - const_maps: &HashMap>, + const_maps: &HashMap>, ) -> Result> { let Some((target, key)) = const_map_get_target_and_key(expr) else { return Ok(None); @@ -957,7 +960,7 @@ fn const_map_get_target_and_key(expr: &Expr) -> Option<(&Expr, &Expr)> { let Expr::Access(target, method) = callee.as_ref() else { return None; }; - if !matches!(target.as_ref(), Expr::Var(name) if name == "map") || method_name(method) != Some("get") { + if !matches!(target.as_ref(), Expr::Var(name) if name == "map") || access_member_name(method) != Some("get") { return None; } Some((args[0].as_ref(), args[1].as_ref())) @@ -971,14 +974,6 @@ fn const_map_key_from_expr(expr: &Expr) -> Result> { } } -fn method_name(expr: &Expr) -> Option<&str> { - match expr { - Expr::Var(name) => Some(name.as_str()), - Expr::Literal(value) => value.as_str(), - _ => None, - } -} - fn const_runtime_scalar_loop_key(value: &ConstRuntimeValue) -> Option { match value { ConstRuntimeValue::Nil => Some(ScalarLoopConstKey::Nil), diff --git a/core/src/vm/compiler/lower_into.rs b/core/src/vm/compiler/lower_into.rs index d9cd5b41..cafa502d 100644 --- a/core/src/vm/compiler/lower_into.rs +++ b/core/src/vm/compiler/lower_into.rs @@ -60,38 +60,98 @@ impl Compiler { Ok(true) } Expr::Bin(lhs, op, rhs) => { + // The same concat rendering `lower_bin` does — a string sum + // reaches this path when it is lowered into a register. + if let Some((lhs, rhs)) = self.rendered_concat_operands(lhs, op, rhs) { + let rewritten = Expr::Bin(Box::new(lhs), op.clone(), Box::new(rhs)); + return self.try_lower_expr_to_register(dst, &rewritten); + } + // `u64` compares and divides unsigned — the same rewrite + // `lower_bin` does, because a comparison that feeds a value (as + // in `println(a < b)`) arrives here instead. Two lowering paths + // for one shape is why the first version of this fixed division + // and left the comparison signed. + if self.lower_unsigned_bin_into(dst, lhs, op, rhs)?.is_some() { + return Ok(true); + } let static_flavor = super::support::numeric_flavor(lhs, op, rhs); + // Whether each side is written as an integer literal, before the + // names are shadowed by the registers they lower into. A literal + // beside a machine integer takes that width — see below. + let lhs_is_literal = super::support::is_int_literal(lhs); + let rhs_is_literal = super::support::is_int_literal(rhs); + // The commuted attempt keeps the register it lowered — see the + // same shape in `lower_bin_op`: falling through to lower `rhs` + // again ran the expression twice, so `1 + f(x)` called `f` twice. + // This copy had the identical defect. + let mut commuted_rhs = None; if static_flavor == super::support::NumericFlavor::Int && let Some(immediate) = super::support::commuted_int_immediate_operand(op, lhs) { let rhs = self.lower_readonly_operand(rhs)?; - if self.function.performance.value_kind(rhs) == PerfValueKind::Int { + // Not for a machine integer: the immediate form skips the + // width normalisation below, so `1 + reg` would answer at 64 + // bits while the type says otherwise. + if self.function.performance.value_kind(rhs) == PerfValueKind::Int + && !self.machine_regs.contains_key(&rhs) + { self.emit_int_immediate_to_register(dst, op, rhs, immediate)?; return Ok(true); } + commuted_rhs = Some(rhs); } let lhs = self.lower_readonly_operand(lhs)?; - if let Some(immediate) = super::support::int_immediate_operand(op, rhs) + if commuted_rhs.is_none() + && let Some(immediate) = super::support::int_immediate_operand(op, rhs) && self.function.performance.value_kind(lhs) == PerfValueKind::Int && static_flavor == super::support::NumericFlavor::Int + && !self.machine_regs.contains_key(&lhs) { self.emit_int_immediate_to_register(dst, op, lhs, immediate)?; return Ok(true); } - let rhs = self.lower_readonly_operand(rhs)?; + let rhs = match commuted_rhs { + Some(reg) => reg, + None => self.lower_readonly_operand(rhs)?, + }; + // A literal beside a machine integer takes its width. + // + // `reg + 1` is what driver code is made of, and the type checker + // now accepts it. What makes that *correct* is here: the literal + // is normalised to the same width first, so the wrap that + // follows the operation has two proven operands to agree about. + // Without it the checker would say `u8` while the arithmetic ran + // at 64 bits — `255u8 + 1` answering 256, which is the shape + // this whole path exists to prevent. + self.adopt_machine_width_for_literal(lhs, rhs, lhs_is_literal, rhs_is_literal)?; let flavor = super::facts::numeric_flavor_from_register_facts(&self.function.performance, op, lhs, rhs) .unwrap_or(static_flavor); self.emit_bin_op_to_register_with_flavor(dst, op, lhs, rhs, flavor)?; Ok(true) } - Expr::CallExpr(callee, args) - if self.is_external_module_call(callee, args, "math", "floor", 1) - && math_floor_arg_is_int_like(&args[0], &self.locals, &self.function.performance) => - { + // `math.floor(x)` where `x` is already an integer is the identity, + // so the call can go. That is only true when it *is* one: `/` + // yields a `Float`, so `math.floor(subtotal / 10)` must keep its + // call. Eliding it there answered `11.4` for `math.floor(114 / 10)` + // — a wrong number from an optimisation, which the bench corpus + // caught as a checksum mismatch against Lua. + Expr::CallExpr(callee, args) if self.is_external_module_call(callee, args, "math", "floor", 1) => { + // The midpoint fusion computes `(lo + hi) / 2` floored in one + // opcode, so it is exact and stays. if self.try_lower_int_midpoint_to_register(dst, &args[0])? { return Ok(true); } - self.try_lower_expr_to_register(dst, &args[0]) + if math_floor_arg_is_int_like(&args[0], &self.locals, &self.function.performance) { + return self.try_lower_expr_to_register(dst, &args[0]); + } + // `math.floor(a / b)` over two integers *is* integer division, + // and since `/` yields a `Float` it is the only way to write + // one. Fused so the idiom costs one instruction instead of a + // float divide plus a native call. + if self.try_lower_int_floor_div_to_register(dst, &args[0])? { + return Ok(true); + } + Ok(false) } Expr::CallExpr(callee, args) => { if self.is_external_module_call(callee, args, "map", "get", 2) { @@ -139,6 +199,29 @@ impl Compiler { Ok(true) } + /// `math.floor(a / b)` over two proven `Int`s → one `FloorDivInt`. + pub(super) fn try_lower_int_floor_div_to_register(&mut self, dst: u16, expr: &Expr) -> Result { + let Expr::Bin(numerator, BinOp::Div, divisor) = strip_parens(expr) else { + return Ok(false); + }; + // No proven-`Int` requirement. The opcode answers for any numeric pair + // — two `Int`s take the integer path, anything else divides as `f64` + // and floors — which is exactly what the `math.floor` call it replaces + // did. Demanding a proof only meant the fusion missed the calls that + // needed it most: `math.floor(subtotal / 10)` where `subtotal` came + // out of a map, which no analysis here can type. + let lhs = self.lower_readonly_operand(numerator)?; + let rhs = self.lower_readonly_operand(divisor)?; + self.emit(Instr::abc( + Opcode::FloorDivInt, + checked_u8("floor div dst", dst)?, + checked_u8("floor div lhs", lhs)?, + checked_u8("floor div rhs", rhs)?, + )); + self.set_register_kind(dst, PerfValueKind::Int); + Ok(true) + } + pub(super) fn lower_expr_to_register(&mut self, dst: u16, expr: &Expr, context: &str) -> Result<()> { if self.try_lower_expr_to_register(dst, expr)? { return Ok(()); @@ -248,10 +331,11 @@ fn math_floor_arg_is_int_like( Expr::Bin(lhs, op, rhs) if matches!( op, + // No `Div`: a quotient is a `Float`, so an expression + // containing one is not "already an integer". crate::operator::BinOp::Add | crate::operator::BinOp::Sub | crate::operator::BinOp::Mul - | crate::operator::BinOp::Div | crate::operator::BinOp::Mod ) && super::support::numeric_flavor(lhs, op, rhs) == super::support::NumericFlavor::Int => { diff --git a/core/src/vm/compiler/match_expr.rs b/core/src/vm/compiler/match_expr.rs index 5f27d530..d8b90d5e 100644 --- a/core/src/vm/compiler/match_expr.rs +++ b/core/src/vm/compiler/match_expr.rs @@ -15,20 +15,52 @@ impl Compiler { return Ok(dst); } + // Each arm is its own path, so `emitted_return` is saved and restored + // around every body — the same discipline `lower_if` uses for its two + // branches. Reading the flag *between* arms instead made a `return` in + // the first arm skip the lowering of every later arm's body: `match n + // { 0 => { return 7; } _ => { return 9; } }` compiled to a test with + // nothing behind it, so `n == 1` fell out of the match, past the end + // of a function declared `-> Int`, and answered nil. + let watermark = self.next_reg; let mut end_jumps = Vec::new(); + let mut every_arm_returns = true; + let mut some_arm_matches_everything = false; for arm in arms { - let (condition, previous) = self.lower_pattern_match(&arm.pattern, value)?; - let test_pc = self.emit_test_placeholder(condition)?; - if !self.emitted_return { - self.lower_expr_to_register(dst, &arm.body, "match result")?; + // An arm that matches every value is entered unconditionally: the + // test would always pass, and the edge where it fails is a path + // that does not exist. Both backends read that phantom edge — + // native lowering saw a function whose every arm returns still + // able to fall off its end, and rejected it. + let (test_pc, previous) = match self.bind_catch_all(&arm.pattern, value) { + Some(previous) => (None, previous), + None => { + let (condition, previous) = self.lower_pattern_match(&arm.pattern, value)?; + (Some(self.emit_test_placeholder(condition)?), previous) + } + }; + + self.emitted_return = false; + self.lower_expr_to_register(dst, &arm.body, "match result")?; + let arm_returns = self.emitted_return; + if !arm_returns { end_jumps.push(self.emit_jmp_placeholder()); } + every_arm_returns &= arm_returns; + some_arm_matches_everything |= test_pc.is_none(); + self.restore_pattern_bindings(previous); - let next_arm = self.function.code.len(); - self.patch_test_false_jump(test_pc, next_arm)?; + self.next_reg = watermark; // recycle the arm's bindings and temporaries + if let Some(test_pc) = test_pc { + let next_arm = self.function.code.len(); + self.patch_test_false_jump(test_pc, next_arm)?; + } } - if !self.emitted_return { + // No arm matched: the match answers nil. That path is what makes the + // checker type a match without a catch-all `T?` rather than `T`. + let falls_through = !some_arm_matches_everything; + if falls_through { self.emit(Instr::abc( Opcode::LoadNil, checked_u8("match fallback dst", dst)?, @@ -40,7 +72,7 @@ impl Compiler { for pc in end_jumps { self.patch_jmp(pc, end)?; } - self.emitted_return = false; + self.emitted_return = every_arm_returns && !falls_through; Ok(dst) } } diff --git a/core/src/vm/compiler/pattern_bind.rs b/core/src/vm/compiler/pattern_bind.rs index 76d07c0d..fc688d70 100644 --- a/core/src/vm/compiler/pattern_bind.rs +++ b/core/src/vm/compiler/pattern_bind.rs @@ -18,6 +18,26 @@ impl Compiler { pattern: &Pattern, type_annotation: Option<&crate::val::Type>, value: &Expr, + is_const: bool, + ) -> Result<()> { + // Only for the diagnostic in `load_callable_by_name`: a binding is not + // in scope inside its own initializer, and saying which binding that is + // turns "Compiler undefined callable `fact`" into the rule it broke. + let outer_initializing = self.initializing_binding.take(); + if let Pattern::Variable(name) = pattern { + self.initializing_binding = Some(name.clone()); + } + let lowered = self.lower_let_inner(pattern, type_annotation, value, is_const); + self.initializing_binding = outer_initializing; + lowered + } + + fn lower_let_inner( + &mut self, + pattern: &Pattern, + type_annotation: Option<&crate::val::Type>, + value: &Expr, + is_const: bool, ) -> Result<()> { if let Pattern::Variable(name) = pattern { // NOTE: never alias the binding to a shared loop-literal cache @@ -28,8 +48,23 @@ impl Compiler { // loop (`sort_words`' inner scan). The general path still uses // the cache: the literal store becomes a register move. let watermark = self.next_reg; + let cacheable = self.top_level_binding_is_cacheable(name, is_const); + // The destination stops claiming a width before anything is lowered + // into it. What lands there then establishes its own — a move + // carries the source's, arithmetic sets or clears it — and the + // annotation, if there is one, has the last word. Clearing first is + // what makes a width fact impossible to outlive its value: the + // register may have held a `u32` in a branch that has since been + // recycled. let slot = if let Some(slot) = self.locals.get(name).copied() { - if self.active_loop_binding_slot(name) == Some(slot) || self.cell_locals.contains(name) { + if !self.local_declared_in_current_scope(name) { + // Shadowing a binding from an enclosing scope. Reusing its + // register wrote *through* it, so `if c { let x = 2; }` + // left `x` at 2 after the block — in every construct, with + // nothing said. A fresh register leaves the outer value + // alone for the scope restore to hand back. + self.alloc_reg() + } else if self.active_loop_binding_slot(name) == Some(slot) || self.cell_locals.contains(name) { // A fresh binding must not write the old register in // place: it would clobber the counter the fused loop // opcodes drive (`for i { let i = …; }`), or overwrite a @@ -43,6 +78,7 @@ impl Compiler { } else { self.alloc_reg() }; + self.machine_regs.remove(&slot); if !self.try_lower_expr_to_register(slot, value)? { let value = self.lower_expr(value)?; let move_source = !self.is_current_local_slot(value); @@ -54,9 +90,34 @@ impl Compiler { self.emit_set_global(slot, global_slot)?; } self.record_const_map_local_from_expr(name, value)?; - // An annotated width is one of the two ways the compiler learns a - // register holds a machine integer (the other is `as`). - self.note_machine_reg(slot, type_annotation); + // Past the cache limit the binding is only a global; the register + // goes back and reads resolve through `GetGlobal`. + if !cacheable { + self.clear_const_map_local(name); + self.next_reg = self.live_register_floor().max(watermark); + return Ok(()); + } + // How the compiler learns a register holds a machine integer: the + // annotation if there is one, and otherwise what the initializer + // itself produces. Before the second half existed, `let a = read();` + // and `let a: u32 = read();` computed different sums from the same + // `fn read() -> u32` — see `initializer_machine_width`. + // Which struct this local holds, when the initializer says so — + // the only type the compiler tracks, and only so that `r.field` has + // a declared width to wrap to. + self.note_local_struct_type(name, type_annotation, value); + match type_annotation { + Some(_) => self.note_machine_reg(slot, type_annotation), + // A call establishes nothing on its own, so its declared width + // is applied here. Anything else keeps whatever the value that + // landed in the register established, which is now the answer + // rather than a guess. + None => { + if let Some(kind) = self.initializer_machine_width(value) { + self.machine_regs.insert(slot, super::RegisterWidth::Scalar(kind)); + } + } + } self.insert_fresh_local(name.clone(), slot); self.next_reg = self.live_register_floor().max(watermark).max(slot + 1); return Ok(()); @@ -89,7 +150,7 @@ impl Compiler { } Pattern::Wildcard => Ok(()), Pattern::List { patterns, rest } => { - let condition = self.lower_list_pattern_condition(value, patterns.len())?; + let condition = self.lower_list_pattern_condition(value, patterns.len(), rest.is_none())?; self.emit_pattern_assert(condition)?; self.bind_let_sequence(patterns, value)?; if let Some(rest) = rest { @@ -168,6 +229,7 @@ impl Compiler { checked_u8("let sequence value", value)?, checked_u8("let sequence index", key)?, )); + self.carry_element_width(value, field); self.bind_let_pattern(pattern, field)?; } Ok(()) diff --git a/core/src/vm/compiler/pattern_control.rs b/core/src/vm/compiler/pattern_control.rs index 4bcf062a..b00f740d 100644 --- a/core/src/vm/compiler/pattern_control.rs +++ b/core/src/vm/compiler/pattern_control.rs @@ -68,30 +68,9 @@ impl Compiler { } else { self.lower_readonly_operand(value)? }; - let (condition, previous) = match pattern { - Pattern::List { patterns, .. } => { - let condition = self.lower_list_pattern_condition(value, patterns.len())?; - let previous = Vec::new(); - (condition, previous) - } - Pattern::Map { patterns, .. } => { - let condition = self.lower_map_pattern_condition(value, patterns)?; - let previous = Vec::new(); - (condition, previous) - } - _ => self.lower_pattern_match(pattern, value)?, - }; + let (condition, previous) = self.lower_pattern_match(pattern, value)?; let exit_test = self.emit_test_placeholder(condition)?; - let previous = match pattern { - Pattern::List { .. } | Pattern::Map { .. } => { - let mut previous = previous; - self.bind_irrefutable_pattern(pattern, value, &mut previous)?; - previous - } - _ => previous, - }; - self.loops.push(super::support::LoopPatch::default()); self.emitted_return = false; self.lower_stmt(body)?; @@ -116,6 +95,28 @@ impl Compiler { Ok(()) } + /// The bindings for a `match` arm that matches every value, or `None` when + /// the pattern needs a runtime test. + /// + /// Such an arm gets no test at all. Emitting one is dead work, and for a + /// binding it is also wrong: [`Self::lower_pattern_match`] is shared with + /// `if let`, where a binding means "the value is not nil", which is not + /// what it means in a `match` arm — `match nil { x => 1 }` answered nil + /// while `match nil { _ => 1 }` answered 1, and the type checker called + /// both of them total. + /// + /// An or-pattern is left out: it cannot bind (the compiler refuses + /// variables inside one), so the only thing skipping its test would save + /// is the test itself, and its alternatives' conditions can evaluate + /// arbitrary expressions. + pub(super) fn bind_catch_all(&mut self, pattern: &Pattern, value: u16) -> Option { + match pattern { + Pattern::Wildcard => Some(Vec::new()), + Pattern::Variable(name) => Some(vec![(name.clone(), self.insert_local(name.clone(), value))]), + _ => None, + } + } + pub(super) fn lower_pattern_match(&mut self, pattern: &Pattern, value: u16) -> Result<(u16, PatternBindings)> { let mut previous = Vec::new(); let condition = match pattern { @@ -138,14 +139,10 @@ impl Compiler { condition } Pattern::Wildcard => self.lower_val(&LiteralVal::Bool(true))?, - Pattern::List { patterns, .. } => { - let condition = self.lower_list_pattern_condition(value, patterns.len())?; - self.bind_irrefutable_pattern(pattern, value, &mut previous)?; - condition - } - Pattern::Map { patterns, .. } => { - let condition = self.lower_map_pattern_condition(value, patterns)?; - self.bind_irrefutable_pattern(pattern, value, &mut previous)?; + Pattern::List { .. } | Pattern::Map { .. } => { + let mut slots = self.nil_pattern_slots(pattern)?; + let condition = self.lower_container_pattern(pattern, value, &mut previous, &mut slots)?; + debug_assert!(slots.is_empty(), "pattern slots left unconsumed"); condition } Pattern::Literal(literal) => { @@ -202,7 +199,19 @@ impl Compiler { self.lower_and_condition(ge_start, before_end) } - pub(super) fn lower_list_pattern_condition(&mut self, value: u16, fixed_len: usize) -> Result { + /// The shape test for a list pattern: it is a list, and its length fits. + /// + /// `exact` is what `..rest` decides. Without a rest pattern the length must + /// *equal* the number of sub-patterns; with one, the fixed part is a + /// minimum and the rest takes the tail. + /// + /// It used to be a minimum either way, which made `[]` match every list — + /// so `match xs { [] => …, [a] => …, [a, b] => … }` answered the first arm + /// for a list of any length, and every arm after it was dead. `[a]` matched + /// a two-element list for the same reason. Nothing documented that reading, + /// and every example in the corpus writes `..rest` when it means "at + /// least", which is exactly the distinction this argument restores. + pub(super) fn lower_list_pattern_condition(&mut self, value: u16, fixed_len: usize, exact: bool) -> Result { let is_list = self.alloc_reg(); self.emit(Instr::abc( Opcode::IsList, @@ -223,7 +232,7 @@ impl Compiler { self.set_register_kind(len, PerfValueKind::Int); let expected = self.lower_val(&LiteralVal::Int(fixed_len as i64))?; self.emit(Instr::abc( - Opcode::CmpGeInt, + if exact { Opcode::CmpInt } else { Opcode::CmpGeInt }, checked_u8("pattern list condition", result)?, checked_u8("pattern list len", len)?, checked_u8("pattern list expected", expected)?, @@ -357,76 +366,217 @@ impl Compiler { Ok(condition) } - fn lower_and_condition(&mut self, lhs: u16, rhs: u16) -> Result { - let result = self.lower_val(&LiteralVal::Bool(false))?; - let skip_rhs = self.emit_test_placeholder(lhs)?; - self.emit_move(result, rhs, "and-pattern condition")?; - let end = self.function.code.len(); - self.patch_test_false_jump(skip_rhs, end)?; - Ok(result) - } - - fn bind_irrefutable_pattern( + /// A list or map pattern: the shape, then every sub-pattern against the + /// element it names. + /// + /// The shape test was the whole of it — a list's length, a map's keys — and + /// anything else written inside was refused outright ("Compiler does not + /// support nested refutable pattern yet"). So `match v { [9, b] => … }`, + /// the shape most of pattern matching is written in, did not compile. + /// + /// Extraction happens *inside* the shape guard: reading element 2 of a + /// value that is not a list raises, so the elements may only be touched + /// once the shape is known. Within the guard a short list reads `nil`, + /// which every sub-pattern already answers correctly. + fn lower_container_pattern( &mut self, pattern: &Pattern, value: u16, previous: &mut Vec<(String, Option)>, - ) -> Result<()> { - match pattern { - Pattern::Variable(name) => { - previous.push((name.clone(), self.insert_local(name.clone(), value))); - Ok(()) + slots: &mut alloc::collections::VecDeque, + ) -> Result { + let shape = match pattern { + Pattern::List { patterns, rest } => { + self.lower_list_pattern_condition(value, patterns.len(), rest.is_none())? } - Pattern::Wildcard => Ok(()), + Pattern::Map { patterns, .. } => self.lower_map_pattern_condition(value, patterns)?, + other => bail!("not a container pattern: {:?}", pattern_kind(other)), + }; + let result = self.lower_val(&LiteralVal::Bool(false))?; + // The element registers are allocated and set to `nil` *before* the + // guard, so every path through this defines them. Only the extraction + // is guarded — the values themselves have to exist on the + // shape-mismatch path too, because a register read on a path that + // cannot run is still a register read to native lowering, which builds + // SSA and has no `nil` to fall back on. (The VM does not care: an + // unwritten register *is* nil there. Matching that explicitly is what + // keeps the two backends compiling the same program.) + let width = match pattern { + Pattern::List { patterns, .. } => patterns.len(), + Pattern::Map { patterns, .. } => patterns.len(), + other => bail!("not a container pattern: {:?}", pattern_kind(other)), + }; + let fields: Vec = slots.drain(..width.min(slots.len())).collect(); + if fields.len() != width { + bail!("pattern slot supply exhausted"); + } + // The `..rest` slot is seeded with an **empty container of its own + // kind** before the guard, and filled inside it. Not `nil`: what lands + // in it is a list or a map handle, and a register that is nil on one + // path and a handle on the other has no single type native lowering + // can give it. Not outside the guard either — `..rest` against a Map + // ran `SliceFrom` on it and raised "not sliceable" from an arm that + // simply does not match. + let rest_slot = match pattern { + Pattern::List { rest: Some(_), .. } | Pattern::Map { rest: Some(_), .. } => { + let slot = self.alloc_reg(); + let opcode = if matches!(pattern, Pattern::Map { .. }) { + Opcode::NewMap + } else { + Opcode::NewList + }; + self.emit(Instr::abc(opcode, checked_u8("pattern rest slot", slot)?, 0, 0)); + Some(slot) + } + _ => None, + }; + let skip = self.emit_test_placeholder(shape)?; + let mut condition = self.lower_val(&LiteralVal::Bool(true))?; + match pattern { Pattern::List { patterns, rest } => { - for (index, pattern) in patterns.iter().enumerate() { + for (index, sub) in patterns.iter().enumerate() { + let field = fields[index]; let index = i64::try_from(index).map_err(|_| anyhow::anyhow!("Compiler pattern index overflow"))?; let key = self.lower_val(&LiteralVal::Int(index))?; - let field = self.alloc_reg(); self.emit(Instr::abc( Opcode::GetIndex, checked_u8("pattern sequence field", field)?, checked_u8("pattern sequence value", value)?, checked_u8("pattern sequence index", key)?, )); - self.bind_irrefutable_pattern(pattern, field, previous)?; + if let Some(sub_condition) = self.lower_subpattern(sub, field, previous, slots)? { + condition = self.lower_and_condition(condition, sub_condition)?; + } } - if let Some(rest) = rest { + if let (Some(rest), Some(slot)) = (rest, rest_slot) { let start = self.lower_val(&LiteralVal::Int(patterns.len() as i64))?; - let slice = self.alloc_reg(); self.emit(Instr::abc( Opcode::SliceFrom, - checked_u8("pattern rest slice", slice)?, + checked_u8("pattern rest slice", slot)?, checked_u8("pattern rest value", value)?, checked_u8("pattern rest start", start)?, )); - previous.push((rest.clone(), self.insert_local(rest.clone(), slice))); + previous.push((rest.clone(), self.insert_local(rest.clone(), slot))); } - Ok(()) } Pattern::Map { patterns, rest } => { - for (key, pattern) in patterns { + for (index, (key, sub)) in patterns.iter().enumerate() { + let field = fields[index]; let key = self.lower_val(&LiteralVal::from_str(key))?; - let field = self.alloc_reg(); self.emit(Instr::abc( Opcode::GetIndex, checked_u8("pattern map field", field)?, checked_u8("pattern map value", value)?, checked_u8("pattern map key", key)?, )); - self.bind_irrefutable_pattern(pattern, field, previous)?; + if let Some(sub_condition) = self.lower_subpattern(sub, field, previous, slots)? { + condition = self.lower_and_condition(condition, sub_condition)?; + } } - if let Some(rest) = rest { + if let (Some(rest), Some(slot)) = (rest, rest_slot) { let map = self.lower_map_rest(value, patterns)?; - previous.push((rest.clone(), self.insert_local(rest.clone(), map))); + self.emit_move(slot, map, "pattern map rest")?; + previous.push((rest.clone(), self.insert_local(rest.clone(), slot))); } - Ok(()) } - other => bail!( - "Compiler does not support nested refutable pattern yet: {:?}", - pattern_kind(other) - ), + other => bail!("not a container pattern: {:?}", pattern_kind(other)), } + self.emit_move(result, condition, "pattern container condition")?; + let end = self.function.code.len(); + self.patch_test_false_jump(skip, end)?; + Ok(result) + } + + /// One `nil` register per value the pattern tree will pull out, in the + /// order [`Self::lower_container_pattern`] consumes them: this container's + /// positions first, then each position's own subtree. + /// + /// Allocated here, before the first shape test, so a nested container's + /// registers are defined on the *outer* mismatch path as well — the + /// recursion happens inside the outer guard, so allocating there would + /// leave them undefined exactly the way the flat case was. + fn nil_pattern_slots(&mut self, pattern: &Pattern) -> Result> { + let mut slots = alloc::collections::VecDeque::new(); + self.push_nil_pattern_slots(pattern, &mut slots)?; + Ok(slots) + } + + fn push_nil_pattern_slots( + &mut self, + pattern: &Pattern, + slots: &mut alloc::collections::VecDeque, + ) -> Result<()> { + let (width, subs): (usize, Vec<&Pattern>) = match pattern { + Pattern::List { patterns, .. } => (patterns.len(), patterns.iter().collect()), + Pattern::Map { patterns, .. } => (patterns.len(), patterns.iter().map(|(_, p)| p).collect()), + Pattern::Guard { pattern, .. } => return self.push_nil_pattern_slots(pattern, slots), + Pattern::Or(alts) => { + for alt in alts { + self.push_nil_pattern_slots(alt, slots)?; + } + return Ok(()); + } + _ => return Ok(()), + }; + for _ in 0..width { + let slot = self.alloc_reg(); + self.emit(Instr::abx(Opcode::LoadNil, checked_u8("pattern slot", slot)?, 0)); + slots.push_back(slot); + } + for sub in subs { + self.push_nil_pattern_slots(sub, slots)?; + } + Ok(()) + } + + /// One sub-pattern of a container pattern. `None` means it always matches, + /// so no test is emitted for it. + /// + /// A name binds here rather than testing, which is what distinguishes this + /// from [`Self::lower_pattern_match`]: that one is shared with `if let`, + /// where a bare name means "the value is not nil". Inside `[a, b]` it means + /// what it means in a `match` arm — any element, `nil` included. + fn lower_subpattern( + &mut self, + pattern: &Pattern, + value: u16, + previous: &mut Vec<(String, Option)>, + slots: &mut alloc::collections::VecDeque, + ) -> Result> { + match pattern { + Pattern::Wildcard => Ok(None), + Pattern::Variable(name) => { + previous.push((name.clone(), self.insert_local(name.clone(), value))); + Ok(None) + } + Pattern::List { .. } | Pattern::Map { .. } => { + Ok(Some(self.lower_container_pattern(pattern, value, previous, slots)?)) + } + Pattern::Guard { pattern, guard } => { + let inner = self.lower_subpattern(pattern, value, previous, slots)?; + let inner = match inner { + Some(condition) => condition, + None => self.lower_val(&LiteralVal::Bool(true))?, + }; + Ok(Some(self.lower_guard_condition(inner, guard)?)) + } + // Literal, Range and Or are pure tests with no `if let` reading to + // differ from, so they take the same path a top-level arm does. + _ => { + let (condition, nested) = self.lower_pattern_match(pattern, value)?; + previous.extend(nested); + Ok(Some(condition)) + } + } + } + + fn lower_and_condition(&mut self, lhs: u16, rhs: u16) -> Result { + let result = self.lower_val(&LiteralVal::Bool(false))?; + let skip_rhs = self.emit_test_placeholder(lhs)?; + self.emit_move(result, rhs, "and-pattern condition")?; + let end = self.function.code.len(); + self.patch_test_false_jump(skip_rhs, end)?; + Ok(result) } fn lower_pattern_literal(&mut self, literal: &LiteralVal) -> Result { diff --git a/core/src/vm/compiler/range_loop.rs b/core/src/vm/compiler/range_loop.rs index 4bc61b38..3454748d 100644 --- a/core/src/vm/compiler/range_loop.rs +++ b/core/src/vm/compiler/range_loop.rs @@ -74,6 +74,20 @@ impl Compiler { body: &Stmt, ) -> Result<()> { let zero = self.lower_val(&LiteralVal::Int(0))?; + + // The step is only known now, and a zero one makes `is_positive` below + // false — which would send the loop down the descending branch and let + // `for i in 0..3..s` quietly do nothing. Refuse it once, on entry, with + // the same words `NewRange` and `iter.range` use. + let step_is_nonzero = self.alloc_reg(); + self.emit(Instr::abc( + Opcode::CmpNeInt, + checked_u8("for step nonzero dst", step_is_nonzero)?, + checked_u8("for step", step)?, + checked_u8("for zero", zero)?, + )); + self.emit_assert(step_is_nonzero, "Range step cannot be zero")?; + let loop_start = self.function.code.len(); let is_positive = self.alloc_reg(); self.emit(Instr::abc( diff --git a/core/src/vm/compiler/stmt_lower.rs b/core/src/vm/compiler/stmt_lower.rs index 941555e6..3c56220f 100644 --- a/core/src/vm/compiler/stmt_lower.rs +++ b/core/src/vm/compiler/stmt_lower.rs @@ -3,9 +3,27 @@ use super::*; impl Compiler { pub(super) fn lower_stmt(&mut self, stmt: &Stmt) -> Result<()> { match stmt { - Stmt::Attributed { item, .. } => self.lower_stmt(item)?, + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => self.lower_stmt(item)?, Stmt::Empty => {} - Stmt::Expr(expr) => { + // A `try` here is used for effect, so it computes no value — the + // bytecode is exactly what the statement form always emitted. That + // matters beyond size: a value written *inside* the protected + // region has to survive it, which the native back end does by + // boxing the register into a cell, and a type with no unboxer is a + // rejection. Reserving a value nobody reads would have taken + // `try { f(); } catch e { … }` off the native path. + Stmt::Expr { value: expr, .. } if matches!(expr.as_ref(), Expr::Try { .. }) => { + let Expr::Try { + body, + catch_var, + handler, + } = expr.as_ref() + else { + unreachable!("matched above"); + }; + self.lower_try_stmt(body, catch_var, handler)?; + } + Stmt::Expr { value: expr, .. } => { let watermark = self.next_reg; if !self.try_lower_rewritten_set_index_expr(expr)? && !self.try_lower_builtin_method_statement(expr)? @@ -27,9 +45,10 @@ impl Compiler { pattern, type_annotation, value, + is_const, .. - } => self.lower_let(pattern, type_annotation.as_ref(), value)?, - Stmt::Define { name, value } => self.lower_define(name, value)?, + } => self.lower_let(pattern, type_annotation.as_ref(), value, *is_const)?, + Stmt::Define { name, value, .. } => self.lower_define(name, value)?, Stmt::Assign { name, value, .. } => { let watermark = self.next_reg; self.lower_assign(name, value)?; @@ -60,24 +79,36 @@ impl Compiler { } => self.lower_for(pattern, iterable, body)?, Stmt::Break => self.lower_break()?, Stmt::Continue => self.lower_continue()?, - Stmt::Import(_) | Stmt::Struct { .. } | Stmt::TypeAlias { .. } => {} - Stmt::Trait { name, methods } => self.lower_trait_decl(name, methods)?, + // A `struct` emits no code, but its *field order* is module data: + // it is the order `display` prints an instance's fields in, and the + // only place it survives is here (an object's fields live in a hash + // map, whose order nothing in the source explains). + Stmt::Struct { name, fields } => { + self.type_info.structs.push(crate::vm::StructDecl { + name: name.clone(), + fields: fields + .iter() + .map(|(field, ty)| crate::vm::StructFieldDecl { + name: field.clone(), + ty: ty.as_ref().map(Type::display), + }) + .collect(), + }); + } + Stmt::Import(_) | Stmt::TypeAlias { .. } => {} + Stmt::Trait { name, methods, .. } => self.lower_trait_decl(name, methods)?, Stmt::Impl { trait_name, target_type, methods, - } => self.lower_impl_decl(trait_name, target_type, methods)?, + } => self.lower_impl_decl(trait_name.as_deref(), target_type, methods)?, Stmt::Function { name, .. } => self.lower_function_decl(name)?, - Stmt::Try { - body, - catch_var, - handler, - } => self.lower_try(body, catch_var, handler)?, Stmt::Block { statements } => { let watermark = self.next_reg; let locals = self.locals.clone(); let cell_locals = self.cell_locals.clone(); let const_map_locals = self.const_map_locals.clone(); + let scopes = self.enter_scope(); self.local_rebind_suppression += 1; self.lower_stmt_sequence(statements)?; self.local_rebind_suppression -= 1; @@ -87,6 +118,7 @@ impl Compiler { self.cell_locals = self.scope_restored_cell_locals(&locals, cell_locals); self.locals = locals; self.const_map_locals = const_map_locals; + self.exit_scope(scopes); if !self.emitted_return { self.next_reg = self.live_register_floor().max(watermark); } @@ -276,8 +308,14 @@ impl Compiler { // The general path below still consumes the cache: the literal store // becomes a register move instead of a constant load. let watermark = self.next_reg; + // A `define` is never a `const`. + let cacheable = self.top_level_binding_is_cacheable(name, false); let slot = if let Some(slot) = self.locals.get(name).copied() { - if self.active_loop_binding_slot(name) == Some(slot) || self.cell_locals.contains(name) { + if !self.local_declared_in_current_scope(name) { + // See `lower_let`: shadowing an enclosing binding must not + // write through its register. + self.alloc_reg() + } else if self.active_loop_binding_slot(name) == Some(slot) || self.cell_locals.contains(name) { // A fresh binding must not write the old register in place: // it would clobber the counter the fused loop opcodes drive // (`for i { let i = …; }`), or overwrite a promoted cell that @@ -302,6 +340,15 @@ impl Compiler { self.emit_set_global(slot, global_slot)?; } self.record_const_map_local_from_expr(name, value)?; + // Past the limit the binding is only a global: the register goes back, + // and reads resolve through `GetGlobal` — which is the one place a + // *function* could ever see this value from, so nothing about its + // meaning changes. + if !cacheable { + self.clear_const_map_local(name); + self.next_reg = self.live_register_floor().max(watermark); + return Ok(()); + } self.insert_fresh_local(name.to_string(), slot); self.next_reg = self.live_register_floor().max(watermark).max(slot + 1); Ok(()) diff --git a/core/src/vm/compiler/support.rs b/core/src/vm/compiler/support.rs index ba7bb515..ecc13a02 100644 --- a/core/src/vm/compiler/support.rs +++ b/core/src/vm/compiler/support.rs @@ -1,7 +1,6 @@ use crate::compat::collections::{HashMap, HashSet}; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::fast_hash_map_new; use anyhow::{Result, anyhow, bail}; @@ -16,7 +15,7 @@ use crate::{ use alloc::sync::Arc; -use super::{ConstHeapValue, GlobalSlot, NativeEntry, free_vars::collect_function_free_vars}; +use super::{ConstHeapValue, GlobalSlot, free_vars::collect_function_free_vars}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(super) enum ShortCircuitKind { @@ -35,6 +34,10 @@ pub(super) enum NumericFlavor { pub(super) enum RangeStepSign { Positive, Negative, + /// A step that is literally `0` — a loop that cannot advance. Kept apart + /// from `Dynamic` so it is refused while compiling instead of being lowered + /// into a comparison that happens to be false on the first turn. + Zero, Dynamic, } @@ -110,6 +113,116 @@ pub(super) fn item_without_attributes(stmt: &Stmt) -> &Stmt { } } +/// Top-level functions whose declared return type is a machine int. +/// +/// The compiler needs this to know when arithmetic on a call's result has to +/// wrap: `fn read() -> u32` makes `let a = read(); a + b` `u32` arithmetic, and +/// before this the width was simply lost unless somebody wrote it down again at +/// the binding. +pub(super) fn collect_function_machine_returns(program: &Program) -> HashMap { + let mut widths = HashMap::new(); + for stmt in &program.statements { + if let Stmt::Function { + name, + return_type: Some(declared), + .. + } = item_without_attributes(stmt) + && let Some(width) = super::register_width_of(declared) + { + widths.insert(name.clone(), width); + } + } + widths +} + +/// Struct fields whose declared type is a machine int, by struct then field. +/// +/// The compiler has no type checker to ask, so a field's width has to be +/// carried the same way a function's return width is: collected once from the +/// declarations, and looked up by name. Without it `r.value + 1` on a `u32` +/// field added at 64 bits — the register holding the field has no width, so +/// nothing wraps. +/// Every method name any `impl` block in this program declares. +/// +/// The compiler lowers `x.len()`, `x.push(v)`, `x.set(k, v)`, `x.split(s)` and +/// `x.join(s)` to dedicated opcodes on the method *name* alone — it has no type +/// for the receiver there. That is right for a list or a string and wrong for a +/// struct with a method of that name: `impl S { fn len(self) -> Int { … } }` +/// made `s.len()` answer "Len target object is not sized", and the four with +/// arguments failed at *compile* time on arity ("Compiler method push expects 1 +/// arg, got 0"), so the method could not even be written. +/// +/// A name declared by an impl is therefore never assumed builtin: those calls +/// go through the ordinary dynamic dispatch, which asks the receiver. The cost +/// falls only on programs that name a method after a builtin one, and only for +/// that name. +pub(super) fn collect_impl_method_names(program: &Program) -> HashSet { + fn visit(stmt: &Stmt, names: &mut HashSet) { + match item_without_attributes(stmt) { + Stmt::Impl { methods, .. } => { + for method in methods { + if let Stmt::Function { name, .. } = item_without_attributes(method) { + names.insert(name.clone()); + } + } + } + Stmt::Block { statements } => { + for inner in statements { + visit(inner, names); + } + } + _ => {} + } + } + let mut names = HashSet::new(); + for stmt in &program.statements { + visit(stmt, &mut names); + } + names +} + +pub(super) fn collect_struct_field_machine_widths( + program: &Program, +) -> HashMap> { + let mut structs: HashMap> = HashMap::new(); + for stmt in &program.statements { + if let Stmt::Struct { name, fields } = item_without_attributes(stmt) { + let mut widths = HashMap::new(); + for (field, declared) in fields { + if let Some(width) = declared.as_ref().and_then(super::register_width_of) { + widths.insert(field.clone(), width); + } + } + if !widths.is_empty() { + structs.insert(name.clone(), widths); + } + } + } + structs +} + +/// Top-level bindings whose declared type is a machine int. +/// +/// `const RAH_VALID: u32 = 0x80000000;` is the shape a driver is made of — +/// `drivers/e1000.lk` alone has two dozen — and a read of one lands in a fresh +/// register through `GetGlobal`, which carries no width. Without this every use +/// of a register constant computed at 64 bits. +pub(super) fn collect_top_level_machine_widths(program: &Program) -> HashMap { + let mut widths = HashMap::new(); + for stmt in &program.statements { + if let Stmt::Let { + pattern: crate::expr::Pattern::Variable(name), + type_annotation: Some(declared), + .. + } = item_without_attributes(stmt) + && let Some(width) = super::register_width_of(declared) + { + widths.insert(name.clone(), width); + } + } + widths +} + pub(super) fn collect_function_names(program: &Program) -> Result> { let mut names = HashMap::new(); let mut next = 1_u32; @@ -187,7 +300,25 @@ pub(super) fn range_step_sign(step: Option<&Expr>) -> RangeStepSign { match const_int_expr_value(step) { Some(value) if value > 0 => RangeStepSign::Positive, Some(value) if value < 0 => RangeStepSign::Negative, - _ => RangeStepSign::Dynamic, + Some(_) => RangeStepSign::Zero, + None => RangeStepSign::Dynamic, + } +} + +/// The member name an `Expr::Access` spells, when it spells one. +/// +/// `a.f` parses to `Access(a, Literal("f"))` — the member is a *string +/// literal*, always. `a[i]` parses to `Access(a, Var("i"))`, and that is an +/// index, not a member: the value of `i` picks the element. +/// +/// Reading a bare `Var` as a member name is what made `fs[i]()` mean `fs.i()`, +/// so calling a closure out of a list by a variable index raised +/// `List has no method 'i'` — while `fs[0]()`, whose index is not an +/// identifier, worked. +pub(super) fn access_member_name(expr: &Expr) -> Option<&str> { + match expr { + Expr::Literal(value) => value.as_str(), + _ => None, } } @@ -220,12 +351,7 @@ pub(super) fn pattern_binds_scrutinee_directly(pattern: &Pattern) -> bool { fn collect_mutated_names(stmt: &Stmt, names: &mut HashSet) { match stmt { - Stmt::Attributed { item, .. } => collect_mutated_names(item, names), - Stmt::Try { body, handler, .. } => { - for stmt in body.iter().chain(handler) { - collect_mutated_names(stmt, names); - } - } + Stmt::Attributed { item, .. } | Stmt::Defer { body: item, .. } => collect_mutated_names(item, names), Stmt::If { condition, then_stmt, @@ -279,7 +405,7 @@ fn collect_mutated_names(stmt: &Stmt, names: &mut HashSet) { collect_mutated_names(method, names); } } - Stmt::Expr(expr) => collect_mutated_names_in_expr(expr, names), + Stmt::Expr { value: expr, .. } => collect_mutated_names_in_expr(expr, names), Stmt::Return { value } => { if let Some(value) = value { collect_mutated_names_in_expr(value, names); @@ -367,6 +493,11 @@ fn collect_mutated_names_in_expr(expr: &Expr, names: &mut HashSet) { collect_mutated_names(stmt, names); } } + Expr::Try { body, handler, .. } => { + for stmt in body.iter().chain(handler) { + collect_mutated_names(stmt, names); + } + } Expr::Range { start, end, step, .. } => { for expr in [start, end, step].into_iter().flatten() { collect_mutated_names_in_expr(expr, names); @@ -471,6 +602,41 @@ fn collect_global_name_from_top_level_stmt( } } +/// Every top-level name this program binds to *user data* — `let`, `const` and +/// `:=` alike. +/// +/// Deliberately not folded into [`collect_function_visible_let_names`]: that +/// set also decides whether a top-level global may be cached in a register +/// (`Compiler::can_cache_global`), so widening it would slow down every +/// `:=` script. This set answers a different question — "is this name a value +/// this program declared, or an imported module object?" — which is all +/// method dispatch needs (`is_external_global_access_target`). Getting the two +/// confused is why `xs := [1,2]` followed by `fn h() { xs.len() }` compiled +/// `xs.len` into an *index* read with the string `"len"` as the key. +pub(super) fn collect_top_level_data_global_names(program: &Program) -> HashSet { + let mut names = HashSet::new(); + for stmt in &program.statements { + collect_top_level_data_global_name(stmt, &mut names); + } + names +} + +fn collect_top_level_data_global_name(stmt: &Stmt, names: &mut HashSet) { + match stmt { + Stmt::Attributed { item, .. } => collect_top_level_data_global_name(item, names), + Stmt::Define { name, .. } => { + names.insert(name.clone()); + } + Stmt::Let { + pattern: Pattern::Variable(name), + .. + } => { + names.insert(name.clone()); + } + _ => {} + } +} + fn collect_top_level_let_names(program: &Program) -> HashSet { let mut names = HashSet::new(); for stmt in &program.statements { @@ -584,19 +750,23 @@ pub(super) fn global_slots_from_names(names: &HashMap) -> Vec Result> { - let mut names = HashMap::new(); - for (index, native) in natives.iter().enumerate() { - let index = u32::try_from(index).map_err(|_| anyhow!("Compiler native index overflow"))?; - if names.insert(native.name.clone(), index).is_some() { - bail!("Compiler duplicate native `{}`", native.name); - } - } - Ok(names) -} - +/// Narrow a register number to the 8 bits an instruction has for it. +/// +/// This is the one place 301 call sites funnel through, and all a program could +/// ever see from it was `Compiler dst register 256 exceeds u8 encoding` — an +/// encoding detail, with nothing about the limit being *per function* or what to +/// do about it. A body with 300 `let`s is a real thing to write; being told the +/// operand width is not an answer to it. (Lua, with the same design, says "too +/// many local variables".) pub(super) fn checked_u8(name: &str, value: u16) -> Result { - u8::try_from(value).map_err(|_| anyhow!("Compiler {name} register {} exceeds u8 encoding", value)) + u8::try_from(value).map_err(|_| { + anyhow!( + "this function needs more than {} registers (it reached {value} for a {name}): \ + every instruction names its registers in 8 bits, and a function's locals and \ + temporaries share that one set — split the body into smaller functions", + u8::MAX as u16 + 1, + ) + }) } #[inline] @@ -689,7 +859,7 @@ pub(super) fn const_heap_list_from_expr_literals(values: &[Box]) -> Result } pub(super) fn const_heap_map_from_expr_literals(entries: &[(Box, Box)]) -> Result> { - let mut const_entries = fast_hash_map_new(); + let mut const_entries = crate::util::value_map::value_map_new(); for (key, value) in entries { let Expr::Literal(key) = &**key else { return Ok(None); @@ -779,3 +949,16 @@ pub(super) fn pattern_kind(pattern: &Pattern) -> &'static str { Pattern::Range { .. } => "Range", } } + +/// Whether an expression is written as an integer literal, through parentheses. +/// +/// Used to decide that a literal beside a machine integer should take its width: +/// a *variable* of another numeric type is a width mistake, and only a literal +/// is retyped. +pub(super) fn is_int_literal(expr: &Expr) -> bool { + match expr { + Expr::Paren(inner) => is_int_literal(inner), + Expr::Literal(LiteralVal::Int(_)) => true, + _ => false, + } +} diff --git a/core/src/vm/compiler/tests.rs b/core/src/vm/compiler/tests.rs index adcfa11d..0aa28181 100644 --- a/core/src/vm/compiler/tests.rs +++ b/core/src/vm/compiler/tests.rs @@ -712,8 +712,7 @@ fn compiler_lowers_int_math_floor_directly_into_destination() { return mid; "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["math"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["math"]).expect("compile module"); let function = module.entry_function().expect("entry function"); let mid = function @@ -757,8 +756,7 @@ fn compiler_midpoint_floor_preserves_current_int_division_semantics() { return math.floor((lo + hi) / 2); "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["math"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["math"]).expect("compile module"); let function = module.entry_function().expect("entry function"); assert!( @@ -784,8 +782,7 @@ fn compiler_lowers_map_get_directly_into_destination() { return value; "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["map"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["map"]).expect("compile module"); let function = module.entry_function().expect("entry function"); let get = function @@ -998,3 +995,134 @@ fn compiler_lowers_map_literal_and_string_access() { assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(42)]); } + +/// A binding is not in scope inside its own initializer, so a lambda cannot +/// call itself — and the report used to be "Compiler undefined callable +/// `fact`": a sentence about an operand, for a rule about scope, with nothing +/// the reader could do about it. Recursion goes through a top-level `fn`, and +/// the message says so. +#[test] +fn a_lambda_calling_itself_is_told_the_rule() { + let error = compile_source("let fact = |n| { if (n <= 1) { return 1; } return n * fact(n - 1); };") + .expect_err("a self-call cannot resolve"); + let text = alloc::format!("{error:#}"); + assert!(text.contains("not in scope inside its own initializer"), "{text}"); + assert!(text.contains("top-level `fn fact("), "{text}"); + + // A name that is simply not there still reads as what it is — and no + // longer as "Compiler undefined callable", which named this compiler and + // one of its operand kinds for what is almost always a typo. + let typo = compile_source("let f = |x| { return nope(x); };").expect_err("no such callable"); + let typo_text = alloc::format!("{typo:#}"); + assert!(typo_text.contains("undefined function `nope`"), "{typo_text}"); + + // An *outer* binding of the same name is a different function, and calling + // it is fine. + compile_source("fn fact(n: Int) -> Int { return n; }\nlet fact = |n| { return fact(n) + 1; };") + .expect("calling the outer `fact` is not a self-call"); +} + +/// An unresolved name says what the program did, and offers the near miss. +/// +/// `Compiler undefined local/global `nope`` named this compiler and two of its +/// storage classes for what is, essentially always, a typo — and said nothing +/// about the names that *are* in scope, which are sitting right there. +/// +/// The measurement is the same `edit_distance` the unknown-*type* hint uses, +/// and it counts a transposition as one edit: `nmae` for `name` is the +/// commonest typo there is, and under plain Levenshtein it cost two and was +/// therefore never suggested for a short name. +#[test] +fn an_unresolved_name_offers_the_near_miss() { + // No builtins in these programs: this harness compiles without the standard + // library, so `println` would be the undefined name. + for (source, expected) in [ + ("let value = 1;\nlet a = Value;", "did you mean `value`?"), + ("let name = 1;\nlet a = nmae;", "did you mean `name`?"), + ("let count = 1;\nlet a = cuont;", "did you mean `count`?"), + ( + "fn helper() -> Int { return 1; }\nlet a = helpr();", + "did you mean `helper`?", + ), + ] { + let error = compile_source(source).expect_err("the name does not resolve"); + let text = alloc::format!("{error:#}"); + assert!(text.contains(expected), "{source} → {text}"); + assert!( + !text.contains("Compiler"), + "the message is for the program, not the compiler: {text}" + ); + } + + // Nothing close by invents nothing. + let far = compile_source("let value = 1;\nlet a = zzzzzz;").expect_err("no such name"); + let far_text = alloc::format!("{far:#}"); + assert!(far_text.contains("undefined name `zzzzzz`"), "{far_text}"); + assert!( + !far_text.contains("did you mean"), + "should not invent a suggestion: {far_text}" + ); +} + +/// A range with no end says what to write, not that a compiler does not support +/// something. +#[test] +fn an_open_ended_range_says_what_to_write() { + let loop_error = compile_source("for i in 0.. { println(i); }").expect_err("no end"); + let loop_text = alloc::format!("{loop_error:#}"); + assert!(loop_text.contains("would never finish"), "{loop_text}"); + assert!(!loop_text.contains("Compiler"), "{loop_text}"); + + let expr_error = compile_source("let r = 0..;").expect_err("no end"); + let expr_text = alloc::format!("{expr_error:#}"); + assert!(expr_text.contains("a range needs an end"), "{expr_text}"); + assert!(!expr_text.contains("Compiler"), "{expr_text}"); +} + +/// The call-width limits say what the program hit, not which operand ran out. +/// +/// `Compiler call has 300 args, max 127` named this compiler and a number with +/// no explanation — the number is the `Call` opcode's 7-bit count, which is a +/// fact about the encoding. It appeared as a bare `max 127` in three places and +/// as `max 255` in a fourth, all for the same kind of limit; it is one named +/// constant now, and each message says what to do instead. +/// +/// The parameter side is said **at the declaration**: a function with more +/// parameters than a call can pass is one nothing can call, and reporting that +/// at some later call site pointed at the wrong line. +#[test] +fn the_call_width_limits_are_stated_in_the_programs_terms() { + let params: alloc::vec::Vec = (0..300).map(|i| alloc::format!("a{i}")).collect(); + + let closure = compile_source(&alloc::format!("let f = |{}| a0;", params.join(", "))) + .expect_err("a closure that nothing can call"); + let closure_text = alloc::format!("{closure:#}"); + assert!(closure_text.contains("could never be called"), "{closure_text}"); + assert!(!closure_text.contains("Compiler"), "{closure_text}"); + assert!( + !closure_text.contains("registers"), + "the parameters are the problem: {closure_text}" + ); + + // Named parameters are *not* bounded by this: they ride a wider field, and + // a struct's generated constructor is one named parameter per field — the + // 200-field literal in `compile_cli_test` is exactly that, and counting + // named parameters here refused a struct literal that works. + let named: alloc::vec::Vec = (0..200).map(|i| alloc::format!("f{i}: Int")).collect(); + compile_source(&alloc::format!( + "fn wide({{{}}}) -> Int {{ return f0; }}", + named.join(", ") + )) + .expect("200 named parameters are within the named field's range"); + + let typed: alloc::vec::Vec = (0..300).map(|i| alloc::format!("a{i}: Int")).collect(); + let args = alloc::vec!["1"; 300].join(", "); + let call = compile_source(&alloc::format!( + "fn f({}) -> Int {{ return a0; }}\nlet x = f({args});", + typed.join(", ") + )) + .expect_err("a call wider than the instruction"); + let call_text = alloc::format!("{call:#}"); + assert!(call_text.contains("is the most"), "{call_text}"); + assert!(!call_text.contains("Compiler"), "{call_text}"); +} diff --git a/core/src/vm/compiler/tests/arithmetic.rs b/core/src/vm/compiler/tests/arithmetic.rs index 5e38a3a0..b8940dab 100644 --- a/core/src/vm/compiler/tests/arithmetic.rs +++ b/core/src/vm/compiler/tests/arithmetic.rs @@ -292,3 +292,162 @@ fn compiler_keeps_global_compound_add_semantics_when_rhs_reads_target() { let result = execute_module(&module).expect("execute module"); assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(30)]); } + +/// A machine int wraps whether or not its width was written down. +/// +/// The wrap is emitted where the width is *proven*, and proof used to come from +/// exactly two places: an annotation and an `as` cast. A value whose width came +/// from anywhere else — a function that declares a machine return, a builtin +/// whose name is a width, a read of a local already known to hold one — was +/// left unproven and did not wrap. +/// +/// So these two computed different numbers from the same types and the same +/// values, and which one you got depended on whether a width had been typed +/// out. 4000000000 + 4000000000 is 8000000000, and as a `u32` it is 3705032704. +#[test] +fn compiler_wraps_machine_ints_whose_width_was_inferred() { + let module = compile_source_module( + r#" + fn read() -> u32 { return 4000000000 as u32; } + let inferred_a = read(); + let inferred_b = read(); + let annotated_a: u32 = 4000000000; + let annotated_b: u32 = 4000000000; + let through_a = annotated_a; + let through_b = annotated_b; + // Summed rather than listed, so the assertion is one number: any of + // the three failing to wrap makes it too big by a known amount. + return (inferred_a + inferred_b) as Int + + (annotated_a + annotated_b) as Int + + (through_a + through_b) as Int; + "#, + ) + .expect("compile module"); + + let result = execute_module(&module).expect("execute module"); + assert_eq!( + result.returns, + vec![crate::val::RuntimeVal::Int(3 * 3_705_032_704)], + "a u32 sum must wrap the same way however its width was learned" + ); +} + +/// A width fact does not outlive the value it described. +/// +/// `machine_regs` is keyed by register, and registers are recycled: the one a +/// `u32` lived in inside a branch is handed to the next binding after it. If +/// the fact stayed, an unrelated `Int` would inherit it and wrap — a wrong +/// answer with nothing to point at, which is what the note in +/// `emit_bin_op_to_register_with_flavor` has always warned about. +/// +/// Every site that writes a register used to be responsible for remembering to +/// clear. Now a binding clears its destination *before* anything is lowered +/// into it, and a move carries the source's width or clears it — so the fact is +/// established by whatever landed in the register, not by whatever was there +/// before. +#[test] +fn compiler_does_not_let_a_machine_width_outlive_its_value() { + let module = compile_source_module( + r#" + fn narrow(flag: Bool) -> Int { + if (flag) { + let x: u32 = 4000000000; + let y: u32 = 4000000000; + return (x + y) as Int; + } + // The same registers, now holding plain integers. 8000000000 fits + // in an Int and must not come back as a u32 sum. + let p = 4000000000; + let q = 4000000000; + return p + q; + } + return narrow(false) + narrow(true); + "#, + ) + .expect("compile module"); + + let result = execute_module(&module).expect("execute module"); + assert_eq!( + result.returns, + vec![crate::val::RuntimeVal::Int(8_000_000_000 + 3_705_032_704)], + "the Int branch must not inherit the u32 branch's width" + ); +} + +/// `1 + f(x)` evaluates `f(x)` **once**, in every syntactic position. +/// +/// The immediate form of an int binary op wants the constant on the right, so +/// `const + expr` lowered `expr` first to ask whether its value is a proven +/// `Int`. When the answer was no — which it is for any call whose return type is +/// not annotated — the code fell through and lowered `expr` *again*, leaving the +/// first lowering's instructions in the stream. The operand then ran twice: `1 + +/// side(7)` called `side` twice, `f(5)` made 63 calls instead of 6, and `f(50)` +/// never finished at all. The *answer* stayed right for a pure function, which is +/// how it survived. +/// +/// Both lowerings had it, and they cover different positions: `lower_into` takes +/// `let`/element/argument destinations, `lower_bin_op` takes `return` and template +/// interpolation. Fixing one and testing the other would have looked green, so the +/// count below spans both. +/// +/// A *condition* (`if (1 + f(x) > 0)`) had a third mechanism for the same +/// wrongness and is counted here too: the condition path tries fused branch +/// shapes in turn, and each helper lowered an operand before checking a register +/// fact, so every rejected attempt left its instructions in the stream — three +/// evaluations, one per attempt that looked and declined. The attempts are now +/// restricted to operands that are free to lower twice +/// (`is_free_to_lower_twice`), which is what makes speculation-by-lowering sound +/// at all. +/// +/// Pinned to a *number*, not to the other backend, because **no differential +/// test can see this**: both backends lower from this bytecode, so both doubled +/// the call identically and agreed with each other. A VM-vs-native comparison is +/// blind to a front-end bug by construction. +#[test] +fn a_commuted_immediate_evaluates_its_operand_once() { + let module = crate::vm::compile_source_module( + r#" + let calls = 0; + fn side(x) { calls = calls + 1; return x; } + fn through_return() { return 1 + side(1); } + fn through_condition() { if (1 + side(1) > 0) { return 0; } return 0; } + fn through_while() { while (1 + side(1) > 99) { return 0; } return 0; } + let through_let = 1 + side(1); + let through_element = [2 * side(1)]; + let through_template = "${1 + side(1)}"; + through_return(); + through_condition(); + through_while(); + return calls; + "#, + ) + .expect("compile module"); + + let result = crate::vm::execute_module(&module).expect("run module"); + assert_eq!( + result.returns, + vec![crate::val::RuntimeVal::Int(6)], + "six `side` calls are written, so six must run — the doubling made this 7, 9, 12 or more \ + depending on which positions were involved" + ); + + // The same claim on the instruction stream, where it is a property of the + // code rather than of one run: the entry emits exactly the calls the source + // spells. + let entry = module.entry_function().expect("entry function"); + let emitted = entry + .code + .iter() + // Any call-shaped opcode, by name: the lowering picks between + // `CallDirect`/`Call`/`CallNamed` on grounds this test does not care + // about, and matching an explicit list would let a new one through + // silently. + .filter(|instr| alloc::format!("{:?}", instr.opcode()).starts_with("Call")) + .count(); + assert_eq!( + emitted, + 6, + "three `side` operands in the entry plus the three calls to the helpers: {:?}", + entry.code.iter().map(|i| i.opcode()).collect::>() + ); +} diff --git a/core/src/vm/compiler/tests/call_intrinsics.rs b/core/src/vm/compiler/tests/call_intrinsics.rs index 13ed40d9..5a4f8716 100644 --- a/core/src/vm/compiler/tests/call_intrinsics.rs +++ b/core/src/vm/compiler/tests/call_intrinsics.rs @@ -234,7 +234,10 @@ fn compiler_inlines_direct_function_with_while_early_return() { let lo = 0; let hi = limit - 1; while (lo <= hi) { - let mid = (lo + hi) / 2; + // `/` yields a Float, so an integer midpoint has to say so. + // (`math.floor` is the idiom; this crate's tests have no + // standard library, so the cast stands in for it.) + let mid = ((lo + hi) / 2) as Int; let value = mid * 2; if value == target { return mid; @@ -323,8 +326,7 @@ fn compiler_runs_direct_function_with_string_method() { return price("pro", 49, 8); "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["__lk_call_method"]).expect("compile"); + let module = Compiler::compile_module_with_globals(&program, ["__lk_call_method"]).expect("compile"); let entry = module.entry_function().expect("entry"); assert!( entry.code.iter().any(|instr| instr.opcode() == Opcode::CallDirect), @@ -397,8 +399,47 @@ fn compiler_drops_set_method_nil_result_for_statement() { assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(42)]); } +/// `set` answers the receiver, so a write chains. +/// +/// `set` answers the thing it wrote to. +/// +/// It used to lower the receiver expression twice — once for the write, once +/// for the answer. On a local that is the same slot and nothing shows; on any +/// other expression it wrote into one value and answered another, so +/// `[1, 2, 3].set(0, 9)` was `[1, 2, 3]`. (The other half — a receiver with +/// side effects running twice — is +/// `set_evaluates_a_side_effecting_receiver_once` in the exec tests, which can +/// run a program with functions in it.) `push` next door has always had the +/// single-lowering shape. #[test] -fn compiler_lowers_set_method_preserving_nil_result() { +fn compiler_set_method_answers_the_list_it_wrote_to() { + let function = compile_source( + r#" + let answered = [1, 2, 3].set(0, 9); + return answered; + "#, + ) + .expect("compile source"); + + let result = execute(&function).expect("execute"); + let crate::val::RuntimeVal::Obj(handle) = result.returns[0] else { + panic!("expected list return"); + }; + let Some(crate::val::HeapValue::List(crate::val::TypedList::Int(values))) = result.state.heap.get(handle) else { + panic!("expected an int list return"); + }; + assert_eq!( + values.as_slice(), + [9, 2, 3], + "the answer must be the list the write went into, not a second one" + ); +} + +/// It used to answer `nil`, which made writing one element the one mutating +/// method you could not chain — `push` beside it has always answered the +/// container. This test pinned the `nil`; it pins the receiver now. +#[test] +fn compiler_lowers_set_method_answering_the_receiver() { let function = compile_source( r#" let hist = {}; @@ -422,7 +463,15 @@ fn compiler_lowers_set_method_preserving_nil_result() { let Some(crate::val::HeapValue::List(crate::val::TypedList::Mixed(values))) = result.state.heap.get(handle) else { panic!("expected mixed list return"); }; - assert_eq!(values, &[crate::val::RuntimeVal::Nil, crate::val::RuntimeVal::Int(42)]); + // `result` is the map itself, so the pair is [the map, the value it holds]. + let crate::val::RuntimeVal::Obj(answered) = values[0] else { + panic!("set should answer the receiver"); + }; + assert!( + matches!(result.state.heap.get(answered), Some(crate::val::HeapValue::Map(_))), + "set should answer the map it wrote to" + ); + assert_eq!(values[1], crate::val::RuntimeVal::Int(42)); } #[test] @@ -628,8 +677,7 @@ fn compiler_lowers_map_get_module_call_to_get_index() { return map.get(hist, key); "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["map"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["map"]).expect("compile module"); let entry = module.entry_function().expect("entry"); assert!( @@ -652,7 +700,7 @@ fn compiler_errors_on_map_get_missing_receiver_in_call() { return id(map.get("x")); "#, ); - let err = Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["map"]) + let err = Compiler::compile_module_with_globals(&program, ["map"]) .expect_err("map.get missing receiver must not lower as method get"); assert!( @@ -735,8 +783,7 @@ fn compiler_folds_const_map_get_literal_key() { return map.get(hist, "answer"); "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["map"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["map"]).expect("compile module"); let entry = module.entry_function().expect("entry"); assert!( @@ -797,8 +844,7 @@ fn compiler_hoists_loop_const_map_get_folded_scalar_values() { return total; "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["map"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["map"]).expect("compile module"); let entry = module.entry_function().expect("entry"); let admin_loads = entry .code @@ -870,8 +916,7 @@ fn compiler_does_not_fold_const_map_get_after_mutation() { return map.get(hist, "answer"); "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["map"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["map"]).expect("compile module"); let entry = module.entry_function().expect("entry"); assert!( @@ -932,8 +977,7 @@ fn compiler_does_not_fold_loop_local_mutated_empty_map_get() { return total; "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["map"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["map"]).expect("compile module"); let entry = module.entry_function().expect("entry"); assert!( @@ -957,8 +1001,7 @@ fn compiler_lowers_math_floor_of_int_to_identity() { return math.floor(x + 2); "#, ); - let module = - Compiler::compile_module_with_natives_and_globals(&program, Vec::new(), ["math"]).expect("compile module"); + let module = Compiler::compile_module_with_globals(&program, ["math"]).expect("compile module"); let entry = module.entry_function().expect("entry"); assert!( diff --git a/core/src/vm/compiler/tests/loops.rs b/core/src/vm/compiler/tests/loops.rs index e919ce0c..6c2ccb28 100644 --- a/core/src/vm/compiler/tests/loops.rs +++ b/core/src/vm/compiler/tests/loops.rs @@ -14,9 +14,7 @@ fn compiler_for_over_local_string_does_not_clone_iterable_local() { ) .expect("compile source"); - crate::vm::vm_runtime_metrics_reset(); let result = execute(&function).expect("execute"); - let metrics = crate::vm::vm_runtime_metrics_snapshot(); assert!( !function.code.iter().any(|instr| instr.opcode() == Opcode::ToIter), @@ -33,10 +31,6 @@ fn compiler_for_over_local_string_does_not_clone_iterable_local() { function.code ); assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(19)]); - assert_eq!( - metrics.local_store_heap_clones, 0, - "readonly for iterable should use the local string slot directly" - ); } #[test] @@ -345,6 +339,45 @@ fn compiler_keeps_dynamic_for_range_step_sign_fallback() { assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(10)]); } +#[test] +fn compiler_refuses_literal_zero_for_range_step() { + let error = compile_source( + r#" + for i in 0..3..0 { + println(i); + } + return 1; + "#, + ) + .expect_err("a step of zero never advances the index"); + + assert!( + error.to_string().contains("Range step cannot be zero"), + "a `for` header should refuse a zero step in the same words as a range value: {error}" + ); +} + +#[test] +fn compiler_refuses_dynamic_zero_for_range_step() { + let function = compile_source( + r#" + let step = 0; + let sum = 0; + for i in 0..5..step { + sum += i; + } + return sum; + "#, + ) + .expect("compile source"); + + let error = execute(&function).expect_err("a zero step is refused on loop entry, not silently skipped"); + assert!( + error.to_string().contains("Range step cannot be zero"), + "a dynamic zero step should raise rather than run the descending branch: {error}" + ); +} + #[test] fn compiler_for_range_reuses_unmutated_local_end() { let function = compile_source( diff --git a/core/src/vm/compiler/tests/misc.rs b/core/src/vm/compiler/tests/misc.rs index 04adb7a4..59685085 100644 --- a/core/src/vm/compiler/tests/misc.rs +++ b/core/src/vm/compiler/tests/misc.rs @@ -35,9 +35,15 @@ fn compiler_lowers_struct_literal_and_field_access() { assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(42)]); } +/// Declarations that contribute no instructions of their own still compile. +/// +/// Runs through the module path: a `struct` also declares its hidden +/// constructor (`stmt::struct_ctors`), and a program that declares a function +/// cannot be executed as a bare `Function` — `LoadFunction` publishes it, which +/// needs a module. #[test] fn compiler_accepts_type_only_declarations_as_noop() { - let function = compile_source( + let result = crate::vm::execute_source( r#" struct Point { x: Int, y: Int } type Count = Int; @@ -46,9 +52,7 @@ fn compiler_accepts_type_only_declarations_as_noop() { return point.x + point.y; "#, ) - .expect("compile source"); - - let result = execute(&function).expect("execute"); + .expect("execute source"); assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(42)]); } @@ -360,17 +364,15 @@ fn compiler_lowers_native_call_through_module() { Ok(crate::val::RuntimeVal::Int(lhs + rhs)) } - let module = compile_source_module_with_natives( + // The native arrives as a *global*, the only way a running program gets one + // (see `exec_tests::execute_source_with_natives`). Compiling it into the + // module's own table emitted `LoadNative`, which every production caller + // makes unreachable by passing an empty table. + let result = crate::vm::exec::exec_tests::execute_source_with_natives( "return native_add(19, 23);", - vec![NativeEntry { - name: "native_add".to_string(), - arity: 2, - function: NativeFunction::Plain(native_add), - }], + &[("native_add", NativeFunction::Plain(native_add), 2)], ) - .expect("compile module"); - - let result = execute_module(&module).expect("execute module"); + .expect("execute source"); assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(42)]); } @@ -399,6 +401,118 @@ fn compiler_lowers_top_level_define_to_global_slot() { assert!(matches!(result.state.globals[1], crate::val::RuntimeVal::Obj(_))); } +/// A program with more top-level constants than there are registers still +/// compiles, and still computes with them. +/// +/// The register file is 256 deep — they are `u8` in the instruction encoding — +/// and the top level is one function. Every global-backed top-level binding +/// used to keep one register permanently as a cache of its global slot, so at +/// 256 of them the next statement's temporaries had nowhere to go and the +/// compiler reported `Compiler global dst register 256 exceeds u8 encoding`, +/// naming whichever constant happened to be added last. +/// +/// That is not an exotic program. It is what `bare-metal-x86/program.lk` became +/// once its drivers — each a file of perfectly ordinary constants — were +/// bundled into it, and adding one more driver was enough. +/// +/// 400 rather than 257: the cache limit leaves the rest of the register file +/// for one statement's working set, so a test that only just crosses it would +/// pass with the eviction rule doing nothing. This one has three hundred +/// bindings past the point where caching stops, and reads the first and the +/// last of them from a function — which can only see them through the global +/// slots, the thing the cache was ever a cache *of*. +#[test] +fn compiler_compiles_more_top_level_constants_than_registers() { + let mut source = String::new(); + for index in 0..400 { + source.push_str(&format!("const K{index} = {index};\n")); + } + source.push_str("fn ends() { return K0 + K399; }\n"); + source.push_str("return ends() + K1 + K398;\n"); + + let module = compile_source_module(&source).expect("compile module"); + let result = execute_module(&module).expect("execute module"); + + // 0 + 399 from the function, 1 + 398 from the top level. + assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(798)]); +} + +/// A program with more top-level `fn` declarations than there are registers +/// still compiles, and the functions still work. +/// +/// Publishing a declaration is `LoadFunction r; SetGlobal r, slot`, and the +/// register is dead the moment the store lands — but it used to be a fresh one +/// every time, so a program paid one register per `fn` for the whole of its top +/// level. `bare-metal-x86/program.lk` with its drivers bundled in declares 236 +/// functions, which is most of the 256 a `u8` register field allows, and it ran +/// out on the *constants* that came afterwards. +/// +/// 300 rather than 257 so the shared register is doing real work rather than +/// just crossing the line, and two of them are called so the test fails on a +/// register that stopped meaning what the caller thought. +#[test] +fn compiler_compiles_more_top_level_functions_than_registers() { + let mut source = String::new(); + for index in 0..300 { + source.push_str(&format!("fn f{index}() {{ return {index}; }}\n")); + } + source.push_str("return f0() + f299();\n"); + + let module = compile_source_module(&source).expect("compile module"); + let result = execute_module(&module).expect("execute module"); + + assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(299)]); +} + +/// The register those declarations share is *only* ever a function value. +/// +/// Not a nicety: the AOT lowering tracks what a register means keyed by +/// `(block, register)` with no notion of time, so a register that once held a +/// function value keeps that meaning for the rest of the block. Recycling it — +/// handing it back for a later `let` to use — makes the next `SetGlobal` from +/// it read as declaration bookkeeping, and a global write is silently elided. +/// +/// So this asserts the shape rather than the outcome: after the declarations, +/// a top-level binding must not land on the register they published through. +#[test] +fn compiler_does_not_reuse_the_function_publish_register() { + let module = compile_source_module( + r#" + fn a() { return 1; } + fn b() { return 2; } + answer := 40; + fn read() { return answer + 2; } + return read(); + "#, + ) + .expect("compile module"); + + // Every `SetGlobal` whose source register also appears as a `LoadFunction` + // destination is a declaration; no other `SetGlobal` may share one. + let code = &module.functions[module.entry as usize].code; + let mut function_regs = std::collections::HashSet::new(); + for instr in code { + if instr.opcode() == crate::vm::Opcode::LoadFunction { + function_regs.insert(instr.a()); + } + } + assert!(!function_regs.is_empty(), "no function declarations were emitted"); + for instr in code { + if instr.opcode() == crate::vm::Opcode::SetGlobal { + let name = module.globals[instr.bx() as usize].name.as_ref(); + if name == "answer" { + assert!( + !function_regs.contains(&instr.a()), + "a value global is stored from the register function declarations publish through" + ); + } + } + } + + let result = execute_module(&module).expect("execute module"); + assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(42)]); +} + #[test] fn compiler_keeps_top_level_let_in_entry_frame() { let module = compile_source_module( @@ -1307,3 +1421,245 @@ fn a_call_above_its_definition_still_knows_the_signature() { assert_eq!(result.returns, vec![crate::val::RuntimeVal::Int(2)], "{order}"); } } + +/// Compiling n functions costs O(n), not O(n²). +/// +/// Every function gets its own `Compiler`, and each one used to receive a deep +/// **clone** of the ten tables that describe the program — names, signatures, +/// inlinable bodies (which hold ASTs), widths. So compiling the n-th function +/// A function may call 256 distinct methods, not 128. +/// +/// `CallMethodK` carries the method name's constant index in **8 bits** — the +/// `abc` form is full (7 opcode + 8 A + 1 K + 8 B + 8 C) — and a name past 255 +/// falls back to a `__lk_call_method` helper call, which the native backend +/// cannot lower. So the whole program silently loses native compilation. +/// +/// The bound *read* like 256 method names and *was* 129: a function's constant +/// pool is shared with everything else it mentions, so 130 structs each +/// constructed and called once pushed the method names past the byte with their +/// own type and field names. Seeding the pool with the body's method names +/// first makes the two agree. +/// +/// Asserted on the instruction, not on whether it compiles: the fallback path +/// still produces a working program, so only the opcode says which one ran. +#[test] +fn a_function_may_call_two_hundred_distinct_methods() { + fn method_calls(n: usize) -> String { + let mut out = String::new(); + for i in 0..n { + out.push_str(&alloc::format!( + "struct S{i} {{ x: Int }}\nimpl S{i} {{ fn m{i}(self) -> Int {{ return self.x + 1; }} }}\n" + )); + } + out.push_str("fn main() -> Int {\n"); + for i in 0..n { + out.push_str(&alloc::format!(" let v{i} = S{i} {{ x: {i} }}.m{i}();\n")); + } + out.push_str(" return 0;\n}\nmain();\n"); + out + } + + let generic_calls = |source: &str| { + let program = parse_program(source); + let module = crate::vm::Compiler::compile_module(&program).expect("compile module"); + module + .functions + .iter() + .flat_map(|function| function.code.iter().copied()) + .filter(|instr| instr.opcode() == crate::vm::Opcode::CallMethodK) + .count() + }; + + // 200 distinct methods, each called once: every call is a `CallMethodK`. + assert_eq!(generic_calls(&method_calls(200)), 200); +} + +/// copied everything the n-1 before it had declared. It was not subtle: 1000 +/// functions took 0.55s, 2000 took 2.30s, 4000 took 10.9s, and before the type +/// checker's scopes stopped cloning too, 4000 took 23s. +/// +/// A wall-clock assertion would be flaky, so this measures the *shape*: double +/// the input and the work must not quadruple. The bound is generous (3x for a +/// 2x input) because a real machine has noise and allocation is not free — it +/// fails on quadratic (which is 4x) and passes on linear. +/// +/// **`#[ignore]`d, and run alone in CI** (`.github/workflows/check.yml`, the +/// same treatment `lsp/tests/perf_latency_test.rs` got). Min-of-5 was the first +/// attempt at making it survive `cargo test --workspace --all-features`, and it +/// was not enough: the assertion is a *ratio of two* wall-clock measurements, +/// so a lucky-fast `small` against an unlucky-slow `large` blows it up even +/// when both minima are clean. It failed a workspace run again on 2026-08-06 +/// (that run took 0.68s against 0.17s on its own — the suite was sharing cores +/// with a `cargo clippy`), and passed eight times in a row alone, including +/// four with three parallel builds running. +/// +/// A gate that fails for reasons the change did not cause teaches people to +/// re-run it, which is worse than no gate. Run it with: +/// +/// ```sh +/// cargo test -p lk-core --lib -- --ignored --test-threads=1 compiling_many_functions +/// ``` +#[test] +#[ignore = "wall-clock ratio: runs alone in CI, see the doc comment"] +fn compiling_many_functions_stays_linear() { + fn source(n: usize) -> String { + let mut out = String::new(); + for i in 0..n { + out.push_str(&alloc::format!( + "fn f{i}(a: Int, b: Int) -> Int {{ let x = a + b; return x * 2; }}\n" + )); + } + out.push_str("return 0;\n"); + out + } + + // The **fastest** of several runs, not one run. + // + // A wall-clock ratio is the only cheap way to say "not quadratic", and one + // sample of it is a coin flip: this assertion failed once inside + // `cargo test --workspace --all-features` — where a few dozen test threads + // share the cores — and passed five times in a row on its own. A gate that + // fails for reasons the change did not cause is worse than no gate, because + // the habit it teaches is to re-run it. + // + // The minimum is the right estimator here: scheduler noise, page faults and + // frequency scaling can only ever make a run *slower*, so the smallest + // sample is the closest one to the work actually being measured. + let time = |n: usize| { + let program = parse_program(&source(n)); + (0..5) + .map(|_| { + let start = std::time::Instant::now(); + crate::vm::Compiler::compile_module(&program).expect("compile module"); + start.elapsed() + }) + .min() + .expect("five samples") + }; + + // Warm the allocator so the first measurement is not the outlier. + let _ = time(200); + let small = time(400); + let large = time(800); + assert!( + large < small * 3, + "doubling the function count roughly tripled or worse — quadratic is back: \ + 400 fns in {small:?}, 800 fns in {large:?}" + ); +} + +/// The call-kind counters count. None of them may be structurally zero. +/// +/// `native_call_ops`, `closure_call_ops` and `method_call_ops` had match arms +/// adding them up and **no site constructing one**, so `lk coverage --runtime` +/// reported zero native calls for a program that calls `println` in a loop. A +/// number that is always zero is worse than an absent one: `bench/README.md` +/// decides which fused opcodes to keep from these proportions. +/// +/// Asserted as an exact partition — every call lands in exactly one bucket, so +/// the parts must sum to the total. That is what catches the next version of +/// this bug: a call classified twice, or a new call opcode that forgets to +/// classify at all, breaks the sum. +#[test] +fn every_call_lands_in_exactly_one_bucket() { + fn a_native(args: NativeArgs<'_>, _runtime: &mut crate::vm::NativeRuntime<'_>) -> Result { + let [value] = args.as_slice() else { + bail!("a_native expects one argument"); + }; + Ok(*value) + } + + // The native is *installed*, not compiled into the module: the counter this + // test defends is bumped by `exec::call`'s classification of the value being + // called, so proving it through an inline `NativeEntry` proved it for a path + // that does not ship — no production caller ever fills that table. + // + // A *builtin* method for the method bucket (`xs.unique()` lowers to + // `CallMethodK`), so this needs no impl table. + crate::vm::vm_runtime_metrics_reset(); + crate::vm::exec::exec_tests::execute_source_with_natives( + r#" + fn direct(n: Int) -> Int { return n + 1; } + let closure = |x: Int| x + 1; + let xs = [3, 1, 2, 1]; + let total = 0; + total = total + direct(1); + total = total + closure(1); + total = total + xs.unique().len(); + total = total + a_native(1); + return total; + "#, + &[("a_native", NativeFunction::Plain(a_native), 1)], + ) + .expect("execute source"); + let metrics = crate::vm::vm_runtime_metrics_snapshot(); + + assert!( + metrics.native_call_ops > 0, + "`a_native(1)` is a native call: {metrics:?}" + ); + assert!( + metrics.closure_call_ops > 0, + "the lambda is a closure call: {metrics:?}" + ); + assert!( + metrics.method_call_ops > 0, + "`xs.unique()` is a method call: {metrics:?}" + ); + assert!(metrics.exact_call_ops > 0, "`direct(1)` is a direct call: {metrics:?}"); + + let classified = metrics.native_call_ops + + metrics.closure_call_ops + + metrics.method_call_ops + + metrics.exact_call_ops + + metrics.named_call_ops; + assert_eq!( + classified, metrics.call_ops, + "every call is counted once and classified once: {metrics:?}" + ); + + // The same identity for register writes. `register_writes` was bumped in one + // helper while the sources are recorded at every opcode that writes, so the + // report printed a total of 210 above parts summing to 763 — a reader takes + // the first line for the sum of the rest. The total is now computed *from* + // the breakdown, and this says so. + assert!(metrics.register_writes > 0, "the program writes registers: {metrics:?}"); + assert_eq!( + metrics.register_writes, + metrics.register_write_sources.iter().sum::(), + "the register-write total is the sum of its sources: {metrics:?}" + ); +} + +/// A method call whose receiver is a top-level `:=` global, read from inside a +/// function, dispatches as a *method*. +/// +/// It used to compile to `GetIndex` keyed by the string `"len"`, because the +/// "is this a module object or user data?" check consulted a `let`-only set. +/// The program then failed at run time with `register 2 expected Int, got +/// String` — for `let xs = [1,2]` beside it, the identical code worked. +#[test] +fn method_call_on_define_global_inside_function_dispatches_as_method() { + let program = crate::syntax::parse_program_source( + "xs := [1,2];\nfn h() { return xs.len(); }\nreturn h();", + crate::syntax::ParseOptions::default(), + ) + .expect("parse"); + let module = crate::vm::Compiler::compile_module(&program).expect("compile"); + let body = module + .functions + .iter() + .find(|function| function.debug_name.as_deref() == Some("h")) + .expect("compiled `h`"); + + assert!( + body.code.iter().any(|instr| instr.opcode() == Opcode::Len), + "expected a Len opcode in {:?}", + body.code + ); + assert!( + !body.code.iter().any(|instr| instr.opcode() == Opcode::GetIndex), + "the method name must not become an index key: {:?}", + body.code + ); +} diff --git a/core/src/vm/compiler/tests/template.rs b/core/src/vm/compiler/tests/template.rs index 1c92589c..270d8985 100644 --- a/core/src/vm/compiler/tests/template.rs +++ b/core/src/vm/compiler/tests/template.rs @@ -238,3 +238,61 @@ fn compiler_template_string_preserves_to_string_for_single_expression() { let result = execute(&function).expect("execute"); assert_eq!(returned_string(&result), "7"); } + +/// A `u64` in a template string renders unsigned. +/// +/// Absolute rather than differential: two backends that both print a physical +/// address as a negative number agree with each other perfectly. The value was +/// never wrong — `one << 63` has the right bits — but the display handed the +/// carrier to an `i64` formatter, so this asserts the digits. +#[test] +fn compiler_template_string_renders_u64_unsigned() { + // `execute_source` rather than this module's bare `compile_source`: the + // rendering is a call to a runtime builtin, and the bare harness installs + // none. The comparison is done in LK so the answer is a `Bool` and no heap + // string has to be reached into. + let result = crate::vm::execute_source( + r#" + let minus_one = 0 - 1; + let top = minus_one as u64; + return "${top}" == "18446744073709551615"; + "#, + ) + .expect("execute source"); + + assert_eq!(result.first_return(), &crate::val::RuntimeVal::Bool(true)); +} + +/// And with more than one part, where the flag that forces a `ToString` is off +/// because `Concat` stringifies at run time instead — which is exactly where the +/// width has already been lost. +#[test] +fn compiler_template_string_renders_u64_unsigned_among_other_parts() { + let result = crate::vm::execute_source( + r#" + let minus_one = 0 - 1; + let top = minus_one as u64; + return "at ${top} end" == "at 18446744073709551615 end"; + "#, + ) + .expect("execute source"); + + assert_eq!(result.first_return(), &crate::val::RuntimeVal::Bool(true)); +} + +/// A width below the carrier is untouched: its high bits are zero, so the signed +/// reading and the unsigned one are the same digits, and inserting a call there +/// would be cost without a difference. +#[test] +fn compiler_template_string_leaves_narrow_widths_alone() { + let function = compile_source( + r#" + let value: u32 = 4294967295; + return "${value}"; + "#, + ) + .expect("compile source"); + + let result = execute(&function).expect("execute"); + assert_eq!(returned_string(&result), "4294967295"); +} diff --git a/core/src/vm/context.rs b/core/src/vm/context.rs index 1dde4c50..5e624d98 100644 --- a/core/src/vm/context.rs +++ b/core/src/vm/context.rs @@ -1,6 +1,7 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; use crate::util::fast_map::{FastHashMap, fast_hash_map_new}; +use crate::util::value_map::ValueMap; use crate::vm::ModuleResolver; use alloc::sync::Arc; @@ -17,7 +18,7 @@ use crate::vm::{ use crate::typ::{TraitDef, TraitImpl}; mod core_methods; -pub(crate) use core_methods::core_call_method_windowed; +pub use core_methods::core_call_method_windowed; use core_methods::{core_call_method_builtin, core_call_method_named_builtin, core_set_builtin}; /// Where a trait-impl method's body lives. @@ -74,28 +75,28 @@ pub struct VmContext { /// The outer key is what makes the table *correct* rather than merely fast. /// Keyed by type name alone, two modules that both declare `Point` shared /// one entry and the later registration silently won for both of them (see - /// [`crate::vm::TypeScope`]). Scoping it also makes registration + /// [`crate::val::TypeScope`]). Scoping it also makes registration /// order-independent, which is what lets the transitive closure of loaded /// modules be registered wholesale without any of them clobbering another. /// /// Nested rather than tuple-keyed so a lookup borrows every part of the /// key: a flat map forced two `String` allocations on *every* dynamic /// method dispatch just to build a throwaway probe. - methods: FastHashMap>>, + methods: FastHashMap>>, /// Identity to stamp on the module compiled in this context, and therefore /// on every object it constructs. Set by the loader, which knows the path; - /// the compiler does not (see [`crate::vm::TypeScope`]). - type_scope: crate::vm::TypeScope, + /// the compiler does not (see [`crate::val::TypeScope`]). + type_scope: crate::val::TypeScope, /// Which module declared each `impl Trait for ` — keyed by /// `(type name, trait name)`. /// /// A builtin type has no declaring module, so every module's impls for it - /// share one scope (see [`crate::vm::TypeScope::builtin`]) and the later + /// share one scope (see [`crate::val::TypeScope::builtin`]) and the later /// registration used to overwrite the earlier one *silently*: with two /// modules implementing `Doubler for Int`, `(5).dbl()` answered whichever /// was imported last, so moving a `use` line changed the result. Recording /// the owner turns the overlap into an error at registration. - builtin_impl_owner: FastHashMap<(String, String), crate::vm::TypeScope>, + builtin_impl_owner: FastHashMap<(String, String), crate::val::TypeScope>, call_stack: Vec, /// Per-context handle to the async (tokio) runtime. Replaces the former /// process-global runtime; clones (spawned tasks, shallow clones) share the @@ -109,7 +110,7 @@ impl Default for VmContext { } } -/// 调用帧信息,用于错误报告。 +/// One call frame, for error reporting. #[derive(Debug, Clone)] pub struct CallFrameInfo { pub function_name: Arc, @@ -118,7 +119,7 @@ pub struct CallFrameInfo { } impl VmContext { - /// 创建一个空上下文。 + /// An empty context. pub fn new() -> Self { let mut ctx = Self::new_without_core_vm_builtins(); ctx.type_checker = Some(TypeChecker::new()); @@ -138,14 +139,14 @@ impl VmContext { type_checker: None, structs: fast_hash_map_new(), methods: fast_hash_map_new(), - type_scope: crate::vm::TypeScope::anonymous(), + type_scope: crate::val::TypeScope::anonymous(), builtin_impl_owner: fast_hash_map_new(), call_stack: Vec::new(), async_runtime: crate::rt::AsyncRuntimeHandle::new(), } } - /// 当前全局缓存版本。 + /// The global cache version. #[inline] pub fn generation(&self) -> u64 { self.generation @@ -200,7 +201,7 @@ impl VmContext { self.generation = generation; } - /// 构建函数,允许自定义组件。 + /// Builds one with the components given. pub fn with_resolver(mut self, resolver: Arc) -> Self { for (name, value) in resolver.runtime_builtin_iter() { if self.runtime_globals.contains_key(name.as_ref()) { @@ -213,22 +214,22 @@ impl VmContext { self } - /// 设置类型检查器。 + /// Installs a type checker. pub fn with_type_checker(mut self, type_checker: Option) -> Self { self.type_checker = type_checker; self } /// Identity to stamp on the module compiled here (see - /// [`crate::vm::TypeScope`]). The loader sets this before compiling a file + /// [`crate::val::TypeScope`]). The loader sets this before compiling a file /// module; anything else keeps the anonymous scope. - pub fn with_type_scope(mut self, type_scope: crate::vm::TypeScope) -> Self { + pub fn with_type_scope(mut self, type_scope: crate::val::TypeScope) -> Self { self.type_scope = type_scope; self } #[inline] - pub fn type_scope(&self) -> &crate::vm::TypeScope { + pub fn type_scope(&self) -> &crate::val::TypeScope { &self.type_scope } @@ -258,7 +259,7 @@ impl VmContext { self.define_runtime_global(name, RuntimeExport::from_value(value, heap)); } - /// 手动递增版本号,用于强制失效缓存。 + /// Bumps the version, invalidating the caches. #[inline] pub fn touch(&mut self) { self.bump_generation(); @@ -268,7 +269,7 @@ impl VmContext { self.generation = self.generation.wrapping_add(1); } - /// 调用栈管理:进入函数调用 + /// Pushes a call frame. pub fn push_call_frame(&mut self, name: N, location: Option) where N: Into>, @@ -281,23 +282,24 @@ impl VmContext { }); } - /// 调用栈管理:退出函数调用 + /// Pops a call frame. pub fn pop_call_frame(&mut self) -> Option { self.call_stack.pop() } - /// 获取当前调用栈信息 + /// The current call stack. pub fn call_stack(&self) -> &[CallFrameInfo] { &self.call_stack } - /// 获取当前函数名 + /// The function being executed. pub fn current_function(&self) -> Option<&str> { self.call_stack.last().map(|frame| frame.function_name.as_ref()) } - /// 返回当前调用栈的格式化字符串。深栈截断打印(头 20 帧 + 尾 10 帧): - /// 递归打满调用深度上限时,完整 traceback 会有几十万行,淹没真正的错误。 + /// The call stack, rendered. A deep stack prints its first 20 and last 10 + /// frames: a recursion that reaches the depth limit has a traceback of + /// hundreds of thousands of lines, which buries the actual error. pub fn call_stack_report(&self) -> Option { const HEAD_FRAMES: usize = 20; const TAIL_FRAMES: usize = 10; @@ -335,7 +337,7 @@ impl VmContext { Some(msg) } - /// 生成增强的错误信息,包含调用栈上下文 + /// The error with its call-stack context attached. pub fn format_error_with_context(&self, error_message: &str) -> String { if let Some(report) = self.call_stack_report() { let mut msg = error_message.to_string(); @@ -347,27 +349,27 @@ impl VmContext { } } - /// 获取模块解析器的引用 + /// The module resolver. pub fn resolver(&self) -> &Arc { &self.resolver } - /// 获取类型检查器的引用 + /// The type checker. pub fn type_checker(&self) -> &Option { &self.type_checker } - /// 获取结构体定义的引用 + /// The struct declarations. pub fn structs(&self) -> &FastHashMap> { &self.structs } - /// 获取类型检查器的可变引用 + /// The type checker, mutably. pub fn get_type_checker_mut(&mut self) -> Option<&mut TypeChecker> { self.type_checker.as_mut() } - /// 注册结构体模式 + /// Registers a struct shape. pub fn register_struct_schema(&mut self, name: String, fields: FastHashMap) { self.structs.insert(name, fields); } @@ -409,6 +411,39 @@ impl VmContext { NativeFunction::Plain(core_cpu_wait_for_interrupt_builtin), 0, ); + // The one x86 instruction whose operand a program cannot supply: `int` + // takes its vector as an immediate. Without this a kernel written in + // this language can handle an interrupt but not raise one, which is the + // difference between defining a syscall and merely answering it. + self.install_runtime_builtin( + "cpu_raise_interrupt", + NativeFunction::Plain(core_cpu_raise_interrupt_builtin), + 1, + ); + // System control: descriptor tables, CR2/CR3, the TLB. Gated like port + // I/O rather than always refused — the bare-metal x86 kernel hosts this + // interpreter, and a program it loads off a disk reaches the same + // builtins the compiled kernel does. + self.install_runtime_builtin("cpu_load_idt", NativeFunction::Plain(core_cpu_load_idt_builtin), 2); + self.install_runtime_builtin("cpu_load_gdt", NativeFunction::Plain(core_cpu_load_gdt_builtin), 2); + self.install_runtime_builtin( + "cpu_reload_segments", + NativeFunction::Plain(core_cpu_reload_segments_builtin), + 2, + ); + self.install_runtime_builtin( + "cpu_load_task_register", + NativeFunction::Plain(core_cpu_load_task_register_builtin), + 1, + ); + self.install_runtime_builtin("cpu_read_cr2", NativeFunction::Plain(core_cpu_read_cr2_builtin), 0); + self.install_runtime_builtin("cpu_read_cr3", NativeFunction::Plain(core_cpu_read_cr3_builtin), 0); + self.install_runtime_builtin("cpu_write_cr3", NativeFunction::Plain(core_cpu_write_cr3_builtin), 1); + self.install_runtime_builtin( + "cpu_invalidate_page", + NativeFunction::Plain(core_cpu_invalidate_page_builtin), + 1, + ); // Volatile MMIO access. // // Whether this can mean anything depends on where the VM itself is @@ -439,6 +474,7 @@ impl VmContext { self.install_runtime_builtin("port_out_u32", NativeFunction::Plain(core_port_out_u32), 2); self.install_runtime_builtin("__lk_bit_and", NativeFunction::Plain(core_bit_and_builtin), 2); self.install_runtime_builtin("__lk_bit_or", NativeFunction::Plain(core_bit_or_builtin), 2); + self.install_runtime_builtin("__lk_bit_xor", NativeFunction::Plain(core_bit_xor_builtin), 2); self.install_runtime_builtin("__lk_bit_not", NativeFunction::Plain(core_bit_not_builtin), 1); // Function pointers: the address of an exported function, and a call // through one. Native-only, like the rest of `hardware` — the VM @@ -447,6 +483,24 @@ impl VmContext { self.install_runtime_builtin("call_address_2", NativeFunction::Plain(core_call_address_2_builtin), 3); self.install_runtime_builtin("__lk_shl", NativeFunction::Plain(core_shl_builtin), 2); self.install_runtime_builtin("__lk_shr", NativeFunction::Plain(core_shr_builtin), 2); + // The same shift, logical. The compiler picks this name when the left + // operand is a `u64`: every value rides an `i64` carrier, so for a `u8`, + // `u16` or `u32` the high bits are zero and an arithmetic shift happens + // to be right — a `u64` fills the carrier, and bit 63 is part of the + // value rather than its sign. + self.install_runtime_builtin("__lk_shr_u", NativeFunction::Plain(core_shr_unsigned_builtin), 2); + // The other three the `i64` carrier cannot answer for a `u64`: a value + // with bit 63 set *is* a negative carrier, so a signed compare puts it + // below 1 and a signed divide answers a negative. One comparison + // primitive rather than four — `a > b` is `b < a`, and the inclusive + // forms are those negated. + self.install_runtime_builtin("__lk_lt_u", NativeFunction::Plain(core_lt_unsigned_builtin), 2); + self.install_runtime_builtin("__lk_div_u", NativeFunction::Plain(core_div_unsigned_builtin), 2); + self.install_runtime_builtin("__lk_mod_u", NativeFunction::Plain(core_mod_unsigned_builtin), 2); + self.install_runtime_builtin("__lk_u64_to_float", NativeFunction::Plain(core_u64_to_float_builtin), 1); + // And the display, which the compiler inserts at the places that render + // a value rather than compute with it. + self.install_runtime_builtin("__lk_u64_str", NativeFunction::Plain(core_u64_to_str_builtin), 1); } /// Looks up a trait-impl method for the type `type_name` **as declared by @@ -455,7 +509,7 @@ impl VmContext { /// The scope is not optional and there is deliberately no name-only /// fallback: falling back would re-admit exactly the cross-module /// collision this key exists to prevent, and would do it silently. - pub fn trait_method(&self, scope: &crate::vm::TypeScope, type_name: &str, method: &str) -> Option<&MethodImpl> { + pub fn trait_method(&self, scope: &crate::val::TypeScope, type_name: &str, method: &str) -> Option<&MethodImpl> { self.methods.get(scope)?.get(type_name)?.get(method) } @@ -469,14 +523,21 @@ impl VmContext { /// is fine — it is the *same* owner. fn claim_builtin_impl( &mut self, - scope: &crate::vm::TypeScope, - declaring: &crate::vm::TypeScope, + scope: &crate::val::TypeScope, + declaring: &crate::val::TypeScope, type_name: &str, - trait_name: &str, + trait_name: Option<&str>, ) -> Result<()> { if !scope.is_builtin() { return Ok(()); } + // An inherent impl claims nothing: the conflict this guards against is + // "one trait implemented twice for a builtin type", and there is no + // trait here. Two inherent impls of the same method on a builtin would + // collide in the dispatch table instead, where every type does. + let Some(trait_name) = trait_name else { + return Ok(()); + }; let key = (type_name.to_string(), trait_name.to_string()); match self.builtin_impl_owner.get(&key) { Some(owner) if owner != declaring => Err(anyhow!( @@ -507,7 +568,7 @@ impl VmContext { } for decl in &module.type_info.impls { let scope = impl_target_scope(&decl.type_name, &module.type_scope); - self.claim_builtin_impl(&scope, &module.type_scope, &decl.type_name, &decl.trait_name)?; + self.claim_builtin_impl(&scope, &module.type_scope, &decl.type_name, decl.trait_name.as_deref())?; let by_method = self .methods .entry(scope) @@ -549,7 +610,7 @@ impl VmContext { // loop afterwards left a rejected module half-registered. for decl in &type_info.impls { let scope = impl_target_scope(&decl.type_name, &module.type_scope); - self.claim_builtin_impl(&scope, &module.type_scope, &decl.type_name, &decl.trait_name)?; + self.claim_builtin_impl(&scope, &module.type_scope, &decl.type_name, decl.trait_name.as_deref())?; } // Checker registration only happens when there *is* a checker; the // dispatch table below is unconditional. Returning early without one @@ -581,8 +642,14 @@ impl VmContext { .iter() .map(|method| (method.name.clone(), (method.function, Type::parse(&method.ty)))) .collect(); + // Only a *trait* impl is registered with the checker: there is + // nothing to conform to otherwise, and dispatch keys on the + // target type either way (the table above). + let Some(trait_name) = decl.trait_name.clone() else { + continue; + }; let impl_def = TraitImpl { - trait_name: decl.trait_name.clone(), + trait_name, target_type, methods, }; @@ -614,7 +681,12 @@ impl VmContext { Ok(()) } - fn install_runtime_builtin(&mut self, name: &str, function: NativeFunction, arity: u16) { + /// Install a native under a global name — the shape every stdlib native + /// arrives in (a global holding a `CallableValue::RuntimeNative`). + /// + /// `pub(crate)` so tests can reach the *shipped* path; see + /// `exec_tests::execute_source_with_natives`. + pub(crate) fn install_runtime_builtin(&mut self, name: &str, function: NativeFunction, arity: u16) { if self.runtime_globals.contains_key(name) { return; } @@ -627,30 +699,42 @@ impl VmContext { /// /// A user-declared type (`Type::Named`) belongs to the module that declared it; /// anything else is a builtin, shared by every module (see -/// [`crate::vm::TypeScope::builtin`]). An unparseable target is treated as +/// [`crate::val::TypeScope::builtin`]). An unparseable target is treated as /// declared — the conservative side, since filing it under the builtin scope /// would let it collide with every other module's. -fn impl_target_scope(target_type: &str, declaring: &crate::vm::TypeScope) -> crate::vm::TypeScope { +fn impl_target_scope(target_type: &str, declaring: &crate::val::TypeScope) -> crate::val::TypeScope { match Type::parse(target_type) { // A user *generic* (`Wrapper` → `Type::Generic`) is as module-local // as a plain `Named`: two modules may each declare their own `Wrapper`. // Lumping it in with the builtins made them share one coherence key and // conflict with each other. + // Two builtin types have no `Type` variant of their own: `Bytes` parses + // as `Named` and `Slice` as a `Generic`, so both were filed with the + // declared types. That put `impl Bytes { … }` in the *module's* scope + // while a bytes value dispatches in the builtin one — + // `receiver_type_scope` has no declared type to read off a non-`Object` + // — so the impl was registered where nothing would look for it and + // `"ab".bytes().mine()` said "Bytes has no method 'mine'". They are the + // only two; every other builtin has a variant and takes the last arm. + Some(Type::Named(name)) if name == "Bytes" => crate::val::TypeScope::builtin(), + Some(Type::Generic { ref name, .. }) if matches!(name.as_str(), "Slice" | "Stream") => { + crate::val::TypeScope::builtin() + } Some(Type::Named(_)) | Some(Type::Generic { .. }) | None => declaring.clone(), - Some(_) => crate::vm::TypeScope::builtin(), + Some(_) => crate::val::TypeScope::builtin(), } } /// The scope to dispatch `receiver`'s methods in: its own, if it is a declared /// type; the builtin scope otherwise. Only a heap `Object` carries a declared /// type — every other receiver is an `Int`, a `List`, a string, and so on. -pub fn receiver_type_scope(receiver: &RuntimeVal, heap: &HeapStore) -> crate::vm::TypeScope { +pub fn receiver_type_scope(receiver: &RuntimeVal, heap: &HeapStore) -> crate::val::TypeScope { if let RuntimeVal::Obj(handle) = receiver && let Some(HeapValue::Object(object)) = heap.get(*handle) { return object.type_scope().clone(); } - crate::vm::TypeScope::builtin() + crate::val::TypeScope::builtin() } fn core_make_struct_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> anyhow::Result { @@ -671,7 +755,7 @@ fn core_make_struct_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_ .unwrap_or_default(); let fields = match args.get(1).expect("arity checked") { - RuntimeVal::Nil => fast_hash_map_new(), + RuntimeVal::Nil => crate::util::value_map::value_map_new(), RuntimeVal::Obj(handle) => { let value = runtime .heap() @@ -687,13 +771,59 @@ fn core_make_struct_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_ } other => { return Err(anyhow!( - "__lk_make_struct expects fields as map, got {:?}", - other.kind() + "__lk_make_struct expects fields as map, got {}", + other.type_name_in(runtime.heap()) )); } }; - let ty = Arc::new(crate::vm::DeclaredType::new(type_scope, type_name)); + // The declaration's field order travels with the type, exactly as it does + // for an ordinary `NewObject` (see `exec::container::declared_type`). + // Without it `display` had nothing to order by and fell back to the hash + // map's own iteration, so `P { ..base, x: 9 }` printed its fields in a + // different order from the `P { … }` two lines above it — the same type, + // two renderings, decided by which syntax built the value. + let declared: Arc<[crate::val::DeclaredField]> = runtime + .module() + .and_then(|module| { + module + .type_info + .structs + .iter() + .find(|decl| decl.name.as_str() == &*type_name) + }) + .map(|decl| { + decl.fields + .iter() + .map(|field| { + crate::val::DeclaredField::new( + Arc::::from(field.name.as_str()), + field.ty.as_deref().and_then(crate::val::Type::parse), + ) + }) + .collect() + }) + .unwrap_or_else(|| Arc::from([] as [crate::val::DeclaredField; 0])); + let ty = Arc::new(crate::val::DeclaredType::with_fields( + type_scope, + Arc::::from(&*type_name), + declared, + )); + // The spread spelling builds from a *map*, whose values the checker never + // measured against the declaration — `A { ..m }` with `m["v"]` a String is + // the same hole `A { v: x }` was. + for (key, value) in &fields { + if let Some(declared) = ty.field_type(key) + && !crate::val::value_satisfies_declared(value, declared, runtime.heap()) + { + return Err(anyhow!( + "field `{key}` of {} is declared {}, and a {} cannot be stored in it", + ty.name, + declared.display(), + value.type_name_in(runtime.heap()) + )); + } + } Ok(RuntimeVal::Obj( runtime .heap_mut() @@ -705,19 +835,22 @@ fn core_typeof_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> let value = args .get(0) .ok_or_else(|| anyhow!("typeof(value) expects exactly one argument"))?; - let name = match value { - RuntimeVal::Int(_) => "Int", - RuntimeVal::Float(_) => "Float", - RuntimeVal::Bool(_) => "Bool", - RuntimeVal::ShortStr(_) => "String", - RuntimeVal::Nil => "Nil", + // Owned before `heap_mut()`: a struct instance's name borrows the heap + // (it is the declared name, not a `&'static str`). + let name: alloc::string::String = match value { + RuntimeVal::Int(_) => "Int".into(), + RuntimeVal::Float(_) => "Float".into(), + RuntimeVal::Bool(_) => "Bool".into(), + RuntimeVal::ShortStr(_) => "String".into(), + RuntimeVal::Nil => "Nil".into(), RuntimeVal::Obj(handle) => runtime .heap() .get(*handle) .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - .type_name(), + .type_name() + .into(), }; - Ok(runtime_string_value(name, runtime.heap_mut())) + Ok(runtime_string_value(&name, runtime.heap_mut())) } fn core_set_field_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> anyhow::Result { @@ -735,7 +868,10 @@ fn core_set_field_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { HeapValue::Map(map) => HeapValue::Map(set_string_field_on_map(map, key, field_value)), - HeapValue::Object(object) => HeapValue::Object(set_string_field_on_object(object, key, field_value)), + HeapValue::Object(object) => { + check_declared_field(object, &key, &field_value, runtime.heap())?; + HeapValue::Object(set_string_field_on_object(object, key, field_value)) + } other => Err(anyhow!( "__lk_set_field target must be Map or Object, got {}", other.type_name() @@ -744,8 +880,8 @@ fn core_set_field_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(updated))) } other => Err(anyhow!( - "__lk_set_field target must be Map or Object, got {:?}", - other.kind() + "__lk_set_field target must be Map or Object, got {}", + other.type_name_in(runtime.heap()) )), } } @@ -775,8 +911,8 @@ fn core_merge_fields_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<' RuntimeVal::Nil => None, other => { return Err(anyhow!( - "__lk_merge_fields base must be Object, Map, or Nil, got {:?}", - other.kind() + "__lk_merge_fields base must be Object, Map, or Nil, got {}", + other.type_name_in(runtime.heap()) )); } }; @@ -799,38 +935,69 @@ fn core_merge_fields_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime<' }; Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::Map(fields)))) } - other => Err(anyhow!("__lk_merge_fields overlay must be Map, got {:?}", other.kind())), + other => Err(anyhow!( + "__lk_merge_fields overlay must be Map, got {}", + other.kind().scalar_type_name() + )), } } -fn set_string_field_on_object(object: &RuntimeObject, key: Arc, value: RuntimeVal) -> RuntimeObject { - let mut fields = fast_hash_map_new(); - for (field_key, field_value) in &object.fields { - if field_key.as_ref() != key.as_ref() { - fields.insert(Arc::clone(field_key), *field_value); - } - } - fields.insert(Arc::clone(&key), value); - - let mut field_slots = object.field_slots.clone(); - if !field_slots.iter().any(|field_key| field_key.as_ref() == key.as_ref()) { - field_slots.push(key); - } +/// A store into a declared field, checked against the type it was declared +/// with. +/// +/// A `struct P { count: Int }` whose `count` can hold a String makes the +/// declaration decorative. The type checker catches the store it can see; this +/// is the one it cannot — a write through an untyped binding: +/// +/// ```lk +/// fn poison(p) { p["v"] = "s"; } +/// ``` +/// +/// Scalars only (`val::value_satisfies_declared` says why), so the cost is a +/// discriminant test on a path that was already cloning a map. +fn check_declared_field( + object: &RuntimeObject, + key: &str, + value: &RuntimeVal, + heap: &crate::val::HeapStore, +) -> anyhow::Result<()> { + let Some(declared) = object.ty.field_type(key) else { + return Ok(()); + }; + if crate::val::value_satisfies_declared(value, declared, heap) { + return Ok(()); + } + Err(anyhow!( + "field `{key}` of {} is declared {}, and a {} cannot be stored in it", + object.ty.name, + declared.display(), + value.type_name_in(heap) + )) +} +fn set_string_field_on_object(object: &RuntimeObject, key: Arc, value: RuntimeVal) -> RuntimeObject { + // Clone and overwrite: an existing field keeps its position and a new one + // lands at the end, which is `IndexMap::insert`'s behaviour and the only + // sensible reading of "the same object with one value replaced". + // + // This used to rebuild the map *skipping* the key and then append it, which + // moved an existing field to the end — invisible only because a parallel + // slot table, which did keep the position, was what `display` read. One + // ordered map cannot disagree with itself. + let mut fields = object.fields.clone(); + fields.insert(key, value); RuntimeObject { - // Setting a field produces the same object with one value replaced — - // same type, so the identity is shared, not rebuilt. + // Same type, so the identity is shared, not rebuilt. ty: Arc::clone(&object.ty), fields, - field_slots, } } fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap { match (map, value) { (TypedMap::Mixed(entries), value) => { - let runtime_key = RuntimeMapKey::String(key); - let mut out = fast_hash_map_new(); + let runtime_key = RuntimeMapKey::from_shared(key); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if *entry_key != runtime_key { out.insert(entry_key.clone(), *entry_value); @@ -840,7 +1007,7 @@ fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap::Mixed(out) } (TypedMap::StringMixed(entries), value) => { - let mut out = fast_hash_map_new(); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if entry_key.as_ref() != key.as_ref() { out.insert(Arc::clone(entry_key), *entry_value); @@ -850,7 +1017,7 @@ fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap::StringMixed(out) } (TypedMap::StringInt(entries), RuntimeVal::Int(value)) => { - let mut out = fast_hash_map_new(); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if entry_key.as_ref() != key.as_ref() { out.insert(Arc::clone(entry_key), *entry_value); @@ -860,7 +1027,7 @@ fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap::StringInt(out) } (TypedMap::StringFloat(entries), RuntimeVal::Float(value)) => { - let mut out = fast_hash_map_new(); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if entry_key.as_ref() != key.as_ref() { out.insert(Arc::clone(entry_key), *entry_value); @@ -870,7 +1037,7 @@ fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap::StringFloat(out) } (TypedMap::StringBool(entries), RuntimeVal::Bool(value)) => { - let mut out = fast_hash_map_new(); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if entry_key.as_ref() != key.as_ref() { out.insert(Arc::clone(entry_key), *entry_value); @@ -880,7 +1047,7 @@ fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap::StringBool(out) } (TypedMap::StringInt(entries), value) => { - let mut out = fast_hash_map_new(); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if entry_key.as_ref() != key.as_ref() { out.insert(Arc::clone(entry_key), RuntimeVal::Int(*entry_value)); @@ -890,7 +1057,7 @@ fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap::StringMixed(out) } (TypedMap::StringFloat(entries), value) => { - let mut out = fast_hash_map_new(); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if entry_key.as_ref() != key.as_ref() { out.insert(Arc::clone(entry_key), RuntimeVal::Float(*entry_value)); @@ -900,7 +1067,7 @@ fn set_string_field_on_map(map: &TypedMap, key: Arc, value: RuntimeVal) -> TypedMap::StringMixed(out) } (TypedMap::StringBool(entries), value) => { - let mut out = fast_hash_map_new(); + let mut out = crate::util::value_map::value_map_new(); for (entry_key, entry_value) in entries { if entry_key.as_ref() != key.as_ref() { out.insert(Arc::clone(entry_key), RuntimeVal::Bool(*entry_value)); @@ -920,7 +1087,7 @@ enum FieldMergeBase<'a> { fn merge_field_maps(base: FieldMergeBase<'_>, overlay: &TypedMap) -> TypedMap { match base { FieldMergeBase::Object(object) => { - let mut entries = fast_hash_map_new(); + let mut entries = crate::util::value_map::value_map_new(); for (key, value) in &object.fields { if !typed_map_contains_str(overlay, key.as_ref()) { entries.insert(Arc::clone(key), *value); @@ -938,77 +1105,30 @@ fn merge_field_maps(base: FieldMergeBase<'_>, overlay: &TypedMap) -> TypedMap { } } +/// A copy of the map, keys and all. +/// +/// A plain `clone`, which it could not be while iteration order was the hash +/// layout's: reproducing an order meant *replaying the insertion sequence*, so +/// every copy re-hashed every key into a fresh table. Insertion order makes the +/// copy structural — the entry vector and its index table are memcpy'd — which +/// is both the simpler code and the faster one. fn copy_typed_map(map: &TypedMap) -> TypedMap { - match map { - TypedMap::Mixed(entries) => { - let mut out = fast_hash_map_new(); - for (key, value) in entries { - out.insert(key.clone(), *value); - } - TypedMap::Mixed(out) - } - TypedMap::StringMixed(entries) => { - let mut out = fast_hash_map_new(); - for (key, value) in entries { - out.insert(Arc::clone(key), *value); - } - TypedMap::StringMixed(out) - } - TypedMap::StringInt(entries) => TypedMap::StringInt(copy_string_map_entries(entries)), - TypedMap::StringFloat(entries) => TypedMap::StringFloat(copy_string_map_entries(entries)), - TypedMap::StringBool(entries) => TypedMap::StringBool(copy_string_map_entries(entries)), - } + map.clone() } +/// The same copy, minus whatever the overlay is about to shadow. +/// +/// `retain` rather than a rebuild: it keeps the survivors in order and touches +/// the index table once, where inserting one key at a time hashed each survivor +/// a second time. fn copy_typed_map_without_overlay_keys(map: &TypedMap, overlay: &TypedMap) -> TypedMap { - match map { - TypedMap::Mixed(entries) => { - let mut out = fast_hash_map_new(); - for (key, value) in entries { - if !typed_map_contains(overlay, key) { - out.insert(key.clone(), *value); - } - } - TypedMap::Mixed(out) - } - TypedMap::StringMixed(entries) => { - let mut out = fast_hash_map_new(); - for (key, value) in entries { - if !typed_map_contains_str(overlay, key.as_ref()) { - out.insert(Arc::clone(key), *value); - } - } - TypedMap::StringMixed(out) - } - TypedMap::StringInt(entries) => { - TypedMap::StringInt(copy_string_map_entries_without_overlay_keys(entries, overlay)) - } - TypedMap::StringFloat(entries) => { - TypedMap::StringFloat(copy_string_map_entries_without_overlay_keys(entries, overlay)) - } - TypedMap::StringBool(entries) => { - TypedMap::StringBool(copy_string_map_entries_without_overlay_keys(entries, overlay)) - } - } -} - -fn copy_string_map_entries(entries: &FastHashMap, T>) -> FastHashMap, T> { - let mut out = fast_hash_map_new(); - for (key, value) in entries { - out.insert(Arc::clone(key), *value); - } - out -} - -fn copy_string_map_entries_without_overlay_keys( - entries: &FastHashMap, T>, - overlay: &TypedMap, -) -> FastHashMap, T> { - let mut out = fast_hash_map_new(); - for (key, value) in entries { - if !typed_map_contains_str(overlay, key.as_ref()) { - out.insert(Arc::clone(key), *value); - } + let mut out = map.clone(); + match &mut out { + TypedMap::Mixed(entries) => entries.retain(|key, _| !typed_map_contains(overlay, key)), + TypedMap::StringMixed(entries) => entries.retain(|key, _| !typed_map_contains_str(overlay, key.as_ref())), + TypedMap::StringInt(entries) => entries.retain(|key, _| !typed_map_contains_str(overlay, key.as_ref())), + TypedMap::StringFloat(entries) => entries.retain(|key, _| !typed_map_contains_str(overlay, key.as_ref())), + TypedMap::StringBool(entries) => entries.retain(|key, _| !typed_map_contains_str(overlay, key.as_ref())), } out } @@ -1027,7 +1147,7 @@ fn typed_map_contains_str(map: &TypedMap, key: &str) -> bool { match map { TypedMap::Mixed(entries) => { ShortStr::new(key).is_some_and(|key| entries.contains_key(&RuntimeMapKey::ShortStr(key))) - || entries.contains_key(&RuntimeMapKey::String(Arc::::from(key))) + || entries.contains_key(&RuntimeMapKey::from_text(key)) } TypedMap::StringMixed(entries) => entries.contains_key(key), TypedMap::StringInt(entries) => entries.contains_key(key), @@ -1046,7 +1166,10 @@ fn runtime_string_arg(value: &RuntimeVal, heap: &HeapStore, func: &str) -> anyho HeapValue::String(value) => Ok(value.clone()), other => Err(anyhow!("{func} expects string argument, got {}", other.type_name())), }, - other => Err(anyhow!("{func} expects string argument, got {:?}", other.kind())), + other => Err(anyhow!( + "{func} expects string argument, got {}", + other.type_name_in(heap) + )), } } @@ -1058,8 +1181,8 @@ fn runtime_string_value(value: &str, heap: &mut HeapStore) -> RuntimeVal { } } -fn runtime_object_fields_from_map(map: &TypedMap) -> anyhow::Result, RuntimeVal>> { - let mut fields = fast_hash_map_new(); +fn runtime_object_fields_from_map(map: &TypedMap) -> anyhow::Result, RuntimeVal>> { + let mut fields = crate::util::value_map::value_map_new(); match map { TypedMap::Mixed(entries) => { for (key, value) in entries { @@ -1106,22 +1229,22 @@ fn extend_typed_map(out: &mut TypedMap, map: &TypedMap) { } TypedMap::StringMixed(entries) => { for (key, value) in entries { - out.set(RuntimeMapKey::String(key.clone()), *value); + out.set(RuntimeMapKey::from_shared(key.clone()), *value); } } TypedMap::StringInt(entries) => { for (key, value) in entries { - out.set(RuntimeMapKey::String(key.clone()), RuntimeVal::Int(*value)); + out.set(RuntimeMapKey::from_shared(key.clone()), RuntimeVal::Int(*value)); } } TypedMap::StringFloat(entries) => { for (key, value) in entries { - out.set(RuntimeMapKey::String(key.clone()), RuntimeVal::Float(*value)); + out.set(RuntimeMapKey::from_shared(key.clone()), RuntimeVal::Float(*value)); } } TypedMap::StringBool(entries) => { for (key, value) in entries { - out.set(RuntimeMapKey::String(key.clone()), RuntimeVal::Bool(*value)); + out.set(RuntimeMapKey::from_shared(key.clone()), RuntimeVal::Bool(*value)); } } } @@ -1130,7 +1253,10 @@ fn extend_typed_map(out: &mut TypedMap, map: &TypedMap) { fn bit_arg(value: &crate::val::RuntimeVal, func: &str) -> anyhow::Result { match value { crate::val::RuntimeVal::Int(i) => Ok(*i), - other => Err(anyhow!("{func} expects Int arguments, got {:?}", other.kind())), + other => Err(anyhow!( + "{func} expects Int arguments, got {}", + other.kind().scalar_type_name() + )), } } @@ -1160,6 +1286,19 @@ fn core_bit_or_builtin( )) } +fn core_bit_xor_builtin( + args: NativeArgs<'_>, + _runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result { + if args.len() != 2 { + return Err(anyhow!("__lk_bit_xor(left, right) expects exactly 2 arguments")); + } + Ok(crate::val::RuntimeVal::Int( + bit_arg(args.get(0).expect("arity checked"), "__lk_bit_xor")? + ^ bit_arg(args.get(1).expect("arity checked"), "__lk_bit_xor")?, + )) +} + /// The shift amount both shifts accept. /// /// Out of range is an error rather than a wrap or a zero. The hardware masks it @@ -1167,10 +1306,15 @@ fn core_bit_or_builtin( /// same thing on every target, and a shift by a variable that turned out to be /// 64 is a bug wherever it happens. The native path raises from /// `lkrt_i64_sh*_checked`, so both back ends fail identically. +/// +/// The message does not name `__lk_shl`: that is the internal builtin the +/// parser desugars `<<` to, and a program that wrote `<<` has never heard of +/// it. `func` is still what the argument-type errors use, where naming the +/// operand position matters more. fn shift_amount(value: &crate::val::RuntimeVal, func: &str) -> anyhow::Result { let amount = bit_arg(value, func)?; if !(0..64).contains(&amount) { - return Err(anyhow!("{func} shift amount {amount} is out of range 0..63")); + return Err(anyhow!("shift amount {amount} is out of range 0..63")); } Ok(amount as u32) } @@ -1195,6 +1339,91 @@ fn core_shr_builtin(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> a Ok(crate::val::RuntimeVal::Int(lhs.wrapping_shr(rhs))) } +fn core_shr_unsigned_builtin( + args: NativeArgs<'_>, + _runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result { + if args.len() != 2 { + return Err(anyhow!("__lk_shr_u(left, right) expects exactly 2 arguments")); + } + let lhs = bit_arg(args.get(0).expect("arity checked"), "__lk_shr_u")?; + let rhs = shift_amount(args.get(1).expect("arity checked"), "__lk_shr_u")?; + Ok(crate::val::RuntimeVal::Int(((lhs as u64).wrapping_shr(rhs)) as i64)) +} + +fn core_lt_unsigned_builtin( + args: NativeArgs<'_>, + _runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result { + if args.len() != 2 { + return Err(anyhow!("__lk_lt_u(left, right) expects exactly 2 arguments")); + } + let lhs = bit_arg(args.get(0).expect("arity checked"), "__lk_lt_u")? as u64; + let rhs = bit_arg(args.get(1).expect("arity checked"), "__lk_lt_u")? as u64; + Ok(crate::val::RuntimeVal::Bool(lhs < rhs)) +} + +fn core_div_unsigned_builtin( + args: NativeArgs<'_>, + _runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result { + if args.len() != 2 { + return Err(anyhow!("__lk_div_u(left, right) expects exactly 2 arguments")); + } + let lhs = bit_arg(args.get(0).expect("arity checked"), "__lk_div_u")? as u64; + let rhs = bit_arg(args.get(1).expect("arity checked"), "__lk_div_u")? as u64; + if rhs == 0 { + return Err(anyhow!("division by zero")); + } + Ok(crate::val::RuntimeVal::Int((lhs / rhs) as i64)) +} + +fn core_mod_unsigned_builtin( + args: NativeArgs<'_>, + _runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result { + if args.len() != 2 { + return Err(anyhow!("__lk_mod_u(left, right) expects exactly 2 arguments")); + } + let lhs = bit_arg(args.get(0).expect("arity checked"), "__lk_mod_u")? as u64; + let rhs = bit_arg(args.get(1).expect("arity checked"), "__lk_mod_u")? as u64; + if rhs == 0 { + return Err(anyhow!("modulo by zero")); + } + Ok(crate::val::RuntimeVal::Int((lhs % rhs) as i64)) +} + +fn core_u64_to_float_builtin( + args: NativeArgs<'_>, + _runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result { + if args.len() != 1 { + return Err(anyhow!("__lk_u64_to_float(value) expects exactly 1 argument")); + } + let value = bit_arg(args.get(0).expect("arity checked"), "__lk_u64_to_float")? as u64; + Ok(crate::val::RuntimeVal::Float(value as f64)) +} + +/// The unsigned decimal rendering of the carrier. +/// +/// Every other member of this family fixes an *operation*; this one fixes the +/// *display*, which is the last place a `u64` above `i64::MAX` still told a +/// visible lie. The value was always right — `top + 5` computes the right bits — +/// but `println` handed those bits to an `i64` formatter and got a negative +/// number, so a page-table entry or a physical address printed as nonsense. +fn core_u64_to_str_builtin( + args: NativeArgs<'_>, + runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result { + use alloc::string::ToString; + if args.len() != 1 { + return Err(anyhow!("__lk_u64_str(value) expects exactly 1 argument")); + } + let value = bit_arg(args.get(0).expect("arity checked"), "__lk_u64_str")? as u64; + // 20 digits at most, so never a `ShortStr` — `runtime_string_value` picks. + Ok(runtime_string_value(&value.to_string(), runtime.heap_mut())) +} + fn core_bit_not_builtin( args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>, @@ -1233,7 +1462,16 @@ hardware_builtins! { core_cpu_irq_save_builtin => super::hardware::cpu_irq_save; core_cpu_irq_restore_builtin => super::hardware::cpu_irq_restore; core_cpu_wait_for_interrupt_builtin => super::hardware::cpu_wait_for_interrupt; + core_cpu_raise_interrupt_builtin => super::hardware::cpu_raise_interrupt; core_cpu_timestamp_builtin => super::hardware::cpu_timestamp; + core_cpu_load_idt_builtin => super::hardware::cpu_load_idt; + core_cpu_load_gdt_builtin => super::hardware::cpu_load_gdt; + core_cpu_reload_segments_builtin => super::hardware::cpu_reload_segments; + core_cpu_load_task_register_builtin => super::hardware::cpu_load_task_register; + core_cpu_read_cr2_builtin => super::hardware::cpu_read_cr2; + core_cpu_read_cr3_builtin => super::hardware::cpu_read_cr3; + core_cpu_write_cr3_builtin => super::hardware::cpu_write_cr3; + core_cpu_invalidate_page_builtin => super::hardware::cpu_invalidate_page; core_symbol_address_builtin => super::hardware::symbol_address; core_call_address_2_builtin => super::hardware::call_address_2; core_volatile_read_u8 => super::hardware::volatile_read_u8; @@ -1255,15 +1493,15 @@ hardware_builtins! { #[cfg(test)] mod tests { use super::*; - use crate::util::fast_map::fast_hash_map_from_iter; + use crate::val::TypedList; use crate::vm::{Module, RuntimeModuleState}; fn module_with_impl(type_name: &str, method: &str, function: u32) -> Arc { - scoped_module_with_impl(crate::vm::TypeScope::anonymous(), type_name, method, function) + scoped_module_with_impl(crate::val::TypeScope::anonymous(), type_name, method, function) } fn scoped_module_with_impl( - type_scope: crate::vm::TypeScope, + type_scope: crate::val::TypeScope, type_name: &str, method: &str, function: u32, @@ -1276,7 +1514,7 @@ mod tests { methods: vec![(method.to_string(), "Function".to_string())], }], impls: vec![crate::vm::ImplDecl { - trait_name: "Area".to_string(), + trait_name: Some("Area".to_string()), type_name: type_name.to_string(), methods: vec![crate::vm::ImplMethod { name: method.to_string(), @@ -1286,6 +1524,7 @@ mod tests { reads_globals: Vec::new(), }], }], + structs: Vec::new(), }, ..Module::default() }) @@ -1297,7 +1536,7 @@ mod tests { // entry has to name the module it indexes into. Re-registering the // *same* scope replaces the entry (the REPL and the hybrid bridge both // do it); it must still point at the module it came from. - let scope = crate::vm::TypeScope::from_path("a.lk"); + let scope = crate::val::TypeScope::from_path("a.lk"); let mut ctx = VmContext::new_without_core_vm_builtins(); let first = scoped_module_with_impl(scope.clone(), "Sq", "area", 3); ctx.register_module_types(&first).expect("register first module"); @@ -1321,9 +1560,9 @@ mod tests { fn same_type_name_in_two_modules_keeps_two_entries() { // `struct Point` in `a.lk` and in `b.lk` are different types. Keyed by // the bare name they shared one slot and the later registration won for - // both, so `a`'s value ran `b`'s method body (see `vm::TypeScope`). - let a = crate::vm::TypeScope::from_path("a.lk"); - let b = crate::vm::TypeScope::from_path("b.lk"); + // both, so `a`'s value ran `b`'s method body (see `val::TypeScope`). + let a = crate::val::TypeScope::from_path("a.lk"); + let b = crate::val::TypeScope::from_path("b.lk"); let mut ctx = VmContext::new_without_core_vm_builtins(); let from_a = scoped_module_with_impl(a.clone(), "Point", "tag", 3); let from_b = scoped_module_with_impl(b.clone(), "Point", "tag", 9); @@ -1347,7 +1586,7 @@ mod tests { // `impl Doubler for Int` has no declaring module to be scoped to — the // receiver is a bare `5` — so it is filed under the shared builtin // scope and found from anywhere. - let declaring = crate::vm::TypeScope::from_path("a.lk"); + let declaring = crate::val::TypeScope::from_path("a.lk"); let mut ctx = VmContext::new_without_core_vm_builtins(); ctx.register_module_types(&scoped_module_with_impl(declaring.clone(), "Int", "dbl", 2)) .expect("register"); @@ -1356,7 +1595,7 @@ mod tests { "a builtin target does not belong to the declaring module's scope" ); assert!(matches!( - ctx.trait_method(&crate::vm::TypeScope::builtin(), "Int", "dbl"), + ctx.trait_method(&crate::val::TypeScope::builtin(), "Int", "dbl"), Some(MethodImpl::Local { function: 2, .. }) )); } @@ -1391,7 +1630,7 @@ mod tests { ctx.register_module_types(&module_with_impl("Sq", "area", 1)) .expect("register without a checker"); assert!(matches!( - ctx.trait_method(&crate::vm::TypeScope::anonymous(), "Sq", "area"), + ctx.trait_method(&crate::val::TypeScope::anonymous(), "Sq", "area"), Some(MethodImpl::Local { function: 1, .. }) )); } @@ -1461,6 +1700,7 @@ mod tests { "__lk_merge_fields", "__lk_bit_and", "__lk_bit_or", + "__lk_bit_xor", "__lk_bit_not", ] { let value = ctx @@ -1481,14 +1721,9 @@ mod tests { #[test] fn core_make_struct_reads_typed_map_backing_directly() { let mut state = RuntimeModuleState::default(); - let fields = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([( - Arc::::from("answer"), - 42, - )])))), - ); + let fields = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([(Arc::::from("answer"), 42)]), + )))); let name = RuntimeVal::ShortStr(crate::val::ShortStr::new("Point").expect("short")); let args = [name, fields]; let mut runtime = NativeRuntime::new(&mut state, None, None); @@ -1508,22 +1743,12 @@ mod tests { #[test] fn core_merge_fields_reads_typed_map_backing_directly() { let mut state = RuntimeModuleState::default(); - let base = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([( - Arc::::from("a"), - 1, - )])))), - ); - let overlay = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([( - Arc::::from("b"), - 2, - )])))), - ); + let base = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([(Arc::::from("a"), 1)]), + )))); + let overlay = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([(Arc::::from("b"), 2)]), + )))); let args = [base, overlay]; let mut runtime = NativeRuntime::new(&mut state, None, None); @@ -1544,14 +1769,12 @@ mod tests { #[test] fn core_set_field_preserves_typed_string_int_map_without_copying_overwritten_entry() { let mut state = RuntimeModuleState::default(); - let base = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([ - (Arc::::from("answer"), 1), - (Arc::::from("keep"), 2), - ])))), - ); + let base = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([ + (Arc::::from("answer"), 1), + (Arc::::from("keep"), 2), + ]), + )))); let key = RuntimeVal::ShortStr(crate::val::ShortStr::new("answer").expect("short")); let args = [base, key, RuntimeVal::Int(42)]; let mut runtime = NativeRuntime::new(&mut state, None, None); @@ -1575,14 +1798,12 @@ mod tests { #[test] fn core_set_field_pollutes_typed_map_without_copying_overwritten_entry() { let mut state = RuntimeModuleState::default(); - let base = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([ - (Arc::::from("answer"), 1), - (Arc::::from("keep"), 2), - ])))), - ); + let base = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([ + (Arc::::from("answer"), 1), + (Arc::::from("keep"), 2), + ]), + )))); let key = RuntimeVal::ShortStr(crate::val::ShortStr::new("answer").expect("short")); let args = [base, key, RuntimeVal::Bool(true)]; let mut runtime = NativeRuntime::new(&mut state, None, None); @@ -1606,22 +1827,15 @@ mod tests { #[test] fn core_merge_fields_filters_base_keys_overwritten_by_overlay() { let mut state = RuntimeModuleState::default(); - let base = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([ - (Arc::::from("answer"), 1), - (Arc::::from("keep"), 2), - ])))), - ); - let overlay = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([( - Arc::::from("answer"), - 42, - )])))), - ); + let base = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([ + (Arc::::from("answer"), 1), + (Arc::::from("keep"), 2), + ]), + )))); + let overlay = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([(Arc::::from("answer"), 42)]), + )))); let args = [base, overlay]; let mut runtime = NativeRuntime::new(&mut state, None, None); @@ -1644,14 +1858,9 @@ mod tests { #[test] fn core_merge_fields_nil_base_preserves_overlay_typed_backing() { let mut state = RuntimeModuleState::default(); - let overlay = RuntimeVal::Obj( - state - .heap - .alloc(HeapValue::Map(TypedMap::StringBool(fast_hash_map_from_iter([( - Arc::::from("ok"), - true, - )])))), - ); + let overlay = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(TypedMap::StringBool( + crate::util::value_map::value_map_from_iter([(Arc::::from("ok"), true)]), + )))); let args = [RuntimeVal::Nil, overlay]; let mut runtime = NativeRuntime::new(&mut state, None, None); @@ -1666,4 +1875,62 @@ mod tests { assert!(matches!(map, TypedMap::StringBool(_))); assert_eq!(map.get_str("ok"), Some(RuntimeVal::Bool(true))); } + + /// A list's representation follows its *contents*, not the route that + /// built it. + /// + /// Every projection used to allocate `TypedList::Mixed` unconditionally, so + /// `[1, 2, 3].map(f)` came back as 16-bytes-per-element boxes even when + /// every element was an `Int`, and so did `push`, `keys`, `values`, + /// `flatten`, `chunk`, `zip`. Representation is not observable from LK, so + /// nothing failed — it just cost double the memory and gave up the typed + /// fast paths. (In-place mutation may still *degrade* a list; re-narrowing + /// on every write would be O(n) per write. This is about construction.) + #[test] + fn a_new_list_narrows_to_the_shape_of_what_is_in_it() { + fn returned_list_variant(source: &str) -> String { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::stmt_parser::StmtParser::new(&tokens) + .parse_program() + .expect("parse"); + let result = crate::vm::test_support::run_program_default(&program).expect("run"); + let RuntimeVal::Obj(handle) = result.first_return() else { + panic!("{source} should return a list"); + }; + let Some(HeapValue::List(list)) = result.state.heap.get(*handle) else { + panic!("{source} should return a list"); + }; + match list { + TypedList::Mixed(_) => "Mixed", + TypedList::Int(_) => "Int", + TypedList::Float(_) => "Float", + TypedList::Bool(_) => "Bool", + TypedList::String(_) => "String", + } + .to_string() + } + + for (source, expected) in [ + ("return [1, 2, 3];", "Int"), + ("return [1, 2, 3].map(|x| x * 2);", "Int"), + ("return [1, 2, 3].filter(|x| x > 1);", "Int"), + ("return [1, 2].push(3);", "Int"), + ("return [1, 2, 3].flatten();", "Int"), + ("return [[1, 2], [3]].flatten();", "Int"), + ("return {\"a\": 1, \"b\": 2}.values();", "Int"), + ("return {\"a\": 1, \"b\": 2}.keys();", "String"), + ("return [1, 2, 3].map(|x| \"long-enough-to-heap\");", "String"), + // A genuinely mixed result stays mixed — the scan reports what is + // there, it does not force a shape. + ("return [1, \"two\", 3.0];", "Mixed"), + // Pairs are heap objects, so zip is mixed no matter what went in. + ("return [1, 2].zip([3, 4]);", "Mixed"), + ] { + assert_eq!( + returned_list_variant(source), + expected, + "{source} should be represented as {expected}" + ); + } + } } diff --git a/core/src/vm/context/core_methods.rs b/core/src/vm/context/core_methods.rs index b7686023..b9e17ac8 100644 --- a/core/src/vm/context/core_methods.rs +++ b/core/src/vm/context/core_methods.rs @@ -2,14 +2,20 @@ use crate::compat::prelude::*; use alloc::sync::Arc; -use anyhow::{anyhow, bail}; +use anyhow::{Result, anyhow, bail}; use arcstr::ArcStr; +mod bytes_dispatch; mod list_dispatch; +mod slice_dispatch; +use self::bytes_dispatch::*; use self::list_dispatch::*; +use self::slice_dispatch::*; use crate::{ - val::{HeapRef, HeapStore, HeapValue, RuntimeMapKey, RuntimeSet, RuntimeVal, ShortStr, Type, TypedList}, + val::{ + HeapRef, HeapStore, HeapValue, RuntimeMapKey, RuntimeSet, RuntimeVal, ShortStr, SliceValue, Type, TypedList, + }, vm::{ NativeArgs, NativeRuntime, call_runtime_value_runtime_list_args, call_runtime_value_runtime_named_map_list_args, }, @@ -49,8 +55,8 @@ fn method_name_detached(helper: &str, method: &RuntimeVal, heap: &HeapStore) -> None => Err(anyhow!("heap object {} out of bounds", handle.index())), }, other => Err(anyhow!( - "{helper} expects method name as string, got {:?}", - other.kind() + "{helper} expects method name as string, got {}", + other.type_name_in(heap) )), } } @@ -101,11 +107,14 @@ pub(super) fn core_call_method_named_builtin( /// dispatchers are mutually exclusive on receiver type, so dispatch probes /// exactly one instead of trying each in turn (every probe copies the /// positional args out of the heap list). +#[derive(Clone, Copy, PartialEq, Eq)] enum BuiltinReceiver { Map, Set, Str, List, + Slice, + Bytes, Other, } @@ -117,19 +126,57 @@ fn builtin_receiver_kind(receiver: &RuntimeVal, heap: &HeapStore) -> BuiltinRece Some(HeapValue::Set(_)) => BuiltinReceiver::Set, Some(HeapValue::String(_)) => BuiltinReceiver::Str, Some(HeapValue::List(_)) => BuiltinReceiver::List, + Some(HeapValue::Slice(_)) => BuiltinReceiver::Slice, + Some(HeapValue::Bytes(_)) => BuiltinReceiver::Bytes, _ => BuiltinReceiver::Other, }, _ => BuiltinReceiver::Other, } } +/// The declared arity for `method` on `kind`, checked before dispatch. +/// +/// Each dispatcher used to state its own arity in a `bail!` guard, which made +/// the declaration and the implementation two sources that drifted apart — +/// `bytes.slice`, `map.get` and `str.slice` each accepted a shape the checker +/// rejected, or the reverse, and only a hand-run comparison found them. The +/// declaration decides here; a guard that disagrees is now unreachable rather +/// than quietly authoritative. +fn check_declared_arity(kind: BuiltinReceiver, method: &str, count: usize) -> anyhow::Result<()> { + let declared = match kind { + BuiltinReceiver::Map => crate::typ::BuiltinReceiverKind::Map, + BuiltinReceiver::Set => crate::typ::BuiltinReceiverKind::Set, + BuiltinReceiver::Str => crate::typ::BuiltinReceiverKind::Str, + BuiltinReceiver::Slice => crate::typ::BuiltinReceiverKind::Slice, + BuiltinReceiver::Bytes => crate::typ::BuiltinReceiverKind::Bytes, + BuiltinReceiver::List => crate::typ::BuiltinReceiverKind::List, + BuiltinReceiver::Other => return Ok(()), + }; + // A name the table does not declare is left to the implementation: a map's + // entries are its fields, so `m.f(x)` need not be a method at all. + let Some((required, most)) = crate::typ::builtin_method_arity(declared, method) else { + return Ok(()); + }; + if count < required || count > most { + let expected = if required == most { + alloc::format!("{required}") + } else { + alloc::format!("{required} to {most}") + }; + bail!("{method}() expects {expected} arguments, got {count}"); + } + Ok(()) +} + fn dispatch_builtin_method( receiver: &RuntimeVal, method: &str, positional: MethodPositionalArgs, runtime: &mut NativeRuntime<'_>, ) -> anyhow::Result> { - match builtin_receiver_kind(receiver, runtime.heap()) { + let kind = builtin_receiver_kind(receiver, runtime.heap()); + check_declared_arity(kind, method, positional.len(runtime.heap())?)?; + match kind { BuiltinReceiver::Map => positional.with_slice(runtime.heap_mut(), |positional, heap| { dispatch_map_builtin_method(receiver, method, positional, heap) }), @@ -139,6 +186,12 @@ fn dispatch_builtin_method( BuiltinReceiver::Str => positional.with_slice(runtime.heap_mut(), |positional, heap| { dispatch_string_builtin_method(receiver, method, positional, heap) }), + BuiltinReceiver::Slice => positional.with_slice(runtime.heap_mut(), |positional, heap| { + dispatch_slice_builtin_method(receiver, method, positional, heap) + }), + BuiltinReceiver::Bytes => positional.with_slice(runtime.heap_mut(), |positional, heap| { + dispatch_bytes_builtin_method(receiver, method, positional, heap) + }), BuiltinReceiver::List => positional.with_slice(runtime.heap_mut(), |positional, heap| { dispatch_list_builtin_method(receiver, method, positional, heap) }), @@ -157,12 +210,36 @@ fn is_list_hof(method: &str) -> bool { /// consume the slice directly; only the rare tails (callable property, list /// HOF, trait method) materialize a heap list, which the generic /// `__lk_call_method` shape would have allocated anyway. -pub(crate) fn core_call_method_windowed( +/// +/// Public because the standard library calls it: `iter.map(xs, f)` is defined +/// as `xs.map(f)`, and defining it that way is what makes the two spellings +/// impossible to drift apart. Everything a module form would otherwise +/// reimplement — the truthiness rule, the host-root pinning around callbacks, +/// which list representation comes back — is decided once, here. +pub fn core_call_method_windowed( receiver: RuntimeVal, method_name: &str, args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>, ) -> anyhow::Result { + // The receiver's own method wins over a same-named key or field. + // + // This used to run *after* the key lookup, which made the documented rule + // ("方法优先", docs/semantics.md) true for exactly one method: `len`, and + // only because the compiler emits a dedicated opcode for it. + // `{"keys": 5, "z": 1}.keys()` answered `5`, `{"is_empty": 5}.is_empty()` + // answered `5` — the key had shadowed the method, and which of the two you + // got depended on whether the method happened to have its own opcode. + // + // Method-first is the rule because the other order makes a builtin method + // vanish from *some* maps with no diagnostic, while a shadowed key still + // has an unambiguous spelling (`m["len"]`). + if let Some(result) = dispatch_builtin_method_slice(&receiver, method_name, args, runtime)? { + return Ok(result); + } + // No builtin of that name: the key (or struct field) may hold the callable, + // or be a plain value read with `()` — `m.f(1)` where `f` is a stored + // function is the shape this exists for. if !is_list_hof(method_name) && let Some(prop) = runtime_access(&receiver, method_name, runtime.heap_mut())? { @@ -177,9 +254,6 @@ pub(crate) fn core_call_method_windowed( return Ok(prop); } } - if let Some(result) = dispatch_builtin_method_slice(&receiver, method_name, args, runtime)? { - return Ok(result); - } // Rare tails share the list-shaped generic path. let positional = match materialize_positional_list(args, runtime.heap_mut()) { Some(handle) => MethodPositionalArgs::List(handle), @@ -214,6 +288,8 @@ fn dispatch_builtin_method_slice( BuiltinReceiver::Set => dispatch_set_builtin_method(receiver, method, args, runtime.heap_mut()), BuiltinReceiver::Str => dispatch_string_builtin_method(receiver, method, args, runtime.heap_mut()), BuiltinReceiver::List => dispatch_list_builtin_method(receiver, method, args, runtime.heap_mut()), + BuiltinReceiver::Slice => dispatch_slice_builtin_method(receiver, method, args, runtime.heap_mut()), + BuiltinReceiver::Bytes => dispatch_bytes_builtin_method(receiver, method, args, runtime.heap_mut()), BuiltinReceiver::Other => Ok(None), } } @@ -227,18 +303,20 @@ fn call_method_positional_runtime( // Try dispatch for methods that need runtime state BEFORE heap closure let method_str = method.as_str(); if is_list_hof(method_str) { - // Check if receiver is a list - let is_list = match &receiver { - RuntimeVal::Obj(h) => matches!(runtime.heap().get(*h), Some(HeapValue::List(_))), - _ => false, - }; - if is_list { - let list = clone_list(&receiver, runtime.heap_mut())?; - let items: Vec = list_runtime_items(list, runtime.heap_mut()); + // Every sequence, not only a list: a window and a `Bytes` have elements + // too, and `map`/`filter`/`reduce` mean the same thing over them. What + // the callback loop below needs is the elements, and nothing about it + // cares where they came from. + let sequence_kind = sequence_receiver_kind(&receiver, runtime.heap()); + if let Some(sequence_kind) = sequence_kind { + let items: Vec = sequence_items(&receiver, sequence_kind, runtime)?; let pos_args: Vec = match &positional { MethodPositionalArgs::Empty => vec![], - MethodPositionalArgs::List(handle) => match runtime.heap().get(*handle) { - Some(HeapValue::List(list)) => list.collect_owned(), + MethodPositionalArgs::List(handle) => match runtime.heap().get(*handle).cloned() { + // Cloned, then materialized through the allocating path: an + // argument can be a string past the inline limit, and + // `collect_owned` cannot produce one. + Some(HeapValue::List(list)) => list_runtime_items(list, runtime.heap_mut()), _ => vec![], }, }; @@ -256,8 +334,15 @@ fn call_method_positional_runtime( _ => Ok(None), }; state.host_roots_truncate(mark); - if let Some(r) = result? { - return Ok(r); + if let Some(result) = result? { + // `filter` keeps a subset of the elements, so the result is + // still bytes; `map` may produce anything, so it is not. + // That is the whole rule for which operations preserve a + // sequence's type. + if matches!(sequence_kind, SequenceKind::Bytes) && method_str == "filter" { + return rebuild_bytes(&result, runtime); + } + return Ok(result); } } return call_trait_method_runtime(receiver, ArcStr::from(method.as_str()), positional, runtime); @@ -338,7 +423,11 @@ fn dispatch_map_builtin_method( if let Some(HeapValue::Map(map)) = heap.get_mut(handle) { map.set(key, value); } - Ok(Some(RuntimeVal::Nil)) + // The receiver, so writes chain the way `push`/`insert` do. A + // mutating method answers the container unless it has something + // better to say — `delete` hands back what it removed, `add` + // reports whether the value was new. + Ok(Some(*receiver)) } "get" => { if positional.is_empty() || positional.len() > 2 { @@ -363,15 +452,31 @@ fn dispatch_map_builtin_method( if positional.len() != 1 { bail!("map.has() expects 1 argument (key), got {}", positional.len()); } - let key = runtime_map_key_from_value(&positional[0], heap, "map.has() key")?; - let found = matches!(heap.get(handle), Some(HeapValue::Map(m)) if m.get(&key).is_some()); + // `m.has(k)` and `k in m` are one question, so they answer the + // same way: a value that cannot be a key is not a key the map + // holds. `in` says `false` and this said "map.has() key: Float + // cannot be a map key or set member" — two answers, decided by + // which spelling the program used. + // + // `delete` below keeps refusing, and the difference is the same one + // `map_contains` draws: asking is a predicate, removing names a key. + let found = match runtime_map_key_from_value(&positional[0], heap, "map.has() key") { + Ok(key) => matches!(heap.get(handle), Some(HeapValue::Map(m)) if m.get(&key).is_some()), + Err(_) => false, + }; Ok(Some(RuntimeVal::Bool(found))) } "delete" => { if positional.len() != 1 { bail!("map.delete() expects 1 argument (key), got {}", positional.len()); } - let key = runtime_map_key_from_value(&positional[0], heap, "map.delete() key")?; + // Removing a key the map cannot hold removes nothing — and cannot + // corrupt the map's key type, which is why this joins the + // predicates rather than the key *builders* (`set`, indexing). + // `m - k` already answered this way. + let Ok(key) = runtime_map_key_from_value(&positional[0], heap, "map.delete() key") else { + return Ok(Some(RuntimeVal::Nil)); + }; let removed = match heap.get_mut(handle) { Some(HeapValue::Map(map)) => map.remove(&key).unwrap_or(RuntimeVal::Nil), _ => RuntimeVal::Nil, @@ -385,7 +490,7 @@ fn dispatch_map_builtin_method( if let Some(HeapValue::Map(map)) = heap.get_mut(handle) { map.clear(); } - Ok(Some(RuntimeVal::Nil)) + Ok(Some(*receiver)) } "len" => { if !positional.is_empty() { @@ -425,9 +530,8 @@ fn dispatch_map_builtin_method( } _ => return Ok(None), }; - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(keys))), - ))) + let keys = TypedList::from_runtime_values(&keys, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(keys))))) } "values" => { if !positional.is_empty() { @@ -447,9 +551,8 @@ fn dispatch_map_builtin_method( } _ => return Ok(None), }; - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(vals))), - ))) + let vals = TypedList::from_runtime_values(&vals, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(vals))))) } _ => Ok(None), } @@ -489,12 +592,17 @@ fn dispatch_set_builtin_method( }; Ok(Some(RuntimeVal::Bool(is_empty))) } - "has" | "contains" => { + "contains" => { if positional.len() != 1 { bail!("set.{method}() expects 1 argument (value), got {}", positional.len()); } - let key = runtime_map_key_from_value(&positional[0], heap, "set.has() value")?; - let found = matches!(heap.get(handle), Some(HeapValue::Set(values)) if values.contains(&key)); + // `s.contains(v)` and `v in s` are one question, and answer alike: + // a value that cannot be a member is not one. `add` below still + // refuses, because it builds the key rather than asking after it. + let found = match runtime_map_key_from_value(&positional[0], heap, "set.contains() value") { + Ok(key) => matches!(heap.get(handle), Some(HeapValue::Set(values)) if values.contains(&key)), + Err(_) => false, + }; Ok(Some(RuntimeVal::Bool(found))) } "add" => { @@ -508,11 +616,16 @@ fn dispatch_set_builtin_method( }; Ok(Some(RuntimeVal::Bool(inserted))) } - "delete" | "remove" => { + // `remove` was a second name for this and is gone; nothing used it. + "delete" => { if positional.len() != 1 { bail!("set.{method}() expects 1 argument (value), got {}", positional.len()); } - let key = runtime_map_key_from_value(&positional[0], heap, "set.delete() value")?; + // Removing a value the set cannot hold removes nothing, for + // `map.delete`'s reason. `add` still refuses. + let Ok(key) = runtime_map_key_from_value(&positional[0], heap, "set.delete() value") else { + return Ok(Some(RuntimeVal::Bool(false))); + }; let removed = match heap.get_mut(handle) { Some(HeapValue::Set(values)) => values.remove(&key), _ => false, @@ -526,7 +639,69 @@ fn dispatch_set_builtin_method( if let Some(HeapValue::Set(values)) = heap.get_mut(handle) { values.clear(); } - Ok(Some(RuntimeVal::Nil)) + Ok(Some(*receiver)) + } + // The set operations. A `Set` that can only add, delete, test a member + // and hand back a list is a deduplicating bag; these are what make it a + // set, and none of them existed. + // + // **The insertion sequence is the contract.** A set's iteration order + // is its hash order (see `DYN_SET` and the mirror discipline), so two + // sets with the same members can still iterate differently if they were + // filled in different sequences. Each operation below therefore fills + // the answer in one stated order — the receiver's own order first, then + // the argument's — and the native mirror replays exactly that. Building + // the same answer "some other way" is how the two ends come to print a + // set differently. + "union" | "intersection" | "difference" | "symmetric_difference" => { + if positional.len() != 1 { + bail!("set.{method}() expects 1 argument (other), got {}", positional.len()); + } + let mine = set_entries(handle, heap); + let theirs = set_entries_of_value(&positional[0], heap, method)?; + let other: crate::util::fast_map::FastHashSet = theirs.iter().cloned().collect(); + let mut out = crate::util::fast_map::fast_hash_set_new(); + match method { + "union" => { + out.extend(mine.iter().cloned()); + out.extend(theirs.iter().cloned()); + } + "intersection" => out.extend(mine.iter().filter(|key| other.contains(*key)).cloned()), + "difference" => out.extend(mine.iter().filter(|key| !other.contains(*key)).cloned()), + _ => { + let owned: crate::util::fast_map::FastHashSet = mine.iter().cloned().collect(); + out.extend(mine.iter().filter(|key| !other.contains(*key)).cloned()); + out.extend(theirs.iter().filter(|key| !owned.contains(*key)).cloned()); + } + } + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Set(RuntimeSet::from_entries(out))), + ))) + } + // The three predicates. `is_disjoint` is not `!intersection().is_empty()` + // spelled out — it stops at the first shared member and allocates + // nothing. + "is_subset" | "is_superset" | "is_disjoint" => { + if positional.len() != 1 { + bail!("set.{method}() expects 1 argument (other), got {}", positional.len()); + } + let mine = set_entries(handle, heap); + let theirs = set_entries_of_value(&positional[0], heap, method)?; + let answer = match method { + "is_subset" => { + let other: crate::util::fast_map::FastHashSet = theirs.iter().cloned().collect(); + mine.iter().all(|key| other.contains(key)) + } + "is_superset" => { + let owned: crate::util::fast_map::FastHashSet = mine.iter().cloned().collect(); + theirs.iter().all(|key| owned.contains(key)) + } + _ => { + let other: crate::util::fast_map::FastHashSet = theirs.iter().cloned().collect(); + !mine.iter().any(|key| other.contains(key)) + } + }; + Ok(Some(RuntimeVal::Bool(answer))) } "values" => { if !positional.is_empty() { @@ -536,13 +711,12 @@ fn dispatch_set_builtin_method( Some(HeapValue::Set(values)) => values.entries().cloned().collect::>(), _ => Vec::new(), }; - let vals = vals + let vals: Vec = vals .into_iter() .map(|value| runtime_map_key_to_value(value, heap)) .collect(); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(vals))), - ))) + let vals = TypedList::from_runtime_values(&vals, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(vals))))) } _ => Ok(None), } @@ -554,19 +728,23 @@ pub(super) fn core_set_builtin(args: NativeArgs<'_>, runtime: &mut NativeRuntime } let set = match args.get(0) { None => RuntimeSet::new(), - Some(value) => runtime_set_from_value(value, runtime.heap())?, + Some(value) => runtime_set_from_value(value, runtime.heap_mut())?, }; Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::Set(set)))) } -fn runtime_set_from_value(value: &RuntimeVal, heap: &HeapStore) -> anyhow::Result { +/// Takes `&mut HeapStore` because a list element can be a string past the +/// inline limit, which has to be materialized on the heap before it can become +/// a set key. +fn runtime_set_from_value(value: &RuntimeVal, heap: &mut HeapStore) -> anyhow::Result { let RuntimeVal::Obj(handle) = value else { - bail!("Set(value) expects List or Set, got {:?}", value.kind()); + bail!("Set(value) expects List or Set, got {}", value.type_name_in(heap)); }; match heap.get(*handle) { Some(HeapValue::List(list)) => { + let list = list.clone(); let mut set = RuntimeSet::new(); - for item in list.collect_owned() { + for item in list_runtime_items(list, heap) { set.insert(runtime_map_key_from_value(&item, heap, "Set() item")?); } Ok(set) @@ -583,19 +761,10 @@ fn runtime_set_from_value(value: &RuntimeVal, heap: &HeapStore) -> anyhow::Resul } } +/// The key a value is used under, with the caller's name on the front — see +/// [`RuntimeMapKey::from_value`], which is the one conversion. fn runtime_map_key_from_value(value: &RuntimeVal, heap: &HeapStore, context: &str) -> anyhow::Result { - match value { - RuntimeVal::Nil => Ok(RuntimeMapKey::Nil), - RuntimeVal::Bool(value) => Ok(RuntimeMapKey::Bool(*value)), - RuntimeVal::Int(value) => Ok(RuntimeMapKey::Int(*value)), - RuntimeVal::Float(_) => bail!("{context}: Float cannot be used as a key"), - RuntimeVal::ShortStr(s) => Ok(RuntimeMapKey::ShortStr(*s)), - RuntimeVal::Obj(handle) => match heap.get(*handle) { - Some(HeapValue::String(s)) => Ok(RuntimeMapKey::String(Arc::clone(s))), - Some(_) => Ok(RuntimeMapKey::Obj(*handle)), - None => bail!("{context}: heap object out of bounds"), - }, - } + RuntimeMapKey::from_value(value, heap).map_err(|error| anyhow!("{context}: {error}")) } fn runtime_map_key_to_value(value: RuntimeMapKey, heap: &mut HeapStore) -> RuntimeVal { @@ -605,7 +774,6 @@ fn runtime_map_key_to_value(value: RuntimeMapKey, heap: &mut HeapStore) -> Runti RuntimeMapKey::Int(value) => RuntimeVal::Int(value), RuntimeMapKey::ShortStr(value) => RuntimeVal::ShortStr(value), RuntimeMapKey::String(value) => make_string_val(&value, heap), - RuntimeMapKey::Obj(value) => RuntimeVal::Obj(value), } } @@ -618,7 +786,7 @@ fn extract_string_detached(value: &RuntimeVal, heap: &HeapStore, context: &str) Some(v) => bail!("{context}: expected string, got {}", v.type_name()), None => bail!("{context}: heap object out of bounds"), }, - other => bail!("{context}: expected string, got {:?}", other.kind()), + other => bail!("{context}: expected string, got {}", other.type_name_in(heap)), } } @@ -633,6 +801,17 @@ fn make_string_val(s: &str, heap: &mut HeapStore) -> RuntimeVal { /// Dispatch built-in string instance methods: split, starts_with, ends_with, contains, trim. /// Returns Some(value) if handled, None to fall through. +/// The character at `index`, counting from the end when negative, `nil` when +/// out of range — the rule every sequence's `get` follows. +fn string_char_at(text: &str, index: i64, heap: &mut HeapStore) -> RuntimeVal { + let total = crate::util::text::char_len(text) as i64; + let resolved = if index < 0 { total + index } else { index }; + if resolved < 0 || resolved >= total { + return RuntimeVal::Nil; + } + make_string_val(crate::util::text::substring(text, resolved as usize, 1), heap) +} + fn dispatch_string_builtin_method( receiver: &RuntimeVal, method: &str, @@ -666,6 +845,29 @@ fn dispatch_string_builtin_method( let handle = heap.alloc(HeapValue::List(TypedList::String(parts))); Ok(Some(RuntimeVal::Obj(handle))) } + "byte_at" => { + if positional.len() != 1 { + bail!("string.byte_at() expects 1 argument (index), got {}", positional.len()); + } + let index = match &positional[0] { + RuntimeVal::Int(value) => *value, + other => bail!( + "string.byte_at() index must be an Int, got {}", + other.kind().scalar_type_name() + ), + }; + let bytes = s.as_bytes(); + // Nil past either end. This answered `-1` while `string.byte_at` + // answered nil — the same operation with two answers — and `-1` is + // not what the method declares either (`Int?`). It is a sentinel in + // a language that says nil everywhere else it means absent: + // `find`, `get`, `first`, `last`, `pop`, and the module form of + // this very function. + if index < 0 || index >= bytes.len() as i64 { + return Ok(Some(RuntimeVal::Nil)); + } + Ok(Some(RuntimeVal::Int(bytes[index as usize] as i64))) + } "starts_with" => { if positional.len() != 1 { bail!( @@ -693,7 +895,14 @@ fn dispatch_string_builtin_method( positional.len() ); } - let needle = extract_string_detached(&positional[0], heap, "string.contains() needle")?; + // Total, like `in` on the same string and like every other + // container's membership: a needle that is not a string is not a + // substring. `1 in "abc"` has always said `false` here, and this + // said "string.contains() needle: expected string, got Int" — one + // question, two answers, chosen by which spelling was written. + let Ok(needle) = extract_string_detached(&positional[0], heap, "string.contains() needle") else { + return Ok(Some(RuntimeVal::Bool(false))); + }; Ok(Some(RuntimeVal::Bool(s.contains(needle.as_str())))) } "trim" => { @@ -720,39 +929,101 @@ fn dispatch_string_builtin_method( } Ok(Some(make_string_val(&s.to_uppercase(), heap))) } - "find" => { - if positional.len() != 1 { - bail!("string.find() expects 1 argument (needle), got {}", positional.len()); - } - let needle = extract_string_detached(&positional[0], heap, "string.find() needle")?; - match s.find(needle.as_str()) { - Some(pos) => Ok(Some(RuntimeVal::Int(pos as i64))), - None => Ok(Some(RuntimeVal::Int(-1))), + // The read surface `List` / `Slice` / `Bytes` share. A `String` is a + // sequence of characters — that is what `len()` counts and what `[i]` + // indexes — and was the one sequence type without them. + // + // `slice(start, end)` in particular is why this matters beyond tidiness: + // `substring(start, length)` looks identical at the call site and means + // something else, so `xs.slice(1, 3)` and `s.substring(1, 3)` take + // different windows from the same numbers. + "slice" => { + if positional.is_empty() || positional.len() > 2 { + bail!( + "string.slice() expects 1 or 2 arguments (start[, end]), got {}", + positional.len() + ); } + let total = crate::util::text::char_len(s); + let start = slice_position(&positional[0], total, "string.slice() start")?; + // Omitting `end` means "to the end", as it does on every other + // sequence. + let end = match positional.get(1) { + Some(RuntimeVal::Nil) | None => total, + Some(value) => slice_position(value, total, "string.slice() end")?, + }; + let text = crate::util::text::substring(s, start, end.saturating_sub(start)); + Ok(Some(make_string_val(text, heap))) } - "substring" => { - if positional.len() != 2 { + "index_of" => { + if positional.len() != 1 { bail!( - "string.substring() expects 2 arguments (start, length), got {}", + "string.index_of() expects 1 argument (needle), got {}", positional.len() ); } - let RuntimeVal::Int(start) = &positional[0] else { - bail!("string.substring() start must be Int"); + // Absent, for `contains`'s reason. + let Ok(needle) = extract_string_detached(&positional[0], heap, "string.index_of() needle") else { + return Ok(Some(RuntimeVal::Nil)); }; - let RuntimeVal::Int(length) = &positional[1] else { - bail!("string.substring() length must be Int"); + match crate::util::text::find_char_index(s, needle.as_str()) { + Some(index) => Ok(Some(RuntimeVal::Int(index as i64))), + None => Ok(Some(RuntimeVal::Nil)), + } + } + "get" => { + if positional.len() != 1 { + bail!("string.get() expects 1 argument (index), got {}", positional.len()); + } + let RuntimeVal::Int(index) = &positional[0] else { + bail!("string.get() index must be Int"); }; - let start_val = *start as usize; - let length_val = *length as usize; - - let end = (start_val.saturating_add(length_val)).min(s.len()); - - if end <= start_val { - Ok(Some(make_string_val("", heap))) - } else { - Ok(Some(make_string_val(&s[start_val..end], heap))) + Ok(Some(string_char_at(s, *index, heap))) + } + "first" => { + if !positional.is_empty() { + bail!("string.first() expects no arguments, got {}", positional.len()); + } + Ok(Some(string_char_at(s, 0, heap))) + } + "last" => { + if !positional.is_empty() { + bail!("string.last() expects no arguments, got {}", positional.len()); + } + Ok(Some(string_char_at(s, -1, heap))) + } + "take" => { + if positional.len() != 1 { + bail!("string.take() expects 1 argument (count), got {}", positional.len()); + } + let RuntimeVal::Int(count) = &positional[0] else { + bail!("string.take() count must be Int"); + }; + // Refused, not clamped — the same rule `list.take()` follows. A + // count is not a position: a negative *position* means "from the + // end" here, and that decision is what made `.max(0)` look + // reasonable, but `take(-1)` is a mistake in any reading and the + // List carrier has said so all along. + if *count < 0 { + bail!("string.take() count must be non-negative, got {count}"); + } + let text = crate::util::text::substring(s, 0, *count as usize); + Ok(Some(make_string_val(text, heap))) + } + "skip" => { + if positional.len() != 1 { + bail!("string.skip() expects 1 argument (count), got {}", positional.len()); + } + let RuntimeVal::Int(count) = &positional[0] else { + bail!("string.skip() count must be Int"); + }; + let total = crate::util::text::char_len(s); + if *count < 0 { + bail!("string.skip() count must be non-negative, got {count}"); } + let start = *count as usize; + let text = crate::util::text::substring(s, start, total.saturating_sub(start)); + Ok(Some(make_string_val(text, heap))) } "reverse" => { if !positional.is_empty() { @@ -768,52 +1039,250 @@ fn dispatch_string_builtin_method( let RuntimeVal::Int(n) = &positional[0] else { bail!("string.repeat() count must be Int"); }; - if *n <= 0 { + // Zero repeats is the empty string; a *negative* count is a + // mistake, and every other count-taking method says so. + if *n < 0 { + bail!("string.repeat() count must be non-negative, got {n}"); + } + if *n == 0 { return Ok(Some(make_string_val("", heap))); } let repeated: String = s.repeat(*n as usize); Ok(Some(make_string_val(&repeated, heap))) } + "bytes" => { + if !positional.is_empty() { + bail!("string.bytes() expects no arguments, got {}", positional.len()); + } + // The way out. Positions in a string are characters, so anything + // that genuinely needs bytes — a protocol frame, a buffer length — + // asks for them, and gets a `Bytes` the `bytes` module operates on. + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(s.as_bytes()))), + ))) + } "chars" => { if !positional.is_empty() { bail!("string.chars() expects no arguments, got {}", positional.len()); } - let chars: Vec = s - .chars() - .map(|c| { - let mut buf = [0u8; 4]; - let encoded = c.encode_utf8(&mut buf); - let s = String::from(encoded); - RuntimeVal::ShortStr(ShortStr::new(&s).unwrap_or_else(|| ShortStr::new("?").unwrap())) - }) - .collect(); + // `TypedList::String`, the same variant `string.chars` builds. As + // `Mixed` the identical list printed differently — `[a,b]` here + // against `["a","b"]` there — because rendering asks the variant. + let chars: Vec> = s.chars().map(|c| Arc::::from(c.to_string())).collect(); Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(chars))), + heap.alloc(HeapValue::List(TypedList::String(chars))), ))) } "replace" => { - if positional.len() != 2 { + // The optional third argument is what the module spelling has had + // all along: `all: false` replaces the first occurrence only. The + // method could not say it, so the two spellings were not the same + // operation — and the module could not simply forward here. + if !(2..=3).contains(&positional.len()) { bail!( - "string.replace() expects 2 arguments (from, to), got {}", + "string.replace() expects 2 or 3 arguments (from, to[, all]), got {}", positional.len() ); } let from = extract_string_detached(&positional[0], heap, "string.replace() from")?; let to = extract_string_detached(&positional[1], heap, "string.replace() to")?; - Ok(Some(make_string_val(&s.replace(from.as_str(), to.as_str()), heap))) + let all = match positional.get(2) { + None | Some(RuntimeVal::Nil) => true, + Some(RuntimeVal::Bool(all)) => *all, + Some(_) => bail!("string.replace() `all` must be Bool"), + }; + let replaced = if all { + s.replace(from.as_str(), to.as_str()) + } else { + s.replacen(from.as_str(), to.as_str(), 1) + }; + Ok(Some(make_string_val(&replaced, heap))) + } + // The nine operations the `string` module used to own outright. They are + // receiver-first questions about a string, so they belong here with the + // rest — and moving them is what lets the module forward instead of + // holding a second body (see `forward` there). + "capitalize" => { + if !positional.is_empty() { + bail!("string.capitalize() expects no arguments, got {}", positional.len()); + } + let mut chars = s.chars(); + let mut out = String::with_capacity(s.len()); + if let Some(first) = chars.next() { + out.extend(first.to_uppercase()); + } + for ch in chars { + out.extend(ch.to_lowercase()); + } + Ok(Some(make_string_val(&out, heap))) + } + "title" => { + if !positional.is_empty() { + bail!("string.title() expects no arguments, got {}", positional.len()); + } + let mut out = String::with_capacity(s.len()); + let mut start_of_word = true; + for ch in s.chars() { + if ch.is_whitespace() { + start_of_word = true; + out.push(ch); + } else if start_of_word { + out.extend(ch.to_uppercase()); + start_of_word = false; + } else { + out.extend(ch.to_lowercase()); + } + } + Ok(Some(make_string_val(&out, heap))) + } + "count" => { + if positional.len() != 1 { + bail!("string.count() expects 1 argument (needle), got {}", positional.len()); + } + // Zero, for `contains`'s reason. + let Ok(needle) = extract_string_detached(&positional[0], heap, "string.count() needle") else { + return Ok(Some(RuntimeVal::Int(0))); + }; + // An empty needle matches between every pair of characters and at + // both ends — `str::matches` says so, and counting characters + 1 + // said something else for any multi-byte string. + Ok(Some(RuntimeVal::Int(s.matches(needle.as_str()).count() as i64))) + } + "strip" => { + if positional.len() != 1 { + bail!("string.strip() expects 1 argument (chars), got {}", positional.len()); + } + let chars = extract_string_detached(&positional[0], heap, "string.strip() chars")?; + let stripped = s.trim_matches(|ch| chars.as_str().contains(ch)); + Ok(Some(make_string_val(stripped, heap))) + } + "strip_prefix" | "strip_suffix" => { + if positional.len() != 1 { + bail!("string.{method}() expects 1 argument, got {}", positional.len()); + } + let affix = extract_string_detached(&positional[0], heap, "string.strip_prefix/suffix() affix")?; + let stripped = if method == "strip_prefix" { + s.strip_prefix(affix.as_str()) + } else { + s.strip_suffix(affix.as_str()) + }; + // `String?`: nil when it was not there, which is what makes the + // answer distinguishable from "it was there and left nothing". + Ok(Some(match stripped { + Some(text) => make_string_val(text, heap), + None => RuntimeVal::Nil, + })) + } + "pad_left" | "pad_right" => { + if !(1..=2).contains(&positional.len()) { + bail!( + "string.{method}() expects 1 or 2 arguments (width[, fill]), got {}", + positional.len() + ); + } + let RuntimeVal::Int(width) = &positional[0] else { + bail!("string.{method}() width must be Int"); + }; + if *width < 0 { + bail!("string.{method}() width must be non-negative, got {width}"); + } + let fill = match positional.get(1) { + None | Some(RuntimeVal::Nil) => " ".to_string(), + Some(value) => { + let fill = extract_string_detached(value, heap, "string.pad_left() fill")?; + if fill.as_str().is_empty() { + bail!("string.{method}() fill must not be empty"); + } + fill.as_str().to_string() + } + }; + // Width counts *characters*, because that is the unit everything + // else in the language counts — `s.len()`, `s[i]`, `s.slice(a, b)`. + // And the fill repeats by `cycle().take(n)` rather than by slicing a + // repeated string, so there is no byte boundary to get wrong: the + // byte-sliced version panicked the process on `pad_left("a", 5, + // "中")`, and a Rust panic is not something a script can catch. + let len = crate::util::text::char_len(s); + let width = *width as usize; + if len >= width { + return Ok(Some(make_string_val(s, heap))); + } + let padding: String = fill.chars().cycle().take(width - len).collect(); + let padded = if method == "pad_left" { + alloc::format!("{padding}{s}") + } else { + alloc::format!("{s}{padding}") + }; + Ok(Some(make_string_val(&padded, heap))) + } + "format" => { + // `"{} and {}".format(a, b)` — the receiver is the template, which + // is exactly the shape `string.format(template, …)` already had. + let mut out = String::with_capacity(s.len()); + let mut chars = s.chars().peekable(); + let mut next_arg = 0usize; + while let Some(ch) = chars.next() { + if ch == '{' && chars.peek() == Some(&'}') { + chars.next(); + match positional.get(next_arg) { + Some(value) => { + out.push_str(&crate::vm::display_runtime_value(value, heap)); + next_arg += 1; + } + // A placeholder with no argument left stays literal, + // which is what `println`'s format does with the same + // shape. + None => out.push_str("{}"), + } + } else { + out.push(ch); + } + } + // …and an argument with no placeholder left is appended, space + // separated — also `println`'s rule. Dropping it silently is the + // one answer that loses data. + if next_arg < positional.len() { + if !out.is_empty() { + out.push(' '); + } + for (index, value) in positional[next_arg..].iter().enumerate() { + if index > 0 { + out.push(' '); + } + out.push_str(&crate::vm::display_runtime_value(value, heap)); + } + } + Ok(Some(make_string_val(&out, heap))) } _ => Ok(None), } } -fn list_index_arg(value: &RuntimeVal, context: &str) -> anyhow::Result { - let RuntimeVal::Int(index) = value else { - bail!("{context} must be Int"); - }; - if *index < 0 { - bail!("{context} must be non-negative"); - } - Ok(*index as usize) +/// A `slice` boundary resolved against `len`. +/// +/// One convention for positions, the language's own: **negative counts from the +/// end** — `-1` is the last element, exactly as in `xs[-1]` and `xs.get(-1)` — +/// and the result is clamped into `0..=len`, like every other position here. +/// +/// The four `slice` implementations had four answers for a negative one. List +/// and Bytes raised; Slice and String clamped it to `0` and returned a window +/// nobody asked for; and the *native* string slice already counted from the end, +/// so `"abcde".slice(1, -1)` was `""` interpreted and `"bcd"` compiled — the +/// same program, two answers. Counting from the end is what the rest of the +/// language already means by a negative position, so that is what this says. +pub(super) fn slice_position(value: &RuntimeVal, len: usize, context: &str) -> anyhow::Result { + crate::val::position::read_position(value, len, context) +} + +/// A *write* position against a container of `len` elements. +/// +/// Negative counts from the end, as everywhere else — `xs.set(-1, v)` writes +/// the last element, which is what `xs[-1]` reads. Still out of range after +/// that is an error and stays one: reading past the end is nil, writing past it +/// is not something a program can mean. The caller does the upper-bound check, +/// because `insert` accepts `len` and the others do not. +pub(super) fn write_index_arg(value: &RuntimeVal, len: usize, context: &str) -> anyhow::Result { + crate::val::position::write_position(value, len, context) } fn list_runtime_items(list: TypedList, heap: &mut HeapStore) -> Vec { @@ -829,36 +1298,542 @@ fn list_runtime_items(list: TypedList, heap: &mut HeapStore) -> Vec } } -fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal) -> bool { - match (left, right) { - (RuntimeVal::Nil, RuntimeVal::Nil) => true, - (RuntimeVal::Bool(left), RuntimeVal::Bool(right)) => left == right, - (RuntimeVal::Int(left), RuntimeVal::Int(right)) => left == right, - (RuntimeVal::Float(left), RuntimeVal::Float(right)) => left.to_bits() == right.to_bits(), - (RuntimeVal::Int(left), RuntimeVal::Float(right)) => (*left as f64).to_bits() == right.to_bits(), - (RuntimeVal::Float(left), RuntimeVal::Int(right)) => left.to_bits() == (*right as f64).to_bits(), - (RuntimeVal::ShortStr(left), RuntimeVal::ShortStr(right)) => left.as_str() == right.as_str(), - (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) => left == right, - _ => false, +/// The list reversed, in the representation it already has. +/// +/// `reverse` used to materialize every element — allocating a heap string per +/// element past seven bytes — reverse the `RuntimeVal`s, and box the result as +/// `Mixed`. Reversing a `Vec>` is a pointer shuffle; the old path cost +/// about two hundred nanoseconds an element to do the same thing, and left the +/// list boxed so every later read took the slow path. +pub(super) fn typed_list_reversed(list: &TypedList) -> TypedList { + fn flipped(values: &[T]) -> Vec { + let mut out = values.to_vec(); + out.reverse(); + out + } + match list { + TypedList::Mixed(values) => TypedList::Mixed(flipped(values)), + TypedList::Int(values) => TypedList::Int(flipped(values)), + TypedList::Float(values) => TypedList::Float(flipped(values)), + TypedList::Bool(values) => TypedList::Bool(flipped(values)), + TypedList::String(values) => TypedList::String(flipped(values)), } } -fn compare_runtime_values(left: &RuntimeVal, right: &RuntimeVal) -> core::cmp::Ordering { +/// The two lists joined, keeping the representation when they share one. +/// +/// `None` when they do not — the caller falls back to materializing, which is +/// the only thing that can join an `Int` list to a `String` one. +pub(super) fn typed_lists_concatenated(left: &TypedList, right: &TypedList) -> Option { + fn joined(left: &[T], right: &[T]) -> Vec { + let mut out = Vec::with_capacity(left.len() + right.len()); + out.extend_from_slice(left); + out.extend_from_slice(right); + out + } + Some(match (left, right) { + (TypedList::Mixed(left), TypedList::Mixed(right)) => TypedList::Mixed(joined(left, right)), + (TypedList::Int(left), TypedList::Int(right)) => TypedList::Int(joined(left, right)), + (TypedList::Float(left), TypedList::Float(right)) => TypedList::Float(joined(left, right)), + (TypedList::Bool(left), TypedList::Bool(right)) => TypedList::Bool(joined(left, right)), + (TypedList::String(left), TypedList::String(right)) => TypedList::String(joined(left, right)), + _ => return None, + }) +} + +/// The list sorted ascending, in the representation it already has. +/// +/// A typed list sorts its own scalars — an `i64` sort is a comparison, where +/// the materialized path built a `RuntimeVal` per element first and then +/// compared through `compare_runtime_values`. The order is the same one: +/// `compare_runtime_values` on two `Int`s *is* `i64`'s. +/// The sum of a list of numbers. +/// +/// Integers wrap, floats add as floats, and a mix promotes to float — the same +/// three rules `+` follows, because `xs.sum()` is `+` applied down the list and +/// a second set of rules for it would be a second answer. +/// +/// An empty list is `0`, the identity `reduce(0, …)` would have started from. +/// Anything that is not a number is a refusal naming what was found: summing +/// strings has no meaning here (`+` concatenates them, but a list of strings +/// asked for its *sum* is a mistake, not a join). +pub(super) fn typed_list_sum(list: &TypedList, heap: &HeapStore) -> Result { + match list { + TypedList::Int(values) => Ok(RuntimeVal::Int( + values.iter().fold(0i64, |total, value| total.wrapping_add(*value)), + )), + TypedList::Float(values) => Ok(RuntimeVal::Float(values.iter().sum())), + TypedList::Bool(_) => bail!("list.sum() adds numbers, and this is a list of Bool"), + TypedList::String(_) => bail!("list.sum() adds numbers, and this is a list of String"), + TypedList::Mixed(values) => { + let mut total_int: i64 = 0; + let mut total_float = 0.0f64; + let mut saw_float = false; + for value in values { + match value { + RuntimeVal::Int(value) => { + total_int = total_int.wrapping_add(*value); + total_float += *value as f64; + } + RuntimeVal::Float(value) => { + saw_float = true; + total_float += *value; + } + other => bail!( + "list.sum() adds numbers, and this list holds a {}", + other.type_name_in(heap) + ), + } + } + Ok(if saw_float { + RuntimeVal::Float(total_float) + } else { + RuntimeVal::Int(total_int) + }) + } + } +} + +/// Where the smallest (or largest) element is, by the order +/// [`typed_list_sorted`] sorts with — the same comparison, not a second one +/// that happens to agree today. +/// +/// An *index*, so the caller materializes the element through +/// [`typed_list_element`] like every other single-element read does: a string +/// element has to be allocated into the heap, and that is the one place that +/// knows how. +/// +/// `None` for an empty list, which the callers turn into nil — what +/// `first`/`last` answer there, and "the largest of nothing" is the same +/// question. +pub(super) fn typed_list_extreme_index(list: &TypedList, heap: &HeapStore, want_max: bool) -> Option { + let better = |left: usize, right: usize| -> bool { + let ordering = match list { + TypedList::Int(values) => values[left].cmp(&values[right]), + TypedList::Float(values) => crate::val::compare_floats(values[left], values[right]), + TypedList::Bool(values) => values[left].cmp(&values[right]), + TypedList::String(values) => values[left].as_ref().cmp(values[right].as_ref()), + TypedList::Mixed(values) => compare_runtime_values(&values[left], &values[right], heap), + }; + // Ties keep the earlier element: `min`/`max` name a *value*, and the + // first one that has it is the one a reader would point at. + match ordering { + core::cmp::Ordering::Less => !want_max, + core::cmp::Ordering::Equal => true, + core::cmp::Ordering::Greater => want_max, + } + }; + (0..list.len()).reduce(|best, index| if better(best, index) { best } else { index }) +} + +pub(super) fn typed_list_sorted(list: &TypedList, heap: &HeapStore) -> TypedList { + match list { + TypedList::Int(values) => { + let mut out = values.to_vec(); + out.sort_unstable(); + TypedList::Int(out) + } + TypedList::Float(values) => { + let mut out = values.to_vec(); + out.sort_by(|left, right| crate::val::compare_floats(*left, *right)); + TypedList::Float(out) + } + TypedList::Bool(values) => { + let mut out = values.to_vec(); + out.sort_unstable(); + TypedList::Bool(out) + } + TypedList::String(values) => { + let mut out = values.to_vec(); + out.sort_by(|left, right| left.as_ref().cmp(right.as_ref())); + TypedList::String(out) + } + // Mixed elements can be anything, including heap values whose order + // needs the comparison the executor defines. + TypedList::Mixed(values) => { + let mut out = values.to_vec(); + out.sort_by(|left, right| compare_runtime_values(left, right, heap)); + TypedList::Mixed(out) + } + } +} + +/// One element of a list, allocating only for that element. +/// +/// The single-element reads — `first`, `last`, `get`, `pop` — used to call +/// `list_runtime_items`, which materializes *every* element and allocates a +/// heap string for each one past seven bytes. Two thousand `pop`s on a +/// twenty-thousand-element string list therefore did forty million +/// allocations to return two thousand values. +/// +/// Out of range is nil, as everywhere else. +pub(super) fn typed_list_element(list_handle: HeapRef, index: usize, heap: &mut HeapStore) -> RuntimeVal { + enum Element { + Ready(RuntimeVal), + Text(Arc), + } + let element = match heap.get(list_handle) { + Some(HeapValue::List(list)) => match list { + TypedList::Mixed(values) => values.get(index).copied().map(Element::Ready), + TypedList::Int(values) => values.get(index).copied().map(RuntimeVal::Int).map(Element::Ready), + TypedList::Float(values) => values.get(index).copied().map(RuntimeVal::Float).map(Element::Ready), + TypedList::Bool(values) => values.get(index).copied().map(RuntimeVal::Bool).map(Element::Ready), + // The one case that can allocate — and only for this element. + TypedList::String(values) => values.get(index).cloned().map(Element::Text), + }, + _ => None, + }; + match element { + Some(Element::Ready(value)) => value, + Some(Element::Text(text)) => make_string_val(text.as_ref(), heap), + None => RuntimeVal::Nil, + } +} + +/// Where `needle` first appears in `list`, or `None`. +/// +/// Searches the `TypedList` **in place**. `contains`/`index_of`/`unique` used to +/// clone the list and materialize every element into a `RuntimeVal` first — +/// which allocates a heap string for every element past seven bytes — to answer +/// a question that reads each element once and often stops at the first. A +/// twenty-thousand-element string list cost twenty thousand allocations per +/// call, whatever the answer was. +/// +/// The typed variants never touch the heap at all: an `Int` list compares +/// integers, a `String` list compares text against text. +/// A set's members in *its own* iteration order. +/// +/// Detached from the heap because the answer is built into a fresh set while +/// the source is still borrowed; the keys are cheap to clone and there are two +/// of them to read. +fn set_entries(handle: crate::val::HeapRef, heap: &HeapStore) -> Vec { + match heap.get(handle) { + Some(HeapValue::Set(values)) => values.entries().cloned().collect(), + _ => Vec::new(), + } +} + +/// The same, for the argument of a set operation — which must be a `Set`. +fn set_entries_of_value(value: &RuntimeVal, heap: &HeapStore, method: &str) -> Result> { + let RuntimeVal::Obj(handle) = value else { + bail!("set.{method}() argument must be a Set"); + }; + match heap.get(*handle) { + Some(HeapValue::Set(values)) => Ok(values.entries().cloned().collect()), + _ => bail!("set.{method}() argument must be a Set"), + } +} + +pub(super) fn typed_list_position(list: &TypedList, needle: &RuntimeVal, heap: &HeapStore) -> Result> { + let mut found = None; + typed_list_scan(list, needle, heap, |index| { + found = Some(index); + false + })?; + Ok(found) +} + +/// How many elements equal `needle`, under the same rules. +pub(super) fn typed_list_count(list: &TypedList, needle: &RuntimeVal, heap: &HeapStore) -> Result { + let mut found = 0; + typed_list_scan(list, needle, heap, |_| { + found += 1; + true + })?; + Ok(found) +} + +/// Every index whose element equals `needle`, in order, until `on_match` +/// answers `false`. +/// +/// One function rather than one per question, because the *rules* are the +/// payload: an `Int` element equals a `Float` needle when the numbers match +/// (`1.0 == 1`, the language's rule for `==`), a `Float` list compares by value +/// so `0.0` finds `-0.0`, and a `Mixed` list defers to `runtime_values_equal`. +/// `index_of` and `count` are the same scan with different accumulators, and +/// writing them apart is how two spellings of one operation come to disagree. +fn typed_list_scan( + list: &TypedList, + needle: &RuntimeVal, + heap: &HeapStore, + mut on_match: impl FnMut(usize) -> bool, +) -> Result<()> { + fn scan(values: &[T], mut eq: impl FnMut(&T) -> bool, on_match: &mut impl FnMut(usize) -> bool) { + for (index, value) in values.iter().enumerate() { + if eq(value) && !on_match(index) { + return; + } + } + } + match list { + TypedList::Int(values) => match needle { + RuntimeVal::Int(needle) => scan(values, |value| value == needle, &mut on_match), + RuntimeVal::Float(needle) => scan(values, |value| *value as f64 == *needle, &mut on_match), + _ => {} + }, + TypedList::Float(values) => match needle { + RuntimeVal::Float(needle) => scan(values, |value| value == needle, &mut on_match), + RuntimeVal::Int(needle) => scan(values, |value| *value == *needle as f64, &mut on_match), + _ => {} + }, + TypedList::Bool(values) => { + if let RuntimeVal::Bool(needle) = needle { + scan(values, |value| value == needle, &mut on_match); + } + } + TypedList::String(values) => { + if let Some(needle) = runtime_value_text(needle, heap) { + scan(values, |value| value.as_ref() == needle, &mut on_match); + } + } + TypedList::Mixed(values) => { + for (index, value) in values.iter().enumerate() { + if crate::val::runtime_values_equal(value, needle, heap)? && !on_match(index) { + break; + } + } + } + } + Ok(()) +} + +/// The list with later duplicates dropped, order preserved, representation kept. +/// +/// The typed variants dedup through a hash set — the previous implementation +/// compared each element against every element already kept, which is O(n²): +/// twenty thousand elements with five thousand distinct ones took a hundred +/// million comparisons. It also materialized every element first, and returned +/// a `Mixed` list whatever it was given, so an `Int` list came back boxed and +/// every later read of it took the slow path. +/// +/// `Mixed` keeps the quadratic scan, and has to: its elements are arbitrary +/// values whose equality needs the heap, and there is no key to hash them by. +pub(super) fn typed_list_unique(list: &TypedList, heap: &HeapStore) -> Result { + Ok(match list { + TypedList::Int(values) => { + let mut seen = crate::util::fast_map::fast_hash_set_new(); + TypedList::Int(values.iter().copied().filter(|value| seen.insert(*value)).collect()) + } + TypedList::Float(values) => { + let mut seen = crate::util::fast_map::fast_hash_set_new(); + let mut nan_ordinal = 0u64; + // Keyed by what `==` says, not by bits. `0.0` and `-0.0` are equal, + // so they share a key; no `NaN` equals any `NaN`, so each gets a + // fresh one. Bits said the opposite on both counts — the only two + // places `unique()` still disagreed with `==`. + // + // Still one hash lookup per element: canonicalising the key is what + // keeps this from becoming the O(n²) scan that value equality would + // otherwise force. + TypedList::Float( + values + .iter() + .copied() + .filter(|value| { + let key = if value.is_nan() { + nan_ordinal += 1; + (u64::MAX, nan_ordinal) + } else if *value == 0.0 { + (0f64.to_bits(), 0) + } else { + (value.to_bits(), 0) + }; + seen.insert(key) + }) + .collect(), + ) + } + TypedList::Bool(values) => { + let mut seen = crate::util::fast_map::fast_hash_set_new(); + TypedList::Bool(values.iter().copied().filter(|value| seen.insert(*value)).collect()) + } + TypedList::String(values) => { + let mut seen = crate::util::fast_map::fast_hash_set_new(); + TypedList::String( + values + .iter() + .filter(|value| seen.insert(value.as_ref().to_string())) + .cloned() + .collect(), + ) + } + TypedList::Mixed(values) => { + let mut unique: Vec = Vec::new(); + for value in values { + let mut seen_before = false; + for seen in &unique { + if crate::val::runtime_values_equal(seen, value, heap)? { + seen_before = true; + break; + } + } + if !seen_before { + unique.push(*value); + } + } + TypedList::Mixed(unique) + } + }) +} + +/// The text a value holds, without allocating — `None` when it is not a string. +fn runtime_value_text<'a>(value: &'a RuntimeVal, heap: &'a HeapStore) -> Option<&'a str> { + match value { + RuntimeVal::ShortStr(value) => Some(value.as_str()), + RuntimeVal::Obj(handle) => match heap.get(*handle) { + Some(HeapValue::String(value)) => Some(value.as_ref()), + _ => None, + }, + _ => None, + } +} + +/// The order `sort` puts values in. +/// +/// Takes the heap because a string longer than `ShortStr`'s seven inline bytes +/// lives there, and two of them used to fall through to the by-kind ranking +/// below — both `Obj`, same rank, therefore *equal*. So sorting long strings +/// did nothing at all while sorting short ones worked: +/// +/// ```text +/// ["zzz", "aaa", "mmm"].sort() → ["aaa", "mmm", "zzz"] +/// ["zzzzzzzzzz", "aaaaaaaaaa", …].sort() → unchanged +/// ``` +/// +/// Same seven-byte boundary as the equality and search bugs, in the ordering. +/// +/// Containers were the other half of that hole and are handled below: two +/// *lists* compare element by element, and every other pair of heap values by +/// their kind. Before that they were both `Obj`, one rank, therefore equal — +/// so sorting a list of lists also did nothing at all: +/// +/// ```text +/// [[1,"b"], [1,"a"], [0,"c"]].sort() → unchanged +/// ``` +fn compare_runtime_values(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore) -> core::cmp::Ordering { + compare_runtime_values_at(left, right, heap, 0) +} + +fn compare_runtime_values_at( + left: &RuntimeVal, + right: &RuntimeVal, + heap: &HeapStore, + depth: u32, +) -> core::cmp::Ordering { match (left, right) { (RuntimeVal::Nil, RuntimeVal::Nil) => core::cmp::Ordering::Equal, (RuntimeVal::Bool(left), RuntimeVal::Bool(right)) => left.cmp(right), (RuntimeVal::Int(left), RuntimeVal::Int(right)) => left.cmp(right), - (RuntimeVal::Float(left), RuntimeVal::Float(right)) => { - left.partial_cmp(right).unwrap_or(core::cmp::Ordering::Equal) - } - (RuntimeVal::Int(left), RuntimeVal::Float(right)) => { - (*left as f64).partial_cmp(right).unwrap_or(core::cmp::Ordering::Equal) + // Every float-involving arm goes through the total order: a mixed list + // sorts with this comparator too, so a NaN anywhere in it had the same + // panic as a float list. + (RuntimeVal::Float(left), RuntimeVal::Float(right)) => crate::val::compare_floats(*left, *right), + (RuntimeVal::Int(left), RuntimeVal::Float(right)) => crate::val::compare_floats(*left as f64, *right), + (RuntimeVal::Float(left), RuntimeVal::Int(right)) => crate::val::compare_floats(*left, *right as f64), + _ => match (runtime_value_text(left, heap), runtime_value_text(right, heap)) { + // Two strings, wherever each of them lives. + (Some(left), Some(right)) => left.cmp(right), + _ => compare_heap_values(left, right, heap, depth), + }, + } +} + +/// Two values of which at least one is a heap object. +/// +/// Lists (and windows over them, which are lists by every other measure) +/// compare lexicographically — element by element, and a prefix sorts before +/// what extends it, which is what `==` already treats them as. Everything else +/// compares by *kind*: a map has no order against another map, but grouping +/// them deterministically is still better than calling them equal. +fn compare_heap_values(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore, depth: u32) -> core::cmp::Ordering { + let (RuntimeVal::Obj(left_handle), RuntimeVal::Obj(right_handle)) = (left, right) else { + return runtime_val_kind_rank(left).cmp(&runtime_val_kind_rank(right)); + }; + let (Some(left_value), Some(right_value)) = (heap.get(*left_handle), heap.get(*right_handle)) else { + return runtime_val_kind_rank(left).cmp(&runtime_val_kind_rank(right)); + }; + // Past the bound the values are cyclic or pathological. `sort_by` wants an + // `Ordering`, not a `Result` — and raising half way through a sort would + // leave the list rearranged anyway — so this is the one place the depth + // limit answers rather than reports. See `crate::val::MAX_VALUE_DEPTH`. + if depth < crate::val::MAX_VALUE_DEPTH + && let (Some(left_items), Some(right_items)) = (list_view(left_value, heap), list_view(right_value, heap)) + { + return compare_list_views(&left_items, &right_items, heap, depth + 1); + } + heap_kind_rank(left_value).cmp(&heap_kind_rank(right_value)) +} + +/// A list, or the window a slice reads through — both are sequences here. +fn list_view(value: &HeapValue, heap: &HeapStore) -> Option { + match value { + HeapValue::List(list) => Some(list.clone()), + HeapValue::Slice(slice) => { + let RuntimeVal::Obj(source) = slice.source else { + return Some(TypedList::Mixed(Vec::new())); + }; + let Some(HeapValue::List(list)) = heap.get(source) else { + return Some(TypedList::Mixed(Vec::new())); + }; + Some(list.window(slice.start, slice.live_len(heap))) } - (RuntimeVal::Float(left), RuntimeVal::Int(right)) => { - left.partial_cmp(&(*right as f64)).unwrap_or(core::cmp::Ordering::Equal) + _ => None, + } +} + +fn compare_list_views(left: &TypedList, right: &TypedList, heap: &HeapStore, depth: u32) -> core::cmp::Ordering { + for index in 0..left.len().min(right.len()) { + let ordering = match (list_item_text(left, index), list_item_text(right, index)) { + (Some(left), Some(right)) => left.cmp(right), + _ => compare_runtime_values_at( + &list_item_value(left, index), + &list_item_value(right, index), + heap, + depth, + ), + }; + if ordering != core::cmp::Ordering::Equal { + return ordering; } - (RuntimeVal::ShortStr(left), RuntimeVal::ShortStr(right)) => left.as_str().cmp(right.as_str()), - _ => runtime_val_kind_rank(left).cmp(&runtime_val_kind_rank(right)), + } + left.len().cmp(&right.len()) +} + +/// A `TypedList::String` element is an `Arc`, which no `RuntimeVal` +/// carries past seven bytes — the same reason equality reads it as text. +fn list_item_text(list: &TypedList, index: usize) -> Option<&str> { + match list { + TypedList::String(values) => values.get(index).map(|text| text.as_ref()), + _ => None, + } +} + +fn list_item_value(list: &TypedList, index: usize) -> RuntimeVal { + match list { + TypedList::Mixed(values) => values.get(index).copied().unwrap_or(RuntimeVal::Nil), + TypedList::Int(values) => values.get(index).copied().map_or(RuntimeVal::Nil, RuntimeVal::Int), + TypedList::Float(values) => values.get(index).copied().map_or(RuntimeVal::Nil, RuntimeVal::Float), + TypedList::Bool(values) => values.get(index).copied().map_or(RuntimeVal::Nil, RuntimeVal::Bool), + TypedList::String(values) => values + .get(index) + .and_then(|text| ShortStr::new(text).map(RuntimeVal::ShortStr)) + .unwrap_or(RuntimeVal::Nil), + } +} + +/// Heap kinds in a fixed order, so a list and a map sort into groups instead of +/// comparing equal. Arbitrary, but stated once and stable. +fn heap_kind_rank(value: &HeapValue) -> u8 { + match value { + HeapValue::String(_) => 0, + HeapValue::Bytes(_) => 1, + HeapValue::List(_) | HeapValue::Slice(_) => 2, + HeapValue::Map(_) => 3, + HeapValue::Set(_) => 4, + HeapValue::Object(_) => 5, + HeapValue::Callable(_) => 6, + HeapValue::ErrorVal(_) => 7, + _ => 8, } } @@ -892,7 +1867,7 @@ fn list_join_parts(list: &TypedList, heap: &HeapStore) -> anyhow::Result bail!("list.join(): element is not a string ({})", other.type_name()), None => bail!("list.join(): heap object out of bounds"), }, - other => bail!("list.join(): element is not a string ({:?})", other.kind()), + other => bail!("list.join(): element is not a string ({})", other.type_name_in(heap)), }; out.push(string); } @@ -925,7 +1900,7 @@ fn list_filter( filtered.push(*item); } } - let result = TypedList::Mixed(filtered); + let result = TypedList::from_runtime_values(&filtered, state.heap()); Ok(Some(RuntimeVal::Obj(state.heap_mut().alloc(HeapValue::List(result))))) } @@ -948,7 +1923,7 @@ fn list_map( state.host_root_push(result); mapped.push(result); } - let result = TypedList::Mixed(mapped); + let result = TypedList::from_runtime_values(&mapped, state.heap()); Ok(Some(RuntimeVal::Obj(state.heap_mut().alloc(HeapValue::List(result))))) } @@ -980,6 +1955,79 @@ fn list_reduce( Ok(Some(acc)) } +/// A filtered `Bytes` back as `Bytes`. +/// +/// The callback loop works in `RuntimeVal`s, so it hands back a list; every +/// element of it came out of a `Bytes` and is therefore a byte again. +fn rebuild_bytes(filtered: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> anyhow::Result { + let RuntimeVal::Obj(handle) = filtered else { + return Ok(*filtered); + }; + let Some(HeapValue::List(list)) = runtime.heap().get(*handle) else { + return Ok(*filtered); + }; + let mut bytes = Vec::with_capacity(list.len()); + for value in list_runtime_items(list.clone(), runtime.heap_mut()) { + let RuntimeVal::Int(value) = value else { + bail!("bytes.filter() kept a non-byte value"); + }; + bytes.push(u8::try_from(value).map_err(|_| anyhow!("bytes.filter() kept {value}, which is not a byte"))?); + } + Ok(RuntimeVal::Obj( + runtime.heap_mut().alloc(HeapValue::Bytes(Arc::<[u8]>::from(bytes))), + )) +} + +/// Which sequence a receiver is, for the higher-order methods. +/// +/// `None` means "not a sequence", and the caller falls through to trait +/// dispatch — the same answer it gave for everything but a list before windows +/// and `Bytes` had elements the language could reach. +#[derive(Clone, Copy)] +enum SequenceKind { + List, + Slice, + Bytes, +} + +fn sequence_receiver_kind(receiver: &RuntimeVal, heap: &HeapStore) -> Option { + let RuntimeVal::Obj(handle) = receiver else { + return None; + }; + match heap.get(*handle) { + Some(HeapValue::List(_)) => Some(SequenceKind::List), + Some(HeapValue::Slice(_)) => Some(SequenceKind::Slice), + Some(HeapValue::Bytes(_)) => Some(SequenceKind::Bytes), + _ => None, + } +} + +/// A sequence's elements, materialized for the callback loop. +/// +/// Materializing is what a callback loop needs either way — it hands each +/// element to user code — so a window pays here what it saved everywhere else, +/// and only here. +fn sequence_items( + receiver: &RuntimeVal, + kind: SequenceKind, + runtime: &mut NativeRuntime<'_>, +) -> anyhow::Result> { + match kind { + SequenceKind::List => { + let list = clone_list(receiver, runtime.heap_mut())?; + Ok(list_runtime_items(list, runtime.heap_mut())) + } + SequenceKind::Slice | SequenceKind::Bytes => { + // Both answer `to_list`, which is exactly this question, and + // answering it twice is how the two would drift apart. + let materialized = dispatch_builtin_method_slice(receiver, "to_list", &[], runtime)? + .ok_or_else(|| anyhow!("sequence receiver has no to_list"))?; + let list = clone_list(&materialized, runtime.heap_mut())?; + Ok(list_runtime_items(list, runtime.heap_mut())) + } + } +} + fn clone_list(receiver: &RuntimeVal, heap: &mut HeapStore) -> anyhow::Result { let handle = match receiver { RuntimeVal::Obj(h) => *h, @@ -998,7 +2046,12 @@ fn call_trait_method_runtime( runtime: &mut NativeRuntime<'_>, ) -> anyhow::Result { let receiver_type = runtime_dispatch_type(&receiver, runtime.heap()); - let receiver_type_name = runtime_type_name(&receiver, runtime.heap()); + // Owned because `parts_mut` takes the heap mutably below, and this borrows + // it: `type_name_in` names a struct instance `P`, which is not a `'static` + // string. That is the whole point — these messages used to say "Object has + // no method 'nonexistent'" while `declared_type` sat two lines down with the + // real name in it, already computed for dispatch. + let receiver_type_name = receiver.type_name_in(runtime.heap()).to_string(); // Taken before `parts_mut` borrows the heap mutably: a struct instance // dispatches in the scope of the module that declared it, which is the // half of its identity the bare type name does not carry. @@ -1009,14 +2062,21 @@ fn call_trait_method_runtime( let Some(ctx) = ctx else { bail!("{} has no method '{}'", receiver_type_name, method); }; - // Dispatch on the *declared* type name (`Sq`), not the diagnostic one - // (`runtime_type_name` reports the heap kind, i.e. "Object", for any - // struct instance). let declared_type = receiver_type.display(); let Some(impl_ref) = ctx .trait_method(&receiver_scope, &declared_type, method.as_str()) .cloned() else { + // A map is the one receiver where a miss has two possible causes, so it + // says both: `m.thing()` looks for a method *and* for a key holding a + // function, and "Map has no method `thing`" left the second half out — + // for the receiver whose members are usually keys. + if matches!(receiver_type_name.as_str(), "Map") { + bail!( + "a Map has no method `{method}`, and this map has no key `{method}` holding a function \ + either" + ); + } bail!("{} has no method '{}'", receiver_type_name, method); }; crate::vm::call_trait_method( @@ -1037,45 +2097,39 @@ fn runtime_access(receiver: &RuntimeVal, field: &str, heap: &mut HeapStore) -> a match receiver { RuntimeVal::ShortStr(value) => Ok(runtime_string_access(value.as_str(), field)), RuntimeVal::Obj(handle) => { - enum RuntimeAccess { - Ready(Option), - CopyPayload(crate::rt::RuntimePayload), - String(String), - } - let access = match heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::String(value) => RuntimeAccess::Ready(runtime_string_access(value.as_ref(), field)), - HeapValue::Bytes(value) => match field { - "len" => RuntimeAccess::Ready(Some(RuntimeVal::Int(value.len() as i64))), - _ => RuntimeAccess::Ready(None), - }, - HeapValue::List(values) => RuntimeAccess::Ready(runtime_list_access(values, field)), - HeapValue::Map(values) => RuntimeAccess::Ready(values.get_str(field)), - HeapValue::Slice(slice) => match field { - "len" => RuntimeAccess::Ready(Some(RuntimeVal::Int(slice.len as i64))), - _ => RuntimeAccess::Ready(None), - }, - HeapValue::Object(object) => RuntimeAccess::Ready(object.get_field(field)), - HeapValue::Task(task) if field == "value" => match &task.value { - Some(value) => RuntimeAccess::CopyPayload(value.clone()), - None => RuntimeAccess::Ready(Some(RuntimeVal::Nil)), - }, - HeapValue::Channel(channel) => match field { - "capacity" => RuntimeAccess::Ready(Some(RuntimeVal::Int(channel.capacity.unwrap_or(0)))), - "type" => RuntimeAccess::String(format!("{:?}", channel.inner_type)), - _ => RuntimeAccess::Ready(None), + // A channel's `capacity`/`type` and a task's `value` used to be + // readable here as *properties*, and nothing could reach them: the + // checker refuses a field access on either type, and the dynamic + // route refuses them as not indexable (`index target object is not + // indexable: "Channel"`). Instrumented, both arms were dead in every + // example, every test and every probe. The spelling the language has + // is the module function — `chans.capacity(ch)`, which + // `concurrency_demo.lk` uses and docs/semantics.md documents. + // + // They were also the only reason this read a `RuntimeAccess` enum + // rather than an `Option`: one arm needed a payload + // copied out of another heap and one needed a string allocated, + // both while the heap was still borrowed. Neither remains. + Ok( + match heap + .get(*handle) + .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? + { + HeapValue::String(value) => runtime_string_access(value.as_ref(), field), + HeapValue::Bytes(value) => match field { + "len" => Some(RuntimeVal::Int(value.len() as i64)), + _ => None, + }, + HeapValue::List(values) => runtime_list_access(values, field), + HeapValue::Map(values) => values.get_str(field), + HeapValue::Slice(slice) => match field { + "len" => Some(RuntimeVal::Int(slice.len as i64)), + _ => None, + }, + HeapValue::Object(object) => object.get_field(field), + _ => None, }, - _ => RuntimeAccess::Ready(None), - }; - match access { - RuntimeAccess::Ready(value) => Ok(value), - RuntimeAccess::CopyPayload(value) => { - Ok(Some(crate::vm::copy_runtime_value(&value.value, &value.heap, heap)?)) - } - RuntimeAccess::String(value) => Ok(Some(runtime_string_value(value, heap))), - } + ) } _ => Ok(None), } @@ -1083,7 +2137,9 @@ fn runtime_access(receiver: &RuntimeVal, field: &str, heap: &mut HeapStore) -> a fn runtime_string_access(value: &str, field: &str) -> Option { match field { - "len" => Some(RuntimeVal::Int(value.len() as i64)), + // Characters, like `s.len()` and `s[i]`. This answered bytes, so + // `s.len` and `s.len()` disagreed on the same string. + "len" => Some(RuntimeVal::Int(crate::util::text::char_len(value) as i64)), _ => None, } } @@ -1159,7 +2215,10 @@ fn runtime_positional_arg_list( let handle = match value { RuntimeVal::Nil => return Ok(MethodPositionalArgs::Empty), RuntimeVal::Obj(h) => *h, - other => bail!("{helper} expects positional arguments as list, got {:?}", other.kind()), + other => bail!( + "{helper} expects positional arguments as list, got {}", + other.kind().scalar_type_name() + ), }; let heap_val = heap @@ -1225,7 +2284,10 @@ fn runtime_named_arg_map(helper: &str, value: &RuntimeVal, heap: &HeapStore) -> let handle = match value { RuntimeVal::Nil => return Ok(None), RuntimeVal::Obj(h) => *h, - other => bail!("{helper} expects named arguments as map, got {:?}", other.kind()), + other => bail!( + "{helper} expects named arguments as map, got {}", + other.type_name_in(heap) + ), }; let heap_val = heap @@ -1237,14 +2299,6 @@ fn runtime_named_arg_map(helper: &str, value: &RuntimeVal, heap: &HeapStore) -> Ok(Some(handle)) } -fn runtime_string_value(value: String, heap: &mut HeapStore) -> RuntimeVal { - if let Some(short) = ShortStr::new(&value) { - RuntimeVal::ShortStr(short) - } else { - RuntimeVal::Obj(heap.alloc(HeapValue::String(Arc::::from(value)))) - } -} - fn runtime_is_callable(value: &RuntimeVal, heap: &HeapStore) -> anyhow::Result { let RuntimeVal::Obj(handle) = value else { return Ok(false); @@ -1278,28 +2332,24 @@ fn heap_dispatch_type(value: &HeapValue) -> Type { named_params: Vec::new(), return_type: Box::new(Type::Any), }, + // The element is dropped, as it is for a list and a map above: an impl + // target names the *constructor* (`impl Channel`), so a receiver + // carrying its own inner type would key on something no impl registers + // under. `Task` already did; these two did not. HeapValue::Task(_) => Type::Task(Box::new(Type::Any)), - HeapValue::Channel(channel) => Type::Channel(Box::new(channel.inner_type.clone())), - HeapValue::Stream(stream) => Type::Generic { + HeapValue::Channel(_) => Type::Channel(Box::new(Type::Any)), + HeapValue::Stream(_) => Type::Generic { name: "Stream".to_string(), - params: vec![stream.inner_type.clone()], + params: vec![Type::Any], }, HeapValue::StreamCursor(_) => Type::Named("StreamCursor".to_string()), - HeapValue::Slice(_) => Type::Named("Slice".to_string()), + // `Slice`, not a bare `Slice`: an impl target written `Slice` is + // parsed as `Slice` the way `List` is parsed as `List`, and + // this is the key the registration is looked up by. + HeapValue::Slice(_) => crate::typ::slice_of(Type::Any), HeapValue::Resource(resource) => Type::Named(resource.kind.to_string()), HeapValue::Object(object) => Type::Named(object.type_name().to_string()), HeapValue::UpvalCell(_) => Type::Any, HeapValue::ErrorVal(_) => Type::Named("Error".to_string()), } } - -fn runtime_type_name(value: &RuntimeVal, heap: &HeapStore) -> &'static str { - match value { - RuntimeVal::Nil => "Nil", - RuntimeVal::Bool(_) => "Bool", - RuntimeVal::Int(_) => "Int", - RuntimeVal::Float(_) => "Float", - RuntimeVal::ShortStr(_) => "String", - RuntimeVal::Obj(handle) => heap.get(*handle).map(HeapValue::type_name).unwrap_or("Object"), - } -} diff --git a/core/src/vm/context/core_methods/bytes_dispatch.rs b/core/src/vm/context/core_methods/bytes_dispatch.rs new file mode 100644 index 00000000..b1041af1 --- /dev/null +++ b/core/src/vm/context/core_methods/bytes_dispatch.rs @@ -0,0 +1,300 @@ +use super::*; + +/// Built-in methods on `Bytes`. +/// +/// `Bytes` was the one sequence in the language with no methods at all: `b[0]` +/// did not index, `for x in b` did not iterate, and anything sequence-shaped +/// went through `bytes.to_list(b)` — a copy that also inflates each byte into +/// an eight-byte `RuntimeVal::Int`. So the only way to read bytes was to stop +/// having bytes. +/// +/// What is here is the **read** half of the list surface: the operations whose +/// meaning does not depend on the element type, and which therefore mean the +/// same thing on a `Bytes` as on a `List`. The transforming half is not, and +/// deliberately: `b.map(|x| x + 1000)` cannot answer a `Bytes`, because 1000 is +/// not a byte. Those live on `List`, reachable through `to_list`. +pub(super) fn dispatch_bytes_builtin_method( + receiver: &RuntimeVal, + method: &str, + positional: &[RuntimeVal], + heap: &mut HeapStore, +) -> anyhow::Result> { + let RuntimeVal::Obj(handle) = receiver else { + return Ok(None); + }; + let Some(HeapValue::Bytes(bytes)) = heap.get(*handle) else { + return Ok(None); + }; + let bytes = bytes.clone(); + + match method { + "len" => { + if !positional.is_empty() { + bail!("bytes.len() expects no arguments, got {}", positional.len()); + } + Ok(Some(RuntimeVal::Int(bytes.len() as i64))) + } + "is_empty" => { + if !positional.is_empty() { + bail!("bytes.is_empty() expects no arguments, got {}", positional.len()); + } + Ok(Some(RuntimeVal::Bool(bytes.is_empty()))) + } + // Same index rule as everywhere else: a negative counts from the end, + // and outside is nil rather than a raise. + "get" => { + if positional.len() != 1 { + bail!("bytes.get() expects 1 argument (index), got {}", positional.len()); + } + let RuntimeVal::Int(index) = &positional[0] else { + bail!("bytes.get() index must be Int"); + }; + Ok(Some(byte_at(&bytes, *index))) + } + // A `Bytes` is a sequence of numbers, so the three reductions mean the + // same here as on a list — and a receiver kind that answers `first`, + // `len` and `contains` but not `sum` would be the half-surface this + // dispatch was unified to remove. + "sum" => { + if !positional.is_empty() { + bail!("bytes.sum() expects no arguments, got {}", positional.len()); + } + Ok(Some(RuntimeVal::Int( + bytes + .iter() + .fold(0i64, |total, byte| total.wrapping_add(i64::from(*byte))), + ))) + } + "min" | "max" => { + if !positional.is_empty() { + bail!("bytes.{method}() expects no arguments, got {}", positional.len()); + } + let extreme = if method == "max" { + bytes.iter().max() + } else { + bytes.iter().min() + }; + // Empty answers nil, as `first` does here and as `min` does on a list. + Ok(Some(match extreme { + Some(byte) => RuntimeVal::Int(i64::from(*byte)), + None => RuntimeVal::Nil, + })) + } + "contains" => { + if positional.len() != 1 { + bail!("bytes.contains() expects 1 argument (value), got {}", positional.len()); + } + // Total, like `in` on the same byte string: a byte string holds + // byte values, so nothing else can be in it. `"a" in b` has always + // answered `false` and this refused the needle's type — one + // question, two answers, chosen by the spelling. + let RuntimeVal::Int(value) = &positional[0] else { + return Ok(Some(RuntimeVal::Bool(false))); + }; + let found = u8::try_from(*value).is_ok_and(|byte| bytes.contains(&byte)); + Ok(Some(RuntimeVal::Bool(found))) + } + "index_of" => { + if positional.len() != 1 { + bail!("bytes.index_of() expects 1 argument (value), got {}", positional.len()); + } + // Absent, for `contains`'s reason. + let RuntimeVal::Int(value) = &positional[0] else { + return Ok(Some(RuntimeVal::Nil)); + }; + let found = u8::try_from(*value) + .ok() + .and_then(|byte| bytes.iter().position(|candidate| *candidate == byte)); + Ok(Some( + found.map_or(RuntimeVal::Nil, |index| RuntimeVal::Int(index as i64)), + )) + } + "first" => { + if !positional.is_empty() { + bail!("bytes.first() expects no arguments, got {}", positional.len()); + } + Ok(Some(byte_at(&bytes, 0))) + } + "last" => { + if !positional.is_empty() { + bail!("bytes.last() expects no arguments, got {}", positional.len()); + } + Ok(Some(byte_at(&bytes, -1))) + } + // A window over bytes is `Bytes` again, not a `Slice`: the element type + // is what makes this a distinct type, and a window over it has the same + // elements. (`Slice` windows a `List` without copying; this copies, + // because `Arc<[u8]>` has no cheap sub-range.) + "slice" => { + if positional.is_empty() || positional.len() > 2 { + bail!( + "bytes.slice() expects 1 or 2 arguments (start[, end]), got {}", + positional.len() + ); + } + let start = super::slice_position(&positional[0], bytes.len(), "bytes.slice() start")?; + let end = match positional.get(1) { + Some(RuntimeVal::Nil) | None => bytes.len(), + Some(value) => super::slice_position(value, bytes.len(), "bytes.slice() end")?, + }; + let end = end.max(start); + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(&bytes[start..end]))), + ))) + } + // A contiguous run of bytes is still bytes. + "take" | "skip" => { + if positional.len() != 1 { + bail!("bytes.{method}() expects 1 argument (count), got {}", positional.len()); + } + let RuntimeVal::Int(count) = &positional[0] else { + bail!("bytes.{method}() count must be Int"); + }; + if *count < 0 { + bail!("bytes.{method}() count must be non-negative, got {count}"); + } + let count = (*count as usize).min(bytes.len()); + let kept = if method == "take" { + &bytes[..count] + } else { + &bytes[count..] + }; + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(kept))), + ))) + } + // The three that used to exist only as `bytes.f(b, …)` module + // functions. Each is a receiver-first question about a `Bytes`, so it + // belongs here with the rest and the module forwards to it — the split + // is what let `bytes.slice(b, 3, 1)` raise while `b.slice(3, 1)` + // answered an empty window. + "to_string_utf8" => { + if !positional.is_empty() { + bail!("bytes.to_string_utf8() expects no arguments, got {}", positional.len()); + } + // Raises on invalid UTF-8, unlike `to_string_lossy` next door: the + // two exist precisely so the caller says which one they mean. + let text = core::str::from_utf8(&bytes).map_err(|err| anyhow!("bytes are not valid UTF-8: {err}"))?; + Ok(Some(make_string_val(text, heap))) + } + "to_string_lossy" => { + if !positional.is_empty() { + bail!("bytes.to_string_lossy() expects no arguments, got {}", positional.len()); + } + Ok(Some(make_string_val(&String::from_utf8_lossy(&bytes), heap))) + } + "concat" => { + if positional.len() != 1 { + bail!("bytes.concat() expects 1 argument (other), got {}", positional.len()); + } + let RuntimeVal::Obj(other) = &positional[0] else { + bail!("bytes.concat() argument must be Bytes"); + }; + let Some(HeapValue::Bytes(other)) = heap.get(*other) else { + bail!("bytes.concat() argument must be Bytes"); + }; + let mut out = Vec::with_capacity(bytes.len() + other.len()); + out.extend_from_slice(&bytes); + out.extend_from_slice(other); + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(out))), + ))) + } + // Shape-preserving, element-type-independent, and therefore a `Bytes` + // again — the same reading `take`, `skip`, `slice` and `concat` already + // take. `reverse` was on `List` and on `Str` and on neither of the two + // carriers that have every other read of the list surface. + "reverse" => { + if !positional.is_empty() { + bail!("bytes.reverse() expects no arguments, got {}", positional.len()); + } + let mut out = bytes.to_vec(); + out.reverse(); + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(out))), + ))) + } + // Byte values are ordered scalars, so both mean here exactly what they + // mean on a `List` — and both keep the carrier, because every + // element of the answer is still a byte. + "sort" => { + if !positional.is_empty() { + bail!("bytes.sort() expects no arguments, got {}", positional.len()); + } + let mut out = bytes.to_vec(); + out.sort_unstable(); + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(out))), + ))) + } + "unique" => { + if !positional.is_empty() { + bail!("bytes.unique() expects no arguments, got {}", positional.len()); + } + // Later duplicates dropped, order preserved — `List::unique`'s + // rule. 256 possible values, so the "seen" set is a bitmap. + let mut seen = [false; 256]; + let mut out = Vec::with_capacity(bytes.len()); + for byte in bytes.iter() { + if !seen[*byte as usize] { + seen[*byte as usize] = true; + out.push(*byte); + } + } + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(out))), + ))) + } + // `count` is `index_of`'s sibling — how many rather than where — and + // `index_of` is on all four sequence carriers while `count` was on + // `Str` alone. A value no byte can equal counts zero, which is the + // same answer `contains` gives it. + "count" => { + if positional.len() != 1 { + bail!("bytes.count() expects 1 argument (value), got {}", positional.len()); + } + // Zero, for `contains`'s reason. + let RuntimeVal::Int(needle) = &positional[0] else { + return Ok(Some(RuntimeVal::Int(0))); + }; + let found = u8::try_from(*needle) + .map(|needle| bytes.iter().filter(|byte| **byte == needle).count()) + .unwrap_or(0); + Ok(Some(RuntimeVal::Int(found as i64))) + } + "to_list" => { + if !positional.is_empty() { + bail!("bytes.to_list() expects no arguments, got {}", positional.len()); + } + let values: Vec = bytes.iter().map(|byte| *byte as i64).collect(); + Ok(Some(RuntimeVal::Obj( + heap.alloc(HeapValue::List(TypedList::Int(values))), + ))) + } + // The operations whose answer is a *list of the elements*, whatever the + // elements were: they mean the same thing here as on a `List` and + // cannot keep the carrier, so they are the list's, reached by + // materializing. One arm rather than six bodies — `enumerate`'s pairs, + // `chunk`'s grouping and `flatten`'s one level are rules, and a second + // copy of a rule is how two spellings of one operation come to + // disagree. + "enumerate" | "zip" | "chain" | "chunk" => { + let values: Vec = bytes.iter().map(|byte| *byte as i64).collect(); + let list = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Int(values)))); + super::dispatch_list_builtin_method(&list, method, positional, heap) + } + _ => Ok(None), + } +} + +/// One byte as an `Int`, with the language's index rule: negative counts from +/// the end, outside is nil. +fn byte_at(bytes: &[u8], index: i64) -> RuntimeVal { + let index = if index < 0 { bytes.len() as i64 + index } else { index }; + if index < 0 { + return RuntimeVal::Nil; + } + bytes + .get(index as usize) + .map_or(RuntimeVal::Nil, |byte| RuntimeVal::Int(*byte as i64)) +} diff --git a/core/src/vm/context/core_methods/list_dispatch.rs b/core/src/vm/context/core_methods/list_dispatch.rs index 80335275..75c83025 100644 --- a/core/src/vm/context/core_methods/list_dispatch.rs +++ b/core/src/vm/context/core_methods/list_dispatch.rs @@ -20,24 +20,20 @@ pub(super) fn dispatch_list_builtin_method( if !positional.is_empty() { bail!("list.first() expects no arguments, got {}", positional.len()); } - let list = clone_list(receiver, heap)?; - if list.is_empty() { - return Ok(Some(RuntimeVal::Nil)); - } - let first = list_runtime_items(list, heap) - .into_iter() - .next() - .unwrap_or(RuntimeVal::Nil); - Ok(Some(first)) + Ok(Some(typed_list_element(handle, 0, heap))) } "last" => { if !positional.is_empty() { bail!("list.last() expects no arguments, got {}", positional.len()); } - let list = clone_list(receiver, heap)?; - let items = list_runtime_items(list, heap); - let last = items.into_iter().last().unwrap_or(RuntimeVal::Nil); - Ok(Some(last)) + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let len = list.len(); + Ok(Some(match len.checked_sub(1) { + Some(last) => typed_list_element(handle, last, heap), + None => RuntimeVal::Nil, + })) } "get" => { if positional.len() != 1 { @@ -47,11 +43,21 @@ pub(super) fn dispatch_list_builtin_method( bail!("list.get() index must be Int"); }; let list = clone_list(receiver, heap)?; - if *idx < 0 || *idx as usize >= list.len() { + // `.get(i)` is `xs[i]` that answers nil instead of failing, so it + // indexes the same way: a negative counts from the end. + // + // This arm used to reject a negative outright — and never got the + // chance to, because the compiler lowers every `x.get(k)` call to + // `GetIndex` (`lower_map_get_method_call`). So the rule written + // here was not the rule the language had; `xs.get(-1)` answered the + // last element, as it still does. Leaving the two spellings + // disagreeing meant whichever path a call happened to take decided + // its meaning. + let index = if *idx < 0 { list.len() as i64 + *idx } else { *idx }; + if index < 0 || index as usize >= list.len() { return Ok(Some(RuntimeVal::Nil)); } - let items = list_runtime_items(list, heap); - Ok(Some(items.into_iter().nth(*idx as usize).unwrap_or(RuntimeVal::Nil))) + Ok(Some(typed_list_element(handle, index as usize, heap))) } "skip" => { if positional.len() != 1 { @@ -60,10 +66,17 @@ pub(super) fn dispatch_list_builtin_method( let RuntimeVal::Int(n) = &positional[0] else { bail!("list.skip() count must be Int"); }; - let mut list = clone_list(receiver, heap)?; - if *n > 0 { - list.drain_prefix(*n as usize); + // A count is not an index: there is nothing for a negative one to + // mean, so it is an error rather than a value. It used to be + // ignored (`if *n > 0`), which turned an off-by-one that computed + // `-1` into "the whole list" — the answer most likely to look + // right. `iter.skip` has always raised here; the two spellings now + // agree. + if *n < 0 { + bail!("list.skip() count must be non-negative, got {n}"); } + let mut list = clone_list(receiver, heap)?; + list.drain_prefix(*n as usize); Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(list))))) } "take" => { @@ -73,45 +86,90 @@ pub(super) fn dispatch_list_builtin_method( let RuntimeVal::Int(n) = &positional[0] else { bail!("list.take() count must be Int"); }; + // As in `skip` — and here the old code was not even ignoring the + // negative, it was casting it: `-1 as usize` is `usize::MAX`, so + // `take(-1)` took everything by way of an unchecked wrap. + if *n < 0 { + bail!("list.take() count must be non-negative, got {n}"); + } let list = clone_list(receiver, heap)?; let taken = list.take_prefix(*n as usize); Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(taken))))) } + // `min`/`max`/`sum`: the reductions a list API is expected to have. + // + // `map`, `filter`, `reduce`, `unique`, `zip` and `chunk` were all here + // and these were not, so the three most ordinary questions about a list + // of numbers had to be written as folds — with a comparison lambda that + // then had to agree with `sort`'s order, which nothing checked. + "min" | "max" => { + if !positional.is_empty() { + bail!("list.{method}() expects no arguments, got {}", positional.len()); + } + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + // The order is `sort`'s, from the same comparison: `xs.sort().first()` + // and `xs.min()` cannot disagree, because there is only one rule. + let index = typed_list_extreme_index(list, heap, method == "max"); + Ok(Some(match index { + Some(index) => typed_list_element(handle, index, heap), + None => RuntimeVal::Nil, + })) + } + "sum" => { + if !positional.is_empty() { + bail!("list.sum() expects no arguments, got {}", positional.len()); + } + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + Ok(Some(typed_list_sum(list, heap)?)) + } "unique" => { if !positional.is_empty() { bail!("list.unique() expects no arguments, got {}", positional.len()); } - let items = list_runtime_items(clone_list(receiver, heap)?, heap); - let mut unique: Vec = Vec::new(); - for item in items { - if !unique.iter().any(|seen| runtime_values_equal(seen, &item)) { - unique.push(item); - } - } - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(unique))), - ))) + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let unique = typed_list_unique(list, heap)?; + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(unique))))) } "contains" => { if positional.len() != 1 { bail!("list.contains() expects 1 argument (value), got {}", positional.len()); } - let items = list_runtime_items(clone_list(receiver, heap)?, heap); + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; Ok(Some(RuntimeVal::Bool( - items.iter().any(|item| runtime_values_equal(item, &positional[0])), + typed_list_position(list, &positional[0], heap)?.is_some(), ))) } "index_of" => { if positional.len() != 1 { bail!("list.index_of() expects 1 argument (value), got {}", positional.len()); } - let items = list_runtime_items(clone_list(receiver, heap)?, heap); - let index = items - .iter() - .position(|item| runtime_values_equal(item, &positional[0])) - .map(|index| index as i64) - .unwrap_or(-1); - Ok(Some(RuntimeVal::Int(index))) + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let index = typed_list_position(list, &positional[0], heap)? + .map_or(RuntimeVal::Nil, |index| RuntimeVal::Int(index as i64)); + Ok(Some(index)) + } + // `index_of`'s sibling: how many rather than where. It was on `Str` + // alone, so `"aa".count("a")` answered 2 and `[1, 1].count(1)` was + // "List has no method 'count'". + "count" => { + if positional.len() != 1 { + bail!("list.count() expects 1 argument (value), got {}", positional.len()); + } + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let found = typed_list_count(list, &positional[0], heap)?; + Ok(Some(RuntimeVal::Int(found as i64))) } "is_empty" => { if !positional.is_empty() { @@ -119,54 +177,126 @@ pub(super) fn dispatch_list_builtin_method( } Ok(Some(RuntimeVal::Bool(clone_list(receiver, heap)?.is_empty()))) } - "reverse" => { + // The inverse of `b.to_list()`, which existed on its own for as long as + // the way *back* was spelled `bytes.from_list(xs)` — a constructor in + // another module for what is a question about this list. The module + // spelling stays and forwards here. + "to_bytes" => { if !positional.is_empty() { - bail!("list.reverse() expects no arguments, got {}", positional.len()); + bail!("list.to_bytes() expects no arguments, got {}", positional.len()); } - let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); - items.reverse(); + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let checked = |value: i64| { + u8::try_from(value) + .map_err(|_| anyhow::anyhow!("list.to_bytes() expects byte values in 0..=255, got {value}")) + }; + let bytes: Vec = match list { + TypedList::Int(values) => values + .iter() + .map(|value| checked(*value)) + .collect::>()?, + TypedList::Mixed(values) => values + .iter() + .map(|value| match value { + RuntimeVal::Int(value) => checked(*value), + other => bail!("list.to_bytes() expects Int items, got {}", other.type_name_in(heap)), + }) + .collect::>()?, + // An empty list has no element type to disagree with. + TypedList::Bool(values) if values.is_empty() => Vec::new(), + _ => bail!("list.to_bytes() expects Int items"), + }; Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(items))), + heap.alloc(HeapValue::Bytes(Arc::<[u8]>::from(bytes))), ))) } + "reverse" => { + if !positional.is_empty() { + bail!("list.reverse() expects no arguments, got {}", positional.len()); + } + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let reversed = typed_list_reversed(list); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(reversed))))) + } + // `pop` takes the last element *off*. It used to be a byte-for-byte + // duplicate of `last` above — same body, same declared type, same doc + // sentence — so the language had two names for "peek" and no way at all + // to remove the last element (`remove_at` does not mutate either). A + // name that every language uses for "remove and return" must not + // quietly mean "read". "pop" => { if !positional.is_empty() { bail!("list.pop() expects no arguments, got {}", positional.len()); } - let items = list_runtime_items(clone_list(receiver, heap)?, heap); - Ok(Some(items.into_iter().last().unwrap_or(RuntimeVal::Nil))) + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let Some(last) = list.len().checked_sub(1) else { + return Ok(Some(RuntimeVal::Nil)); + }; + let value = typed_list_element(handle, last, heap); + if let Some(HeapValue::List(list)) = heap.get_mut(handle) { + list.truncate(last); + } + Ok(Some(value)) } + // In place, answering the list itself — the same as `xs.push(v)` from + // LK. This used to copy the whole list into a new one and hand that + // back, so whether pushing changed the list depended on which side + // called the method. "push" => { if positional.len() != 1 { bail!("list.push() expects 1 argument (value), got {}", positional.len()); } - let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); - items.push(positional[0]); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(items))), - ))) + // Read the text out before the mutable borrow: a `TypedList::String` + // holds `Arc`, which no `RuntimeVal` carries past seven bytes. + let string_value = runtime_value_text(&positional[0], heap).map(Arc::::from); + let Some(HeapValue::List(list)) = heap.get_mut(handle) else { + return Ok(None); + }; + list.push(positional[0], string_value)?; + Ok(Some(*receiver)) + } + "clear" => { + if !positional.is_empty() { + bail!("list.clear() expects no arguments, got {}", positional.len()); + } + if let Some(HeapValue::List(list)) = heap.get_mut(handle) { + list.clear(); + } + Ok(Some(*receiver)) } "slice" => { + // A window, not a copy. This used to materialize `items[a..b]` into + // a fresh list, which meant every window over a large list + // duplicated the part it looked at. `to_list()` is how you ask for + // the copy now, and asking is the point — the two are different + // operations and used to share one name. if positional.is_empty() || positional.len() > 2 { bail!( "list.slice() expects 1 or 2 arguments (start[, end]), got {}", positional.len() ); } - let start = list_index_arg(&positional[0], "list.slice() start")?; - let items = list_runtime_items(clone_list(receiver, heap)?, heap); + let source_len = clone_list(receiver, heap)?.len(); + let start = super::slice_position(&positional[0], source_len, "list.slice() start")?; let end = match positional.get(1) { - Some(value) => list_index_arg(value, "list.slice() end")?.min(items.len()), - None => items.len(), + Some(RuntimeVal::Nil) | None => source_len, + Some(value) => super::slice_position(value, source_len, "list.slice() end")?, }; - let sliced = if start >= end { - Vec::new() - } else { - items[start..end].to_vec() - }; - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(sliced))), - ))) + // Clamped, like every other position in this language. + let end = end.max(start); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::Slice(Arc::new( + SliceValue { + source: *receiver, + start, + len: end - start, + }, + )))))) } "insert" => { if positional.len() != 2 { @@ -175,30 +305,75 @@ pub(super) fn dispatch_list_builtin_method( positional.len() ); } - let index = list_index_arg(&positional[0], "list.insert() index")?; - let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); - if index > items.len() { - bail!("list.insert() index {} out of bounds (len={})", index, items.len()); + let value = positional[1]; + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + // `-1` inserts before the last element, the same "from the end" the + // read side means; `len` (the past-the-end position) stays legal + // because that is where an append goes. + let index = write_index_arg(&positional[0], list.len(), "list.insert() index")?; + if index > list.len() { + bail!("list.insert() index {} out of bounds (len={})", index, list.len()); + } + // In place, like `push` and `set`. It used to copy the whole list, + // insert, and hand back a *new* one — so `xs.insert(…)` left `xs` + // alone while `xs.push(…)` changed it, two opposite answers to + // "does adding an element change this list". + // + // The typed cases move memory and keep the representation; a value + // that does not fit the representation (or a string, whose text + // lives on the heap) goes the long way and is written back to the + // same handle, so it mutates either way. + let inserted_in_place = match (heap.get_mut(handle), value) { + (Some(HeapValue::List(TypedList::Int(values))), RuntimeVal::Int(value)) => { + values.insert(index, value); + true + } + (Some(HeapValue::List(TypedList::Float(values))), RuntimeVal::Float(value)) => { + values.insert(index, value); + true + } + (Some(HeapValue::List(TypedList::Bool(values))), RuntimeVal::Bool(value)) => { + values.insert(index, value); + true + } + (Some(HeapValue::List(TypedList::Mixed(values))), value) => { + values.insert(index, value); + true + } + _ => false, + }; + if !inserted_in_place { + let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); + items.insert(index, value); + let items = TypedList::from_runtime_values(&items, heap); + if let Some(slot) = heap.get_mut(handle) { + *slot = HeapValue::List(items); + } } - items.insert(index, positional[1]); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(items))), - ))) + Ok(Some(*receiver)) } "remove_at" => { if positional.len() != 1 { bail!("list.remove_at() expects 1 argument (index), got {}", positional.len()); } - let index = list_index_arg(&positional[0], "list.remove_at() index")?; - let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); - if index >= items.len() { - bail!("list.remove_at() index {} out of bounds (len={})", index, items.len()); - } - let old = items.remove(index); - let updated = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(items)))); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(vec![updated, old]))), - ))) + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let index = write_index_arg(&positional[0], list.len(), "list.remove_at() index")?; + if index >= list.len() { + bail!("list.remove_at() index {} out of bounds (len={})", index, list.len()); + } + // Returns the element it removed, the way `pop` does. It used to + // return a two-element list `[updated, old]` — the only method in + // the language shaped that way — *and* leave the receiver alone, + // so the "updated" list was a copy nobody was holding. + let removed = typed_list_element(handle, index, heap); + if let Some(HeapValue::List(list)) = heap.get_mut(handle) { + list.remove_at(index); + } + Ok(Some(removed)) } "set" => { if positional.len() != 2 { @@ -207,37 +382,104 @@ pub(super) fn dispatch_list_builtin_method( positional.len() ); } - let index = list_index_arg(&positional[0], "list.set() index")?; - let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); - let Some(slot) = items.get_mut(index) else { - bail!("list.set() index {} out of bounds (len={})", index, items.len()); + let value = positional[1]; + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); }; - let old = core::mem::replace(slot, positional[1]); - let updated = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(items)))); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(vec![updated, old]))), - ))) + // Worded like the index-assignment path, not like `insert`/`remove_at`. + // The compiler rewrites every `xs.set(k, v)` into a `SetIndex`, so + // this arm is not reached from compiled code — a literal receiver, an + // unannotated parameter, a map-indexed receiver and a module-global + // list all report the assignment wording, and a sentinel put here + // survived the whole test suite and every example unseen. It stays + // because `set` is a real method and a program should not depend on + // which route the compiler picked; what it must not do is *disagree* + // with that route, which is what "list.set() index N out of bounds + // (len=N)" did. `insert`/`remove_at` keep their own wording because + // they have no rewrite and that wording is what a program sees. + let RuntimeVal::Int(requested) = positional[0] else { + bail!("list index must be Int"); + }; + let resolved = if requested < 0 { + list.len() as i64 + requested + } else { + requested + }; + if resolved < 0 { + // The same wording as the other end. `xs[-1]` is the last + // element, so "must be non-negative" states a rule the language + // does not have — and the assertion two lines below in this + // file's own test, that `set(-1, 7)` succeeds, is the proof. + bail!("list index {requested} out of bounds"); + } + let index = resolved as usize; + if index >= list.len() { + bail!("list index {requested} out of bounds"); + } + // In place, answering the receiver — the same thing the compiler's + // own lowering does. This arm used to copy the list and return a + // `[updated, old]` pair, so the fallback and the fast path + // disagreed about both the effect and the answer; only the fact + // that the fallback is unreachable for `set` kept it from showing. + let written_in_place = match (heap.get_mut(handle), value) { + (Some(HeapValue::List(TypedList::Int(values))), RuntimeVal::Int(value)) => { + values[index] = value; + true + } + (Some(HeapValue::List(TypedList::Float(values))), RuntimeVal::Float(value)) => { + values[index] = value; + true + } + (Some(HeapValue::List(TypedList::Bool(values))), RuntimeVal::Bool(value)) => { + values[index] = value; + true + } + (Some(HeapValue::List(TypedList::Mixed(values))), value) => { + values[index] = value; + true + } + _ => false, + }; + if !written_in_place { + let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); + items[index] = value; + let items = TypedList::from_runtime_values(&items, heap); + if let Some(slot) = heap.get_mut(handle) { + *slot = HeapValue::List(items); + } + } + Ok(Some(*receiver)) } "sort" => { if !positional.is_empty() { bail!("list.sort() expects no arguments, got {}", positional.len()); } - let mut items = list_runtime_items(clone_list(receiver, heap)?, heap); - items.sort_by(compare_runtime_values); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(items))), - ))) + let Some(HeapValue::List(list)) = heap.get(handle) else { + return Ok(None); + }; + let sorted = typed_list_sorted(list, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(sorted))))) } - "concat" => { + // One operation, two spellings — they had two identical bodies. + "concat" | "chain" => { if positional.len() != 1 { - bail!("list.concat() expects 1 argument (list), got {}", positional.len()); - } - let lhs = list_runtime_items(clone_list(receiver, heap)?, heap); - let rhs = list_runtime_items(clone_list(&positional[0], heap)?, heap); - let merged: Vec = lhs.into_iter().chain(rhs).collect(); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(merged))), - ))) + bail!("list.{method}() expects 1 argument (list), got {}", positional.len()); + } + let merged = { + let left = clone_list(receiver, heap)?; + let right = clone_list(&positional[0], heap)?; + match typed_lists_concatenated(&left, &right) { + Some(merged) => merged, + // Different representations: materializing is the only + // thing that can join an `Int` list to a `String` one. + None => { + let mut items = list_runtime_items(left, heap); + items.extend(list_runtime_items(right, heap)); + TypedList::from_runtime_values(&items, heap) + } + } + }; + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(merged))))) } "zip" => { if positional.len() != 1 { @@ -247,13 +489,11 @@ pub(super) fn dispatch_list_builtin_method( let rhs = list_runtime_items(clone_list(&positional[0], heap)?, heap); let mut pairs = Vec::with_capacity(lhs.len().min(rhs.len())); for (a, b) in lhs.into_iter().zip(rhs) { - pairs.push(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(vec![a, b]))), - )); + let pair = TypedList::from_runtime_values(&[a, b], heap); + pairs.push(RuntimeVal::Obj(heap.alloc(HeapValue::List(pair)))); } - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(pairs))), - ))) + let pairs = TypedList::from_runtime_values(&pairs, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(pairs))))) } "flatten" => { if !positional.is_empty() { @@ -271,9 +511,8 @@ pub(super) fn dispatch_list_builtin_method( } flat.push(item); } - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(flat))), - ))) + let flat = TypedList::from_runtime_values(&flat, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(flat))))) } "chunk" => { if positional.len() != 1 { @@ -291,12 +530,12 @@ pub(super) fn dispatch_list_builtin_method( while i < items.len() { let end = (i + *size as usize).min(items.len()); let chunk: Vec = items[i..end].to_vec(); - chunks.push(RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(chunk))))); + let chunk = TypedList::from_runtime_values(&chunk, heap); + chunks.push(RuntimeVal::Obj(heap.alloc(HeapValue::List(chunk)))); i = end; } - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(chunks))), - ))) + let chunks = TypedList::from_runtime_values(&chunks, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(chunks))))) } "enumerate" => { if !positional.is_empty() { @@ -305,25 +544,11 @@ pub(super) fn dispatch_list_builtin_method( let items = list_runtime_items(clone_list(receiver, heap)?, heap); let mut pairs = Vec::with_capacity(items.len()); for (i, item) in items.into_iter().enumerate() { - pairs.push(RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(vec![ - RuntimeVal::Int(i as i64), - item, - ]))))); - } - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(pairs))), - ))) - } - "chain" => { - if positional.len() != 1 { - bail!("list.chain() expects 1 argument (list), got {}", positional.len()); + let pair = TypedList::from_runtime_values(&[RuntimeVal::Int(i as i64), item], heap); + pairs.push(RuntimeVal::Obj(heap.alloc(HeapValue::List(pair)))); } - let lhs = list_runtime_items(clone_list(receiver, heap)?, heap); - let rhs = list_runtime_items(clone_list(&positional[0], heap)?, heap); - let merged: Vec = lhs.into_iter().chain(rhs).collect(); - Ok(Some(RuntimeVal::Obj( - heap.alloc(HeapValue::List(TypedList::Mixed(merged))), - ))) + let pairs = TypedList::from_runtime_values(&pairs, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(pairs))))) } "join" => { if positional.len() != 1 { @@ -340,3 +565,50 @@ pub(super) fn dispatch_list_builtin_method( _ => Ok(None), } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::val::TypedList; + + /// The `set` arm words its range failures like the index-assignment path. + /// + /// Reached from here rather than from LK because it cannot be reached from + /// LK: the compiler rewrites every `xs.set(k, v)` into a `SetIndex`, and a + /// sentinel put in this arm survived the whole test suite and every example + /// unseen. That is exactly why it needs a test — an arm no program reaches is + /// an arm whose wording nothing checks, and this one used to say + /// `list.set() index N out of bounds (len=N)` where the route programs + /// actually take says `list index N out of bounds`. + #[test] + fn the_unreachable_set_arm_agrees_with_the_assignment_path() { + let mut heap = HeapStore::new(); + let handle = heap.alloc(HeapValue::List(TypedList::Int(vec![1, 2]))); + let receiver = RuntimeVal::Obj(handle); + + let mut message = |index: i64| { + dispatch_list_builtin_method( + &receiver, + "set", + &[RuntimeVal::Int(index), RuntimeVal::Int(5)], + &mut heap, + ) + .expect_err("out of range") + .to_string() + }; + assert_eq!(message(9), "list index 9 out of bounds"); + assert_eq!(message(-9), "list index -9 out of bounds"); + + // And it still writes, in place, answering the receiver — the effect the + // rewritten route has. + let answer = + dispatch_list_builtin_method(&receiver, "set", &[RuntimeVal::Int(-1), RuntimeVal::Int(7)], &mut heap) + .expect("in range") + .expect("handled"); + assert_eq!(answer, receiver, "`set` answers the list it wrote to"); + assert!( + matches!(heap.get(handle), Some(HeapValue::List(TypedList::Int(values))) if values == &[1, 7]), + "the write lands in the receiver's own list" + ); + } +} diff --git a/core/src/vm/context/core_methods/slice_dispatch.rs b/core/src/vm/context/core_methods/slice_dispatch.rs new file mode 100644 index 00000000..9fed85f4 --- /dev/null +++ b/core/src/vm/context/core_methods/slice_dispatch.rs @@ -0,0 +1,266 @@ +use super::*; + +/// Built-in methods on a slice — a window over a list that does not copy it. +/// +/// These were `stdlib/crates/slice`, a module whose eight exports overlapped +/// `bytes` in six names and whose other two (`sub`, `to_string`) were `bytes`' +/// operations under different spellings. What it had that `bytes` did not is +/// the list window, and that is what moved here: taking a window is something a +/// list can do, not a module you have to import first. +pub(super) fn dispatch_slice_builtin_method( + receiver: &RuntimeVal, + method: &str, + positional: &[RuntimeVal], + heap: &mut HeapStore, +) -> anyhow::Result> { + let RuntimeVal::Obj(handle) = receiver else { + return Ok(None); + }; + let Some(HeapValue::Slice(slice)) = heap.get(*handle) else { + return Ok(None); + }; + let slice = slice.clone(); + // Not `slice.len`: the source can have shrunk since the window was taken, + // and every method here has to agree about how long it is *now*. + let len = slice.live_len(heap); + + match method { + "len" => { + if !positional.is_empty() { + bail!("slice.len() expects no arguments, got {}", positional.len()); + } + Ok(Some(RuntimeVal::Int(len as i64))) + } + "is_empty" => { + if !positional.is_empty() { + bail!("slice.is_empty() expects no arguments, got {}", positional.len()); + } + Ok(Some(RuntimeVal::Bool(len == 0))) + } + "get" => { + if positional.len() != 1 { + bail!("slice.get() expects 1 argument (index), got {}", positional.len()); + } + let RuntimeVal::Int(index) = &positional[0] else { + bail!("slice.get() index must be Int"); + }; + // Same rule as `list.get` and as `w[i]`: negative counts from the + // window's end (see the note in `list_dispatch.rs`). + let index = if *index < 0 { len as i64 + *index } else { *index }; + if index < 0 || index as usize >= len { + return Ok(Some(RuntimeVal::Nil)); + } + Ok(Some(slice_item(&slice, index as usize, heap))) + } + // A window on a window, resolved against the original rather than + // nested — otherwise a loop that keeps re-slicing builds a chain. + "slice" => { + if positional.is_empty() || positional.len() > 2 { + bail!( + "slice.slice() expects 1 or 2 arguments (start[, end]), got {}", + positional.len() + ); + } + let start = super::slice_position(&positional[0], len, "slice.slice() start")?; + let end = match positional.get(1) { + Some(RuntimeVal::Nil) | None => len, + Some(value) => super::slice_position(value, len, "slice.slice() end")?, + }; + let end = end.max(start); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::Slice(Arc::new( + SliceValue { + source: slice.source, + start: slice.start + start, + len: end - start, + }, + )))))) + } + // A contiguous run of a window is still a window, so these cost + // nothing. `filter` cannot be one — what it keeps is not contiguous — + // and materializes a list instead. + "take" | "skip" => { + if positional.len() != 1 { + bail!("slice.{method}() expects 1 argument (count), got {}", positional.len()); + } + let RuntimeVal::Int(count) = &positional[0] else { + bail!("slice.{method}() count must be Int"); + }; + if *count < 0 { + bail!("slice.{method}() count must be non-negative, got {count}"); + } + let count = (*count as usize).min(len); + let (start, window_len) = if method == "take" { + (slice.start, count) + } else { + (slice.start + count, len - count) + }; + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::Slice(Arc::new( + SliceValue { + source: slice.source, + start, + len: window_len, + }, + )))))) + } + "first" => { + if !positional.is_empty() { + bail!("slice.first() expects no arguments, got {}", positional.len()); + } + if len == 0 { + return Ok(Some(RuntimeVal::Nil)); + } + Ok(Some(slice_item(&slice, 0, heap))) + } + "last" => { + if !positional.is_empty() { + bail!("slice.last() expects no arguments, got {}", positional.len()); + } + if len == 0 { + return Ok(Some(RuntimeVal::Nil)); + } + Ok(Some(slice_item(&slice, len - 1, heap))) + } + // A window over a list is a sequence too, and it answers the same three + // reductions — through the list's own helpers, so a slice and the list + // it borrows cannot give different answers for the same elements. + "min" | "max" | "sum" => { + if !positional.is_empty() { + bail!("slice.{method}() expects no arguments, got {}", positional.len()); + } + // The window's own elements, as a list: the source may have shrunk + // since the window was taken, so `live_len` decides how far it goes + // — the same rule every other method here follows. + let RuntimeVal::Obj(source) = slice.source else { + return Ok(Some(RuntimeVal::Nil)); + }; + let Some(HeapValue::List(list)) = heap.get(source) else { + return Ok(Some(RuntimeVal::Nil)); + }; + let window = list.window(slice.start, len); + if method == "sum" { + return Ok(Some(typed_list_sum(&window, heap)?)); + } + let index = typed_list_extreme_index(&window, heap, method == "max"); + Ok(Some(match index { + Some(index) => slice_item(&slice, index, heap), + None => RuntimeVal::Nil, + })) + } + "contains" | "index_of" => { + if positional.len() != 1 { + bail!("slice.{method}() expects 1 argument (value), got {}", positional.len()); + } + let needle = positional[0]; + let mut found = None; + for index in 0..len { + let item = slice_item(&slice, index, heap); + if crate::val::runtime_values_equal(&item, &needle, heap)? { + found = Some(index); + break; + } + } + Ok(Some(if method == "contains" { + RuntimeVal::Bool(found.is_some()) + } else { + found.map_or(RuntimeVal::Nil, |index| RuntimeVal::Int(index as i64)) + })) + } + // A window is a *range of its source*, and a reversed range is not one + // — so unlike `take`/`skip`/`slice`, which answer sub-windows, this + // materializes. That is the same rule `map` already follows here. + "reverse" => { + if !positional.is_empty() { + bail!("slice.reverse() expects no arguments, got {}", positional.len()); + } + let mut items: Vec = (0..len).map(|index| slice_item(&slice, index, heap)).collect(); + items.reverse(); + let items = TypedList::from_runtime_values(&items, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(items))))) + } + // Neither answer is a range of the source, so both materialize — the + // same rule `reverse` and `map` follow here. + "sort" | "unique" => { + if !positional.is_empty() { + bail!("slice.{method}() expects no arguments, got {}", positional.len()); + } + let items: Vec = (0..len).map(|index| slice_item(&slice, index, heap)).collect(); + let items = TypedList::from_runtime_values(&items, heap); + // Routed through the list implementations rather than repeated: + // `sort`'s order and `unique`'s "later duplicates dropped, order + // preserved" are rules, and a second copy of a rule is how two + // spellings of one operation come to disagree. + let answer = if method == "sort" { + typed_list_sorted(&items, heap) + } else { + typed_list_unique(&items, heap)? + }; + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(answer))))) + } + // `index_of`'s sibling, and it was on `Str` alone. + "count" => { + if positional.len() != 1 { + bail!("slice.count() expects 1 argument (value), got {}", positional.len()); + } + let needle = positional[0]; + let mut found = 0i64; + for index in 0..len { + let item = slice_item(&slice, index, heap); + if crate::val::runtime_values_equal(&item, &needle, heap)? { + found += 1; + } + } + Ok(Some(RuntimeVal::Int(found))) + } + "to_list" => { + if !positional.is_empty() { + bail!("slice.to_list() expects no arguments, got {}", positional.len()); + } + let items: Vec = (0..len).map(|index| slice_item(&slice, index, heap)).collect(); + let items = TypedList::from_runtime_values(&items, heap); + Ok(Some(RuntimeVal::Obj(heap.alloc(HeapValue::List(items))))) + } + // As in `bytes_dispatch`: the operations whose answer is a list of the + // elements are the list's, reached by materializing the window once. + // `join` too — a window's elements display the same as a list's. + "enumerate" | "zip" | "chain" | "chunk" | "concat" => { + let items: Vec = (0..len).map(|index| slice_item(&slice, index, heap)).collect(); + let items = TypedList::from_runtime_values(&items, heap); + let list = RuntimeVal::Obj(heap.alloc(HeapValue::List(items))); + super::dispatch_list_builtin_method(&list, method, positional, heap) + } + _ => Ok(None), + } +} + +/// The element at `index` *within the window*, or nil when the source is no +/// longer a list. +/// +/// Takes `&mut HeapStore` because a `TypedList::String` element is an +/// `Arc` that has to be handed back as a heap string — reading one +/// element can allocate, even though the window itself never copies. +fn slice_item(slice: &SliceValue, index: usize, heap: &mut HeapStore) -> RuntimeVal { + let RuntimeVal::Obj(handle) = slice.source else { + return RuntimeVal::Nil; + }; + let position = slice.start + index; + + enum Element { + Ready(RuntimeVal), + Text(Arc), + } + let element = match heap.get(handle) { + Some(HeapValue::List(list)) => match list { + TypedList::Mixed(values) => values.get(position).copied().map(Element::Ready), + TypedList::Int(values) => values.get(position).copied().map(RuntimeVal::Int).map(Element::Ready), + TypedList::Float(values) => values.get(position).copied().map(RuntimeVal::Float).map(Element::Ready), + TypedList::Bool(values) => values.get(position).copied().map(RuntimeVal::Bool).map(Element::Ready), + TypedList::String(values) => values.get(position).cloned().map(Element::Text), + }, + _ => None, + }; + match element { + Some(Element::Ready(value)) => value, + Some(Element::Text(text)) => make_string_val(text.as_ref(), heap), + None => RuntimeVal::Nil, + } +} diff --git a/core/src/vm/exec.rs b/core/src/vm/exec.rs index 06cf95a6..1e708f33 100644 --- a/core/src/vm/exec.rs +++ b/core/src/vm/exec.rs @@ -9,7 +9,9 @@ mod cell; mod const_load; mod container; mod dispatch; -mod format; +mod display; + +pub use display::runtime_display_value; mod frame; mod gc; mod globals; @@ -32,7 +34,8 @@ pub use imports::import_runtime_export; pub use program::test_support; pub use program::{ ModuleFunctionArg, ModuleFunctionCall, ModuleFunctionOutcome, ProgramExec, call_module_function_with_ctx, - call_module_function_with_ctx_keep_state, compile_program_module_with_ctx, execute_compiled_module_with_ctx, + call_module_function_with_ctx_keep_state, compile_program_module_with_ctx, + compile_program_module_with_ctx_and_data_globals, execute_compiled_module_with_ctx, execute_module_artifact_with_ctx, execute_program, execute_program_with_ctx, execute_program_with_ctx_and_budget, execute_program_with_ctx_and_gc_threshold, execute_program_with_ctx_and_limits, execute_source, }; @@ -45,28 +48,25 @@ pub use runtime_callable::{ copy_runtime_value, copy_runtime_value_same_module, runtime_value_to_callable_shared, }; -use crate::util::fast_map::{FastHashMap, fast_hash_map_new}; use alloc::sync::Arc; use anyhow::{Result, anyhow, bail}; -use crate::val::{ - HeapStore, HeapValue, RuntimeMapKey, RuntimeSet, RuntimeVal, TypedList, TypedMap, typed_map_from_entries, -}; +use crate::val::{HeapStore, HeapValue, RuntimeMapKey, RuntimeVal, TypedList, TypedMap, typed_map_from_entries}; +#[cfg(test)] +use super::GlobalSlot; use super::{ - CallWindow, Function, Module, NativeEntry, Opcode, RegisterIndex, RuntimeExport, RuntimeModuleState, VmContext, + CallWindow, Function, Module, Opcode, RegisterIndex, RuntimeExport, RuntimeModuleState, VmContext, analysis::{ PerfIndexTargetKind, VmCallMetric, VmContainerMetric, VmRegisterWriteSource, record_call_op_known_enabled, record_container_op_known_enabled, vm_runtime_metrics_enabled, }, }; -#[cfg(test)] -use super::{Compiler, GlobalSlot}; use call::push_traceback_frame; use frame::{CallFrame, FrameOutcome}; -pub use handler::LkRaisedValue; use handler::{ErrorHandler, LanguageRaise}; +pub use handler::{LkPanic, LkRaisedValue}; use profile::{RuntimeProfileFrame, index_metric_kind}; use return_values::ReturnValues; use support::*; @@ -92,8 +92,15 @@ pub(crate) struct ExecFailure { #[derive(Debug)] pub struct Executor { state: RuntimeModuleState, - captures: Arc>, - empty_captures: Arc>, + /// The current frame's captures — `None` when the running function has + /// none, which is every plain `fn`. + /// + /// Not an `Arc` to a shared empty vector: that spelling put a refcount + /// increment on every call and a decrement on every return, and the + /// decrement alone was 41% of `finish_return` — about a tenth of the whole + /// program in a call-heavy loop. A closure still shares its captures by + /// `Arc`; a function without any now says so. + captures: Option>>, handler_stack: Vec, frame_base: usize, register_count: u16, @@ -123,12 +130,18 @@ pub struct Executor { /// executor constructs (`NewObject`). Tracked here rather than read off /// `shared_module` because the plain `run_module*` entries pass the module /// by reference and never populate the shared handle. - type_scope: crate::vm::TypeScope, + type_scope: crate::val::TypeScope, + /// Field order for each `struct` the executing module declares — what + /// `display` prints an instance's fields in. Tracked here for the same + /// reason as `type_scope`: the plain `run_module*` entries never populate + /// `shared_module`. Cloned once per module run, and a module has a handful + /// of structs. + struct_decls: Vec, /// The identity `NewObject` built last. A loop constructing the same struct /// hits this every iteration, so the shared `Arc` is allocated once instead /// of per object — which also removes the per-object `Arc` the type /// name used to cost. - last_declared_type: Option>, + last_declared_type: Option>, instruction_budget: Option, instruction_count: u64, /// Optional cap on the number of live heap objects (sandbox memory bound). @@ -192,8 +205,7 @@ impl Executor { pub fn new(register_count: u16) -> Self { let mut this = Self { state: RuntimeModuleState::default(), - captures: Arc::new(Vec::new()), - empty_captures: Arc::new(Vec::new()), + captures: None, handler_stack: Vec::new(), frame_base: 0, register_count, @@ -204,7 +216,8 @@ impl Executor { gc_pending: false, gc_stress: gc_stress_enabled(), shared_module: None, - type_scope: crate::vm::TypeScope::anonymous(), + type_scope: crate::val::TypeScope::anonymous(), + struct_decls: Vec::new(), last_declared_type: None, instruction_budget: None, instruction_count: 0, @@ -339,10 +352,26 @@ impl Executor { let instr = code[self.pc]; let value = *self.read_unchecked(instr.b()); self.write_unchecked(instr.a(), value); + // `Move` was the one common opcode whose register write went + // into no bucket at all: `VmRegisterWriteSource::Move` had a + // slot and no site constructing it, so the write-source + // breakdown silently omitted the second most executed + // instruction — and `bench/README.md` reasons about the + // *proportions* in that breakdown. Recorded per move, inside + // the batching loop, because that is how many writes happen. + profile.record_write_source(VmRegisterWriteSource::Move, collect_metrics); self.pc += 1; if self.pc >= code.len() || code[self.pc].opcode() != Opcode::Move { break; } + // The dispatch loop records one opcode per *dispatch*, and + // this arm consumes a whole run of moves inside one — so a + // run of five counted as one. `bench/README.md` states the + // batching "preserves per-instruction profile accounting"; + // it did not, and the histogram it reasons from undercounted + // the second most executed instruction by the batch factor. + // The first move of the run was recorded before the match. + profile.record_opcode(Opcode::Move, collect_metrics); if BUDGETED { self.consume_instruction()?; } @@ -352,6 +381,9 @@ impl Executor { self.write_unchecked(instr.a(), first); let second = *self.read_unchecked(instr.c()); self.write_unchecked(instr.b(), second); + // Two writes, two records. + profile.record_write_source(VmRegisterWriteSource::Move, collect_metrics); + profile.record_write_source(VmRegisterWriteSource::Move, collect_metrics); self.pc += 1; } Opcode::LoadCapture => { @@ -373,10 +405,6 @@ impl Executor { self.dispatch_cold(Opcode::MakeClosure, function, module, instr, ctx, collect_metrics)?; let _ = &profile; // suppress unused warning } - Opcode::LoadNative => { - self.dispatch_cold(Opcode::LoadNative, function, module, instr, ctx, collect_metrics)?; - let _ = &profile; // suppress unused warning - } Opcode::AddInt => { let (dst, lhs_idx, rhs_idx) = self.stack_abc_unchecked(instr); let lhs = &self.state.stack[lhs_idx]; @@ -402,7 +430,7 @@ impl Executor { profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); self.pc += 1; } - lhs => bail!("AddIntI expected Int lhs, got {:?}", lhs.kind()), + lhs => bail!("AddIntI expected Int lhs, got {}", self.value_type_name(lhs)), } } Opcode::MulIntI => { @@ -414,7 +442,7 @@ impl Executor { profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); self.pc += 1; } - lhs => bail!("MulIntI expected Int lhs, got {:?}", lhs.kind()), + lhs => bail!("MulIntI expected Int lhs, got {}", self.value_type_name(lhs)), } } Opcode::ModIntI => { @@ -422,18 +450,26 @@ impl Executor { let lhs_idx = self.frame_base + instr.b() as usize; let rhs = instr.sc() as i64; if rhs == 0 { - bail!("ModIntI divisor is zero"); + bail!("modulo by zero"); } match &self.state.stack[lhs_idx] { RuntimeVal::Int(lhs) => { - let value = *lhs % rhs; + // `wrapping_rem`, not `%`: integer division overflow + // (`i64::MIN % -1`) *panics* in Rust — in release + // too, because the hardware traps — and a panic is + // an abort no `try` can see. The rest of the + // language's integer arithmetic already wraps at + // `i64::MIN`, and so does the native side + // (`lkrt_i64_mod_checked`), which answered `0` here + // while the interpreter took the process down. + let value = lhs.wrapping_rem(rhs); self.state.stack[dst] = RuntimeVal::Int(value); profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); if !self.try_apply_next_zero_branch_for_written_int(code, instr.a(), value) { self.pc += 1; } } - lhs => bail!("ModIntI expected Int lhs, got {:?}", lhs.kind()), + lhs => bail!("% expects an Int on the left, got {}", self.value_type_name(lhs)), } } Opcode::MinInt => { @@ -445,9 +481,9 @@ impl Executor { self.pc += 1; } (lhs, rhs) => bail!( - "MinInt expected Int operands, got {:?} and {:?}", - lhs.kind(), - rhs.kind() + "MinInt expected Int operands, got {} and {}", + self.value_type_name(lhs), + self.value_type_name(rhs) ), } } @@ -460,9 +496,9 @@ impl Executor { self.pc += 1; } (lhs, rhs) => bail!( - "MaxInt expected Int operands, got {:?} and {:?}", - lhs.kind(), - rhs.kind() + "MaxInt expected Int operands, got {} and {}", + self.value_type_name(lhs), + self.value_type_name(rhs) ), } } @@ -479,10 +515,10 @@ impl Executor { self.pc += 1; } (acc, lhs, rhs) => bail!( - "AddMulInt expected Int operands, got {:?}, {:?}, and {:?}", - acc.kind(), - lhs.kind(), - rhs.kind() + "AddMulInt expected Int operands, got {}, {}, and {}", + self.value_type_name(acc), + self.value_type_name(lhs), + self.value_type_name(rhs) ), } } @@ -499,10 +535,10 @@ impl Executor { self.pc += 1; } (acc, lhs, rhs) => bail!( - "Add2Int expected Int operands, got {:?}, {:?}, and {:?}", - acc.kind(), - lhs.kind(), - rhs.kind() + "Add2Int expected Int operands, got {}, {}, and {}", + self.value_type_name(acc), + self.value_type_name(lhs), + self.value_type_name(rhs) ), } } @@ -515,20 +551,17 @@ impl Executor { self.pc += 1; } (lhs, rhs) => bail!( - "MidInt expected Int operands, got {:?} and {:?}", - lhs.kind(), - rhs.kind() + "MidInt expected Int operands, got {} and {}", + self.value_type_name(lhs), + self.value_type_name(rhs) ), } } Opcode::AddListInt | Opcode::SubListInt => { let acc_idx = self.stack_index_unchecked(instr.a()); let RuntimeVal::Int(acc) = self.state.stack[acc_idx] else { - bail!( - "{:?} expected Int accumulator, got {:?}", - instr.opcode(), - self.state.stack[acc_idx].kind() - ); + let got = self.value_type_name(&self.state.stack[acc_idx]); + bail!("{:?} expected Int accumulator, got {got}", instr.opcode()); }; let item = self.read_known_int_list_index(instr.b(), instr.c())?; let value = if instr.opcode() == Opcode::AddListInt { @@ -572,14 +605,17 @@ impl Executor { } } } + // `/` is float division whatever the operands are — see + // `check_numeric_binary` for why the runtime moved to the + // checker's rule rather than the reverse. Two `Int`s divide as + // `f64`, so a zero divisor is an infinity, not an error. Opcode::DivInt => { let (dst, lhs_idx, rhs_idx) = self.stack_abc_unchecked(instr); let lhs = &self.state.stack[lhs_idx]; let rhs = &self.state.stack[rhs_idx]; match (lhs, rhs) { - (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("DivInt divisor is zero"), (RuntimeVal::Int(l), RuntimeVal::Int(r)) => { - self.state.stack[dst] = RuntimeVal::Int(*l / *r); + self.state.stack[dst] = RuntimeVal::Float(*l as f64 / *r as f64); profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); self.pc += 1; } @@ -594,9 +630,10 @@ impl Executor { let lhs = &self.state.stack[lhs_idx]; let rhs = &self.state.stack[rhs_idx]; match (lhs, rhs) { - (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("ModInt divisor is zero"), + (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("modulo by zero"), (RuntimeVal::Int(l), RuntimeVal::Int(r)) => { - let value = *l % *r; + // Wrapping, as in `ModIntI` above. + let value = l.wrapping_rem(*r); self.state.stack[dst] = RuntimeVal::Int(value); profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); if !self.try_apply_next_zero_branch_for_written_int(code, instr.a(), value) { @@ -621,29 +658,49 @@ impl Executor { self.float_binary(instr, |lhs, rhs| lhs * rhs)?; profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); } + // Float division by zero is IEEE's answer, not an error. + // + // `Float` *is* `f64`, and `1.0 / 0.0` is `inf` there. LK + // already admits both results as values — `math.inf` and + // `math.nan` are constants, and `math.nan + 1` propagates + // silently — so raising here protected nothing; it only made + // the natural way to reach them the one spelling that failed. + // Integer *remainder* still raises: `1 % 0` has no answer, and + // `%` — unlike `/` — keeps the operand type. Opcode::DivFloat => { - let lhs = self.read_number_unchecked(instr.b()); - let rhs = self.read_number_unchecked(instr.c()); - if rhs == 0.0 { - bail!("DivFloat divisor is zero"); - } - self.write_unchecked(instr.a(), RuntimeVal::Float(lhs / rhs)); + self.float_binary(instr, |lhs, rhs| lhs / rhs)?; profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); - self.pc += 1; } Opcode::ModFloat => { - let lhs = self.read_number_unchecked(instr.b()); - let rhs = self.read_number_unchecked(instr.c()); - if rhs == 0.0 { - bail!("ModFloat divisor is zero"); - } - self.write_unchecked(instr.a(), RuntimeVal::Float(lhs % rhs)); + self.float_binary(instr, |lhs, rhs| lhs % rhs)?; profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); - self.pc += 1; } Opcode::Not => { self.dispatch_cold(Opcode::Not, function, module, instr, ctx, collect_metrics)?; } + Opcode::Neg => { + self.dispatch_cold(Opcode::Neg, function, module, instr, ctx, collect_metrics)?; + } + Opcode::FloorDivInt => { + let (dst, lhs_idx, rhs_idx) = self.stack_abc_unchecked(instr); + match (&self.state.stack[lhs_idx], &self.state.stack[rhs_idx]) { + (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("division by zero"), + (RuntimeVal::Int(l), RuntimeVal::Int(r)) => { + // `div_euclid` panics on `i64::MIN / -1` for the + // same reason `%` does. The wrapping answer is + // `i64::MIN` (negating it overflows back to + // itself), which is what the native side computes. + let quotient = l.checked_div_euclid(*r).unwrap_or_else(|| l.wrapping_neg()); + self.state.stack[dst] = RuntimeVal::Int(quotient); + profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); + self.pc += 1; + } + _ => { + self.dispatch_floor_div_int(instr)?; + profile.record_write_source(VmRegisterWriteSource::Arithmetic, collect_metrics); + } + } + } // Casts are a cold path: driver-ish code does them at // boundaries, not in inner loops. Opcode::CastTo => { @@ -849,7 +906,7 @@ impl Executor { self.pc += 1; } } - value => bail!("BrEqZeroInt expected Int operand, got {:?}", value.kind()), + value => bail!("BrEqZeroInt expected Int operand, got {}", self.value_type_name(value)), } } Opcode::BrNeZeroInt => { @@ -863,7 +920,7 @@ impl Executor { self.pc += 1; } } - value => bail!("BrNeZeroInt expected Int operand, got {:?}", value.kind()), + value => bail!("BrNeZeroInt expected Int operand, got {}", self.value_type_name(value)), } } Opcode::BrEqIntI4 => { @@ -878,7 +935,7 @@ impl Executor { self.pc += 1; } } - value => bail!("BrEqIntI4 expected Int operand, got {:?}", value.kind()), + value => bail!("BrEqIntI4 expected Int operand, got {}", self.value_type_name(value)), } } Opcode::BrNeIntI4 => { @@ -893,7 +950,7 @@ impl Executor { self.pc += 1; } } - value => bail!("BrNeIntI4 expected Int operand, got {:?}", value.kind()), + value => bail!("BrNeIntI4 expected Int operand, got {}", self.value_type_name(value)), } } Opcode::BrModEqZeroIntI4 => { @@ -911,7 +968,10 @@ impl Executor { self.pc += 1; } } - value => bail!("BrModEqZeroIntI4 expected Int operand, got {:?}", value.kind()), + value => bail!( + "BrModEqZeroIntI4 expected Int operand, got {}", + self.value_type_name(value) + ), } } Opcode::BrModNeZeroIntI4 => { @@ -929,7 +989,10 @@ impl Executor { self.pc += 1; } } - value => bail!("BrModNeZeroIntI4 expected Int operand, got {:?}", value.kind()), + value => bail!( + "BrModNeZeroIntI4 expected Int operand, got {}", + self.value_type_name(value) + ), } } Opcode::TestEqInt => { @@ -1150,7 +1213,7 @@ impl Executor { .performance .known_key(self.pc) .and_then(|fact| fact.const_key) - .and_then(|index| function.consts.string(index)) + .and_then(|index| function.consts.shared_string(index)) }; if collect_metrics { record_container_op_known_enabled(index_metric_kind(index_fact)); @@ -1210,7 +1273,7 @@ impl Executor { if collect_metrics { record_container_op_known_enabled(index_metric_kind(index_fact)); } - let Some(key) = function.consts.string(instr.c() as u16) else { + let Some(key) = function.consts.shared_string(instr.c() as u16) else { bail!("GetFieldK const string index {} out of bounds", instr.c()); }; let value = self.get_index( @@ -1265,7 +1328,7 @@ impl Executor { .performance .known_key(self.pc) .and_then(|fact| fact.const_key) - .and_then(|index| function.consts.string(index)) + .and_then(|index| function.consts.shared_string(index)) }; if collect_metrics { record_container_op_known_enabled(index_metric_kind(index_fact)); @@ -1316,7 +1379,7 @@ impl Executor { .container_move(self.pc) .is_some_and(|fact| fact.move_value); let index_fact = self.static_index_fact(function); - let Some(key) = function.consts.string(instr.c() as u16) else { + let Some(key) = function.consts.shared_string(instr.c() as u16) else { bail!("SetFieldK const string index {} out of bounds", instr.c()); }; if collect_metrics { @@ -1378,11 +1441,17 @@ impl Executor { } } Opcode::CallMethodK => { + if collect_metrics { + record_call_op_known_enabled(VmCallMetric::Method); + } + // `method_call_ops` had arms adding it up and no site + // constructing it, so the profile reported zero method calls + // for every program. This is the opcode that makes one. self.dispatch_call_method_k(function, module, instr, ctx)?; profile.record_write_source(VmRegisterWriteSource::CallReturn, collect_metrics); } Opcode::GetGlobal => { - let slot = self.global_slot_from_fact_cache_or_instr(function, instr); + let slot = self.global_slot_from_fact_or_instr(function, instr); let value = self.read_global(slot)?; self.write(instr.a(), value)?; profile.record_write_source(VmRegisterWriteSource::Global, collect_metrics); @@ -1436,12 +1505,18 @@ fn gc_stress_enabled() -> bool { } } -/// Format a single [`RuntimeVal`] against its heap into the VM's canonical -/// display string. Exposed for host embedders that hold a `RuntimeVal` plus the -/// [`HeapStore`] it came from (e.g. `lk-api`'s ergonomic `Value` conversion for -/// heap kinds without a structured host representation). +/// Format a single [`RuntimeVal`] against its heap, for a caller that has +/// nowhere to put an error — a `Debug` impl, a diagnostic. Rendering *can* +/// fail (a dangling handle, a value nested past [`crate::val::MAX_VALUE_DEPTH`]) +/// and this reports that as the text ``. +/// +/// Anything that can propagate should call [`runtime_display_value`] instead: +/// `println` reached this one through a `Result`-returning wrapper, so a value +/// too deep to print came out as `` with the real reason dropped. pub fn display_runtime_value(value: &RuntimeVal, heap: &HeapStore) -> String { - format::format_runtime_val(value, heap, 0) + // The one renderer (`display`), not the VM's old private one: the REPL, a + // host embedder and `println` were showing the same value three ways. + display::runtime_display_value(value, heap).unwrap_or_else(|_| "".to_string()) } pub fn execute(function: &Function) -> Result { @@ -1486,4 +1561,4 @@ pub fn execute_module_with_globals_heap_and_ctx( } #[cfg(test)] -mod exec_tests; +pub(crate) mod exec_tests; diff --git a/core/src/vm/exec/arithmetic.rs b/core/src/vm/exec/arithmetic.rs index d2f70d1e..5d53845a 100644 --- a/core/src/vm/exec/arithmetic.rs +++ b/core/src/vm/exec/arithmetic.rs @@ -1,11 +1,11 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::fast_hash_map_new; +use crate::util::value_map::value_map_new; use alloc::sync::Arc; use anyhow::{Result, bail}; -use crate::val::{HeapStore, HeapValue, RuntimeMapKey, RuntimeSet, RuntimeVal, ShortStr, TypedList, TypedMap}; +use crate::val::{HeapStore, HeapValue, RuntimeMapKey, RuntimeVal, ShortStr, TypedList, TypedMap}; use crate::vm::{Instr, Opcode}; use super::Executor; @@ -115,6 +115,59 @@ fn compare_string_values(opcode: Opcode, lhs: &str, rhs: &str) -> Result { }) } +/// The operator a program wrote, for an arithmetic opcode — `None` when the +/// opcode is not one a source operator maps to. +/// +/// These messages used to print the opcode's own name: a program that wrote `%` +/// was told `ModInt expected Int or Float, got String and Int`. The opcode says +/// which *fused* form the compiler picked; nothing in the source says `ModInt`, +/// and the choice can change without the program changing. +fn operator_symbol(opcode: Opcode) -> Option<&'static str> { + Some(match opcode { + Opcode::AddInt | Opcode::AddIntI | Opcode::AddFloat => "+", + Opcode::SubInt | Opcode::SubFloat => "-", + Opcode::MulInt | Opcode::MulIntI | Opcode::MulFloat => "*", + Opcode::DivInt | Opcode::DivFloat => "/", + Opcode::ModInt | Opcode::ModIntI => "%", + // Not an operator: `//` is a *comment* in LK. Both are fused forms of + // `math.floor(a / b)`, so that is what the program wrote. + Opcode::MidInt | Opcode::FloorDivInt => "math.floor", + // The comparisons had the same problem and never got the same fix: + // `1 < "a"` reported `CmpLtInt expected Int, Float, or String`, naming + // the compiler's typed guess. A program only ever writes the operator. + Opcode::CmpInt => "==", + Opcode::CmpNeInt => "!=", + Opcode::CmpLtInt => "<", + Opcode::CmpLeInt => "<=", + Opcode::CmpGtInt => ">", + Opcode::CmpGeInt => ">=", + _ => return None, + }) +} + +impl Executor { + /// "`%` expects Int or Float, got String and Int" — or, for an opcode with + /// no source spelling, the opcode, because then it is a compiler/executor + /// mismatch and the variant name is the useful one. + /// + /// A method rather than a free function because the operand *type* name + /// needs the heap (see [`Executor::value_type_name`]). + #[cold] + fn arith_operand_error(&self, opcode: Opcode, lhs: &RuntimeVal, rhs: &RuntimeVal) -> anyhow::Error { + self.operand_error(opcode, "expects Int or Float", lhs, rhs) + } + + /// The same, with the operation's own list of what it accepts. + #[cold] + fn operand_error(&self, opcode: Opcode, accepts: &str, lhs: &RuntimeVal, rhs: &RuntimeVal) -> anyhow::Error { + let (lhs, rhs) = (self.value_type_name(lhs), self.value_type_name(rhs)); + match operator_symbol(opcode) { + Some(symbol) => anyhow::anyhow!("{symbol} {accepts}, got {lhs} and {rhs}"), + None => anyhow::anyhow!("{opcode:?} {accepts}, got {lhs} and {rhs}"), + } + } +} + impl Executor { #[cold] pub(super) fn dynamic_add(&mut self, instr: Instr) -> Result<()> { @@ -150,15 +203,37 @@ impl Executor { let list = self.add_list_values(lhs, rhs)?; RuntimeVal::Obj(self.alloc_heap_value(HeapValue::List(list))) } + // Two strings — the shape a loop that builds one is made of, and + // the shape that used to copy the accumulator *three times* per + // step: once for each side's `display_string`, once more into the + // `format!`, and once again into the `Arc`. Plus a fourth + // allocation the guard threw away, because asking "is this a + // string?" through `runtime_value_to_string` builds an `Arc` for a + // short one. + // + // Here it is one buffer of the exact size, filled once. The + // concatenation is still O(n) per step and so a loop is still + // quadratic — that is what a `String` is, and `join` is the answer + // to it. + // + // Measured, because the reasoning oversells it: interleaved + // min-of-nine on a 20k/40k/80k build gives 1.12x / 1.03x / 1.07x. + // Most of the time is the *allocator*, not the copying — ~70% of + // this loop is in libc — so removing two of three copies moves + // less than the count suggests. Making it linear needs a growable + // representation, which `HeapValue::String(Arc)` is matched + // against in 125 places and mirrored in lkrt besides; that is a + // round of its own, not a line here. + _ if let Some(joined) = self.concat_string_operands(&lhs, &rhs) => self.runtime_value_from_string(joined), _ if self.runtime_value_to_string(&lhs)?.is_some() || self.runtime_value_to_string(&rhs)?.is_some() => { let lhs = self.runtime_value_display_string(&lhs)?; let rhs = self.runtime_value_display_string(&rhs)?; self.runtime_value_from_string(Arc::::from(format!("{lhs}{rhs}"))) } _ => bail!( - "Add expected numbers or strings, got {:?} and {:?}", - lhs.kind(), - rhs.kind() + "Add expected numbers or strings, got {} and {}", + self.value_type_name(&lhs), + self.value_type_name(&rhs) ), }; self.write(instr.a(), value)?; @@ -206,15 +281,22 @@ impl Executor { RuntimeVal::Obj(self.alloc_heap_value(HeapValue::Map(map))) } _ if self.runtime_value_is_map(&lhs)? => { - let key = self.runtime_map_key_from_value(&rhs)?; - let lhs = self.runtime_value_to_typed_map(&lhs)?.expect("checked map"); - let map = typed_map_without_key(lhs, &key); + // A value that cannot be a key cannot be *in* the map, so + // removing it removes nothing. Same answer as `m.delete(k)`, + // which is the other spelling of this — and removal is a + // lookup-and-drop, not a key construction, which is the line + // `m[k]`/`m.set(k, v)`/`s.add(v)` stay on the other side of. + let lhs_map = self.runtime_value_to_typed_map(&lhs)?.expect("checked map"); + let map = match self.runtime_map_key_from_value(&rhs) { + Ok(key) => typed_map_without_key(lhs_map, &key), + Err(_) => lhs_map.clone(), + }; RuntimeVal::Obj(self.alloc_heap_value(HeapValue::Map(map))) } _ => bail!( - "Sub expected numbers or list/map lhs, got {:?} and {:?}", - lhs.kind(), - rhs.kind() + "Sub expected numbers or list/map lhs, got {} and {}", + self.value_type_name(&lhs), + self.value_type_name(&rhs) ), }; self.write(instr.a(), value)?; @@ -235,12 +317,7 @@ impl Executor { (RuntimeVal::Int(lhs), RuntimeVal::Float(rhs)) => RuntimeVal::Float(float_op(*lhs as f64, *rhs)), (RuntimeVal::Float(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Float(float_op(*lhs, *rhs as f64)), (RuntimeVal::Float(lhs), RuntimeVal::Float(rhs)) => RuntimeVal::Float(float_op(*lhs, *rhs)), - (lhs, rhs) => bail!( - "{:?} expected Int or Float, got {:?} and {:?}", - instr.opcode(), - lhs.kind(), - rhs.kind() - ), + (lhs, rhs) => return Err(self.arith_operand_error(instr.opcode(), lhs, rhs)), }; self.write_stack_index(dst, value); self.pc += 1; @@ -252,28 +329,16 @@ impl Executor { pub(super) fn dynamic_div(&mut self, instr: Instr) -> Result<()> { let (dst, lhs, rhs) = self.stack_abc_indices(instr)?; let value = match (&self.state.stack[lhs], &self.state.stack[rhs]) { - (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("DivInt divisor is zero"), - (RuntimeVal::Int(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Int(lhs / rhs), - (RuntimeVal::Int(lhs), RuntimeVal::Float(rhs)) => { - if *rhs == 0.0 { - bail!("DivInt divisor is zero"); - } - RuntimeVal::Float(*lhs as f64 / *rhs) - } - (RuntimeVal::Float(lhs), RuntimeVal::Int(rhs)) => { - if *rhs == 0 { - bail!("DivInt divisor is zero"); - } - RuntimeVal::Float(*lhs / *rhs as f64) - } - (RuntimeVal::Float(_), RuntimeVal::Float(rhs)) if *rhs == 0.0 => bail!("DivInt divisor is zero"), + // Every combination divides as `f64` — `/` yields a `Float` — so a + // zero divisor is an infinity or a NaN, both of which LK already + // has (`math.inf`, `math.nan`). This path used to raise for all + // four, with a message naming `DivInt` even when neither operand + // was one, *and* to divide two `Int`s as integers. + (RuntimeVal::Int(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Float(*lhs as f64 / *rhs as f64), + (RuntimeVal::Int(lhs), RuntimeVal::Float(rhs)) => RuntimeVal::Float(*lhs as f64 / *rhs), + (RuntimeVal::Float(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Float(*lhs / *rhs as f64), (RuntimeVal::Float(lhs), RuntimeVal::Float(rhs)) => RuntimeVal::Float(*lhs / *rhs), - (lhs, rhs) => bail!( - "{:?} expected Int or Float, got {:?} and {:?}", - instr.opcode(), - lhs.kind(), - rhs.kind() - ), + (lhs, rhs) => return Err(self.arith_operand_error(instr.opcode(), lhs, rhs)), }; self.write_stack_index(dst, value); self.pc += 1; @@ -285,28 +350,14 @@ impl Executor { pub(super) fn dynamic_mod(&mut self, instr: Instr) -> Result<()> { let (dst, lhs, rhs) = self.stack_abc_indices(instr)?; let value = match (&self.state.stack[lhs], &self.state.stack[rhs]) { - (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("ModInt divisor is zero"), - (RuntimeVal::Int(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Int(lhs % rhs), - (RuntimeVal::Int(lhs), RuntimeVal::Float(rhs)) => { - if *rhs == 0.0 { - bail!("ModInt divisor is zero"); - } - RuntimeVal::Float(*lhs as f64 % *rhs) - } - (RuntimeVal::Float(lhs), RuntimeVal::Int(rhs)) => { - if *rhs == 0 { - bail!("ModInt divisor is zero"); - } - RuntimeVal::Float(*lhs % *rhs as f64) - } - (RuntimeVal::Float(_), RuntimeVal::Float(rhs)) if *rhs == 0.0 => bail!("ModInt divisor is zero"), + // As `dynamic_div`: only `Int % Int` has no answer. + (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("modulo by zero"), + // Wrapping: `i64::MIN % -1` panics with `%` (see `ModIntI`). + (RuntimeVal::Int(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Int(lhs.wrapping_rem(*rhs)), + (RuntimeVal::Int(lhs), RuntimeVal::Float(rhs)) => RuntimeVal::Float(*lhs as f64 % *rhs), + (RuntimeVal::Float(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Float(*lhs % *rhs as f64), (RuntimeVal::Float(lhs), RuntimeVal::Float(rhs)) => RuntimeVal::Float(*lhs % *rhs), - (lhs, rhs) => bail!( - "{:?} expected Int or Float, got {:?} and {:?}", - instr.opcode(), - lhs.kind(), - rhs.kind() - ), + (lhs, rhs) => return Err(self.arith_operand_error(instr.opcode(), lhs, rhs)), }; self.write_stack_index(dst, value); self.pc += 1; @@ -314,14 +365,52 @@ impl Executor { } #[inline] + /// The float family's fast path, falling back to the dynamic form the way + /// the int family already does. + /// + /// The compiler picks `AddFloat` when it can see a `Float` operand, and it + /// does not check what the *other* one is: `"" + (1.0 + 2.0)` folds the + /// parenthesised half to a float constant and then adds a string to it. The + /// int twin has always dispatched — `AddInt` on a non-int pair calls + /// `dynamic_add` — so `"" + (1 + 2)` was fine and the float spelling of the + /// same program raised `register 3 expected Int or Float: got String`, at + /// run time, past a `lk check` that said nothing. + /// + /// Falling back rather than fixing the selection, because the selection is + /// a *guess about types* and this is the place that knows: the guard is + /// already here (it is what raised), so the cold arm costs nothing the + /// error did not. pub(super) fn float_binary(&mut self, instr: Instr, op: impl FnOnce(f64, f64) -> f64) -> Result<()> { - let lhs = self.read_number(instr.b())?; - let rhs = self.read_number(instr.c())?; - self.write(instr.a(), RuntimeVal::Float(op(lhs, rhs)))?; + let (dst, lhs_idx, rhs_idx) = self.stack_abc_indices(instr)?; + let pair = ( + self.number_value(&self.state.stack[lhs_idx]).ok(), + self.number_value(&self.state.stack[rhs_idx]).ok(), + ); + let (Some(lhs), Some(rhs)) = pair else { + return self.dynamic_float_fallback(instr); + }; + self.state.stack[dst] = RuntimeVal::Float(op(lhs, rhs)); self.pc += 1; Ok(()) } + /// What a float opcode means when its operands are not both numbers: the + /// same thing its int twin means, which is the dynamic operation. + #[cold] + fn dynamic_float_fallback(&mut self, instr: Instr) -> Result<()> { + match instr.opcode() { + Opcode::AddFloat => self.dynamic_add(instr), + Opcode::SubFloat => self.dynamic_sub(instr), + // `*` has no `dynamic_mul` of its own; the numeric form is what + // `MulInt` falls back to, and a non-numeric operand raises there + // with the same wording the interpreter gives everywhere else. + Opcode::MulFloat => self.dynamic_numeric_binary(instr, |l, r| l.wrapping_mul(r), |l, r| l * r), + Opcode::DivFloat => self.dynamic_div(instr), + Opcode::ModFloat => self.dynamic_mod(instr), + other => bail!("{other:?} is not a float arithmetic opcode"), + } + } + #[inline] #[cold] pub(super) fn number_compare( @@ -340,12 +429,7 @@ impl Executor { if let (Some(lhs), Some(rhs)) = (self.runtime_string_value(lhs)?, self.runtime_string_value(rhs)?) { compare_string_values(instr.opcode(), &lhs, &rhs)? } else { - bail!( - "{:?} expected Int, Float, or String, got {:?} and {:?}", - instr.opcode(), - lhs.kind(), - rhs.kind() - ) + return Err(self.operand_error(instr.opcode(), "expected Int, Float, or String", lhs, rhs)); } } }; @@ -367,12 +451,7 @@ impl Executor { if let (Some(lhs), Some(rhs)) = (self.runtime_string_value(lhs)?, self.runtime_string_value(rhs)?) { compare_string_values(opcode, &lhs, &rhs) } else { - bail!( - "{:?} expected Int, Float, or String, got {:?} and {:?}", - opcode, - lhs.kind(), - rhs.kind() - ) + Err(self.operand_error(opcode, "expected Int, Float, or String", lhs, rhs)) } } } @@ -384,33 +463,10 @@ impl Executor { self.runtime_values_equal(&self.state.stack[lhs], &self.state.stack[rhs]) } - fn runtime_values_equal(&self, lhs: &RuntimeVal, rhs: &RuntimeVal) -> Result { - Ok(match (lhs, rhs) { - (RuntimeVal::Nil, RuntimeVal::Nil) => true, - (RuntimeVal::Bool(lhs), RuntimeVal::Bool(rhs)) => lhs == rhs, - (RuntimeVal::Int(lhs), RuntimeVal::Int(rhs)) => lhs == rhs, - (RuntimeVal::Float(lhs), RuntimeVal::Float(rhs)) => lhs == rhs, - (RuntimeVal::Int(lhs), RuntimeVal::Float(rhs)) => *lhs as f64 == *rhs, - (RuntimeVal::Float(lhs), RuntimeVal::Int(rhs)) => *lhs == *rhs as f64, - (RuntimeVal::Obj(lhs), RuntimeVal::Obj(rhs)) if lhs == rhs => true, - (RuntimeVal::Obj(lhs), RuntimeVal::Obj(rhs)) => { - let lhs = self - .state - .heap - .get(*lhs) - .ok_or_else(|| anyhow::anyhow!("heap object {} out of bounds", lhs.index()))?; - let rhs = self - .state - .heap - .get(*rhs) - .ok_or_else(|| anyhow::anyhow!("heap object {} out of bounds", rhs.index()))?; - self.heap_values_equal(lhs, rhs)? - } - _ => match (self.runtime_value_to_string(lhs)?, self.runtime_value_to_string(rhs)?) { - (Some(lhs), Some(rhs)) => lhs == rhs, - _ => false, - }, - }) + /// `==`. One implementation, shared with the container methods and living + /// with the value model — see [`crate::val::runtime_values_equal`]. + pub(in crate::vm::exec) fn runtime_values_equal(&self, lhs: &RuntimeVal, rhs: &RuntimeVal) -> Result { + crate::val::runtime_values_equal(lhs, rhs, &self.state.heap) } fn runtime_value_to_list_snapshot(&self, value: &RuntimeVal) -> Result> { @@ -833,7 +889,9 @@ impl Executor { RuntimeListSnapshot::Int(rhs) => self.runtime_values_equal(&lhs, &RuntimeVal::Int(rhs[rhs_index])), RuntimeListSnapshot::Float(rhs) => self.runtime_values_equal(&lhs, &RuntimeVal::Float(rhs[rhs_index])), RuntimeListSnapshot::Bool(rhs) => self.runtime_values_equal(&lhs, &RuntimeVal::Bool(rhs[rhs_index])), - RuntimeListSnapshot::String(rhs) => self.runtime_value_equals_string(&lhs, &rhs[rhs_index]), + RuntimeListSnapshot::String(rhs) => { + crate::val::runtime_value_equals_str(&lhs, &rhs[rhs_index], &self.state.heap) + } } } @@ -844,12 +902,42 @@ impl Executor { rhs_index: usize, ) -> Result { match rhs { - RuntimeListSnapshot::Mixed(rhs) => self.runtime_value_equals_string(&rhs[rhs_index], lhs), + RuntimeListSnapshot::Mixed(rhs) => { + crate::val::runtime_value_equals_str(&rhs[rhs_index], lhs, &self.state.heap) + } RuntimeListSnapshot::String(rhs) => Ok(lhs == &rhs[rhs_index]), _ => Ok(false), } } + /// Both operands as `&str` when both *are* strings, joined into one + /// exactly-sized buffer. `None` when either is not a string, which sends the + /// caller to the general display-concatenation path. + /// + /// Borrowed rather than cloned: a heap string reached through `Arc::clone` + /// costs a refcount, and a short one costs an allocation that is then thrown + /// away. Neither is needed to read a string's bytes. + fn concat_string_operands(&self, lhs: &RuntimeVal, rhs: &RuntimeVal) -> Option> { + let (left, right) = (self.borrowed_str(lhs)?, self.borrowed_str(rhs)?); + let mut joined = String::with_capacity(left.len() + right.len()); + joined.push_str(left); + joined.push_str(right); + Some(Arc::from(joined)) + } + + /// A string operand's bytes without copying them: the inline short form + /// borrows from the value, the heap form from the slot. + fn borrowed_str<'a>(&'a self, value: &'a RuntimeVal) -> Option<&'a str> { + match value { + RuntimeVal::ShortStr(value) => Some(value.as_str()), + RuntimeVal::Obj(handle) => match self.state.heap.get(*handle)? { + HeapValue::String(value) => Some(value), + _ => None, + }, + _ => None, + } + } + fn runtime_string_value(&self, value: &RuntimeVal) -> Result>> { match value { RuntimeVal::ShortStr(value) => Ok(Some(Arc::::from(value.as_str()))), @@ -866,154 +954,6 @@ impl Executor { } } - fn heap_values_equal(&self, lhs: &HeapValue, rhs: &HeapValue) -> Result { - Ok(match (lhs, rhs) { - (HeapValue::String(lhs), HeapValue::String(rhs)) => lhs == rhs, - (HeapValue::Bytes(lhs), HeapValue::Bytes(rhs)) => lhs == rhs, - (HeapValue::List(lhs), HeapValue::List(rhs)) => self.typed_lists_equal(lhs, rhs)?, - (HeapValue::Map(lhs), HeapValue::Map(rhs)) => self.typed_maps_equal(lhs, rhs)?, - (HeapValue::Set(lhs), HeapValue::Set(rhs)) => runtime_sets_equal(lhs, rhs), - _ => false, - }) - } - - fn typed_lists_equal(&self, lhs: &TypedList, rhs: &TypedList) -> Result { - if lhs.len() != rhs.len() { - return Ok(false); - } - match (lhs, rhs) { - (TypedList::Int(lhs), TypedList::Int(rhs)) => return Ok(lhs == rhs), - (TypedList::Float(lhs), TypedList::Float(rhs)) => return Ok(lhs == rhs), - (TypedList::Bool(lhs), TypedList::Bool(rhs)) => return Ok(lhs == rhs), - (TypedList::String(lhs), TypedList::String(rhs)) => return Ok(lhs == rhs), - _ => {} - } - for index in 0..lhs.len() { - if !self.typed_list_items_equal(lhs, index, rhs, index)? { - return Ok(false); - } - } - Ok(true) - } - - fn typed_list_items_equal( - &self, - lhs: &TypedList, - lhs_index: usize, - rhs: &TypedList, - rhs_index: usize, - ) -> Result { - match (lhs, rhs) { - (TypedList::Mixed(lhs), TypedList::Mixed(rhs)) => { - self.runtime_values_equal(&lhs[lhs_index], &rhs[rhs_index]) - } - (TypedList::Mixed(lhs), TypedList::String(rhs)) => { - self.runtime_value_equals_string(&lhs[lhs_index], &rhs[rhs_index]) - } - (TypedList::String(lhs), TypedList::Mixed(rhs)) => { - self.runtime_value_equals_string(&rhs[rhs_index], &lhs[lhs_index]) - } - (TypedList::Int(lhs), _) => { - self.typed_list_runtime_item_equal(RuntimeVal::Int(lhs[lhs_index]), rhs, rhs_index) - } - (TypedList::Float(lhs), _) => { - self.typed_list_runtime_item_equal(RuntimeVal::Float(lhs[lhs_index]), rhs, rhs_index) - } - (TypedList::Bool(lhs), _) => { - self.typed_list_runtime_item_equal(RuntimeVal::Bool(lhs[lhs_index]), rhs, rhs_index) - } - (TypedList::String(lhs), _) => self.typed_list_string_item_equal(&lhs[lhs_index], rhs, rhs_index), - (TypedList::Mixed(lhs), _) => self.typed_list_runtime_item_equal(lhs[lhs_index], rhs, rhs_index), - } - } - - fn typed_list_runtime_item_equal(&self, lhs: RuntimeVal, rhs: &TypedList, rhs_index: usize) -> Result { - match rhs { - TypedList::Mixed(rhs) => self.runtime_values_equal(&lhs, &rhs[rhs_index]), - TypedList::Int(rhs) => self.runtime_values_equal(&lhs, &RuntimeVal::Int(rhs[rhs_index])), - TypedList::Float(rhs) => self.runtime_values_equal(&lhs, &RuntimeVal::Float(rhs[rhs_index])), - TypedList::Bool(rhs) => self.runtime_values_equal(&lhs, &RuntimeVal::Bool(rhs[rhs_index])), - TypedList::String(rhs) => self.runtime_value_equals_string(&lhs, &rhs[rhs_index]), - } - } - - fn typed_list_string_item_equal(&self, lhs: &Arc, rhs: &TypedList, rhs_index: usize) -> Result { - match rhs { - TypedList::Mixed(rhs) => self.runtime_value_equals_string(&rhs[rhs_index], lhs), - TypedList::String(rhs) => Ok(lhs == &rhs[rhs_index]), - _ => Ok(false), - } - } - - fn runtime_value_equals_string(&self, value: &RuntimeVal, expected: &str) -> Result { - Ok(match value { - RuntimeVal::ShortStr(value) => value.as_str() == expected, - RuntimeVal::Obj(handle) => matches!( - self.state - .heap - .get(*handle) - .ok_or_else(|| anyhow::anyhow!("heap object {} out of bounds", handle.index()))?, - HeapValue::String(value) if value.as_ref() == expected - ), - _ => false, - }) - } - - fn typed_maps_equal(&self, lhs: &TypedMap, rhs: &TypedMap) -> Result { - if lhs.len() != rhs.len() { - return Ok(false); - } - match lhs { - TypedMap::Mixed(entries) => { - for (key, value) in entries { - if !self.typed_map_value_equal(rhs, key, value)? { - return Ok(false); - } - } - } - TypedMap::StringMixed(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !self.typed_map_value_equal(rhs, &key, value)? { - return Ok(false); - } - } - } - TypedMap::StringInt(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !self.typed_map_value_equal(rhs, &key, &RuntimeVal::Int(*value))? { - return Ok(false); - } - } - } - TypedMap::StringFloat(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !self.typed_map_value_equal(rhs, &key, &RuntimeVal::Float(*value))? { - return Ok(false); - } - } - } - TypedMap::StringBool(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !self.typed_map_value_equal(rhs, &key, &RuntimeVal::Bool(*value))? { - return Ok(false); - } - } - } - } - Ok(true) - } - - fn typed_map_value_equal(&self, rhs: &TypedMap, key: &RuntimeMapKey, lhs_value: &RuntimeVal) -> Result { - let Some(rhs_value) = rhs.get(key) else { - return Ok(false); - }; - self.runtime_values_equal(lhs_value, &rhs_value) - } - fn runtime_value_to_typed_map(&self, value: &RuntimeVal) -> Result> { let RuntimeVal::Obj(handle) = value else { return Ok(None); @@ -1025,10 +965,6 @@ impl Executor { } } -fn runtime_sets_equal(lhs: &RuntimeSet, rhs: &RuntimeSet) -> bool { - lhs.len() == rhs.len() && lhs.entries().all(|key| rhs.contains(key)) -} - fn merge_typed_maps(lhs: &TypedMap, rhs: &TypedMap) -> TypedMap { let mut replaced_keys = Vec::with_capacity(rhs.len()); for_each_typed_map_key(rhs, |key| replaced_keys.push(key)); @@ -1052,22 +988,22 @@ fn for_each_typed_map_entry(map: &TypedMap, mut visit: impl FnMut(RuntimeMapKey, } TypedMap::StringMixed(entries) => { for (key, value) in entries { - visit(RuntimeMapKey::String(key.clone()), *value); + visit(RuntimeMapKey::from_shared(key.clone()), *value); } } TypedMap::StringInt(entries) => { for (key, value) in entries { - visit(RuntimeMapKey::String(key.clone()), RuntimeVal::Int(*value)); + visit(RuntimeMapKey::from_shared(key.clone()), RuntimeVal::Int(*value)); } } TypedMap::StringFloat(entries) => { for (key, value) in entries { - visit(RuntimeMapKey::String(key.clone()), RuntimeVal::Float(*value)); + visit(RuntimeMapKey::from_shared(key.clone()), RuntimeVal::Float(*value)); } } TypedMap::StringBool(entries) => { for (key, value) in entries { - visit(RuntimeMapKey::String(key.clone()), RuntimeVal::Bool(*value)); + visit(RuntimeMapKey::from_shared(key.clone()), RuntimeVal::Bool(*value)); } } } @@ -1082,22 +1018,22 @@ fn for_each_typed_map_key(map: &TypedMap, mut visit: impl FnMut(RuntimeMapKey)) } TypedMap::StringMixed(entries) => { for key in entries.keys() { - visit(RuntimeMapKey::String(key.clone())); + visit(RuntimeMapKey::from_shared(key.clone())); } } TypedMap::StringInt(entries) => { for key in entries.keys() { - visit(RuntimeMapKey::String(key.clone())); + visit(RuntimeMapKey::from_shared(key.clone())); } } TypedMap::StringFloat(entries) => { for key in entries.keys() { - visit(RuntimeMapKey::String(key.clone())); + visit(RuntimeMapKey::from_shared(key.clone())); } } TypedMap::StringBool(entries) => { for key in entries.keys() { - visit(RuntimeMapKey::String(key.clone())); + visit(RuntimeMapKey::from_shared(key.clone())); } } } @@ -1110,7 +1046,7 @@ fn typed_map_without_key(map: &TypedMap, removed_key: &RuntimeMapKey) -> TypedMa fn typed_map_without_merge_keys(map: &TypedMap, replaced_keys: &[RuntimeMapKey]) -> TypedMap { match map { TypedMap::Mixed(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !replaced_keys.contains(key) { out.insert(key.clone(), *value); @@ -1119,7 +1055,7 @@ fn typed_map_without_merge_keys(map: &TypedMap, replaced_keys: &[RuntimeMapKey]) TypedMap::Mixed(out) } TypedMap::StringMixed(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, replaced_keys) { out.insert(key.clone(), *value); @@ -1128,7 +1064,7 @@ fn typed_map_without_merge_keys(map: &TypedMap, replaced_keys: &[RuntimeMapKey]) TypedMap::StringMixed(out) } TypedMap::StringInt(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, replaced_keys) { out.insert(key.clone(), *value); @@ -1137,7 +1073,7 @@ fn typed_map_without_merge_keys(map: &TypedMap, replaced_keys: &[RuntimeMapKey]) TypedMap::StringInt(out) } TypedMap::StringFloat(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, replaced_keys) { out.insert(key.clone(), *value); @@ -1146,7 +1082,7 @@ fn typed_map_without_merge_keys(map: &TypedMap, replaced_keys: &[RuntimeMapKey]) TypedMap::StringFloat(out) } TypedMap::StringBool(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, replaced_keys) { out.insert(key.clone(), *value); @@ -1160,7 +1096,7 @@ fn typed_map_without_merge_keys(map: &TypedMap, replaced_keys: &[RuntimeMapKey]) fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> TypedMap { match map { TypedMap::Mixed(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !runtime_map_key_removed(key, removed_keys) { out.insert(key.clone(), *value); @@ -1169,7 +1105,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::Mixed(out) } TypedMap::StringMixed(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(key.clone(), *value); @@ -1178,7 +1114,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::StringMixed(out) } TypedMap::StringInt(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(key.clone(), *value); @@ -1187,7 +1123,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::StringInt(out) } TypedMap::StringFloat(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(key.clone(), *value); @@ -1196,7 +1132,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::StringFloat(out) } TypedMap::StringBool(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(key.clone(), *value); diff --git a/core/src/vm/exec/call.rs b/core/src/vm/exec/call.rs index 4504d0c6..4110c812 100644 --- a/core/src/vm/exec/call.rs +++ b/core/src/vm/exec/call.rs @@ -1,5 +1,6 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; +use alloc::borrow::Cow; use alloc::sync::Arc; use anyhow::{Result, anyhow, bail}; @@ -72,8 +73,33 @@ pub(super) fn callable_target( | (PerfCallTargetKind::Unknown, HeapValue::Callable(CallableValue::Runtime(function))) => { Ok(CallableTarget::Runtime(Arc::clone(function))) } + // The value *is* a callable, but not the flavour the call site was + // compiled for — a fact mismatch, which is the runtime's problem and + // not the program's. (_, HeapValue::Callable(_)) => bail!("{error}"), - _ => bail!("{error}"), + // Not a callable at all. Say what it is: the common way to get here is + // calling a module (`use chan;` shadows the `chan()` global with the + // module, which is a Map of its members), and "is not callable" alone + // leaves nothing to act on. + (_, other) => bail!( + "{error}: it is a {}{}", + HeapValue::type_name(other), + module_shaped_hint(other) + ), + } +} + +/// The nudge for a value that is a map. +/// +/// An imported module *is* a map of its members, and `use chan;` binds it over +/// the `chan()` global — so `chan(1)` calls a Map. That is a documented sharp +/// edge (see `docs/semantics.md`), and this is where a program meets it. A map +/// is not callable for any other reason either, so the hint costs nothing when +/// the value is an ordinary one. +fn module_shaped_hint(value: &HeapValue) -> &'static str { + match value { + HeapValue::Map(_) => " — an imported module is a map of its members, so call one of them (`m.f(…)`)", + _ => "", } } @@ -139,7 +165,7 @@ impl Executor { .heap .get(handle) .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?, - "Call callee is not callable", + "this value is not a function", )?; match callable { @@ -148,7 +174,7 @@ impl Executor { captures, } => { let function = checked_positional_function(module, function_index, window.arg_count)?; - self.push_call_frame(function_index, function, captures, window)?; + self.push_call_frame(function_index, function, Some(captures), window)?; Ok(CallOutcome::Pushed(function_index)) } CallableTarget::RuntimeNative { arity, function } => { @@ -160,7 +186,7 @@ impl Executor { ); } let native = NativeEntry { - name: "".to_string(), + name: Cow::Borrowed(""), arity, function, }; @@ -194,10 +220,17 @@ impl Executor { } CallableTarget::Runtime(function) => { let args = self.call_args_stack_range(window)?; - let result = runtime_callable::call_runtime_callable_runtime( + // The executor is the one place that knows which module these + // arguments come from, and a *function* among them needs that: + // it is an index into this module's table, and crossing into + // another module is what promotes it to a callable carrying + // this one (`ClosureCopy::Promote`). + let caller_module = self.shared_module.clone(); + let result = runtime_callable::call_runtime_callable_runtime_from( function.as_ref(), &self.state.stack[args], &mut self.state.heap, + caller_module.as_ref(), ctx.as_deref_mut(), ); result @@ -215,9 +248,11 @@ impl Executor { window: CallWindow, ) -> Result<()> { let module = module.ok_or_else(|| anyhow!("CallDirect requires Module execution"))?; - let captures = Arc::clone(&self.empty_captures); let function = checked_positional_function(module, function_index, window.arg_count)?; - self.push_call_frame(function_index, function, captures, window) + // `None`, not a clone of a shared empty vector: a direct call is the + // most common thing a program does, and the refcount pair it used to + // pay for saying "no captures" showed up as a tenth of the run. + self.push_call_frame(function_index, function, None, window) } /// Push a suspended caller `CallFrame` and switch the executor's "current @@ -229,7 +264,7 @@ impl Executor { &mut self, function_index: u32, function: &Function, - captures: Arc>, + captures: Option>>, window: CallWindow, ) -> Result<()> { let arg_range = self.call_args_stack_range(window)?; @@ -243,8 +278,10 @@ impl Executor { self.state.stack.resize(new_top, RuntimeVal::Nil); } let reg_count = function.register_count as usize; - self.state.stack[new_base..new_base + reg_count].fill(RuntimeVal::Nil); - let param_count = window.arg_count as usize; + // The parameter slots are about to be overwritten wholesale, so they do + // not need nilling first — only the locals above them do. + let param_count = (window.arg_count as usize).min(reg_count); + self.state.stack[new_base + param_count..new_base + reg_count].fill(RuntimeVal::Nil); for i in 0..param_count { let src = arg_range.start + i; let dst = new_base + i; @@ -276,7 +313,7 @@ impl Executor { &mut self, function_index: u32, function: &Function, - captures: Arc>, + captures: Option>>, window: CallWindow, named_count: u16, ) -> Result<()> { diff --git a/core/src/vm/exec/callable_ops.rs b/core/src/vm/exec/callable_ops.rs index f5b9834f..3a8d4834 100644 --- a/core/src/vm/exec/callable_ops.rs +++ b/core/src/vm/exec/callable_ops.rs @@ -45,22 +45,6 @@ impl Executor { self.write(dst, value) } - #[cold] - pub(super) fn load_native_value(&mut self, dst: u8, native_index: u16, module: Option<&Module>) -> Result<()> { - let native_index = native_index as usize; - let module = module.ok_or_else(|| anyhow!("LoadNative requires Module execution"))?; - let native = module - .natives - .get(native_index) - .ok_or_else(|| anyhow!("LoadNative index {} out of bounds", native_index))?; - let value = RuntimeVal::Obj(self.alloc_heap_value(HeapValue::Callable(CallableValue::RuntimeNative { - name: Arc::::from(native.name.as_str()), - arity: native.arity, - function: native.function.clone(), - }))); - self.write(dst, value) - } - fn capture_values(&self, base: u8, count: u16) -> Result> { let count = usize::from(count); if usize::from(base) + count > usize::from(self.register_count) { diff --git a/core/src/vm/exec/const_load.rs b/core/src/vm/exec/const_load.rs index 5d0b8327..0fa7c1cb 100644 --- a/core/src/vm/exec/const_load.rs +++ b/core/src/vm/exec/const_load.rs @@ -1,12 +1,12 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::fast_hash_map_new; +use crate::util::value_map::value_map_new; use anyhow::{Result, anyhow}; use alloc::sync::Arc; use crate::{ - val::{HeapValue, RuntimeVal, ShortStr, TypedList, typed_map_from_entries}, + val::{HeapValue, RuntimeVal, ShortStr, TypedList, TypedMap, typed_map_from_entries}, vm::{ConstHeapValue, ConstRuntimeValue, Function, Instr, Opcode}, }; @@ -58,17 +58,7 @@ impl Executor { self.write(instr.a(), value)?; } } - Opcode::LoadHeapConst => { - let value = function - .consts - .heap_value(instr.bx()) - .ok_or_else(|| anyhow!("LoadHeapConst const index {} out of bounds", instr.bx()))?; - let value = self.materialize_heap_const(value.clone())?; - if !dead_write { - let handle = self.alloc_heap_value(value); - self.write(instr.a(), RuntimeVal::Obj(handle))?; - } - } + Opcode::LoadHeapConst => self.load_heap_const(function, instr, dead_write)?, _ => unreachable!("load_const_instr called for non-const opcode"), } @@ -76,39 +66,77 @@ impl Executor { Ok(()) } - fn materialize_const_value(&mut self, value: ConstRuntimeValue) -> Result { + /// `LoadHeapConst`: rebuild a list/map/long-string constant on the heap. + /// + /// Split out, and `#[cold]`, because of the *stack frame*. Materializing a + /// constant map needs a `FastHashMap` and a recursive walk, and its + /// temporaries sized the frame of the function that also handles + /// `LoadNil`/`LoadInt`/`LoadString` — 184 bytes of `sub rsp` paid by every + /// constant load in the program, and constants are loaded constantly (this + /// function was 6.7% of a rule-scoring workload, with the prologue its + /// single largest line). + #[cold] + fn load_heap_const(&mut self, function: &Function, instr: Instr, dead_write: bool) -> Result<()> { + let value = function + .consts + .heap_value(instr.bx()) + .ok_or_else(|| anyhow!("LoadHeapConst const index {} out of bounds", instr.bx()))?; + let value = self.materialize_heap_const(value)?; + if !dead_write { + let handle = self.alloc_heap_value(value); + self.write(instr.a(), RuntimeVal::Obj(handle))?; + } + Ok(()) + } + + fn materialize_const_value(&mut self, value: &ConstRuntimeValue) -> Result { Ok(match value { ConstRuntimeValue::Nil => RuntimeVal::Nil, - ConstRuntimeValue::Bool(value) => RuntimeVal::Bool(value), - ConstRuntimeValue::Int(value) => RuntimeVal::Int(value), - ConstRuntimeValue::Float(value) => RuntimeVal::Float(value), - ConstRuntimeValue::ShortStr(value) => RuntimeVal::ShortStr(value), + ConstRuntimeValue::Bool(value) => RuntimeVal::Bool(*value), + ConstRuntimeValue::Int(value) => RuntimeVal::Int(*value), + ConstRuntimeValue::Float(value) => RuntimeVal::Float(*value), + ConstRuntimeValue::ShortStr(value) => RuntimeVal::ShortStr(*value), ConstRuntimeValue::Heap(value) => { - let value = self.materialize_heap_const(*value)?; + let value = self.materialize_heap_const(value)?; RuntimeVal::Obj(self.alloc_heap_value(value)) } }) } - fn materialize_heap_const(&mut self, value: ConstHeapValue) -> Result { + /// Builds the runtime value a constant describes. + /// + /// By reference: the constant stays in the function's pool and is read, + /// not consumed. Taking it by value meant cloning the whole structure — + /// every key, every nested constant — and then walking the clone to build + /// the real thing, so a constant container cost two deep copies per load + /// instead of one. `ConstHeapValue::clone` plus its drop was 5% of a + /// map-building workload; a `{}` in a loop is a common shape. + fn materialize_heap_const(&mut self, value: &ConstHeapValue) -> Result { Ok(match value { - ConstHeapValue::LongString(value) => HeapValue::String(value), + // An empty `{}` or `[]` — what a loop body allocates over and over + // — has nothing to walk. The general path still built an empty + // `ValueMap`, called down a level, and ran a shape scan that gave + // up on the first look; that was 4% of a workload whose whole loop + // body is `let config = {};` plus four lookups. + ConstHeapValue::Map(values) if values.is_empty() => HeapValue::Map(TypedMap::Mixed(value_map_new())), + ConstHeapValue::List(values) if values.is_empty() => HeapValue::List(TypedList::Mixed(Vec::new())), + ConstHeapValue::LongString(value) => HeapValue::String(Arc::clone(value)), ConstHeapValue::List(values) => { let list = self.materialize_const_list(values)?; HeapValue::List(list) } ConstHeapValue::Map(values) => { - let mut runtime_entries = fast_hash_map_new(); + let mut runtime_entries = value_map_new(); for (key, value) in values { - runtime_entries.insert(key, self.materialize_const_value(value)?); + runtime_entries.insert(key.clone(), self.materialize_const_value(value)?); } HeapValue::Map(typed_map_from_entries(runtime_entries)) } - ConstHeapValue::UpvalCell(value) => HeapValue::UpvalCell(self.materialize_const_value(*value)?), + ConstHeapValue::UpvalCell(value) => HeapValue::UpvalCell(self.materialize_const_value(value)?), }) } - fn materialize_const_list(&mut self, values: Vec) -> Result { + fn materialize_const_list(&mut self, values: &[ConstRuntimeValue]) -> Result { let mut original = Vec::with_capacity(values.len()); let mut shape = ConstListShape::Empty; for value in values { diff --git a/core/src/vm/exec/container.rs b/core/src/vm/exec/container.rs index ef7ba2db..1048401f 100644 --- a/core/src/vm/exec/container.rs +++ b/core/src/vm/exec/container.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::{FastHashMap, fast_hash_map_new}; +use crate::util::value_map::{ValueMap, value_map_new}; use alloc::sync::Arc; use anyhow::{Result, anyhow, bail}; @@ -10,7 +10,7 @@ use crate::val::{ }; use super::profile::{record_dynamic_index_key_metric, record_index_key_metric}; -use super::{Executor, heap_kind, push_list_value, set_list_value}; +use super::{Executor, set_list_value}; use crate::vm::{ IndexInlineCache, analysis::{PerfIndexFact, PerfIndexTargetKind, PerfValueKind, VM_INDEX_KEY_METRIC_COUNT, VmIndexKeyMetric}, @@ -27,6 +27,8 @@ enum IndexTargetKind { Map, Object, String, + Slice, + Bytes, } enum SliceFromPlan { @@ -78,8 +80,8 @@ impl Executor { Ok(out) } - pub(super) fn read_map_entries(&self, base: u8, count: u8) -> Result> { - let mut values = fast_hash_map_new(); + pub(super) fn read_map_entries(&self, base: u8, count: u8) -> Result> { + let mut values = value_map_new(); for entry in 0..count { let key_reg = base .checked_add(entry.checked_mul(2).expect("map entry register overflow")) @@ -100,8 +102,8 @@ impl Executor { count: u8, move_keys: bool, move_values: bool, - ) -> Result> { - let mut values = fast_hash_map_new(); + ) -> Result> { + let mut values = value_map_new(); for entry in 0..count { let key_reg = base .checked_add(entry.checked_mul(2).expect("map entry register overflow")) @@ -121,12 +123,44 @@ impl Executor { Ok(values) } + /// A struct field's name, as the `Arc` the object will key by. + /// + /// The general conversion renders a value into a fresh `String`, which this + /// then copied into an `Arc` — two allocations per field per construction, + /// and the rendering was 7.5% of a loop that builds one struct. A field name + /// is a string already: a heap one *is* an `Arc` and is shared, and an + /// inline one is copied once. + fn field_name_from_register(&self, register: u8, ty: &crate::val::DeclaredType) -> Result> { + let borrowed = match self.read(register)? { + RuntimeVal::ShortStr(text) => Some(text.as_str()), + RuntimeVal::Obj(handle) => match self.state.heap.get(*handle) { + // A heap field name is an `Arc` already, and the object may + // share it. + Some(HeapValue::String(text)) => return Ok(Arc::clone(text)), + _ => None, + }, + _ => None, + }; + if let Some(text) = borrowed { + // The declaration holds this name, so every instance keys by the + // *same* `Arc` — one allocation for the program, not one per + // construction. A name the declaration does not have (an undeclared + // field, or a struct whose declaration is out of reach) still costs + // its own. + if let Some(shared) = ty.declared_field_name(text) { + return Ok(Arc::clone(shared)); + } + return Ok(Arc::::from(text)); + } + Ok(Arc::::from(self.to_runtime_string(register)?)) + } + pub(super) fn read_object_fields(&mut self, base: u8, count: u8) -> Result { let ty = self.declared_type(base)?; let field_base = base .checked_add(1) .ok_or_else(|| anyhow!("object field base overflow"))?; - let mut fields = fast_hash_map_new(); + let mut fields = value_map_new(); for entry in 0..count { let offset = entry .checked_mul(2) @@ -137,10 +171,22 @@ impl Executor { let value_reg = key_reg .checked_add(1) .ok_or_else(|| anyhow!("object value register overflow"))?; - fields.insert( - Arc::::from(self.to_runtime_string(key_reg)?), - *self.read(value_reg)?, - ); + let key = self.field_name_from_register(key_reg, &ty)?; + let value = *self.read(value_reg)?; + // Construction is checked against the declaration for the same + // reason a store is: `A { v: x }` with an untyped `x` is a write + // the type checker cannot see. + if let Some(declared) = ty.field_type(&key) + && !crate::val::value_satisfies_declared(&value, declared, &self.state.heap) + { + let declared = declared.display(); + bail!( + "field `{key}` of {} is declared {declared}, and a {} cannot be stored in it", + ty.name, + self.value_type_name(&value) + ); + } + fields.insert(key, value); } Ok(RuntimeObject::new(ty, fields)) } @@ -151,7 +197,7 @@ impl Executor { /// Memoized on the last one built: a loop constructing the same struct /// names the same type every iteration, so this allocates once for the /// whole loop rather than once per object. - fn declared_type(&mut self, base: u8) -> Result> { + fn declared_type(&mut self, base: u8) -> Result> { let name = self.to_runtime_string(base)?; if let Some(cached) = &self.last_declared_type && cached.scope.is_same(&self.type_scope) @@ -159,9 +205,27 @@ impl Executor { { return Ok(Arc::clone(cached)); } - let ty = Arc::new(crate::vm::DeclaredType::new( + // The declaration's field order travels with the type, so `display` + // can print an instance the way its `struct` was written. Looked up + // once per distinct type thanks to the memo above, not once per object. + let name = Arc::::from(name); + let fields: Arc<[crate::val::DeclaredField]> = match self.struct_decls.iter().find(|decl| decl.name == *name) { + Some(decl) => decl + .fields + .iter() + .map(|field| { + crate::val::DeclaredField::new( + Arc::::from(field.name.as_str()), + field.ty.as_deref().and_then(crate::val::Type::parse), + ) + }) + .collect(), + None => Arc::from([] as [crate::val::DeclaredField; 0]), + }; + let ty = Arc::new(crate::val::DeclaredType::with_fields( self.type_scope.clone(), - Arc::::from(name), + name, + fields, )); self.last_declared_type = Some(Arc::clone(&ty)); Ok(ty) @@ -189,31 +253,70 @@ impl Executor { { HeapValue::String(value) => self.slice_string_general(Arc::clone(value), start, end), HeapValue::List(list) => { - let items = list.collect_owned(); - let end = end.unwrap_or(items.len() as i64); - let start = if start < 0 { - (items.len() as i64 + start).max(0) - } else { - start - }; - let end = if end < 0 { - (items.len() as i64 + end).max(0) - } else { - end - }; - let start = start as usize; - let end = end as usize; - let end = end.min(items.len()); - let start = start.min(end); - let slice: Vec = items[start..end].to_vec(); - Ok(RuntimeVal::Obj( - self.alloc_heap_value(HeapValue::List(TypedList::Mixed(slice))), - )) + // Cloned first because materializing the window can + // allocate — a string element past the inline limit + // becomes a heap string — and that needs the heap + // mutably while the source is still borrowed from it. + // + // This used to go through `TypedList::collect_owned`, + // which cannot allocate and answered such an element + // with `ShortStr::new(..).unwrap()`: `xs[0..2]` on a + // list of long strings took the process down. + let list = list.clone(); + let source_len = list.len() as i64; + let end = end.unwrap_or(source_len); + let start = if start < 0 { (source_len + start).max(0) } else { start }; + let end = if end < 0 { (source_len + end).max(0) } else { end }; + let end = (end as usize).min(source_len as usize); + let start = (start as usize).min(end); + let mut slice = Vec::with_capacity(end - start); + for index in start..end { + slice.push(self.typed_list_element_allocating(&list, index)); + } + let slice = TypedList::from_runtime_values(&slice, &self.state.heap); + Ok(RuntimeVal::Obj(self.alloc_heap_value(HeapValue::List(slice)))) + } + // `b[a..c]` is `b.slice(a, c)` written the other way, and + // the two have to answer the same thing — a `Bytes`, since + // every element of the answer is still a byte. Clamped and + // counted from the end exactly as the list arm above does. + HeapValue::Bytes(bytes) => { + let bytes = Arc::clone(bytes); + let source_len = bytes.len() as i64; + let end = end.unwrap_or(source_len); + let start = if start < 0 { (source_len + start).max(0) } else { start }; + let end = if end < 0 { (source_len + end).max(0) } else { end }; + let end = (end as usize).min(bytes.len()); + let start = (start as usize).min(end); + Ok(RuntimeVal::Obj(self.alloc_heap_value(HeapValue::Bytes( + Arc::<[u8]>::from(&bytes[start..end]), + )))) + } + // A sub-range of a window is a window, which is what + // `w.slice(a, c)` answers. + HeapValue::Slice(slice) => { + let slice = Arc::clone(slice); + let source_len = slice.live_len(&self.state.heap) as i64; + let end = end.unwrap_or(source_len); + let start = if start < 0 { (source_len + start).max(0) } else { start }; + let end = if end < 0 { (source_len + end).max(0) } else { end }; + let end = (end as usize).min(source_len as usize); + let start = (start as usize).min(end); + Ok(RuntimeVal::Obj(self.alloc_heap_value(HeapValue::Slice(Arc::new( + crate::val::SliceValue { + source: slice.source, + start: slice.start + start, + len: end - start, + }, + ))))) } - _ => bail!("Slice target must be string or list"), + _ => bail!("Slice target must be a string, list, bytes or slice"), } } - other => bail!("Slice target expected string/list, got {:?}", other.kind()), + other => bail!( + "Slice target expected string/list/bytes/slice, got {}", + self.value_type_name(other) + ), } } @@ -259,7 +362,12 @@ impl Executor { HeapValue::Map(_) => Ok(IndexTargetKind::Map), HeapValue::Object(_) => Ok(IndexTargetKind::Object), HeapValue::String(_) => Ok(IndexTargetKind::String), - other => bail!("GetIndex target object is not indexable: {:?}", heap_kind(other)), + HeapValue::Slice(_) => Ok(IndexTargetKind::Slice), + HeapValue::Bytes(_) => Ok(IndexTargetKind::Bytes), + other => bail!( + "GetIndex target object is not indexable: {:?}", + HeapValue::type_name(other) + ), } } @@ -274,12 +382,32 @@ impl Executor { .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { HeapValue::String(value) => Ok(string_char_len(value)), + // Bytes counts bytes — that is the whole point of asking for + // them. `s.len()` counts characters, `s.bytes().len()` counts + // bytes, and the difference is now something the reader chose + // rather than something the implementation decided for them. + HeapValue::Bytes(value) => Ok(value.len()), + // A window's length is the window's, not the source's — and it + // is what the window can still reach, so a source that shrank + // shortens it rather than leaving it pointing past the end. + HeapValue::Slice(slice) => Ok(slice.live_len(&self.state.heap)), HeapValue::List(value) => Ok(value.len()), HeapValue::Map(value) => Ok(value.len()), HeapValue::Set(value) => Ok(value.len()), - other => bail!("Len target object is not sized: {:?}", heap_kind(other)), + // "Len target" is this opcode's operand, not anything the + // program wrote. What it wrote is `x.len()`, and what is wrong + // is the value. + other => bail!("`len()` has no answer for {}", HeapValue::type_name(other)), }, - other => bail!("Len target expected string/list/map/set, got {:?}", other.kind()), + // No article: "a Int" is wrong and "an Int" needs a rule about + // vowels that has nothing to do with anything here. + other => bail!( + "`len()` works on a String, List, Map, Set, Bytes or Slice, got {}", + match self.value_type_name(other) { + "Nil" => "nil", + name => name, + } + ), } } @@ -308,9 +436,41 @@ impl Executor { HeapValue::List(values) => self.list_contains(values, needle), HeapValue::Map(values) => self.map_contains(values, needle), HeapValue::Set(values) => self.set_contains(values, needle), - other => bail!("Contains haystack object is not searchable: {:?}", heap_kind(other)), + // `Bytes` and a window were the two carriers `in` did not + // search, and neither had a reason: both index, both report a + // `len`, both iterate, and `Bytes` even has a `contains` + // method. The operator was the one place they were not + // containers. + HeapValue::Bytes(bytes) => Ok(match needle { + RuntimeVal::Int(byte) => u8::try_from(*byte).is_ok_and(|byte| bytes.contains(&byte)), + // A byte string holds byte values, so nothing else can be + // in it — the VM's answer for a needle of the wrong kind + // is `false`, as it is for a list of Ints searched for a + // string. + _ => false, + }), + // A window is a range of its source, so membership is + // membership in that range — the same reading `len`, + // indexing and `for` already take. + HeapValue::Slice(slice) => { + let RuntimeVal::Obj(source) = slice.source else { + return Ok(false); + }; + let Some(HeapValue::List(values)) = self.state.heap.get(source) else { + return Ok(false); + }; + let window = values.window(slice.start, slice.live_len(&self.state.heap)); + self.list_contains(&window, needle) + } + other => bail!( + "Contains haystack object is not searchable: {:?}", + HeapValue::type_name(other) + ), }, - other => bail!("Contains haystack expected string/list/map/set, got {:?}", other.kind()), + other => bail!( + "Contains haystack expected string/list/map/set/bytes/slice, got {}", + self.value_type_name(other) + ), } } @@ -328,14 +488,20 @@ impl Executor { { HeapValue::List(values) => SliceFromPlan::List(values.slice_from(start)), HeapValue::String(value) => SliceFromPlan::String(Arc::clone(value)), - other => bail!("SliceFrom target object is not sliceable: {:?}", heap_kind(other)), + other => bail!( + "SliceFrom target object is not sliceable: {:?}", + HeapValue::type_name(other) + ), }; match plan { SliceFromPlan::List(values) => Ok(RuntimeVal::Obj(self.alloc_heap_value(HeapValue::List(values)))), SliceFromPlan::String(value) => self.slice_string_from(value, start), } } - other => bail!("SliceFrom target expected string/list object, got {:?}", other.kind()), + other => bail!( + "SliceFrom target expected string/list/bytes/slice object, got {}", + self.value_type_name(&other) + ), } } @@ -358,7 +524,7 @@ impl Executor { .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { HeapValue::Map(map) => map, - other => bail!("MapRest source object is not a map: {:?}", heap_kind(other)), + other => bail!("MapRest source object is not a map: {:?}", HeapValue::type_name(other)), }; let mut removed_keys = Vec::with_capacity(usize::from(key_count)); @@ -375,9 +541,34 @@ impl Executor { fn list_contains(&self, values: &TypedList, needle: &RuntimeVal) -> Result { Ok(match values { - TypedList::Mixed(values) => values.iter().any(|value| value == needle), - TypedList::Int(values) => matches!(needle, RuntimeVal::Int(needle) if values.contains(needle)), - TypedList::Float(values) => matches!(needle, RuntimeVal::Float(needle) if values.contains(needle)), + // `x in xs` compares values, and a mixed list is where non-scalar + // elements live. This used to be `==` — handle identity — so + // `[1, 2] in [[1, 2], [3]]` answered false. + TypedList::Mixed(values) => { + let mut found = false; + for value in values { + if self.runtime_values_equal(value, needle)? { + found = true; + break; + } + } + found + } + // Numeric comparison across `Int`/`Float`, the same rule `==` uses. + // These arms used to demand the *same variant*, so `a == b` was + // true and `a in [b]` was false for the same pair — and the answer + // depended on the list's internal representation, since the + // `Mixed` arm above compares by value. + TypedList::Int(values) => match needle { + RuntimeVal::Int(needle) => values.contains(needle), + RuntimeVal::Float(needle) => values.iter().any(|value| *value as f64 == *needle), + _ => false, + }, + TypedList::Float(values) => match needle { + RuntimeVal::Float(needle) => values.contains(needle), + RuntimeVal::Int(needle) => values.contains(&(*needle as f64)), + _ => false, + }, TypedList::Bool(values) => matches!(needle, RuntimeVal::Bool(needle) if values.contains(needle)), TypedList::String(values) => { let Some(needle) = self.runtime_value_to_string(needle)? else { @@ -388,12 +579,30 @@ impl Executor { }) } + /// `k in m` — whether the map holds that key. + /// + /// A needle that cannot *be* a key is not a member, and the answer is + /// `false` rather than a raise. Building the key and propagating its + /// failure made the answer depend on the map's internal representation: + /// + /// ```lk + /// 1.5 in {"k": 1} // false — a string-keyed carrier + /// 1.5 in {1: 2} // raised — the same question, `Mixed` inside + /// ``` + /// + /// Which carrier a map has is not something a program can see, so that was + /// two answers to one question. `in` is a predicate and answers: a list + /// already says `"s" in [1, 2]` is false rather than refusing the needle's + /// type, and this is the same rule one container over. + /// + /// Only membership. Indexing and insertion still raise, because there the + /// key is being *built* — `m[1.5] = x` has to say so. fn map_contains(&self, values: &TypedMap, needle: &RuntimeVal) -> Result { Ok(match values { - TypedMap::Mixed(values) => { - let key = self.runtime_map_key_from_value(needle)?; - values.contains_key(&key) - } + TypedMap::Mixed(values) => match self.runtime_map_key_from_value(needle) { + Ok(key) => values.contains_key(&key), + Err(_) => false, + }, TypedMap::StringMixed(values) => self.string_map_contains_key(values, needle)?, TypedMap::StringInt(values) => self.string_map_contains_key(values, needle)?, TypedMap::StringFloat(values) => self.string_map_contains_key(values, needle)?, @@ -401,12 +610,40 @@ impl Executor { }) } + /// `v in s` — whether the set holds it. Total, for [`Self::map_contains`]'s + /// reason: a set's members are keys, so a value that cannot be one is not a + /// member. fn set_contains(&self, values: &RuntimeSet, needle: &RuntimeVal) -> Result { - let key = self.runtime_map_key_from_value(needle)?; - Ok(values.contains(&key)) + Ok(match self.runtime_map_key_from_value(needle) { + Ok(key) => values.contains(&key), + Err(_) => false, + }) } #[allow(clippy::wrong_self_convention)] // allocates on the heap, so it needs `&mut self` + /// One element of a typed list, allocating when the element needs it. + /// + /// A `TypedList::String` element longer than a `ShortStr` has to become a + /// heap string; every read path that can allocate goes through here so that + /// none of them has to decide what to do when it cannot. + pub(super) fn typed_list_element_allocating(&mut self, list: &TypedList, index: usize) -> RuntimeVal { + match list { + TypedList::Int(values) => values.get(index).copied().map(RuntimeVal::Int), + TypedList::Float(values) => values.get(index).copied().map(RuntimeVal::Float), + TypedList::Bool(values) => values.get(index).copied().map(RuntimeVal::Bool), + TypedList::Mixed(values) => values.get(index).copied(), + TypedList::String(values) => values.get(index).cloned().map(|text| match ShortStr::new(&text) { + Some(short) => RuntimeVal::ShortStr(short), + None => RuntimeVal::Obj(self.alloc_heap_value(HeapValue::String(text))), + }), + } + .unwrap_or(RuntimeVal::Nil) + } + + #[allow( + clippy::wrong_self_convention, + reason = "`to_iter` names the opcode it implements, and it drives the executor" + )] pub(super) fn to_iter(&mut self, register: u8) -> Result { match *self.read(register)? { RuntimeVal::ShortStr(value) => { @@ -421,14 +658,28 @@ impl Executor { .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { HeapValue::List(_) => ToIterPlan::ExistingList(handle), + // A window is indexable and knows its length, which is all + // the loop needs — copying it out would defeat the point. + HeapValue::Slice(_) => ToIterPlan::ExistingList(handle), + // Same reasoning: a `Bytes` is indexable and knows its + // length. `for b in data` was a type error until now, so + // reading bytes meant `bytes.to_list(data)` — a copy that + // also turns each byte into an eight-byte `Int`. + HeapValue::Bytes(_) => ToIterPlan::ExistingList(handle), HeapValue::String(value) => ToIterPlan::StringChars(string_chars_to_list(value)), HeapValue::Map(map) => ToIterPlan::Map(typed_map_iter_snapshot(map)), HeapValue::Set(values) => ToIterPlan::Set(values.entries().cloned().collect()), - other => bail!("ToIter target object is not iterable: {:?}", heap_kind(other)), + other => bail!( + "ToIter target object is not iterable: {:?}", + HeapValue::type_name(other) + ), }; self.finish_to_iter_plan(plan) } - other => bail!("ToIter target expected string/list/map/set, got {:?}", other.kind()), + other => bail!( + "ToIter target expected string/list/map/set/bytes/slice, got {}", + self.value_type_name(&other) + ), } } @@ -444,13 +695,12 @@ impl Executor { } fn set_values_to_iter_list(&mut self, values: Vec) -> Result { - let values = values + let values: Vec = values .into_iter() .map(|value| self.runtime_map_key_to_value(value)) .collect(); - Ok(RuntimeVal::Obj( - self.alloc_heap_value(HeapValue::List(TypedList::Mixed(values))), - )) + let values = TypedList::from_runtime_values(&values, &self.state.heap); + Ok(RuntimeVal::Obj(self.alloc_heap_value(HeapValue::List(values)))) } fn map_entries_to_iter_list(&mut self, entries: TypedMapIterSnapshot) -> Result { @@ -487,13 +737,12 @@ impl Executor { } } } - Ok(RuntimeVal::Obj( - self.alloc_heap_value(HeapValue::List(TypedList::Mixed(pairs))), - )) + let pairs = TypedList::from_runtime_values(&pairs, &self.state.heap); + Ok(RuntimeVal::Obj(self.alloc_heap_value(HeapValue::List(pairs)))) } fn push_iter_pair(&mut self, pairs: &mut Vec, key: RuntimeVal, value: RuntimeVal) { - let pair = HeapValue::List(TypedList::Mixed(vec![key, value])); + let pair = HeapValue::List(TypedList::from_runtime_values(&[key, value], &self.state.heap)); pairs.push(RuntimeVal::Obj(self.alloc_heap_value(pair))); } @@ -510,7 +759,6 @@ impl Executor { RuntimeVal::Obj(self.alloc_heap_value(HeapValue::String(value))) } } - RuntimeMapKey::Obj(value) => RuntimeVal::Obj(value), } } @@ -527,7 +775,7 @@ impl Executor { let handle = { let target = self.read(target_reg)?; let RuntimeVal::Obj(handle) = target else { - bail!("ListPush target expected Obj, got {:?}", target.kind()); + bail!("ListPush target expected Obj, got {}", self.value_type_name(target)); }; *handle }; @@ -545,7 +793,7 @@ impl Executor { let Some(HeapValue::List(list)) = self.state.heap.get_mut(handle) else { bail!("ListPush target object is not a list"); }; - push_list_value(list, value, string_value)?; + list.push(value, string_value)?; } self.state.heap.bump_shape_generation(handle); @@ -562,7 +810,7 @@ impl Executor { HeapValue::List(TypedList::String(values)) => values.clone(), other => bail!( "ListPush target object changed while materializing string list: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), }; let mut mixed = Vec::with_capacity(values.len() + 1); @@ -600,7 +848,24 @@ impl Executor { target_kind: PerfIndexTargetKind::String, value_kind: PerfValueKind::Unknown, }), - other => bail!("index target object is not indexable: {:?}", heap_kind(other)), + // A window is indexable but has no specialised fast path to record + // a fact for: `Unknown` sends the read down the general path, which + // resolves it against the source list. + HeapValue::Slice(_) => Ok(PerfIndexFact { + target_kind: PerfIndexTargetKind::Unknown, + value_kind: PerfValueKind::Unknown, + }), + // Same as a window: indexable, no specialised fast path. The + // elements *are* known to be `Int`, but `value_kind` describes the + // fast path's output and there is none to describe. + HeapValue::Bytes(_) => Ok(PerfIndexFact { + target_kind: PerfIndexTargetKind::Unknown, + value_kind: PerfValueKind::Unknown, + }), + other => bail!( + "index target object is not indexable: {:?}", + HeapValue::type_name(other) + ), } } @@ -608,7 +873,7 @@ impl Executor { &mut self, pc: usize, handle: HeapRef, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, ) -> Result> { let generation = self .state @@ -626,7 +891,7 @@ impl Executor { Ok(self.state.inline_caches.index(pc, handle, generation)) } - fn object_field_slot_from_heap(&self, handle: HeapRef, key: Option<&str>) -> Result> { + fn object_field_slot_from_heap(&self, handle: HeapRef, key: Option<&Arc>) -> Result> { let Some(key) = key else { return Ok(None); }; @@ -648,14 +913,14 @@ impl Executor { key_reg: u8, moved_key: Option, value: RuntimeVal, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, has_static_fact: bool, mut index_key_metrics: Option<&mut [u64; VM_INDEX_KEY_METRIC_COUNT]>, ) -> Result<()> { let key: Arc = match known_string_key { Some(key_str) => { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::KnownStringKey); - Arc::::from(key_str) + Arc::clone(key_str) } None => { match moved_key.as_ref() { @@ -666,6 +931,21 @@ impl Executor { self.object_key_from_register_or_value(key_reg, moved_key)? } }; + // A declared field is checked against the type it was declared with. + // The type checker catches every store it can see; this is the one it + // cannot — a write through an untyped binding, `fn poison(p) { p["v"] + // = "s"; }` — and without it a `struct P { v: Int }` could hold a + // String and the declaration meant nothing. + if let Some(HeapValue::Object(object)) = self.state.heap.get(handle) + && let Some(declared) = object.ty.field_type(&key) + && !crate::val::value_satisfies_declared(&value, declared, &self.state.heap) + { + let (type_name, declared) = (object.ty.name.clone(), declared.display()); + bail!( + "field `{key}` of {type_name} is declared {declared}, and a {} cannot be stored in it", + self.value_type_name(&value) + ); + } match self .state .heap @@ -678,7 +958,7 @@ impl Executor { } other => bail!( "SetIndex target object changed while writing object: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), }?; if !has_static_fact { @@ -710,9 +990,9 @@ impl Executor { .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { HeapValue::String(value) => Ok(value.clone()), - other => bail!("object field key cannot be object: {:?}", heap_kind(other)), + other => bail!("object field key cannot be object: {:?}", HeapValue::type_name(other)), }, - other => bail!("object field key must be string, got {:?}", other.kind()), + other => bail!("object field key must be string, got {}", self.value_type_name(other)), } } @@ -728,7 +1008,11 @@ impl Executor { let Some(HeapValue::List(TypedList::String(values))) = self.state.heap.get(handle) else { return Ok(None); }; - let index = usize::try_from(*index).map_err(|_| anyhow!("list index must be non-negative"))?; + // Not "must be non-negative": `xs[-1]` is the last element, so that + // sentence describes a rule this language does not have. What happened + // is that the index resolved below 0 — the same out-of-range answer the + // other end gives, and `index` here is the index *as written*. + let index = usize::try_from(*index).map_err(|_| anyhow!("list index {index} out of bounds"))?; if index >= values.len() { bail!("list index {} out of bounds", index); } @@ -764,7 +1048,21 @@ impl Executor { key_reg: u8, known_value_kind: Option, ) -> Result { - let index = usize::try_from(self.read_int(key_reg)?).map_err(|_| anyhow!("list index must be non-negative"))?; + // A negative index has already been resolved against the length by the + // time it reaches this register, so one that is *still* negative is out + // of range at the low end — and a read out of range is `nil`, the same + // answer this gives past the high end and the same one `String`, + // `Bytes` and the native build already gave. + // + // It used to be `usize::try_from(…)` raising `list index must be + // non-negative`: a rule this language does not have (`xs[-1]` is the + // last element), and a VM/native divergence that wording hid — + // `xs[-10]` raised interpreted and answered `nil` compiled. See + // `val::position::element_position`, which states this rule and, until + // now, had no callers. + let Ok(index) = usize::try_from(self.read_int(key_reg)?) else { + return Ok(RuntimeVal::Nil); + }; if let Some(value) = self.index_typed_list_handle(handle, index, known_value_kind)? { return Ok(value); } @@ -807,7 +1105,10 @@ impl Executor { } value.clone() } - other => bail!("GetIndex target object changed while indexing: {:?}", heap_kind(other)), + other => bail!( + "GetIndex target object changed while indexing: {:?}", + HeapValue::type_name(other) + ), }; Ok(RuntimeVal::Obj(self.alloc_heap_value(HeapValue::String(long_string)))) } @@ -857,11 +1158,39 @@ impl Executor { } (PerfValueKind::Unknown, _) => Ok(None), (_, HeapValue::List(_)) => Ok(None), - (_, other) => bail!("GetIndex target object changed while indexing: {:?}", heap_kind(other)), + (_, other) => bail!( + "GetIndex target object changed while indexing: {:?}", + HeapValue::type_name(other) + ), } } - fn index_string_at(&self, value: &str, index: usize) -> Result { + /// `s[index]` — one character, or nil outside. + /// + /// Takes the raw index so the negative-counts-from-the-end rule lives in + /// one place. It counted back from the *byte* length in all three callers, + /// which is the same number only for ASCII: `"中文abc"` has five characters + /// and nine bytes, so `[-1]` asked for character 8 and got nil while `[-5]` + /// answered `"c"`. `len()` counts characters and `[i]` indexes characters; + /// `[-i]` now does too. Two of those three callers also let `len + index` + /// underflow into a huge `usize` and relied on the lookup missing. + fn index_string_at(&self, value: &str, index: i64) -> Result { + let index = if index < 0 { + // For ASCII the byte length *is* the character count, so the cheap + // one is exact there. + let len = if value.is_ascii() { + value.len() as i64 + } else { + value.chars().count() as i64 + }; + let wrapped = len + index; + if wrapped < 0 { + return Ok(RuntimeVal::Nil); + } + wrapped as usize + } else { + index as usize + }; if value.is_ascii() { let Some(byte) = value.as_bytes().get(index).copied() else { return Ok(RuntimeVal::Nil); @@ -892,7 +1221,10 @@ impl Executor { .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { HeapValue::Map(map) => Ok(map.get(key)), - other => bail!("GetIndex target object changed while indexing: {:?}", heap_kind(other)), + other => bail!( + "GetIndex target object changed while indexing: {:?}", + HeapValue::type_name(other) + ), } } @@ -911,7 +1243,7 @@ impl Executor { let HeapValue::Map(map) = heap_value else { bail!( "GetIndex target object changed while indexing: {:?}", - heap_kind(heap_value) + HeapValue::type_name(heap_value) ); }; // When value_kind is known, use it for direct typed dispatch. @@ -972,7 +1304,10 @@ impl Executor { } Ok(object.get_field(key)) } - other => bail!("GetIndex target object changed while indexing: {:?}", heap_kind(other)), + other => bail!( + "GetIndex target object changed while indexing: {:?}", + HeapValue::type_name(other) + ), } } @@ -993,28 +1328,15 @@ impl Executor { fn int_key_from_register_or_value(&self, register: u8, moved_key: Option) -> Result { match moved_key { Some(RuntimeVal::Int(value)) => Ok(value), - Some(other) => bail!("SetIndex list key must be Int, got {:?}", other.kind()), + Some(other) => bail!("a list index must be Int, got {}", self.value_type_name(&other)), None => self.read_int(register), } } + /// The key a value is used under — see [`RuntimeMapKey::from_value`], which + /// is the one conversion. pub(super) fn runtime_map_key_from_value(&self, value: &RuntimeVal) -> Result { - match value { - RuntimeVal::Nil => Ok(RuntimeMapKey::Nil), - RuntimeVal::Bool(value) => Ok(RuntimeMapKey::Bool(*value)), - RuntimeVal::Int(value) => Ok(RuntimeMapKey::Int(*value)), - RuntimeVal::ShortStr(value) => Ok(RuntimeMapKey::ShortStr(*value)), - RuntimeVal::Obj(handle) => match self - .state - .heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::String(value) => Ok(RuntimeMapKey::String(value.clone())), - other => bail!("object cannot be used as map key: {:?}", heap_kind(other)), - }, - RuntimeVal::Float(_) => bail!("Float cannot be used as RuntimeMapKey"), - } + RuntimeMapKey::from_value(value, &self.state.heap) } fn runtime_value_to_key_string(&self, value: &RuntimeVal) -> Result>> { @@ -1036,7 +1358,7 @@ impl Executor { }) } - fn string_map_contains_key(&self, values: &FastHashMap, T>, needle: &RuntimeVal) -> Result { + fn string_map_contains_key(&self, values: &ValueMap, T>, needle: &RuntimeVal) -> Result { let Some(key) = self.runtime_value_to_key_string(needle)? else { return Ok(false); }; @@ -1055,11 +1377,7 @@ fn runtime_map_string_key(value: Arc) -> RuntimeMapKey { #[inline(always)] fn runtime_map_key_from_str(key_str: &str) -> RuntimeMapKey { - if let Some(short) = ShortStr::new(key_str) { - RuntimeMapKey::ShortStr(short) - } else { - RuntimeMapKey::String(Arc::::from(key_str)) - } + RuntimeMapKey::from_text(key_str) } fn list_value_kind(list: &TypedList) -> PerfValueKind { @@ -1075,7 +1393,7 @@ fn list_value_kind(list: &TypedList) -> PerfValueKind { fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> TypedMap { match map { TypedMap::Mixed(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !typed_map_key_removed(key, removed_keys) { out.insert(key.clone(), *value); @@ -1084,7 +1402,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::Mixed(out) } TypedMap::StringMixed(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(Arc::clone(key), *value); @@ -1093,7 +1411,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::StringMixed(out) } TypedMap::StringInt(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(Arc::clone(key), *value); @@ -1102,7 +1420,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::StringInt(out) } TypedMap::StringFloat(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(Arc::clone(key), *value); @@ -1111,7 +1429,7 @@ fn typed_map_without_keys(map: &TypedMap, removed_keys: &[RuntimeMapKey]) -> Typ TypedMap::StringFloat(out) } TypedMap::StringBool(entries) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in entries { if !string_map_key_removed(key, removed_keys) { out.insert(Arc::clone(key), *value); diff --git a/core/src/vm/exec/container/index.rs b/core/src/vm/exec/container/index.rs index 7d7003ef..fabc0496 100644 --- a/core/src/vm/exec/container/index.rs +++ b/core/src/vm/exec/container/index.rs @@ -8,13 +8,12 @@ use crate::val::{HeapValue, RuntimeMapKey, RuntimeVal, ShortStr, ShortStrOrStr, use crate::vm::analysis::{PerfIndexFact, PerfIndexTargetKind, VM_INDEX_KEY_METRIC_COUNT, VmIndexKeyMetric}; use super::{ - Executor, IndexTargetKind, heap_kind, record_dynamic_index_key_metric, record_index_key_metric, - runtime_map_key_from_str, + Executor, IndexTargetKind, record_dynamic_index_key_metric, record_index_key_metric, runtime_map_key_from_str, }; impl Executor { #[inline(always)] - pub(in crate::vm::exec) fn get_list_index(&self, target_reg: u8, key_reg: u8) -> Result { + pub(in crate::vm::exec) fn get_list_index(&mut self, target_reg: u8, key_reg: u8) -> Result { let RuntimeVal::Obj(handle) = self.read_unchecked(target_reg) else { bail!("GetList target expected Obj"); }; @@ -33,7 +32,11 @@ impl Executor { } else { *index as usize }; - Ok(self.get_typed_list_element(list, index)) + match self.get_typed_list_element(list, index) { + Some(value) => Ok(value), + // A long string element: read it again where allocation is allowed. + None => Ok(self.get_typed_list_element_allocating(*handle, index)), + } } #[inline(always)] @@ -78,7 +81,7 @@ impl Executor { })?, Some(other) => bail!( "GetIndexStrI target object changed while indexing: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), None => bail!("heap object {} out of bounds", handle.index()), } @@ -104,7 +107,7 @@ impl Executor { } else { *index as usize }; - Some(self.get_typed_list_element(list, index)) + self.get_typed_list_element(list, index) } #[inline(always)] @@ -168,7 +171,7 @@ impl Executor { pc: usize, target_reg: u8, key_reg: u8, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, index_fact: Option, index_key_metrics: Option<&mut [u64; VM_INDEX_KEY_METRIC_COUNT]>, ) -> Result { @@ -180,7 +183,10 @@ impl Executor { if let RuntimeVal::Obj(h) = self.read_unchecked(key_reg) && let Some(HeapValue::List(list)) = self.state.heap.get(*h) { - let items = list.collect_owned(); + // A materialized range: its elements are the integers `NewRange` + // produced, so this never declines. `unwrap_or_default` rather than + // an expect because an empty answer is already handled below. + let items = list.collect_owned().unwrap_or_default(); if items.is_empty() { return self.get_index_slice(target_reg, 0, Some(0), None); } @@ -198,11 +204,8 @@ impl Executor { let value = *value; let idx_val = self.read_unchecked(key_reg); let idx = match idx_val { - RuntimeVal::Int(n) => { - let len = value.as_str().len() as i64; - if *n < 0 { (len + *n) as usize } else { *n as usize } - } - _ => bail!("String index must be Int"), + RuntimeVal::Int(n) => *n, + _ => bail!("a string index must be Int"), }; self.index_string_at(value.as_str(), idx) } @@ -210,7 +213,7 @@ impl Executor { let handle = *handle; self.get_heap_index(pc, handle, key_reg, known_string_key, index_fact, index_key_metrics) } - other => bail!("GetIndex target expected Obj, got {:?}", other.kind()), + other => bail!("{} is not indexable", self.value_type_name(other)), } } @@ -220,13 +223,32 @@ impl Executor { pc: usize, handle: crate::val::HeapRef, key_reg: u8, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, index_fact: Option, mut index_key_metrics: Option<&mut [u64; VM_INDEX_KEY_METRIC_COUNT]>, ) -> Result { - // Fast path: when index_fact confirms Map target, do direct map lookup. - if let Some(fact) = index_fact { - if fact.target_kind == PerfIndexTargetKind::Map { + // What kind of container this is: the compile-time fact when there is + // one, and otherwise one look at the heap. + // + // A container behind a *parameter* has no fact — `prices.get(sku)` and + // `xs[i]` inside `fn line_total(prices, …)` / `fn at(xs, i)` are the + // ordinary shapes — so every such lookup took the `#[cold]` route to + // learn what the heap says directly. Measured: 7 000 000 of 7 000 000 + // map lookups in a pricing loop, 6 000 000 of 6 000 000 list lookups in + // an indexing loop, and the cold route then answered from the same + // carrier these arms read. + let target_kind = match index_fact { + Some(fact) => Some(fact.target_kind), + None => match self.state.heap.get(handle) { + Some(HeapValue::Map(_)) => Some(PerfIndexTargetKind::Map), + Some(HeapValue::List(_)) => Some(PerfIndexTargetKind::List), + Some(HeapValue::Object(_)) => Some(PerfIndexTargetKind::Object), + Some(HeapValue::String(_)) => Some(PerfIndexTargetKind::String), + _ => None, + }, + }; + match target_kind { + Some(PerfIndexTargetKind::Map) => { if let Some(key_str) = known_string_key { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::KnownStringKey); record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::DirectStringKey); @@ -243,8 +265,7 @@ impl Executor { return self.get_map_index_fast(handle, key_reg, index_key_metrics); } } - // For list with known type, skip the slow path too - if fact.target_kind == PerfIndexTargetKind::List { + Some(PerfIndexTargetKind::List) => { let key_val = self.read_unchecked(key_reg); if let RuntimeVal::Int(n) = key_val && let Some(HeapValue::List(list)) = self.state.heap.get(handle) @@ -258,36 +279,55 @@ impl Executor { } else { *n as usize }; - return Ok(self.get_typed_list_element(list, index)); + if let Some(value) = self.get_typed_list_element(list, index) { + return Ok(value); + } + return Ok(self.get_typed_list_element_allocating(handle, index)); } } - if fact.target_kind == PerfIndexTargetKind::String { + // `p.x` — a struct field read. The object arm lived only in the + // slow path, so every field read of a struct took the cold route: + // 600 000 of 600 000 in a loop that reads two fields. The slow + // path's field-slot cache is not what saves it there either — with + // a static fact no inline cache is consulted at all, so what it + // does is exactly this lookup, behind a `#[cold]` call. + Some(PerfIndexTargetKind::Object) => { + if let Some(key) = known_string_key + && let Some(HeapValue::Object(object)) = self.state.heap.get(handle) + { + record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::KnownStringKey); + record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::ObjectKey); + return Ok(object.get_field(key).unwrap_or(RuntimeVal::Nil)); + } + } + Some(PerfIndexTargetKind::String) => { let key_val = self.read_unchecked(key_reg); if let RuntimeVal::Int(n) = key_val && let Some(HeapValue::String(value)) = self.state.heap.get(handle) { - let index = if *n < 0 { - let index = value.len() as i64 + *n; - if index < 0 { - return Ok(RuntimeVal::Nil); - } - index as usize - } else { - *n as usize - }; - return self.index_string_at(value, index); + return self.index_string_at(value, *n); } } + Some(PerfIndexTargetKind::Unknown) | None => {} } self.get_heap_index_slow_path(pc, handle, key_reg, known_string_key, index_fact, index_key_metrics) } - /// Read a value from a typed list by index, converting to RuntimeVal. - /// Returns RuntimeVal::Nil for out-of-bounds or unsupported types. + /// Read a value from a typed list by index, without allocating. + /// + /// `None` means *this path cannot answer* — not that the element is + /// missing. A `TypedList::String` element longer than a `ShortStr` needs a + /// heap allocation, and this runs behind `&self` on the index fast path. + /// Out of bounds is `Some(Nil)`, which is an answer. + /// + /// Returning `Nil` for the too-long case, which is what this used to do, + /// made `xs[0]` answer nil for an element that was plainly there — while + /// `xs.first()`, which allocates, answered correctly. Same list, two + /// answers, and only for strings over seven bytes. #[inline(always)] - fn get_typed_list_element(&self, list: &TypedList, index: usize) -> RuntimeVal { - match list { + fn get_typed_list_element(&self, list: &TypedList, index: usize) -> Option { + Some(match list { TypedList::Int(values) => values .get(index) .copied() @@ -305,12 +345,74 @@ impl Executor { .unwrap_or(RuntimeVal::Nil), TypedList::Mixed(values) => values.get(index).cloned().unwrap_or(RuntimeVal::Nil), TypedList::String(values) => match values.get(index) { - Some(value) => ShortStr::new(value) - .map(RuntimeVal::ShortStr) - .unwrap_or_else(|| RuntimeVal::Nil), + Some(value) => RuntimeVal::ShortStr(ShortStr::new(value)?), None => RuntimeVal::Nil, }, + }) + } + + /// One element of a window, by its position *within the window*. + /// + /// Negative indices count from the window's end, as they do for a list. + /// Out of range is nil. + pub(in crate::vm::exec) fn slice_element(&mut self, handle: crate::val::HeapRef, index: i64) -> RuntimeVal { + let Some(HeapValue::Slice(slice)) = self.state.heap.get(handle) else { + return RuntimeVal::Nil; + }; + let (source, start, recorded_len) = (slice.source, slice.start, slice.len); + // A negative index counts back from the window's end, and where that + // end *is* depends on whether the source shrank — so only this case + // pays for the extra look at the source. A non-negative index does not + // need to know: past the source, the element read below answers nil on + // its own, which is the same answer clamping would give. + let index = if index < 0 { + slice.live_len(&self.state.heap) as i64 + index + } else { + index + }; + if index < 0 || index as usize >= recorded_len { + return RuntimeVal::Nil; + } + let RuntimeVal::Obj(source) = source else { + return RuntimeVal::Nil; + }; + self.get_typed_list_element_allocating(source, start + index as usize) + } + + /// One byte of a `Bytes`, as an `Int`. + /// + /// Same index rule as every other sequence: a negative counts from the end, + /// outside is nil. `Bytes` was not indexable at all until it had this — + /// `b[0]` answered "index target object is not indexable". + pub(in crate::vm::exec) fn byte_element(&mut self, handle: crate::val::HeapRef, index: i64) -> RuntimeVal { + let Some(HeapValue::Bytes(bytes)) = self.state.heap.get(handle) else { + return RuntimeVal::Nil; + }; + let index = if index < 0 { bytes.len() as i64 + index } else { index }; + if index < 0 { + return RuntimeVal::Nil; } + bytes + .get(index as usize) + .map_or(RuntimeVal::Nil, |byte| RuntimeVal::Int(*byte as i64)) + } + + /// The same read, allowed to allocate. Used where the fast path declines. + fn get_typed_list_element_allocating(&mut self, handle: crate::val::HeapRef, index: usize) -> RuntimeVal { + let Some(HeapValue::List(list)) = self.state.heap.get(handle) else { + return RuntimeVal::Nil; + }; + if let Some(value) = self.get_typed_list_element(list, index) { + return value; + } + // Only a long `TypedList::String` element reaches here. + let Some(HeapValue::List(TypedList::String(values))) = self.state.heap.get(handle) else { + return RuntimeVal::Nil; + }; + let Some(text) = values.get(index).cloned() else { + return RuntimeVal::Nil; + }; + RuntimeVal::Obj(self.alloc_heap_value(HeapValue::String(text))) } /// Fast map index lookup that avoids RuntimeMapKey construction. @@ -339,7 +441,10 @@ impl Executor { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::GenericMapLookup); Ok(map.get_str(key_str).unwrap_or(RuntimeVal::Nil)) } - Some(other) => bail!("GetIndex target object changed while indexing: {:?}", heap_kind(other)), + Some(other) => bail!( + "GetIndex target object changed while indexing: {:?}", + HeapValue::type_name(other) + ), None => bail!("heap object {} out of bounds", handle.index()), } } @@ -348,7 +453,10 @@ impl Executor { let key = RuntimeMapKey::Int(*n); match self.state.heap.get(handle) { Some(HeapValue::Map(map)) => Ok(map.get(&key).unwrap_or(RuntimeVal::Nil)), - Some(other) => bail!("GetIndex target object changed while indexing: {:?}", heap_kind(other)), + Some(other) => bail!( + "GetIndex target object changed while indexing: {:?}", + HeapValue::type_name(other) + ), None => bail!("heap object {} out of bounds", handle.index()), } } @@ -375,7 +483,7 @@ impl Executor { pc: usize, handle: crate::val::HeapRef, key_reg: u8, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, index_fact: Option, mut index_key_metrics: Option<&mut [u64; VM_INDEX_KEY_METRIC_COUNT]>, ) -> Result { @@ -400,6 +508,18 @@ impl Executor { }; match target_kind { + IndexTargetKind::Slice => { + let RuntimeVal::Int(index) = *self.read(key_reg)? else { + bail!("slice index must be Int"); + }; + Ok(self.slice_element(handle, index)) + } + IndexTargetKind::Bytes => { + let RuntimeVal::Int(index) = *self.read(key_reg)? else { + bail!("bytes index must be Int"); + }; + Ok(self.byte_element(handle, index)) + } IndexTargetKind::List => { if let Some(pos) = self.negative_list_index(handle, key_reg) { let orig_val = *self.read(key_reg)?; @@ -419,18 +539,22 @@ impl Executor { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::DirectStringKey); return Ok(value); } - let key = match known_string_key { - Some(key_str) => { - record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::KnownStringKey); - record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::RuntimeMapKey); - runtime_map_key_from_str(key_str) - } - None => { - record_dynamic_index_key_metric(index_key_metrics.as_deref_mut(), self.read(key_reg)?); - record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::RuntimeMapKey); - self.map_key_from_register(key_reg)? - } + let Some(key_str) = known_string_key else { + // A key in a *register* is the ordinary way to look + // something up (`counts.get(word)`), and it took the long + // way round: build a `RuntimeMapKey` — an `Arc` clone for a + // heap string — and then hand it to the generic lookup, + // which for a typed map immediately asks it for the `&str` + // it started from. `get_map_index_fast` is that same + // question answered once, and it was reachable only when + // the *target* had been proven a map at compile time. A + // map behind a parameter has no such proof — which is + // exactly where a lookup keyed by a variable lives. + return self.get_map_index_fast(handle, key_reg, index_key_metrics); }; + record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::KnownStringKey); + record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::RuntimeMapKey); + let key = runtime_map_key_from_str(key_str); record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::GenericMapLookup); Ok(self.lookup_map_handle(handle, &key)?.unwrap_or(RuntimeVal::Nil)) } @@ -438,7 +562,7 @@ impl Executor { let key = match known_string_key { Some(key_str) => { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::KnownStringKey); - Arc::::from(key_str) + Arc::clone(key_str) } None => { record_dynamic_index_key_metric(index_key_metrics.as_deref_mut(), self.read(key_reg)?); @@ -505,15 +629,15 @@ impl Executor { HeapValue::String(value) => { let idx_val = self.read(key_reg)?; let idx = match &idx_val { - RuntimeVal::Int(n) => { - let len = value.len() as i64; - if *n < 0 { (len + *n) as usize } else { *n as usize } - } - _ => bail!("String index must be Int"), + RuntimeVal::Int(n) => *n, + _ => bail!("a string index must be Int"), }; self.index_string_at(value, idx) } - other => bail!("GetIndex target object changed while indexing: {:?}", heap_kind(other)), + other => bail!( + "GetIndex target object changed while indexing: {:?}", + HeapValue::type_name(other) + ), } } } @@ -562,12 +686,11 @@ pub(in crate::vm::exec) fn with_string_int_key(prefix: &str, suffix: i64, f: #[cfg(test)] mod tests { use super::get_string_map_direct; - use crate::util::fast_map::{fast_hash_map_from_iter, fast_hash_map_new}; use crate::val::{RuntimeMapKey, RuntimeVal, ShortStr, TypedMap}; #[test] fn direct_string_map_lookup_returns_nil_for_empty_mixed_map() { - let map = TypedMap::Mixed(fast_hash_map_new()); + let map = TypedMap::Mixed(crate::util::value_map::value_map_new()); assert_eq!(get_string_map_direct(&map, "missing"), Some(RuntimeVal::Nil)); } @@ -575,7 +698,7 @@ mod tests { #[test] fn direct_string_map_lookup_keeps_non_empty_mixed_map_on_generic_path() { let key = RuntimeMapKey::ShortStr(ShortStr::new("present").expect("short key")); - let map = TypedMap::Mixed(fast_hash_map_from_iter([(key, RuntimeVal::Int(1))])); + let map = TypedMap::Mixed(crate::util::value_map::value_map_from_iter([(key, RuntimeVal::Int(1))])); assert_eq!(get_string_map_direct(&map, "missing"), None); assert_eq!(get_string_map_direct(&map, "present"), None); diff --git a/core/src/vm/exec/container/set_index.rs b/core/src/vm/exec/container/set_index.rs index 616543f6..ece98a4f 100644 --- a/core/src/vm/exec/container/set_index.rs +++ b/core/src/vm/exec/container/set_index.rs @@ -8,8 +8,8 @@ use crate::vm::analysis::{ }; use super::{ - Executor, heap_kind, record_dynamic_index_key_metric, record_index_key_metric, runtime_map_key_from_str, - set_list_value, with_string_int_key, + Executor, record_dynamic_index_key_metric, record_index_key_metric, runtime_map_key_from_str, set_list_value, + with_string_int_key, }; /// A small, stack-allocated key representation that avoids String allocation @@ -42,6 +42,39 @@ impl SmallKey { } } +/// A string key on its way into a map. +/// +/// The two carry the same text and differ in what storing it costs. A typed +/// string carrier keys by `Arc`, so inserting a *new* key had to allocate +/// one — and a constant key already is one, sitting in the function's const +/// pool. Passing the pooled `Arc` makes the insert a refcount bump, and the +/// map's later death a decrement rather than a free. +/// +/// One parameter rather than a `&str` plus an optional `Arc` beside it: the +/// two would have to agree, and nothing would check that they did. +enum KeyText<'a> { + /// Text the caller only borrows — a key read out of a register. + Borrowed(&'a str), + /// The pooled constant. + Shared(&'a Arc), +} + +impl KeyText<'_> { + fn as_str(&self) -> &str { + match self { + Self::Borrowed(text) => text, + Self::Shared(text) => text, + } + } + + fn to_arc(&self) -> Arc { + match self { + Self::Borrowed(text) => Arc::::from(*text), + Self::Shared(text) => Arc::clone(text), + } + } +} + impl Executor { #[inline(always)] #[allow(clippy::too_many_arguments)] @@ -53,14 +86,14 @@ impl Executor { value_reg: u8, move_key: bool, move_value: bool, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, index_fact: Option, mut index_key_metrics: Option<&mut [u64; VM_INDEX_KEY_METRIC_COUNT]>, ) -> Result<()> { let handle = { let target = self.read(target_reg)?; let RuntimeVal::Obj(handle) = target else { - bail!("SetIndex target expected Obj, got {:?}", target.kind()); + bail!("{} cannot be indexed for assignment", self.value_type_name(target)); }; *handle }; @@ -135,6 +168,8 @@ impl Executor { } }; + let key = self.resolve_negative_list_key(handle, key)?; + if let Some(done) = self.try_set_string_list(handle, &key, value)? { self.maybe_bump_shape(handle, has_static_fact); return Ok(done); @@ -150,14 +185,17 @@ impl Executor { let RuntimeMapKey::Int(index) = key else { bail!("SetIndex list key must be Int"); }; - let index = usize::try_from(index).map_err(|_| anyhow!("list index must be non-negative"))?; + let index = usize::try_from(index).map_err(|_| anyhow!("list index {index} out of bounds"))?; set_list_value(list, index, value) } HeapValue::Map(map) => { map.set(key, value); Ok::<(), anyhow::Error>(()) } - other => bail!("SetIndex target object changed while writing: {:?}", heap_kind(other)), + other => bail!( + "SetIndex target object changed while writing: {:?}", + HeapValue::type_name(other) + ), }?; self.maybe_bump_shape(handle, has_static_fact); Ok(()) @@ -195,7 +233,7 @@ impl Executor { ); record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::DirectStringKey); with_string_int_key(prefix, suffix, |key| { - if self.try_set_typed_string_map(handle, key, &value, known_value_kind)? { + if self.try_set_typed_string_map(handle, KeyText::Borrowed(key), &value, known_value_kind)? { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::TypedMapDirect); return Ok(()); } @@ -213,7 +251,7 @@ impl Executor { } other => bail!( "SetIndexStrI target object changed while writing map: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), } })??; @@ -232,6 +270,53 @@ impl Executor { } } + /// A write index resolved against the list's length: **negative counts from + /// the end**, exactly as the read does. + /// + /// `xs[-1]` read the last element and `xs[-1] = 9` answered "list index must + /// be non-negative" — the same expression, one direction. Out of range + /// after resolving is still an error: writing past the end is not something + /// you can mean, unlike reading past it (which is nil). + /// + /// The length lookup happens only for a negative index, so the ordinary + /// write pays one predictable compare. + /// + /// **Out of range is reported here, naming the index as written.** The + /// resolved value is what the rest of the write path carries, so raising + /// further down could only say `-6` for `xs.set(-9, v)` on a three-element + /// list — a number the program never wrote, and one the native build + /// (which still has the original) did not print either. Both ends of the + /// range now say `list index N out of bounds` with the same `N`. + #[inline] + pub(super) fn negative_list_index_from_end(&self, handle: HeapRef, index: i64) -> Result { + if index >= 0 { + return Ok(index); + } + match self.state.heap.get(handle) { + Some(HeapValue::List(list)) => { + let resolved = index + list.len() as i64; + if resolved < 0 { + bail!("list index {index} out of bounds"); + } + Ok(resolved) + } + // Not a list: the caller's own dispatch reports what it is. + _ => Ok(index), + } + } + + /// [`Self::negative_list_index_from_end`] for the dynamic path, where the + /// key has already been built and the target may not be a list at all. + #[inline] + fn resolve_negative_list_key(&self, handle: HeapRef, key: RuntimeMapKey) -> Result { + match key { + RuntimeMapKey::Int(index) if index < 0 => { + Ok(RuntimeMapKey::Int(self.negative_list_index_from_end(handle, index)?)) + } + other => Ok(other), + } + } + pub(super) fn set_list_index_handle( &mut self, handle: HeapRef, @@ -241,7 +326,8 @@ impl Executor { known_value_kind: Option, has_static_fact: bool, ) -> Result<()> { - let index = self.int_key_from_register_or_value(key_reg, moved_key)?; + let index = + self.negative_list_index_from_end(handle, self.int_key_from_register_or_value(key_reg, moved_key)?)?; let key = RuntimeMapKey::Int(index); if matches!( self.state.heap.get(handle), @@ -251,7 +337,7 @@ impl Executor { self.maybe_bump_shape(handle, has_static_fact); return Ok(done); } - let index = usize::try_from(index).map_err(|_| anyhow!("list index must be non-negative"))?; + let index = usize::try_from(index).map_err(|_| anyhow!("list index {index} out of bounds"))?; if self.try_set_typed_list_index(handle, index, &value, known_value_kind)? { return Ok(()); } @@ -264,7 +350,7 @@ impl Executor { HeapValue::List(list) => set_list_value(list, index, value), other => bail!( "SetIndex target object changed while writing list: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), }?; self.maybe_bump_shape(handle, has_static_fact); @@ -322,7 +408,7 @@ impl Executor { (PerfValueKind::Unknown, _, _) | (_, HeapValue::List(_), _) => Ok(false), (_, other, _) => bail!( "SetIndex target object changed while writing list: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), } } @@ -335,7 +421,7 @@ impl Executor { key_reg: u8, moved_key: Option, value: RuntimeVal, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, known_value_kind: Option, has_static_fact: bool, mut index_key_metrics: Option<&mut [u64; VM_INDEX_KEY_METRIC_COUNT]>, @@ -345,7 +431,7 @@ impl Executor { if let Some(key_str) = known_string_key { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::KnownStringKey); record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::DirectStringKey); - if self.try_set_typed_string_map(handle, key_str, &value, known_value_kind)? { + if self.try_set_typed_string_map(handle, KeyText::Shared(key_str), &value, known_value_kind)? { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::TypedMapDirect); return Ok(()); } @@ -369,7 +455,7 @@ impl Executor { VmIndexKeyMetric::DynamicShortStringKey, ); record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::DirectStringKey); - if self.try_set_typed_string_map(handle, key_str, &value, known_value_kind)? { + if self.try_set_typed_string_map(handle, KeyText::Borrowed(key_str), &value, known_value_kind)? { record_index_key_metric(index_key_metrics.as_deref_mut(), VmIndexKeyMetric::TypedMapDirect); return Ok(()); } @@ -408,7 +494,7 @@ impl Executor { } other => bail!( "SetIndex target object changed while writing map: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), }?; self.maybe_bump_shape(handle, has_static_fact); @@ -423,7 +509,7 @@ impl Executor { key_reg: u8, moved_key: Option, value: RuntimeVal, - known_string_key: Option<&str>, + known_string_key: Option<&Arc>, known_value_kind: Option, ) -> Result<()> { self.set_map_index_handle( @@ -508,7 +594,7 @@ impl Executor { (PerfValueKind::Unknown, _, _) | (_, HeapValue::Map(_), _) => Ok(false), (_, other, _) => bail!( "SetIndex target object changed while writing map: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), } } @@ -519,10 +605,11 @@ impl Executor { fn try_set_typed_string_map( &mut self, handle: HeapRef, - key_str: &str, + key: KeyText<'_>, value: &RuntimeVal, known_value_kind: Option, ) -> Result { + let key_str = key.as_str(); match ( known_value_kind.unwrap_or_default(), self.state @@ -535,7 +622,7 @@ impl Executor { if let Some(existing) = values.get_mut(key_str) { *existing = *iv; } else { - values.insert(Arc::::from(key_str), *iv); + values.insert(key.to_arc(), *iv); } Ok(true) } @@ -543,7 +630,7 @@ impl Executor { if let Some(existing) = values.get_mut(key_str) { *existing = *fv; } else { - values.insert(Arc::::from(key_str), *fv); + values.insert(key.to_arc(), *fv); } Ok(true) } @@ -551,7 +638,7 @@ impl Executor { if let Some(existing) = values.get_mut(key_str) { *existing = *bv; } else { - values.insert(Arc::::from(key_str), *bv); + values.insert(key.to_arc(), *bv); } Ok(true) } @@ -559,7 +646,7 @@ impl Executor { if let Some(existing) = values.get_mut(key_str) { *existing = *iv; } else { - values.insert(Arc::::from(key_str), *iv); + values.insert(key.to_arc(), *iv); } Ok(true) } @@ -567,7 +654,7 @@ impl Executor { if let Some(existing) = values.get_mut(key_str) { *existing = *fv; } else { - values.insert(Arc::::from(key_str), *fv); + values.insert(key.to_arc(), *fv); } Ok(true) } @@ -575,14 +662,14 @@ impl Executor { if let Some(existing) = values.get_mut(key_str) { *existing = *bv; } else { - values.insert(Arc::::from(key_str), *bv); + values.insert(key.to_arc(), *bv); } Ok(true) } (PerfValueKind::Unknown, _, _) | (_, HeapValue::Map(_), _) => Ok(false), (_, other, _) => bail!( "SetIndex target object changed while writing map: {:?}", - heap_kind(other) + HeapValue::type_name(other) ), } } diff --git a/core/src/vm/exec/dispatch.rs b/core/src/vm/exec/dispatch.rs index 3c65cde0..23016c05 100644 --- a/core/src/vm/exec/dispatch.rs +++ b/core/src/vm/exec/dispatch.rs @@ -70,7 +70,8 @@ impl Executor { pub(super) fn dispatch_load_capture(&mut self, instr: Instr) -> Result<()> { let value = self .captures - .get(instr.bx() as usize) + .as_ref() + .and_then(|captures| captures.get(instr.bx() as usize)) .cloned() .ok_or_else(|| anyhow!("LoadCapture index {} out of bounds", instr.bx()))?; self.write(instr.a(), value)?; @@ -115,13 +116,6 @@ impl Executor { Ok(()) } - #[cold] - pub(super) fn dispatch_load_native(&mut self, instr: Instr, module: Option<&Module>) -> Result<()> { - self.load_native_value(instr.a(), instr.bx(), module)?; - self.pc += 1; - Ok(()) - } - #[cold] /// `A = B as `. /// @@ -150,7 +144,7 @@ impl Executor { RuntimeVal::Int(value) => value != 0, RuntimeVal::Float(value) => value != 0.0, RuntimeVal::Nil => false, - other => bail!("cannot cast {:?} to Bool", other.kind()), + other => bail!("cannot cast {} to Bool", self.value_type_name(&other)), }), CastTarget::Float => RuntimeVal::Float(match source { RuntimeVal::Float(value) => value, @@ -162,12 +156,12 @@ impl Executor { 0.0 } } - other => bail!("cannot cast {:?} to Float", other.kind()), + other => bail!("cannot cast {} to Float", self.value_type_name(&other)), }), CastTarget::Int => RuntimeVal::Int(cast_source_to_i64(&source)?), machine => { let kind = machine.int_kind().expect("non-scalar targets handled above"); - RuntimeVal::Int(truncate_to_width(cast_source_to_i64(&source)?, kind)) + RuntimeVal::Int(cast_to_machine_int(&source, kind)?) } }; @@ -176,12 +170,51 @@ impl Executor { Ok(()) } + /// `A = -B`. + /// + /// Deliberately not lowered as `0 - B`: floats have two zeros, and + /// `-(0.0)` is `-0.0` while `0.0 - 0.0` is `0.0`. Negating `Int::MIN` + /// wraps, the same as every other integer overflow in the VM. + /// `A = floor(B / C)` — the fused `math.floor(a / b)`. + /// + /// Two `Int`s take the integer path, which is *floor* rather than + /// truncation: `-7 / 2` floors to `-4` where Rust's `/` gives `-3`. + /// Anything else divides as `f64` and floors that, which is exactly what + /// the `math.floor` call this replaces would have answered. + pub(super) fn dispatch_floor_div_int(&mut self, instr: Instr) -> Result<()> { + let (dst, lhs_idx, rhs_idx) = self.stack_abc_indices(instr)?; + let value = match (&self.state.stack[lhs_idx], &self.state.stack[rhs_idx]) { + (RuntimeVal::Int(_), RuntimeVal::Int(0)) => bail!("division by zero"), + (RuntimeVal::Int(lhs), RuntimeVal::Int(rhs)) => RuntimeVal::Int(lhs.div_euclid(*rhs)), + (lhs, rhs) => { + let lhs = self.number_value(lhs)?; + let rhs = self.number_value(rhs)?; + RuntimeVal::Int(crate::compat::float::floor(lhs / rhs) as i64) + } + }; + self.write_stack_index(dst, value); + self.pc += 1; + Ok(()) + } + + pub(super) fn dispatch_neg(&mut self, instr: Instr) -> Result<()> { + let index = self.stack_index_unchecked(instr.b()); + let value = match &self.state.stack[index] { + RuntimeVal::Int(value) => RuntimeVal::Int(value.wrapping_neg()), + RuntimeVal::Float(value) => RuntimeVal::Float(-value), + other => bail!("unary '-' expects Int or Float, got {}", self.value_type_name(other)), + }; + self.write_unchecked(instr.a(), value); + self.pc += 1; + Ok(()) + } + pub(super) fn dispatch_not(&mut self, function: &Function, instr: Instr) -> Result<()> { let index = self.stack_index_unchecked(instr.b()); let value = match &self.state.stack[index] { RuntimeVal::Bool(b) => !b, RuntimeVal::Nil => true, - other => bail!("Not expected Bool or Nil, got {:?}", other.kind()), + other => bail!("Not expected Bool or Nil, got {}", self.value_type_name(other)), }; if self.try_fused_bool_branch(function, instr.a(), value, self.collect_metrics)? { return Ok(()); @@ -531,10 +564,32 @@ impl Executor { collect_metrics: bool, ) -> Result> { self.safepoint()?; + let call_fact = self.call_fact_from_static_cache_or_instr(function, instr, false); if collect_metrics { - record_call_op_known_enabled(VmCallMetric::Generic); + // Classified, not just counted. `native_call_ops` / `closure_call_ops` + // were printed by `lk coverage --profile` and were **structurally + // zero**: the variants had arms adding them up and no site ever + // constructed one, so every `Call` landed in `Generic`. A report that + // says a program calling `println` made no native calls is worse than + // one that says nothing, and `bench/README.md` decides fused opcodes + // from these numbers. + // + // The kind comes from the *callee value*, not from the static fact: + // the fact is `Unknown` at most call sites (the compiler proves it + // only for a direct module call), so classifying by it left the same + // zeroes it was supposed to fix. `observe_call_target_kind` reads the + // heap value the call is about to enter — which is what the counter + // is asking about. `Runtime` (a compiled LK function reached through + // a value) has no bucket of its own, so it stays in the unclassified + // total along with `Unknown`. + record_call_op_known_enabled(match self.observe_call_target_kind(call_fact.call_base) { + crate::vm::analysis::PerfCallTargetKind::Native => VmCallMetric::Native, + crate::vm::analysis::PerfCallTargetKind::Closure => VmCallMetric::Closure, + crate::vm::analysis::PerfCallTargetKind::Runtime | crate::vm::analysis::PerfCallTargetKind::Unknown => { + VmCallMetric::Generic + } + }); } - let call_fact = self.call_fact_from_static_cache_or_instr(function, instr, false); let window = CallWindow::new(RegisterIndex::new(call_fact.call_base), call_fact.positional_count, 1); let call_pc = self.pc; match self.call_function(module, window, Some(call_fact.target_kind), ctx)? { @@ -592,7 +647,7 @@ impl Executor { } else { *self.read(instr.a())? }; - let slot = self.global_slot_from_fact_cache_or_instr(function, instr); + let slot = self.global_slot_from_fact_or_instr(function, instr); self.write_global(slot, value)?; self.pc += 1; Ok(()) @@ -625,12 +680,15 @@ impl Executor { Opcode::MakeClosure => { self.dispatch_make_closure(instr, module)?; } - Opcode::LoadNative => { - self.dispatch_load_native(instr, module)?; - } Opcode::Not => { self.dispatch_not(function, instr)?; } + Opcode::Neg => { + self.dispatch_neg(instr)?; + } + Opcode::FloorDivInt => { + self.dispatch_floor_div_int(instr)?; + } Opcode::CastTo => { self.dispatch_cast(instr)?; } @@ -722,7 +780,8 @@ impl Executor { } } RuntimeVal::Int(n) => { - let n_str = n.to_string(); + let mut digits = [0u8; MAX_I64_DIGITS]; + let n_str = int_decimal(*n, &mut digits); if short_len + n_str.len() <= 7 { short_buf[short_len..short_len + n_str.len()].copy_from_slice(n_str.as_bytes()); short_len += n_str.len(); @@ -782,20 +841,86 @@ fn cast_source_to_i64(source: &RuntimeVal) -> Result { // Truncates toward zero, like every other language's float-to-int cast. RuntimeVal::Float(value) => *value as i64, RuntimeVal::Bool(value) => i64::from(*value), - other => bail!("cannot cast {:?} to an integer", other.kind()), + // No heap here, and none is needed: only a scalar can be cast, so a + // handle is exactly the case this refuses. `scalar_type_name` says + // `Object` and names itself for saying it. + other => bail!("cannot cast {} to an integer", other.kind().scalar_type_name()), }) } +/// The `i64` carrier holding `source` narrowed to `kind`. +/// +/// An integer source **wraps** — `300 as u8` is 44, which is what `as` means +/// between integers. A float source **saturates to `kind`'s own range**, and +/// that is the difference this exists for: going through `i64` first saturated +/// to *its* range and then masked the result, so a value out of range came back +/// as an arbitrary bit pattern rather than as the nearest representable one. +/// +/// It showed on division, because `/` is float division and dividing by zero is +/// `inf`: +/// +/// | expression | was | now | +/// | --- | --- | --- | +/// | `1 / 0` at `i32` | `-1` | `2147483647` | +/// | `-1 / 0` at `i32` | `0` | `-2147483648` | +/// | `1 / 0` at `u8` | `255` | `255` | +/// | `0 / 0` at `u8` | `0` | `0` | +/// +/// Two of the four were already right by coincidence — `u8`'s mask happens to +/// keep the low byte of `i64::MAX`, which is `255`. +fn cast_to_machine_int(source: &RuntimeVal, kind: crate::val::IntKind) -> Result { + if let RuntimeVal::Float(value) = source { + let bits = kind.bits().unwrap_or(usize::BITS); + if bits >= 64 { + // The `i64`/`u64` carriers are the full width, so `as` already + // saturates to exactly the right range — except `u64`, whose range + // the carrier holds as a bit pattern. + return Ok(if kind.is_signed() { + *value as i64 + } else { + *value as u64 as i64 + }); + } + let (low, high) = if kind.is_signed() { + (-(1i64 << (bits - 1)), (1i64 << (bits - 1)) - 1) + } else { + (0, (1i64 << bits) - 1) + }; + // NaN casts to zero, as it does everywhere `as` is defined. + if value.is_nan() { + return Ok(0); + } + return Ok(if *value <= low as f64 { + low + } else if *value >= high as f64 { + high + } else { + *value as i64 + }); + } + Ok(truncate_to_width(cast_source_to_i64(source)?, kind)) +} + /// Reduce `value` to `kind`'s width, then widen it back into the `i64` carrier /// by `kind`'s signedness. /// -/// Pointer-width kinds are treated as 64-bit here. That is the width on every -/// target the VM itself runs on; a 32-bit *deployment* target gets its real -/// width from the AOT path, which lowers to a genuine `i32`. +/// A pointer-width kind takes the width of the machine this VM is *running on*, +/// which is what `isize`/`usize` mean: on `thumbv7em-none-eabi` — a target this +/// VM is built for — a `usize` is 32 bits, and a value that does not fit one is +/// not an address that machine can hold. +/// +/// This used to leave them unmasked with the note that "a 32-bit deployment +/// target gets its real width from the AOT path, which lowers to a genuine +/// `i32`". The AOT path cannot: Cranelift's backend set here has no 32-bit +/// target, and every 32-bit triple is refused at `isa::lookup` +/// (`no_32_bit_target_is_reachable_yet` pins that, and names what to fix when +/// one becomes reachable). So the promise was to a mechanism that does not +/// exist, and the VM was the only thing that could have kept it. +/// +/// On a 64-bit host this changes nothing — `usize::BITS` is 64 and the early +/// return below already covered it. fn truncate_to_width(value: i64, kind: crate::val::IntKind) -> i64 { - let Some(bits) = kind.bits() else { - return value; - }; + let bits = kind.bits().unwrap_or(usize::BITS); if bits >= 64 { return value; } @@ -807,3 +932,55 @@ fn truncate_to_width(value: i64, kind: crate::val::IntKind) -> i64 { masked as i64 } } + +/// Widest decimal `i64` — `i64::MIN` is 20 characters including the sign. +const MAX_I64_DIGITS: usize = 20; + +/// `value` in decimal, written into a caller-owned buffer. +/// +/// The interpolation fast path above builds a `ShortStr` in a stack array +/// precisely so that a short result costs no heap allocation. It reached the +/// integer arm through `to_string()`, which allocates a `String` and frees it +/// two lines later — the fast path was paying the allocation it exists to +/// avoid, on every interpolation with an integer in it. +fn int_decimal(value: i64, buf: &mut [u8; MAX_I64_DIGITS]) -> &str { + let mut magnitude = value.unsigned_abs(); + let mut index = buf.len(); + loop { + index -= 1; + buf[index] = b'0' + (magnitude % 10) as u8; + magnitude /= 10; + if magnitude == 0 { + break; + } + } + if value < 0 { + index -= 1; + buf[index] = b'-'; + } + // Only ASCII digits and `-` were written, so this cannot fail. + core::str::from_utf8(&buf[index..]).unwrap_or("") +} + +#[cfg(test)] +mod int_decimal_tests { + use alloc::string::ToString; + + use super::{MAX_I64_DIGITS, int_decimal}; + + /// Against `to_string`, which is what this replaced — including the two + /// values a hand-rolled formatter gets wrong: zero (the loop must run once) + /// and `i64::MIN` (whose magnitude does not fit in `i64`). + #[test] + fn matches_to_string_including_the_edges() { + let cases = [0, 1, -1, 9, 10, -10, 99, 1234567, -1234567, i64::MAX, i64::MIN]; + for value in cases { + let mut buf = [0u8; MAX_I64_DIGITS]; + assert_eq!(int_decimal(value, &mut buf), value.to_string(), "for {value}"); + } + for value in -1000..1000i64 { + let mut buf = [0u8; MAX_I64_DIGITS]; + assert_eq!(int_decimal(value, &mut buf), value.to_string(), "for {value}"); + } + } +} diff --git a/core/src/vm/exec/display.rs b/core/src/vm/exec/display.rs new file mode 100644 index 00000000..cfe04c85 --- /dev/null +++ b/core/src/vm/exec/display.rs @@ -0,0 +1,417 @@ +//! How a value looks — the one rendering, used by everything that prints. +//! +//! There were two. `println` went through the standard library's renderer and +//! the VM had its own for the REPL, `lk-api`, and (once it stopped erroring) +//! template interpolation. They disagreed about separators, about quoting +//! strings inside a list, and about which types they had heard of: +//! +//! | | `println(v)` | `"${v}"` | +//! |---|---|---| +//! | list | `[1,2]` | `[1, 2]` | +//! | map | `{"a":1}` | `{a: 1}` | +//! | bytes | `` | `` | +//! +//! (Bytes now renders its contents, `Bytes([104, 105])`, like every other +//! container — neither of the two old answers said what was in it.) +//! +//! The standard library's is the one every test and differential comparison +//! pins, so it is the one that moved here — where core can use it and the +//! standard library can call back into it. `show` dispatch stays a layer up in +//! `lk_stdlib_common::language`: it needs to call user code, which is not +//! something a renderer can do. + +#[cfg(not(feature = "std"))] +use crate::compat::prelude::*; +use anyhow::{Result, anyhow}; +use core::fmt::Write as _; + +use crate::val::{ + CallableValue, HeapStore, HeapValue, MAX_VALUE_DEPTH, RuntimeMapKey, RuntimeSet, RuntimeVal, SliceValue, TypedList, + TypedMap, +}; + +/// A value inside a container, where a string is quoted. +/// +/// Quoting is what tells `["1"]` from `[1]`, and `["a, b"]` from `["a","b"]`. +/// It used to depend on the list's *internal representation*, which no program +/// can see: a `TypedList::String` quoted its elements and a `TypedList::Mixed` +/// did not, so +/// +/// ```text +/// ["a", "b"] → ["a","b"] +/// [1, "a"] → [1,a] +/// {"k": "v"} → {"k":v} the key quoted, the value not +/// ``` +/// +/// A string on its own is still its text: `println("abc")` prints `abc`. The +/// split is the usual one — a value shown *as data* is quoted, a string printed +/// *as output* is not. +/// +/// Every step further into a container goes through here, so this is where the +/// walk's depth is bounded — see [`MAX_VALUE_DEPTH`]. +fn runtime_display_nested(value: &RuntimeVal, heap: &HeapStore, depth: u32) -> Result { + if depth >= MAX_VALUE_DEPTH { + return Err(anyhow!( + "value nested deeper than {MAX_VALUE_DEPTH} levels; it is cyclic or too deeply nested to print" + )); + } + match value { + RuntimeVal::ShortStr(value) => Ok(quote_string(value.as_str())), + RuntimeVal::Obj(handle) => match heap + .get(*handle) + .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? + { + HeapValue::String(value) => Ok(quote_string(value)), + other => runtime_display_heap_value(other, heap, depth + 1), + }, + other => runtime_display_value_at(other, heap, depth + 1), + } +} + +pub fn runtime_display_value(value: &RuntimeVal, heap: &HeapStore) -> Result { + runtime_display_value_at(value, heap, 0) +} + +fn runtime_display_value_at(value: &RuntimeVal, heap: &HeapStore, depth: u32) -> Result { + match value { + RuntimeVal::Nil => Ok("nil".to_string()), + RuntimeVal::Bool(value) => Ok(value.to_string()), + RuntimeVal::Int(value) => Ok(value.to_string()), + RuntimeVal::Float(value) => Ok(value.to_string()), + RuntimeVal::ShortStr(value) => Ok(value.as_str().to_string()), + RuntimeVal::Obj(handle) => { + let value = heap + .get(*handle) + .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?; + runtime_display_heap_value(value, heap, depth) + } + } +} +fn runtime_display_heap_value(value: &HeapValue, heap: &HeapStore, depth: u32) -> Result { + match value { + HeapValue::String(value) => Ok(value.to_string()), + HeapValue::Bytes(value) => Ok(runtime_display_bytes(value)), + HeapValue::List(values) => runtime_display_list(values, heap, depth), + HeapValue::Slice(slice) => runtime_display_slice(slice, heap, depth), + HeapValue::Map(values) => runtime_display_map(values, heap, depth), + HeapValue::Set(values) => runtime_display_set(values), + HeapValue::Callable(value) => Ok(runtime_display_callable(value)), + HeapValue::Object(value) => { + let mut out = value.type_name().to_string(); + // Declaration order — the order the `struct` was written in, which + // travels with the type (see `DeclaredType::fields`). The fields + // themselves live in a hash map, so without it the order was the + // hasher's: `struct Range { start, end }` printed `end` first, and + // a hasher change would have silently permuted every struct. + // + // Sorted by name when the declaration is out of reach — a struct + // from a module whose type info this executor does not hold, or an + // object a host built. Arbitrary but stable, which hash order is + // not. + let declared = value.ty.fields.as_ref(); + let mut fields: Vec<_> = value.fields.iter().collect(); + if declared.is_empty() { + fields.sort_by_key(|(left, _)| *left); + } else { + let position = |name: &alloc::sync::Arc| { + declared + .iter() + .position(|field| &field.name == name) + .unwrap_or(usize::MAX) + }; + fields.sort_by(|(left, _), (right, _)| { + position(left).cmp(&position(right)).then_with(|| left.cmp(right)) + }); + } + append_display_entries( + &mut out, + fields + // A field's value is data inside a container, so it quotes + // like a list element or a map value. It used to go through + // the top-level renderer instead, so `P { name: "a, b" }` + // printed as `P{name:a, b}` — which reads as two fields. + .into_iter() + .map(|(key, value)| Ok((key.to_string(), runtime_display_nested(value, heap, depth)?))), + )?; + Ok(out) + } + other => Ok(format!("<{}>", other.type_name())), + } +} +/// `Bytes([104, 105])` — the contents, in the shape `Set` already uses. +/// +/// It used to be ``: a count where every other container shows +/// what is in it, so the one way to see a byte buffer was to convert it +/// (`b.to_list()`), and printing one while debugging told you nothing. The +/// wrapper keeps it distinct from the list `[104, 105]`, which is a different +/// value. +fn runtime_display_bytes(value: &[u8]) -> String { + let mut out = String::from("Bytes(["); + let mut first = true; + for byte in value { + push_display_sep(&mut out, &mut first); + let _ = write!(out, "{byte}"); + } + out.push_str("])"); + out +} + +fn runtime_display_set(values: &RuntimeSet) -> Result { + let mut out = String::from("Set("); + out.push('['); + let mut first = true; + // Sorted by *member*, not by rendered text. See + // `RuntimeMapKey::display_order` for what the text sort produced. + let mut entries = values.entries().collect::>(); + entries.sort_by(|a, b| a.display_order(b)); + for key in entries { + push_display_sep(&mut out, &mut first); + out.push_str(&runtime_display_map_key(key)); + } + out.push(']'); + out.push(')'); + Ok(out) +} +fn runtime_display_callable(value: &CallableValue) -> String { + match value { + CallableValue::Closure { + function_index, + captures, + } => format!("", function_index, captures.len()), + CallableValue::RuntimeNative { name, arity, .. } => { + if *arity == crate::vm::NativeEntry::VARIADIC { + format!("", name) + } else { + format!("", name, arity) + } + } + CallableValue::Runtime(function) => { + format!( + "", + function.display_signature(), + function.capture_count() + ) + } + } +} +fn runtime_display_list(values: &TypedList, heap: &HeapStore, depth: u32) -> Result { + let mut out = String::from("["); + let mut first = true; + match values { + TypedList::Mixed(values) => { + for value in values { + push_display_sep(&mut out, &mut first); + out.push_str(&runtime_display_nested(value, heap, depth)?); + } + } + TypedList::Int(values) => { + for value in values { + push_display_sep(&mut out, &mut first); + write!(&mut out, "{value}").expect("write to String cannot fail"); + } + } + TypedList::Float(values) => { + for value in values { + push_display_sep(&mut out, &mut first); + write!(&mut out, "{value}").expect("write to String cannot fail"); + } + } + TypedList::Bool(values) => { + for value in values { + push_display_sep(&mut out, &mut first); + write!(&mut out, "{value}").expect("write to String cannot fail"); + } + } + TypedList::String(values) => { + for value in values { + push_display_sep(&mut out, &mut first); + out.push_str("e_string(value)); + } + } + } + out.push(']'); + Ok(out) +} +/// A window prints as the part of the list it windows — `[1,4,1]`, not +/// ``. It used to fall through to the opaque-handle arm, which is the +/// right answer for a `Stream` or a `Resource` and the wrong one here: a window +/// has elements, and every other way of looking at it (`len`, indexing, +/// `to_list`) already shows them. +fn runtime_display_slice(slice: &SliceValue, heap: &HeapStore, depth: u32) -> Result { + let RuntimeVal::Obj(source) = slice.source else { + return Ok("[]".to_string()); + }; + let Some(HeapValue::List(values)) = heap.get(source) else { + return Ok("[]".to_string()); + }; + // `live_len`: the source can have shrunk since the window was taken. + let window = values.window(slice.start, slice.live_len(heap)); + runtime_display_list(&window, heap, depth) +} +fn runtime_display_map(values: &TypedMap, heap: &HeapStore, depth: u32) -> Result { + let mut out = String::new(); + match values { + TypedMap::Mixed(entries) => append_display_entries( + &mut out, + entries.iter().map(|(key, value)| { + Ok(( + runtime_display_map_key(key), + runtime_display_nested(value, heap, depth)?, + )) + }), + )?, + TypedMap::StringMixed(entries) => append_display_entries( + &mut out, + entries + .iter() + .map(|(key, value)| Ok((quote_string(key), runtime_display_nested(value, heap, depth)?))), + )?, + TypedMap::StringInt(entries) => append_display_entries( + &mut out, + entries + .iter() + .map(|(key, value)| Ok((quote_string(key), value.to_string()))), + )?, + TypedMap::StringFloat(entries) => append_display_entries( + &mut out, + entries + .iter() + .map(|(key, value)| Ok((quote_string(key), value.to_string()))), + )?, + TypedMap::StringBool(entries) => append_display_entries( + &mut out, + entries + .iter() + .map(|(key, value)| Ok((quote_string(key), value.to_string()))), + )?, + } + Ok(out) +} +fn runtime_display_map_key(key: &RuntimeMapKey) -> String { + match key { + RuntimeMapKey::Nil => "nil".to_string(), + RuntimeMapKey::Bool(value) => value.to_string(), + RuntimeMapKey::Int(value) => value.to_string(), + RuntimeMapKey::ShortStr(value) => quote_string(value.as_str()), + RuntimeMapKey::String(value) => quote_string(value), + } +} +fn append_display_entries(out: &mut String, entries: impl IntoIterator>) -> Result<()> { + out.push('{'); + let mut first = true; + for entry in entries { + let (key, value) = entry?; + push_display_sep(out, &mut first); + out.push_str(&key); + out.push(':'); + out.push_str(&value); + } + out.push('}'); + Ok(()) +} +fn push_display_sep(out: &mut String, first: &mut bool) { + if *first { + *first = false; + } else { + out.push(','); + } +} +fn quote_string(value: &str) -> String { + format!("{value:?}") +} + +#[cfg(test)] +mod tests { + use alloc::sync::Arc; + + use super::*; + use crate::val::{DeclaredType, TypeScope}; + use crate::val::{MAX_VALUE_DEPTH, RuntimeObject}; + + fn object_of(fields: &[(&str, RuntimeVal)]) -> RuntimeObject { + RuntimeObject::new( + Arc::new(DeclaredType::new(TypeScope::anonymous(), Arc::::from("P"))), + crate::util::value_map::value_map_from_iter( + fields.iter().map(|(name, value)| (Arc::::from(*name), *value)), + ), + ) + } + + fn declared_object_of(declared: &[&str], fields: &[(&str, RuntimeVal)]) -> RuntimeObject { + RuntimeObject::new( + Arc::new(DeclaredType::with_fields( + TypeScope::anonymous(), + Arc::::from("P"), + declared + .iter() + .map(|name| crate::val::DeclaredField::new(Arc::::from(*name), None)) + .collect(), + )), + crate::util::value_map::value_map_from_iter( + fields.iter().map(|(name, value)| (Arc::::from(*name), *value)), + ), + ) + } + + /// Fields print in the order the `struct` declares them, whatever order the + /// value was built in. They lived in a hash map, so the order used to be + /// the hasher's: `struct Range { start, end }` printed `end` first. + #[test] + fn object_fields_follow_the_declaration_order() { + let mut heap = HeapStore::new(); + let object = RuntimeVal::Obj(heap.alloc(HeapValue::Object(declared_object_of( + &["start", "end"], + &[("end", RuntimeVal::Int(9)), ("start", RuntimeVal::Int(1))], + )))); + + assert_eq!( + runtime_display_value(&object, &heap).expect("render"), + "P{start:1,end:9}" + ); + } + + /// A struct field is data inside a container, so it quotes like a list + /// element. It went through the top-level renderer instead, and + /// `P { name: "a, b" }` printed as `P{name:a, b}` — which reads as two + /// fields. Order is the declaration's, or sorted when the declaration is + /// out of reach — never the hash order a reader cannot predict. + #[test] + fn object_fields_are_quoted_and_ordered() { + let mut heap = HeapStore::new(); + let text = RuntimeVal::Obj(heap.alloc(HeapValue::String(Arc::::from("a, b")))); + let object = RuntimeVal::Obj(heap.alloc(HeapValue::Object(object_of(&[ + ("name", text), + ("count", RuntimeVal::Int(2)), + ])))); + + assert_eq!( + runtime_display_value(&object, &heap).expect("render"), + "P{count:2,name:\"a, b\"}" + ); + } + + /// Printing a chain deeper than the bound raises instead of overflowing the + /// Rust stack, which used to abort the process. + #[test] + fn nesting_past_the_bound_raises_instead_of_aborting() { + let mut heap = HeapStore::new(); + let mut node = RuntimeVal::Int(1); + for _ in 0..(MAX_VALUE_DEPTH + 8) { + node = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(vec![node])))); + } + + let error = runtime_display_value(&node, &heap).expect_err("too deep to print"); + assert!(error.to_string().contains("nested deeper than"), "{error}"); + } + + #[test] + fn nesting_within_the_bound_still_renders() { + let mut heap = HeapStore::new(); + let mut node = RuntimeVal::Int(1); + for _ in 0..3 { + node = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Mixed(vec![node])))); + } + + assert_eq!(runtime_display_value(&node, &heap).expect("render"), "[[[1]]]"); + } +} diff --git a/core/src/vm/exec/exec_tests.rs b/core/src/vm/exec/exec_tests.rs index 27dbdc69..8d7e999d 100644 --- a/core/src/vm/exec/exec_tests.rs +++ b/core/src/vm/exec/exec_tests.rs @@ -5,8 +5,7 @@ use alloc::sync::Arc; use crate::{ val::{CallableValue, HeapRef, HeapStore, HeapValue, RuntimeMapKey, RuntimeVal, ShortStr, TypedList, TypedMap}, vm::{ - ConstHeapValue, ConstPool, Instr, NativeArgs, NativeEntry, NativeFunction, NativeRuntime, Opcode, - RuntimeCallable, VmContext, + ConstHeapValue, ConstPool, Instr, NativeArgs, NativeFunction, NativeRuntime, Opcode, RuntimeCallable, VmContext, }, }; @@ -19,3 +18,48 @@ mod cross_heap; mod gc_cell_error; mod gc_host_roots; mod native; + +/// Run `source` against a context carrying `natives`, the way a program reaches +/// a native for real. +/// +/// Every stdlib native arrives as a **global** holding a +/// `CallableValue::RuntimeNative` — `VmContext::install_runtime_builtin` puts +/// it there, and the compiler resolves the name to a global slot the loader +/// seeds. Several tests in here instead built a `Module` with an inline +/// `NativeEntry` and a `LoadNative` instruction, which no binary could produce: +/// every production caller passed an empty table. The opcode is gone +/// (artifact version 17) and so is the per-module table it read +/// (2026-08-06) — this helper is the only route left, which is the point. +/// +/// The hand-built route cannot be pointed at an installed native either — +/// `execute_module_with_globals_and_ctx` does not seed the module's global +/// vector from the context, so it answers `module expected 1 globals, got 0`. +/// Hence a helper rather than a one-line substitution. +#[cfg(test)] +pub(crate) fn execute_source_with_natives( + source: &str, + natives: &[(&str, NativeFunction, u16)], +) -> anyhow::Result { + execute_source_with_natives_and_gc(source, natives, None) +} + +/// As [`execute_source_with_natives`], collecting after every `threshold` +/// allocations — what a hand-built module got by seeding its own `HeapStore`. +#[cfg(test)] +pub(crate) fn execute_source_with_natives_and_gc( + source: &str, + natives: &[(&str, NativeFunction, u16)], + gc_threshold: Option, +) -> anyhow::Result { + use crate::vm::ProgramExec; + + let mut ctx = VmContext::new(); + for (name, function, arity) in natives { + ctx.install_runtime_builtin(name, function.clone(), *arity); + } + let program = crate::syntax::parse_program_source(source, Default::default())?; + match gc_threshold { + Some(threshold) => crate::vm::execute_program_with_ctx_and_gc_threshold(&program, &mut ctx, threshold), + None => program.execute_with_ctx(&mut ctx), + } +} diff --git a/core/src/vm/exec/exec_tests/attributes.rs b/core/src/vm/exec/exec_tests/attributes.rs index 33fbd25b..bafa65cd 100644 --- a/core/src/vm/exec/exec_tests/attributes.rs +++ b/core/src/vm/exec/exec_tests/attributes.rs @@ -34,3 +34,25 @@ fn execute_source_treats_attributed_struct_as_normal_item() { assert_eq!(result.returns, vec![RuntimeVal::Int(7)]); } + +/// A missing method names the struct the user wrote, not the heap kind. +/// +/// `RuntimeVal::type_name_in` is the function the migration guard points every +/// message at so that none of them prints `Object` — and it printed `Object` for +/// every struct instance, because it returned `&'static str` and a struct's name +/// is not static. Inside `call_trait_method_runtime` the correct name was two +/// lines below the message, already computed for dispatch. +#[test] +fn a_missing_method_on_a_struct_names_the_struct() { + let error = execute_source( + r#" + struct Point { x: Int } + let p = Point { x: 1 }; + return p.nonexistent(); + "#, + ) + .expect_err("a struct has no such method"); + + let message = format!("{error:#}"); + assert!(message.contains("Point has no method 'nonexistent'"), "{message}"); +} diff --git a/core/src/vm/exec/exec_tests/basic.rs b/core/src/vm/exec/exec_tests/basic.rs index abd0dc16..46048e35 100644 --- a/core/src/vm/exec/exec_tests/basic.rs +++ b/core/src/vm/exec/exec_tests/basic.rs @@ -6,7 +6,6 @@ use crate::vm::{ PerfContainerBuildFact, PerfIndexFact, PerfIndexTargetKind, PerfKeyFact, PerfRegisterCopyFact, PerfValueKind, PerformanceFacts, }, - vm_runtime_metrics_reset, }; #[test] fn execute_returns_int_arithmetic_result() { @@ -66,7 +65,7 @@ fn execute_branches_with_test_and_jump() { fn execute_not_rejects_string_operand() { let function = Function { consts: ConstPool { - strings: vec!["ok".to_string()], + strings: vec![alloc::sync::Arc::::from("ok")], ..ConstPool::default() }, code: vec![ @@ -88,7 +87,16 @@ fn execute_not_rejects_string_operand() { } #[test] -fn execute_tostring_rejects_list_operand() { +fn execute_tostring_renders_a_list_like_print_does() { + // This asserted the *error* a list used to raise here. Three ways to print + // one value, two of which worked: + // + // println(xs) → [1,2] + // println("{}", xs) → [1,2] + // println("${xs}") → "object cannot be converted to string" + // + // …and the third failed at run time, after the type checker had approved + // it. A container renders the way `print` renders it now. let function = Function { consts: ConstPool { ints: vec![1], @@ -108,9 +116,13 @@ fn execute_tostring_rejects_list_operand() { ..Function::default() }; - let err = execute(&function).expect_err("list tostring operand must be rejected"); - - assert!(err.to_string().contains("object cannot be converted to string")); + let result = execute(&function).expect("a list renders"); + // Through the renderer rather than by unwrapping a handle: `"[1]"` is + // three bytes, so it arrives inline as a `ShortStr` rather than on the heap. + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "[1]" + ); } #[test] @@ -176,9 +188,12 @@ fn execute_load_heap_const_list_preserves_typed_string_backing() { } #[test] -fn execute_records_move_heap_clone_as_register_copy_metric() { - // RuntimeVal is Copy, so Move just copies the value without clone/move distinction. - // The copy policy metrics are no longer tracked by the Move handler. +fn move_under_a_register_copy_fact_carries_the_heap_value_through() { + // Named for what it checks. The old name — `..._records_move_heap_clone_as_ + // register_copy_metric` — described a recording that stopped happening when + // `RuntimeVal` became `Copy`: a register move copies the value, so there is no + // clone to count. The counters it named were removed; what is left worth + // asserting is that the copy *fact* doesn't corrupt the value it describes. let mut performance = PerformanceFacts::default(); performance.set_register_copy_fact(1, PerfRegisterCopyFact { move_source: false }); let function = Function { @@ -200,17 +215,18 @@ fn execute_records_move_heap_clone_as_register_copy_metric() { ..Function::default() }; - vm_runtime_metrics_reset(); let result = execute(&function).expect("execute"); - assert_eq!(result.returns[0].kind(), crate::val::RuntimeValKind::Obj); - // Move no longer tracks copy policy metrics since RuntimeVal is Copy. + // `kind() == Obj` alone would pass even if Move delivered a different string. + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "longer-than-seven" + ); } #[test] -fn execute_records_move_heap_clone_as_local_store_metric() { - // RuntimeVal is Copy, so Move just copies the value. - // The local copy/store metrics are tracked by the local store handler, not Move. +fn move_under_a_local_copy_fact_carries_the_heap_value_through() { + // The local-slot counterpart of the test above; same reason for the rename. let mut performance = PerformanceFacts::default(); performance.mark_local_slot(1); performance.set_register_copy_fact(1, PerfRegisterCopyFact { move_source: false }); @@ -234,11 +250,12 @@ fn execute_records_move_heap_clone_as_local_store_metric() { ..Function::default() }; - vm_runtime_metrics_reset(); let result = execute(&function).expect("execute"); - assert_eq!(result.returns[0].kind(), crate::val::RuntimeValKind::Obj); - // Move no longer tracks copy policy metrics since RuntimeVal is Copy. + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "longer-than-seven" + ); } #[test] @@ -246,7 +263,7 @@ fn execute_allocates_mixed_list_on_heap() { let function = Function { consts: ConstPool { ints: vec![1, 2], - strings: vec!["x".to_string()], + strings: vec![alloc::sync::Arc::::from("x")], ..ConstPool::default() }, code: vec![ @@ -414,7 +431,7 @@ fn execute_reads_len_for_typed_list_and_short_string() { let function = Function { consts: ConstPool { ints: vec![1, 2], - strings: vec!["abc".to_string()], + strings: vec![alloc::sync::Arc::::from("abc")], ..ConstPool::default() }, code: vec![ @@ -445,7 +462,7 @@ fn execute_to_iter_materializes_map_entries_as_pairs() { let function = Function { consts: ConstPool { ints: vec![1, 2, 0, 1], - strings: vec!["a".to_string(), "b".to_string()], + strings: vec![alloc::sync::Arc::::from("a"), alloc::sync::Arc::::from("b")], ..ConstPool::default() }, code: vec![ @@ -534,7 +551,10 @@ fn execute_allocates_object_and_reads_string_field() { let function = Function { consts: ConstPool { ints: vec![42], - strings: vec!["User".to_string(), "score".to_string()], + strings: vec![ + alloc::sync::Arc::::from("User"), + alloc::sync::Arc::::from("score"), + ], ..ConstPool::default() }, code: vec![ @@ -557,13 +577,10 @@ fn execute_allocates_object_and_reads_string_field() { let result = execute(&function).expect("execute"); assert_eq!(result.returns, vec![RuntimeVal::Int(42)]); - let cache = result - .state - .inline_caches - .index_cache_for_tests(4) - .expect("index cache"); - assert_eq!(cache.fact.target_kind, PerfIndexTargetKind::Object); - assert_eq!(cache.object_field_slot, Some(0)); + // No inline cache: a read whose target the heap can name outright is + // answered on the fast path, and the cache exists to spare the *cold* one. + // It used to be filled here because every object read went cold. + assert!(result.state.inline_caches.index_cache_for_tests(4).is_none()); } #[test] @@ -571,7 +588,7 @@ fn execute_allocates_typed_string_int_map_and_reads_string_key() { let function = Function { consts: ConstPool { ints: vec![42], - strings: vec!["answer".to_string()], + strings: vec![alloc::sync::Arc::::from("answer")], ..ConstPool::default() }, code: vec![ @@ -600,9 +617,9 @@ fn execute_allocates_typed_string_int_map_and_reads_string_key() { panic!("expected typed string-int map"); }; assert_eq!(values.get("answer"), Some(&42)); - let cache = result.state.inline_caches.index_fact_for_tests(4).expect("index cache"); - assert_eq!(cache.target_kind, PerfIndexTargetKind::Map); - assert_eq!(cache.value_kind, PerfValueKind::Int); + // As above: a map the heap names outright is read on the fast path, so + // nothing is cached for it. + assert!(result.state.inline_caches.index_fact_for_tests(4).is_none()); } #[test] @@ -610,7 +627,7 @@ fn execute_new_map_without_build_fact_clones_source_registers() { let function = Function { consts: ConstPool { heap_values: vec![ConstHeapValue::LongString(Arc::::from("longer-than-seven"))], - strings: vec!["answer".to_string()], + strings: vec![alloc::sync::Arc::::from("answer")], ..ConstPool::default() }, code: vec![ @@ -647,7 +664,7 @@ fn execute_new_map_build_fact_consumes_source_registers() { let function = Function { consts: ConstPool { heap_values: vec![ConstHeapValue::LongString(Arc::::from("longer-than-seven"))], - strings: vec!["answer".to_string()], + strings: vec![alloc::sync::Arc::::from("answer")], ..ConstPool::default() }, code: vec![ @@ -683,7 +700,7 @@ fn execute_writes_mixed_map_by_string_key() { let function = Function { consts: ConstPool { ints: vec![1, 42], - strings: vec!["answer".to_string()], + strings: vec![alloc::sync::Arc::::from("answer")], ..ConstPool::default() }, code: vec![ @@ -714,7 +731,7 @@ fn execute_updates_typed_string_int_map_without_materializing() { let function = Function { consts: ConstPool { ints: vec![1, 42], - strings: vec!["answer".to_string()], + strings: vec![alloc::sync::Arc::::from("answer")], ..ConstPool::default() }, code: vec![ @@ -750,7 +767,11 @@ fn execute_materializes_typed_string_int_map_to_string_mixed_on_value_pollution( let function = Function { consts: ConstPool { ints: vec![1], - strings: vec!["answer".to_string(), "label".to_string(), "ok".to_string()], + strings: vec![ + alloc::sync::Arc::::from("answer"), + alloc::sync::Arc::::from("label"), + alloc::sync::Arc::::from("ok"), + ], ..ConstPool::default() }, code: vec![ @@ -787,7 +808,11 @@ fn execute_adds_and_subtracts_typed_string_int_maps_without_runtime_entry_materi let function = Function { consts: ConstPool { ints: vec![1, 2, 3], - strings: vec!["a".to_string(), "b".to_string(), "c".to_string()], + strings: vec![ + alloc::sync::Arc::::from("a"), + alloc::sync::Arc::::from("b"), + alloc::sync::Arc::::from("c"), + ], ..ConstPool::default() }, code: vec![ @@ -841,7 +866,7 @@ fn execute_subtracts_string_key_from_typed_string_int_map_without_cloning_remove let function = Function { consts: ConstPool { ints: vec![1, 2], - strings: vec!["a".to_string(), "b".to_string()], + strings: vec![alloc::sync::Arc::::from("a"), alloc::sync::Arc::::from("b")], ..ConstPool::default() }, code: vec![ @@ -937,7 +962,7 @@ fn execute_pollutes_typed_int_list_by_string_write_without_reclassifying() { let function = Function { consts: ConstPool { ints: vec![7, 8, 1], - strings: vec!["nine".to_string()], + strings: vec![alloc::sync::Arc::::from("nine")], ..ConstPool::default() }, code: vec![ @@ -974,7 +999,11 @@ fn execute_updates_typed_string_list_without_materializing() { let function = Function { consts: ConstPool { ints: vec![1], - strings: vec!["a".to_string(), "b".to_string(), "c".to_string()], + strings: vec![ + alloc::sync::Arc::::from("a"), + alloc::sync::Arc::::from("b"), + alloc::sync::Arc::::from("c"), + ], ..ConstPool::default() }, code: vec![ @@ -1189,7 +1218,10 @@ fn execute_materializes_typed_string_list_on_non_string_write() { let function = Function { consts: ConstPool { ints: vec![0, 42], - strings: vec!["short".to_string(), "longer-than-seven".to_string()], + strings: vec![ + alloc::sync::Arc::::from("short"), + alloc::sync::Arc::::from("longer-than-seven"), + ], ..ConstPool::default() }, code: vec![ @@ -1219,3 +1251,115 @@ fn execute_materializes_typed_string_list_on_non_string_write() { assert_eq!(values[0], RuntimeVal::Int(42)); } + +/// Auto-display looks up **one** method name, and the user-facing docs said +/// three. +/// +/// `LEARN.md` promised "implement `show`, `display`, or `to_string` and +/// `println` will use it"; the VM hard-codes `"show"` +/// (`try_runtime_display_show`), so the other two names did nothing — a reader +/// who wrote `display` saw the default struct rendering and no error. +/// +/// One name rather than three is the choice: a second spelling of one hook is +/// what this codebase keeps removing, and `#[derive(Show)]` already generates +/// `show`. This pins it so the docs and the lookup cannot drift apart again. +#[test] +fn auto_display_uses_show_and_only_show() { + let shown = execute_source( + "struct S { x: Int }\nimpl S { fn show(self) -> String { return \"via-show\"; } }\nreturn \"${S { x: 1 }}\";\n", + ) + .expect("runs"); + let display = crate::vm::display_runtime_value(&shown.returns[0], &shown.state.heap); + assert!(display.contains("via-show"), "{display}"); + + for name in ["display", "to_string", "str", "fmt"] { + let program = format!( + "struct S {{ x: Int }}\nimpl S {{ fn {name}(self) -> String {{ return \"via-{name}\"; }} }}\nreturn \"${{S {{ x: 1 }}}}\";\n" + ); + let result = execute_source(&program).expect("runs"); + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert!( + !display.contains(&format!("via-{name}")), + "`{name}` must not be an auto-display hook, or the docs have to say it is: {display}" + ); + assert!(display.contains("S{x:1}"), "the default rendering: {display}"); + } +} + +/// Every arm's `return` is that arm's own, and returns from the function. +/// +/// `emitted_return` — the "what follows is dead code" flag — was read +/// *between* arms, so the first arm's `return` skipped the lowering of every +/// later arm's body: the test for those arms was emitted with nothing behind +/// it. `g(1)` therefore fell out of the match, off the end of a function +/// declared `-> Int`, and answered nil. The decisive shape is the last one +/// here: a `return` after the match still ran, so control had not stopped at +/// the arm's `return` at all. +#[test] +fn every_match_arm_return_returns() { + let source = "fn g(n: Int) -> Int {\n match n {\n 0 => { return 7; }\n 1 => { return 8; }\n _ => { return 9; }\n }\n}\nreturn [g(0), g(1), g(2)];\n"; + let result = execute_source(source).expect("runs"); + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "[7,8,9]" + ); + + let after = "fn g(n: Int) -> Int {\n match n {\n 0 => { return 7; }\n _ => { return 9; }\n }\n return 99;\n}\nreturn [g(0), g(5)];\n"; + let result = execute_source(after).expect("runs"); + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "[7,9]" + ); +} + +/// A binding arm catches every value, nil included. +/// +/// `lower_pattern_match` is shared with `if let`, where a binding pattern +/// means "the value is not nil". A `match` arm means no such thing, so +/// `match nil { x => 1 }` answered nil while `match nil { _ => 1 }` answered +/// 1 — and the type checker called both of them total, which is what made the +/// difference silent. +#[test] +fn a_binding_arm_catches_nil_like_the_wildcard_does() { + for arm in ["x", "_"] { + let source = format!("return match nil {{ {arm} => 1 }};\n"); + let result = execute_source(&source).expect("runs"); + assert_eq!(result.returns[0], RuntimeVal::Int(1), "arm `{arm}`"); + } +} + +/// A match with no catch-all still answers nil when nothing matches — the +/// path that makes the checker type it `T?` rather than `T`. +#[test] +fn a_match_with_no_catch_all_falls_through_to_nil() { + let result = execute_source("return [match 5 { 1 => 10 2 => 20 }, match 2 { 1 => 10 2 => 20 }];\n").expect("runs"); + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "[nil,20]" + ); +} + +/// A `return` in one branch of a conditional expression is that branch's own. +/// +/// `lower_conditional` never touched `emitted_return`, so a branch whose block +/// returned left the "what follows is dead code" flag set: every statement +/// after the conditional was dropped, and the function fell off its end. +/// `if`/`else` as a statement, `try`/`catch` and `match` all save and restore +/// the flag per branch; the conditional expression was the one that did not. +#[test] +fn a_return_in_one_conditional_branch_does_not_kill_the_code_after_it() { + let source = "fn f(n: Int) -> Int {\n let a = if n > 0 { return 1; } else { 2 };\n return a + 10;\n}\nreturn [f(5), f(-5)];\n"; + let result = execute_source(source).expect("runs"); + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "[1,12]" + ); + + let ternary = + "fn f(n: Int) -> Int {\n let a = n > 0 ? { return 1; } : 2;\n return a + 10;\n}\nreturn [f(5), f(-5)];\n"; + let result = execute_source(ternary).expect("runs"); + assert_eq!( + crate::vm::display_runtime_value(&result.returns[0], &result.state.heap), + "[1,12]" + ); +} diff --git a/core/src/vm/exec/exec_tests/calls.rs b/core/src/vm/exec/exec_tests/calls.rs index 856d7299..019aeae7 100644 --- a/core/src/vm/exec/exec_tests/calls.rs +++ b/core/src/vm/exec/exec_tests/calls.rs @@ -1,5 +1,5 @@ use super::*; -use crate::vm::analysis::{PerfCallFact, PerfCallTargetKind}; +use crate::vm::analysis::PerfCallFact; #[test] fn execute_module_calls_closure_function() { @@ -34,7 +34,6 @@ fn execute_module_calls_closure_function() { }; let module = Module { functions: vec![entry, callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -90,7 +89,6 @@ fn execute_module_uses_call_shape_fact_for_call_window() { ); let module = Module { functions: vec![entry, callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -135,7 +133,6 @@ fn execute_module_caches_call_shape_without_static_fact() { }; let module = Module { functions: vec![entry, callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -147,15 +144,11 @@ fn execute_module_caches_call_shape_without_static_fact() { assert_eq!(result.returns, vec![RuntimeVal::Int(42)]); assert!(module.functions[0].performance.call_site(3).is_none()); - assert_eq!( - result.state.inline_caches.call(3), - Some(PerfCallFact { - call_base: 0, - positional_count: 2, - named_count: 0, - target_kind: PerfCallTargetKind::Closure, - }) - ); + // The shape came off the instruction, which is the point: this module has + // no call-site fact and the call still ran with the right window. It used + // to also be asserted that the *state* cached the shape by pc; that cache + // is gone — it was keyed by pc across every function in the module, and + // nothing ever read it. } #[test] @@ -173,7 +166,7 @@ fn execute_module_caches_named_call_shape_without_static_fact() { let entry = Function { consts: ConstPool { ints: vec![40, 2], - strings: vec!["y".to_string()], + strings: vec![alloc::sync::Arc::::from("y")], ..ConstPool::default() }, code: vec![ @@ -193,7 +186,6 @@ fn execute_module_caches_named_call_shape_without_static_fact() { }; let module = Module { functions: vec![entry, callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -208,15 +200,7 @@ fn execute_module_caches_named_call_shape_without_static_fact() { assert_eq!(result.state.stack[1], RuntimeVal::Nil); assert_eq!(result.state.stack[2], RuntimeVal::Nil); assert_eq!(result.state.stack[3], RuntimeVal::Nil); - assert_eq!( - result.state.inline_caches.call(4), - Some(PerfCallFact { - call_base: 0, - positional_count: 1, - named_count: 1, - target_kind: PerfCallTargetKind::Closure, - }) - ); + // As above: no fact, and the named call still placed its arguments. } #[test] @@ -256,7 +240,6 @@ fn execute_module_calls_closure_with_captured_value() { }; let module = Module { functions: vec![entry, callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -313,7 +296,6 @@ fn execute_module_reuses_shared_stack_for_repeated_closure_calls() { }; let module = Module { functions: vec![entry, callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -364,7 +346,9 @@ fn runtime_value_closure_call_uses_active_shared_stack_window() { ..ConstPool::default() }, code: vec![ - Instr::abx(Opcode::LoadNative, 0, 0), + // Slot 1: the native. Slot 0 stays the closure the native looks up + // through `runtime.globals().first()`. + Instr::abx(Opcode::GetGlobal, 0, 1), Instr::abx(Opcode::LoadInt, 1, 0), Instr::abc(Opcode::Call, 0, 0, 1), Instr::abc(Opcode::Return, 0, 1, 0), @@ -378,12 +362,12 @@ fn runtime_value_closure_call_uses_active_shared_stack_window() { }; let module = Module { functions: vec![entry, callee], - natives: vec![NativeEntry { - name: "invoke_global_closure".to_string(), - arity: 1, - function: NativeFunction::FullState(invoke_global_closure), - }], - globals: vec![GlobalSlot { name: "f".into() }], + globals: vec![ + GlobalSlot { name: "f".into() }, + GlobalSlot { + name: "invoke_global_closure".into(), + }, + ], entry: 0, type_info: Default::default(), type_scope: Default::default(), @@ -393,9 +377,16 @@ fn runtime_value_closure_call_uses_active_shared_stack_window() { function_index: 1, captures: Arc::new(Vec::new()), }))); + // The native as a global too — the shape a loaded module has, rather than + // an inline table nothing outside these tests fills. + let native = RuntimeVal::Obj(heap.alloc(HeapValue::Callable(CallableValue::RuntimeNative { + name: Arc::::from("invoke_global_closure"), + arity: 1, + function: NativeFunction::FullState(invoke_global_closure), + }))); let mut ctx = VmContext::new_without_core_vm_builtins(); - let result = execute_module_with_globals_heap_and_ctx(&module, vec![closure], heap, &mut ctx) + let result = execute_module_with_globals_heap_and_ctx(&module, vec![closure, native], heap, &mut ctx) .expect("execute native-mediated closure call"); assert_eq!(result.returns, vec![RuntimeVal::Int(42)]); diff --git a/core/src/vm/exec/exec_tests/container.rs b/core/src/vm/exec/exec_tests/container.rs index 55a79e9c..d4e32696 100644 --- a/core/src/vm/exec/exec_tests/container.rs +++ b/core/src/vm/exec/exec_tests/container.rs @@ -30,7 +30,7 @@ fn execute_compares_int_ordering() { fn execute_compares_nil_and_short_strings_on_fast_path() { let function = Function { consts: ConstPool { - strings: vec!["ok".to_string(), "no".to_string()], + strings: vec![alloc::sync::Arc::::from("ok"), alloc::sync::Arc::::from("no")], ..ConstPool::default() }, code: vec![ @@ -71,11 +71,11 @@ fn execute_checks_contains_for_typed_list_map_and_string() { consts: ConstPool { ints: vec![2, 9, 1], strings: vec![ - "ab".to_string(), - "z".to_string(), - "abc".to_string(), - "answer".to_string(), - "1".to_string(), + alloc::sync::Arc::::from("ab"), + alloc::sync::Arc::::from("z"), + alloc::sync::Arc::::from("abc"), + alloc::sync::Arc::::from("answer"), + alloc::sync::Arc::::from("1"), ], ..ConstPool::default() }, @@ -138,7 +138,7 @@ fn execute_to_iter_reads_typed_string_int_map_backing_as_pairs() { let function = Function { consts: ConstPool { ints: vec![10, 20], - strings: vec!["a".to_string(), "b".to_string()], + strings: vec![alloc::sync::Arc::::from("a"), alloc::sync::Arc::::from("b")], ..ConstPool::default() }, code: vec![ @@ -202,12 +202,12 @@ fn execute_to_iter_reads_typed_string_int_map_backing_as_pairs() { #[test] fn execute_compares_const_string_key_maps_across_short_and_heap_keys() { - let mut short_key_map = fast_hash_map_new(); + let mut short_key_map = crate::util::value_map::value_map_new(); short_key_map.insert( RuntimeMapKey::ShortStr(crate::val::ShortStr::new("a").expect("short key")), crate::vm::ConstRuntimeValue::Int(42), ); - let mut heap_key_map = fast_hash_map_new(); + let mut heap_key_map = crate::util::value_map::value_map_new(); heap_key_map.insert( RuntimeMapKey::String(alloc::sync::Arc::::from("a")), crate::vm::ConstRuntimeValue::Int(42), @@ -238,7 +238,7 @@ fn execute_compares_const_string_key_maps_across_short_and_heap_keys() { #[test] fn execute_mixed_map_set_index_uses_exact_string_key_semantics() { - let mut map = fast_hash_map_new(); + let mut map = crate::util::value_map::value_map_new(); map.insert( RuntimeMapKey::String(alloc::sync::Arc::::from("a")), crate::vm::ConstRuntimeValue::Int(1), @@ -247,7 +247,7 @@ fn execute_mixed_map_set_index_uses_exact_string_key_semantics() { let function = Function { consts: ConstPool { ints: vec![9], - strings: vec!["a".to_string()], + strings: vec![alloc::sync::Arc::::from("a")], heap_values: vec![ ConstHeapValue::Map(map), ConstHeapValue::LongString(alloc::sync::Arc::::from("a")), @@ -311,7 +311,11 @@ fn execute_builds_map_rest_without_removed_keys() { let function = Function { consts: ConstPool { ints: vec![40, 2, 9], - strings: vec!["a".to_string(), "b".to_string(), "c".to_string()], + strings: vec![ + alloc::sync::Arc::::from("a"), + alloc::sync::Arc::::from("b"), + alloc::sync::Arc::::from("c"), + ], ..ConstPool::default() }, code: vec![ @@ -347,7 +351,7 @@ fn execute_map_rest_preserves_typed_string_int_backing() { let function = Function { consts: ConstPool { ints: vec![40, 2], - strings: vec!["a".to_string(), "b".to_string()], + strings: vec![alloc::sync::Arc::::from("a"), alloc::sync::Arc::::from("b")], ..ConstPool::default() }, code: vec![ @@ -380,3 +384,699 @@ fn execute_map_rest_preserves_typed_string_int_backing() { assert_eq!(values.len(), 1); assert_eq!(values.get("b"), Some(&2)); } + +/// A window does not copy, so the source can shrink under it. Every reader used +/// to answer "how long is this window" differently: `len()` said 3 while +/// `println` showed two elements, `to_list()` produced `[1,2,nil]`, and `==` +/// against those two elements was false. +#[test] +fn a_window_whose_source_shrank_gives_one_answer_everywhere() { + let result = execute_source( + r#" + let xs = [1, 2, 3]; + let window = xs.slice(0, 3); + xs.pop(); + return [window.len(), window.to_list(), window.last(), window == [1, 2], window.first()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[2,[1,2],2,true,1]"); +} + +/// `clear()` is in the container method table for maps, sets *and* lists, but +/// a list did not have it. +#[test] +fn list_clear_empties_in_place_and_answers_the_list() { + let result = execute_source( + r#" + let xs = [1, 2, 3]; + let answered = xs.clear(); + return [xs, answered, xs.push(7)]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[[7],[7],[7]]"); +} + +/// `try` is an expression, like `if` and `match`. It was a statement, so +/// `let r = try { … } catch e { … };` was a syntax error and the way to get a +/// value out was to declare a `nil` first and assign into it from both halves. +#[test] +fn try_is_an_expression_and_both_halves_carry_its_value() { + let result = execute_source( + r#" + fn risky(n) { return 100 % n; } + let ok = try { risky(30) } catch e { -1 }; + let caught = try { risky(0) } catch e { -1 }; + let payload = try { risky(0) } catch e { e }; + // A half that ends in a statement has no value, as in an `if`. + let empty = try { risky(0) } catch e { let unused = 1; }; + // The inner one is the outer's tail, so it is a value too. + let nested = try { try { risky(0) } catch e { 2 } } catch e { 3 }; + return [ok, caught, payload, empty, nested]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[10,-1,\"modulo by zero\",nil,2]"); +} + +/// Statement position is unchanged — the value is discarded, as an `if` or a +/// `match` in statement position is. It compiles to the region it always did, +/// with no value register: one written *inside* a protected region has to +/// survive it, and reserving one nobody reads took `try { f(); } catch e { … }` +/// off the native path. +#[test] +fn try_in_statement_position_still_runs_for_effect() { + let result = execute_source( + r#" + let log = []; + try { let bad = 1 % 0; log.push("body"); } catch e { log.push("handler"); } + try { log.push("fine"); } catch e { log.push("unreachable"); } + return log; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[\"handler\",\"fine\"]"); +} + +/// A receiver that is a plain local *is* that local's register, not a copy. +/// Capturing the same local in a closure boxes it in place, so an argument +/// containing such a closure changed what the already-taken receiver pointed +/// at — and the call ran against the cell: `xs.map(|x| x + xs.len())` answered +/// "UpvalCell has no method 'map'". +#[test] +fn a_method_receiver_survives_an_argument_that_captures_it() { + let result = execute_source( + r#" + let xs = [1, 2]; + let widened = xs.map(|x| x + xs.len()); + let kept = xs.filter(|x| xs.len() > 1); + let boxed: List = [1]; + boxed.push(|| boxed.len()); + let m = {"a": 1}; + m.set("b", || m.len()); + return [widened, kept, boxed.len(), m.len()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[[3,4],[1,2],2,2]"); +} + +/// Containers were the other half of the "both `Obj`, one rank, therefore +/// equal" hole that made sorting long strings a no-op: sorting a list of lists +/// left it exactly as it was. +#[test] +fn sorting_orders_lists_element_by_element() { + let result = execute_source( + r#" + let pairs = [[1, "b"], [1, "a"], [0, "c"]]; + let lengths = [[1, 2, 3], [1, 2], [1]]; + let long = [["zzzzzzzzzz"], ["aaaaaaaaaa"]]; + let mixed: List = [{"a": 1}, [1], "s"]; + return [pairs.sort(), lengths.sort(), long.sort(), mixed.sort()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!( + display, + "[[[0,\"c\"],[1,\"a\"],[1,\"b\"]],[[1],[1,2],[1,2,3]],[[\"aaaaaaaaaa\"],[\"zzzzzzzzzz\"]],[\"s\",[1],{\"a\":1}]]" + ); +} + +/// A local captured by a closure lives in a cell, and the cell lives in the +/// local's register. Compound assignment computed the new value *into that +/// register*, overwriting the cell — so the store that followed found no cell: +/// `let n = 1; let f = || n; n += 1;` raised +/// "StoreCellVal expected UpvalCell object". Plain `n = n + 1` always worked, +/// which is what made it look like an arithmetic problem. +#[test] +fn compound_assignment_to_a_captured_local_updates_its_cell() { + let result = execute_source( + r#" + let n = 1; + let read = || n; + n += 4; + n *= 2; + n -= 3; + let text = "a"; + let read_text = || text; + text += "b"; + return [n, read(), text, read_text()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[7,7,\"ab\",\"ab\"]"); +} + +/// The receiver-aliasing family, in the index-assignment position: a closure in +/// the *value* boxes the target's local, and the write then landed on the cell. +#[test] +fn an_index_assignment_target_survives_a_value_that_captures_it() { + let result = execute_source( + r#" + let xs: List = [1, 2]; + xs[0] = || xs.len(); + let m: Map = {"a": 1}; + m["b"] = || m.len(); + return [xs.len(), m.len()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[2,2]"); +} + +/// `impl Type { … }` was a syntax error, and there is no UFCS — so a struct +/// could only get a method by declaring a trait that said nothing and +/// implementing *that*. The machinery was already there: dispatch keys on the +/// target type, not on the trait. +#[test] +fn an_inherent_impl_gives_a_type_its_own_methods() { + let result = execute_source( + r#" + struct Point { x: Int, y: Int } + impl Point { + fn norm2(self) -> Int { return self.x * self.x + self.y * self.y; } + fn scaled(self, by: Int) -> Point { return Point { x: self.x * by, y: self.y * by }; } + } + trait Area { fn area(self) -> Int; } + impl Area for Point { fn area(self) -> Int { return self.x * self.y; } } + + let p = Point { x: 3, y: 4 }; + return [p.norm2(), p.scaled(2).x, p.area()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[25,6,12]"); +} + +/// An inherent impl carries no trait, so nothing is *promised* — but a trait +/// impl still has to keep its promise. +#[test] +fn a_trait_impl_still_has_to_implement_the_trait() { + let error = execute_source( + r#" + trait Area { fn area(self) -> Int; } + struct Point { x: Int } + impl Area for Point { } + return 1; + "#, + ) + .expect_err("an unimplemented trait method"); + assert!(error.to_string().contains("not implemented"), "{error}"); +} + +/// A trait impl carries the trait's methods and nothing else. It used to accept +/// anything, and it had to: with `impl Type { … }` a syntax error and no UFCS, +/// a trait impl was the only place a method could live. +#[test] +fn a_trait_impl_rejects_a_method_the_trait_never_declared() { + let error = execute_source( + r#" + trait Area { fn area(self) -> Int; } + struct Point { x: Int } + impl Area for Point { + fn area(self) -> Int { return self.x; } + fn unrelated(self) -> Int { return 0; } + } + return 1; + "#, + ) + .expect_err("`unrelated` is not part of `Area`"); + assert!(error.to_string().contains("is not declared by trait"), "{error}"); + + // …and the fix the message names actually works. + let result = execute_source( + r#" + trait Area { fn area(self) -> Int; } + struct Point { x: Int } + impl Area for Point { fn area(self) -> Int { return self.x; } } + impl Point { fn unrelated(self) -> Int { return 7; } } + return [Point { x: 1 }.area(), Point { x: 1 }.unrelated()]; + "#, + ) + .expect("execute source"); + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[1,7]"); +} + +/// A builtin container dispatches with its element type erased — a +/// `TypedList::Mixed` has nothing else to report — so the *checker* has to key +/// on the same thing. It keyed on the static type instead, and +/// `impl T for List` registered under `List` while a call on `[1, 2]` +/// looked up `List`: the method existed and could not be found. `String` +/// and `Map` worked only because neither takes that path. +#[test] +fn a_method_on_a_builtin_container_is_found_whatever_its_elements_are() { + let result = execute_source( + r#" + impl List { fn second(self) -> Any { return self.get(1); } } + impl Map { fn size(self) -> Int { return self.len(); } } + impl Set { fn size(self) -> Int { return self.len(); } } + return [[1, 2].second(), ["a", "b"].second(), {"k": 1}.size(), Set([1, 2]).size()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[2,\"b\",1,2]"); +} + +/// The compiler picks a dedicated opcode for `len`/`push`/`set`/`split`/`join` +/// from the method *name* alone — it has no type for the receiver there. That +/// is right for a list and wrong for a struct with a method of that name: +/// `s.len()` answered "Len target object is not sized", and the four that take +/// arguments failed at *compile* time on arity, so the method could not even be +/// written. +#[test] +fn a_user_method_named_after_a_builtin_one_is_still_reachable() { + let result = execute_source( + r#" + struct Boxed { items: List } + impl Boxed { + fn len(self) -> Int { return 99; } + fn push(self) -> Int { return 1; } + fn set(self) -> Int { return 2; } + fn split(self) -> Int { return 3; } + fn join(self) -> Int { return 4; } + } + let b = Boxed { items: [1] }; + // The builtins keep working on the types they belong to. + let xs = [1, 2, 3]; + return [b.len(), b.push(), b.set(), b.split(), b.join(), xs.len()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[99,1,2,3,4,3]"); +} + +/// A receiver with side effects runs **once** under `set`. +/// +/// `set` lowered its receiver expression twice — once for the write, once for +/// the answer — so `make().set(0, 9)` called `make` twice, wrote into the first +/// list and answered the second. Every other mutating method (`push`, +/// `insert`, `remove_at`, `clear`) lowers the receiver once. +#[test] +fn set_evaluates_a_side_effecting_receiver_once() { + let result = execute_source( + r#" + let calls = []; + fn make() -> List { + calls.push(1); + return [1, 2, 3]; + } + let answered = make().set(0, 9); + return [calls.len(), answered.get(0) ?? -1, answered.len()]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[1,9,3]", "one call, and the answer is the list written to"); +} + +/// A top-level `let` a function can see is **one** variable, not two. +/// +/// It used to be two: the top level kept a register copy while functions read +/// and wrote the global slot, and the two agreed only until the first write on +/// either side. `let n = 0; fn bump() { n = n + 1; } bump();` left the +/// function's view at 1 and the top level's at 0, and a top-level `n = 5` was +/// invisible to the function. Both backends did it, so no differential test +/// could see it. +/// +/// A `const` still keeps its register: nothing can write it, so the copy cannot +/// come apart — and the register is where a machine-integer width lives. +#[test] +fn a_top_level_let_and_its_functions_share_one_variable() { + let result = execute_source( + r#" + let n = 0; + fn bump() { n = n + 1; } + fn get() -> Int { return n; } + bump(); + bump(); + let after_calls = [n, get()]; + n = 5; + let after_top_level_write = [n, get()]; + return [after_calls, after_top_level_write]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[[2,2],[5,5]]", "one storage, whichever side writes it"); +} + +/// A struct compares by its **fields**, like every other aggregate. +/// +/// It used to compare by handle, so `P { x: 1 } == P { x: 1 }` was false while +/// `[1] == [1]`, `{"a": 1} == {"a": 1}` and `Set([1]) == Set([1])` were all +/// true. The silence was the worst part: `xs.contains(p)`, `index_of` and +/// `unique` inherited it, so a list of structs could not be searched. +#[test] +fn a_struct_compares_by_its_fields_like_every_other_aggregate() { + let result = execute_source( + r#" + struct P { x: Int, y: Int } + struct Q { x: Int, y: Int } + struct N { inner: P } + let a = P { x: 1, y: 2 }; + return [ + a == P { x: 1, y: 2 }, + a == P { x: 1, y: 3 }, + [a] == [P { x: 1, y: 2 }], + [a].contains(P { x: 1, y: 2 }), + [a, P { x: 1, y: 2 }].unique().len() == 1, + N { inner: a } == N { inner: P { x: 1, y: 2 } }, + // A different declaration with the same shape is a different type. + a == Q { x: 1, y: 2 }, + ]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[true,false,true,true,true,true,false]"); +} + +/// A block is a scope: a `let` inside one does not outlive it. +/// +/// It did, in every construct — `if`, `while`, `for`, a bare block, a `match` +/// arm — because a `let` shadowing an outer name reused that name's *register*, +/// so the enclosing scope resumed reading the inner value. Three separate holes +/// fed it: the statement path reused the register, the inliner never restored +/// bindings at all (which also made `fn f(c) { let y = 1; if c { let y = 2; } +/// let s = 45; return y; }` answer 45 — `y` still pointed at the inner +/// register, and `s` was handed it), and a block *expression* — which is what a +/// match arm body is — had no scope at all. +#[test] +fn a_block_is_a_scope_in_every_construct() { + let result = execute_source( + r#" + fn in_if(c: Bool) -> Int { let y = 1; if c { let y = 2; } return y; } + fn in_while(c: Bool) -> Int { let y = 1; while c { let y = 2; break; } return y; } + fn in_for() -> Int { let y = 1; for i in 0..1 { let y = 7; } return y; } + fn in_block() -> Int { let y = 1; { let y = 3; } return y; } + fn in_match() -> Int { let y = 1; let r = match 1 { 1 => { let y = 5; y + 1 } _ => 0 }; return y * 100 + r; } + // The inline path: small enough to be inlined at the call site, and the + // trailing `let` is what used to collect the shadow's register. + fn inlined(c: Bool) -> Int { let y = 1; if c { let y = 2; } let s = 45; return y; } + return [in_if(true), in_while(true), in_for(), in_block(), in_match(), inlined(true)]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, "[1,1,1,1,106,1]"); +} + +/// A trait method may carry a body: implementors that do not write it get it. +/// +/// Without defaults every implementor repeated the same method — the language +/// forcing on its users exactly the "N copies kept in sync by hand" shape the +/// implementation spends its time removing. The body is copied per implementing +/// type before anything dispatches (`stmt::trait_defaults`), so `self` is that +/// type and nothing downstream knows defaults exist. +#[test] +fn a_trait_method_may_have_a_default_body() { + let result = execute_source( + r#" + trait Greet { + fn name(self) -> String; + fn hi(self) -> String { return "hi ${self.name()}"; } + } + struct P { n: String } + impl Greet for P { fn name(self) -> String { return self.n; } } + struct Q { n: String } + impl Greet for Q { + fn name(self) -> String { return self.n; } + fn hi(self) -> String { return "yo ${self.n}"; } + } + // The trait may also be declared *after* the impl that uses it. + impl Late for R { fn base(self) -> Int { return 7; } } + struct R {} + trait Late { + fn base(self) -> Int; + fn twice(self) -> Int { return self.base() * 2; } + } + return [P { n: "a" }.hi(), Q { n: "b" }.hi(), "${R {}.twice()}"]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, r#"["hi a","yo b","14"]"#); +} + +/// Braces nest inside `${…}`. +/// +/// The lexer balanced them when deciding where an interpolation ends; the +/// parser's own scan of the same content did not, and cut at the first `}`. So +/// `"${R {}}"` reached the struct-literal parser as `R {`, which read past the +/// end of its token stream and **panicked** — a parser must answer with an +/// error, never a panic. +#[test] +fn a_template_interpolation_balances_its_braces() { + let result = execute_source( + r#" + struct R { v: Int } + let m = {"a": 1}; + return ["${R { v: 3 }}", "${m}", "${ {"k": 2} }", "${R { v: 3 }.v}"]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!(display, r#"["R{v:3}","{\"a\":1}","{\"k\":2}","3"]"#); +} + +/// `min`, `max` and `sum` — on a list, a window over one, and a `Bytes`. +/// +/// The three most ordinary questions about a sequence of numbers had no answer: +/// `map`, `filter`, `reduce`, `unique`, `zip` and `chunk` were all there, and +/// these were not, so each had to be written as a fold — with a comparison +/// lambda that then had to agree with `sort`'s order, and nothing checked that +/// it did. +/// +/// So `min`/`max` use `sort`'s own comparison. The assertions below pin that +/// with the case that would catch a second ordering: a mixed list, where +/// numbers sort before strings. +#[test] +fn a_sequence_answers_min_max_and_sum() { + let result = execute_source( + r#" + let xs = [3, 1, 2]; + let mixed = [2, "a", 1]; + let floats = [1.5, 2.5]; + let promoted = [1, 2.5]; + let window = [5, 1, 9, 2].slice(1, 3); + let empty = []; + return [ + [xs.min(), xs.max(), xs.sum()], + [mixed.min(), mixed.sort().first()], + [floats.sum(), promoted.sum()], + [window.min(), window.max(), window.sum()], + [empty.min(), empty.max(), empty.sum()], + [["b", "a"].min(), ["b", "a"].max()], + ]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!( + display, "[[1,3,6],[1,1],[4,3.5],[1,9,10],[nil,nil,0],[\"a\",\"b\"]]", + "min/max follow sort's order, sum promotes to float, empty answers nil and 0" + ); +} + +/// Summing something that is not a number says what it found. +#[test] +fn summing_a_non_number_names_it() { + for (source, expected) in [ + ("[\"a\"].sum()", "list of String"), + ("[true].sum()", "list of Bool"), + ("[1, nil].sum()", "holds a Nil"), + ] { + let program = alloc::format!("let e = try {{ {source} }} catch x {{ x }};\nreturn e;"); + let result = execute_source(&program).expect("execute source"); + let message = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert!(message.contains(expected), "{source} → {message}"); + } +} + +/// A map's member access is a key read, its methods win a name collision, and a +/// function-valued key is callable — all three, pinned. +/// +/// The precedence existed only as the order two lookups happened to run in. +/// Nothing said it, so `{"len": 5}.len()` answering the *entry count* was an +/// implementation detail that any refactor could have flipped, silently, for +/// every program with a key named after a builtin method. +#[test] +fn a_maps_methods_win_a_name_collision_with_its_keys() { + let result = execute_source( + r#" + let plain = {"a": 1}; + let shadowing = {"len": 5, "b": 2}; + let callable = {"f": |x| x + 1}; + return [ + plain.a, + shadowing.len(), + shadowing.len, + shadowing["len"], + callable.f(1), + callable["f"](1), + ]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + assert_eq!( + display, "[1,2,5,5,2,2]", + "`.len()` is the method (2 entries), `.len` and `[\"len\"]` are the key (5), and a \ + function-valued key is callable either way" + ); +} + +/// A missing map member names both routes, and `len()` on a value with no +/// length names the operation rather than the opcode's operand. +#[test] +fn a_map_member_miss_and_a_lengthless_value_say_what_the_program_did() { + let result = execute_source( + r#" + let m = {"a": 1}; + let missing = try { m.nope() } catch e { e }; + let lengthless = try { nil.len() } catch e { e }; + return [missing, lengthless]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + // Both halves for the map: a method *and* a key holding a function. + assert!(display.contains("no method `nope`"), "{display}"); + assert!(display.contains("no key `nope`"), "{display}"); + // "Len target expected string/list/map/set, got Nil" named this opcode's + // operand; the program wrote `len()`. + assert!(display.contains("`len()` works on"), "{display}"); + assert!(!display.contains("Len target"), "{display}"); +} + +/// A negative *count* is refused on a String, as it already was on a List. +/// +/// `[1, 2].take(-1)` raised and `"ab".take(-1)` answered `""`; `skip` had the +/// same split, and `repeat(-1)` was `""` too. One rule, two carriers, two +/// behaviours — the shape the negative-position decision was cleaned up into +/// once already. +/// +/// A negative *position* is a different thing and keeps its meaning: `slice` +/// counts from the end. +#[test] +fn a_negative_count_is_refused_on_a_string_as_it_is_on_a_list() { + let result = execute_source( + r#" + let s = "ab"; + return [ + try { s.take(-1) } catch e { e }, + try { s.skip(-1) } catch e { e }, + try { s.repeat(-1) } catch e { e }, + s.repeat(0), + s.take(1), + s.skip(1), + s.slice(-1, 2), + ]; + "#, + ) + .expect("execute source"); + + let display = crate::vm::display_runtime_value(&result.returns[0], &result.state.heap); + for expected in [ + "string.take() count must be non-negative", + "string.skip() count must be non-negative", + "string.repeat() count must be non-negative", + ] { + assert!(display.contains(expected), "{display}"); + } + // Zero repeats, the ordinary counts, and the negative *position* all keep + // their answers. + assert!(display.contains(r#""","a","b","b""#), "{display}"); +} + +/// The carrier lists in these messages are the messages' whole content, and +/// they had drifted from the arms above them. +/// +/// `len` accepts `Bytes` and a window; its message said "String, List, Map or +/// Set". `slice`, `skip` and `for` were the same, each naming the set the +/// operation had when the message was written. A reader is told the rule, and +/// the rule was wrong in the direction that makes a working program look +/// impossible. +/// +/// This walks every carrier, asks whether the operation accepts it, and +/// requires the rejection message to name exactly the ones it does. Both +/// directions: a carrier that stops being accepted has to leave the message +/// too. +#[test] +fn a_carrier_list_in_an_error_message_matches_what_the_operation_accepts() { + // `(carrier, how to build one, the word the message uses for it)`. + const CARRIERS: &[(&str, &str, &str)] = &[ + ("List", "[1, 2, 3]", "list"), + ("String", "\"ab\"", "string"), + ("Map", "{\"a\": 1}", "map"), + ("Set", "Set([1])", "set"), + ("Bytes", "\"ab\".bytes()", "bytes"), + ("Slice", "[1, 2, 3].slice(0, 2)", "slice"), + ]; + // `(what the program writes, a program that reaches the same opcode with a + // receiver it rejects)`. + // + // `len`'s list lives on the opcode, and reaching it needs a receiver with + // no static type — an out-of-bounds read, whose `nil` the method dispatch + // passes through. `for`'s lives in the checker, which a literal reaches + // directly. The range index `c[a..b]` is where the `Slice target` list + // lives; the `c.slice(a, b)` *method* has no message for this to compare, + // because every receiver it rejects is answered by the method dispatch + // before the opcode. + const OPERATIONS: &[(&str, &str)] = &[ + ("c.len()", "[1][5].len();"), + ("for _x in c {}", "for _x in 1 {}"), + ("c[0..1]", "let c = [1][5]; c[0..1];"), + ]; + + for (op, rejecting) in OPERATIONS { + let message = match execute_source(rejecting) { + Ok(result) => panic!("`{rejecting}` was expected to fail: {result:?}"), + Err(err) => format!("{err}").to_lowercase(), + }; + for (carrier, build, word) in CARRIERS { + let program = format!("let c = {build};\n{op};\n"); + let accepted = execute_source(&program).is_ok(); + assert_eq!( + accepted, + message.contains(word), + "`{op}` {} `{carrier}`, and the message {} name it: {message}", + if accepted { "accepts" } else { "rejects" }, + if accepted { "does not" } else { "does" }, + ); + } + } +} diff --git a/core/src/vm/exec/exec_tests/cross_heap.rs b/core/src/vm/exec/exec_tests/cross_heap.rs index eaa5e930..116c75ca 100644 --- a/core/src/vm/exec/exec_tests/cross_heap.rs +++ b/core/src/vm/exec/exec_tests/cross_heap.rs @@ -1,64 +1,70 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::fast_hash_map_new; use alloc::sync::Arc; use crate::vm::{RuntimeExport, RuntimeModuleState, copy_runtime_value, import_runtime_export}; use super::*; +/// A map key crosses heaps as itself: `RuntimeMapKey` carries no heap handle — +/// a long string is an `Arc` held inline, and a container cannot be a key +/// at all. These used to build an `Obj` key, a shape no program could produce, +/// and assert that the translation followed the handle across. #[test] -fn import_runtime_export_copies_mixed_map_object_keys_into_destination_heap() { +fn a_long_string_map_key_survives_both_crossings_as_itself() { let mut source_heap = HeapStore::new(); - let key = source_heap.alloc(HeapValue::String(Arc::::from("source-key"))); - let mut entries = fast_hash_map_new(); - entries.insert(RuntimeMapKey::Obj(key), RuntimeVal::Int(42)); + let mut entries = crate::util::value_map::value_map_new(); + entries.insert( + RuntimeMapKey::String(Arc::::from("a key too long to live inline")), + RuntimeVal::Int(42), + ); let map = source_heap.alloc(HeapValue::Map(TypedMap::Mixed(entries))); + + let mut copy_heap = HeapStore::new(); + let copied = copy_runtime_value(&RuntimeVal::Obj(map), &source_heap, &mut copy_heap).expect("copy map"); + let state = Arc::new(Mutex::new(RuntimeModuleState::new(source_heap, Vec::new()))); let export = RuntimeExport::new(RuntimeVal::Obj(map), Arc::clone(&state), Arc::new(Module::default())); - let mut dest_heap = HeapStore::new(); - - let imported = import_runtime_export(&export, &mut dest_heap).expect("use export"); + let mut import_heap = HeapStore::new(); + let imported = import_runtime_export(&export, &mut import_heap).expect("use export"); - let RuntimeVal::Obj(imported_map) = imported else { - panic!("use should return map object"); - }; - let Some(HeapValue::Map(TypedMap::Mixed(entries))) = dest_heap.get(imported_map) else { - panic!("imported value should be a mixed map"); - }; - let RuntimeMapKey::Obj(imported_key) = entries.keys().next().expect("map key") else { - panic!("object key should remain object key"); - }; - assert_ne!(*imported_key, imported_map); - assert!(matches!( - dest_heap.get(*imported_key), - Some(HeapValue::String(value)) if value.as_ref() == "source-key" - )); + for (value, heap) in [(copied, ©_heap), (imported, &import_heap)] { + let RuntimeVal::Obj(handle) = value else { + panic!("expected a map object"); + }; + let Some(HeapValue::Map(TypedMap::Mixed(entries))) = heap.get(handle) else { + panic!("expected a mixed map"); + }; + let RuntimeMapKey::String(key) = entries.keys().next().expect("map key") else { + panic!("a long string key stays a string key"); + }; + assert_eq!(key.as_ref(), "a key too long to live inline"); + } } +/// A copy with no module recorded still refuses a function, and says so in the +/// program's terms. +/// +/// This is the channel-payload case: `copy_runtime_value` is handed a value and +/// two heaps, and nothing says which module the closure's `function_index` +/// indexes. Passing a function *as an argument* is a different path — the +/// executor knows the caller's module there, and promotes instead of refusing +/// (see `promote_crossing_closure`). #[test] -fn copy_runtime_value_copies_mixed_map_object_keys_into_destination_heap() { +fn a_function_copied_with_no_module_recorded_is_refused_in_the_programs_terms() { let mut source_heap = HeapStore::new(); - let key = source_heap.alloc(HeapValue::String(Arc::::from("copy-key"))); - let mut entries = fast_hash_map_new(); - entries.insert(RuntimeMapKey::Obj(key), RuntimeVal::Int(7)); - let map = source_heap.alloc(HeapValue::Map(TypedMap::Mixed(entries))); - let mut dest_heap = HeapStore::new(); + let closure = source_heap.alloc(HeapValue::Callable(CallableValue::Closure { + function_index: 0, + captures: Arc::new(Vec::new()), + })); - let copied = copy_runtime_value(&RuntimeVal::Obj(map), &source_heap, &mut dest_heap).expect("copy map"); + let mut dest_heap = HeapStore::new(); + let error = copy_runtime_value(&RuntimeVal::Obj(closure), &source_heap, &mut dest_heap) + .expect_err("a closure with no module recorded has no meaning in another module"); - let RuntimeVal::Obj(copied_map) = copied else { - panic!("copy should return map object"); - }; - let Some(HeapValue::Map(TypedMap::Mixed(entries))) = dest_heap.get(copied_map) else { - panic!("copied value should be a mixed map"); - }; - let RuntimeMapKey::Obj(copied_key) = entries.keys().next().expect("map key") else { - panic!("object key should remain object key"); - }; - assert_ne!(*copied_key, copied_map); - assert!(matches!( - dest_heap.get(*copied_key), - Some(HeapValue::String(value)) if value.as_ref() == "copy-key" - )); + let message = format!("{error:#}"); + assert!( + message.contains("cannot be passed out of the module that defined it here"), + "the refusal should name what the program did: {message}" + ); } diff --git a/core/src/vm/exec/exec_tests/gc_cell_error.rs b/core/src/vm/exec/exec_tests/gc_cell_error.rs index 917bba1d..e3f4f80c 100644 --- a/core/src/vm/exec/exec_tests/gc_cell_error.rs +++ b/core/src/vm/exec/exec_tests/gc_cell_error.rs @@ -64,7 +64,6 @@ fn execute_loads_and_stores_upval_cell_values() { }; let module = Module { functions: vec![function], - natives: Vec::new(), globals: vec![GlobalSlot { name: "cell".into() }], entry: 0, type_info: Default::default(), @@ -106,7 +105,6 @@ fn execute_store_cell_clones_source_without_move_fact() { }; let module = Module { functions: vec![function], - natives: Vec::new(), globals: vec![GlobalSlot { name: "cell".into() }], entry: 0, type_info: Default::default(), @@ -151,7 +149,6 @@ fn execute_store_cell_move_fact_consumes_source_register() { }; let module = Module { functions: vec![function], - natives: Vec::new(), globals: vec![GlobalSlot { name: "cell".into() }], entry: 0, type_info: Default::default(), @@ -382,7 +379,6 @@ fn execute_caller_handler_catches_raise_from_callee() { }; let module = Module { functions: vec![caller, callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -435,7 +431,6 @@ fn execute_callee_return_unwinds_its_try_handlers_before_next_call() { }; let module = Module { functions: vec![caller, returns_inside_try, raises_without_handler], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), diff --git a/core/src/vm/exec/exec_tests/native.rs b/core/src/vm/exec/exec_tests/native.rs index e18242e8..dfeee5f1 100644 --- a/core/src/vm/exec/exec_tests/native.rs +++ b/core/src/vm/exec/exec_tests/native.rs @@ -1,7 +1,13 @@ use super::*; -use crate::util::fast_map::fast_hash_map_from_iter; use crate::vm::ProgramExec; use crate::vm::analysis::PerfGlobalFact; +/// A native is called through the same `Call` opcode as anything else, and the +/// argument window is cleared afterwards. +/// +/// Written against an installed native rather than an inline `NativeEntry`: the +/// inline table is a mechanism no binary reaches (see +/// [`super::execute_source_with_natives`]), so this used to prove the property +/// for a path that does not ship. #[test] fn execute_module_calls_native_function_with_same_call_opcode() { fn native_add(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { @@ -11,89 +17,57 @@ fn execute_module_calls_native_function_with_same_call_opcode() { Ok(RuntimeVal::Int(lhs + rhs)) } - let entry = Function { - consts: ConstPool { - ints: vec![13, 29], - ..ConstPool::default() - }, - code: vec![ - Instr::abx(Opcode::LoadNative, 0, 0), - Instr::abx(Opcode::LoadInt, 1, 0), - Instr::abx(Opcode::LoadInt, 2, 1), - Instr::abc(Opcode::Call, 0, 0, 2), - Instr::abc(Opcode::Return, 0, 1, 0), - ], - register_count: 3, - param_count: 0, - positional_param_count: 0, - param_names: Vec::new(), - capture_count: 0, - ..Function::default() - }; - let module = Module { - functions: vec![entry], - natives: vec![NativeEntry { - name: "native_add".to_string(), - arity: 2, - function: NativeFunction::Plain(native_add), - }], - globals: Vec::new(), - entry: 0, - type_info: Default::default(), - type_scope: Default::default(), - }; - - let result = execute_module(&module).expect("execute module"); + let result = super::execute_source_with_natives( + "return native_add(13, 29);", + &[("native_add", NativeFunction::Plain(native_add), 2)], + ) + .expect("execute source"); assert_eq!(result.returns, vec![RuntimeVal::Int(42)]); + // The call window is cleared: the arguments do not outlive the call, which + // is what keeps a native's arguments from pinning heap values (see + // `clear_call_window_temps`). + // The same two slots the hand-built module asserted on: the argument window + // is cleared after the call, so `13` and `29` do not outlive it. Slot 0 + // holds the callable the global was read into. assert_eq!(result.state.stack[1], RuntimeVal::Nil); assert_eq!(result.state.stack[2], RuntimeVal::Nil); } +/// A native that allocates a value nobody keeps: the collector takes it. +/// +/// Runs the program a user would write, with the collector set to run after +/// every allocation — the hand-built module got that by seeding its own +/// `HeapStore`, which is also why it could not reach an installed native. #[test] fn execute_module_collects_after_native_heap_allocation() { fn native_alloc_dead(_args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { runtime .heap_mut() - .alloc(HeapValue::String(Arc::::from("native-dead"))); + .alloc(HeapValue::String(Arc::::from("dead-native-allocation"))); Ok(RuntimeVal::Nil) } - let entry = Function { - code: vec![ - Instr::abx(Opcode::LoadNative, 0, 0), - Instr::abc(Opcode::Call, 0, 0, 0), - Instr::abc(Opcode::Nop, 0, 0, 0), - Instr::abc(Opcode::Return, 0, 1, 0), - ], - register_count: 1, - param_count: 0, - positional_param_count: 0, - param_names: Vec::new(), - capture_count: 0, - ..Function::default() - }; - let module = Module { - functions: vec![entry], - natives: vec![NativeEntry { - name: "native_alloc_dead".to_string(), - arity: 0, - function: NativeFunction::Plain(native_alloc_dead), - }], - globals: Vec::new(), - entry: 0, - type_info: Default::default(), - type_scope: Default::default(), - }; - let mut heap = HeapStore::new(); - heap.set_gc_threshold(1); - - let result = Executor::new(1) - .run_module_with_globals_and_heap(&module, Vec::new(), heap) - .expect("execute module"); + // Five hundred allocations nobody keeps, collected as they go. + // + // The old form asserted an *empty* heap, which a hand-built module can have + // and a real program cannot — a program's heap holds its globals and the + // callable this native was read from. What still separates "collected" from + // "kept" is that the heap stays *bounded*: without the collector it would + // carry all five hundred strings. + let result = super::execute_source_with_natives_and_gc( + "let i = 0;\nwhile i < 500 { native_alloc_dead(); i = i + 1; }\nreturn i;", + &[("native_alloc_dead", NativeFunction::Plain(native_alloc_dead), 0)], + Some(1), + ) + .expect("execute source"); - assert_eq!(result.returns, vec![RuntimeVal::Nil]); - assert_eq!(result.state.heap.len(), 0); + assert_eq!(result.returns, vec![RuntimeVal::Int(500)]); + assert!( + result.state.heap.len() < 100, + "five hundred unreferenced allocations were not collected: heap holds {}", + result.state.heap.len() + ); assert!(!result.state.heap.should_collect()); } @@ -131,50 +105,21 @@ fn execute_module_calls_full_state_native_with_named_args() { Ok(RuntimeVal::Int((*value).clamp(min, max))) } - let entry = Function { - consts: ConstPool { - ints: vec![52, 40, 50], - strings: vec!["min".to_string(), "max".to_string()], - ..ConstPool::default() - }, - code: vec![ - Instr::abx(Opcode::LoadNative, 0, 0), - Instr::abx(Opcode::LoadInt, 1, 0), - Instr::abx(Opcode::LoadString, 2, 0), - Instr::abx(Opcode::LoadInt, 3, 1), - Instr::abx(Opcode::LoadString, 4, 1), - Instr::abx(Opcode::LoadInt, 5, 2), - Instr::abx(Opcode::CallNamed, 0, (2 << 7) | 1), - Instr::abc(Opcode::Return, 0, 1, 0), - ], - register_count: 6, - param_count: 0, - positional_param_count: 0, - param_names: Vec::new(), - capture_count: 0, - ..Function::default() - }; - let module = Module { - functions: vec![entry], - natives: vec![NativeEntry { - name: "full_state_clamp".to_string(), - arity: 1, - function: NativeFunction::FullState(full_state_clamp), - }], - globals: Vec::new(), - entry: 0, - type_info: Default::default(), - type_scope: Default::default(), - }; - - let result = execute_module(&module).expect("execute module"); + // Named arguments to a native reached through a global, which is how every + // stdlib native with named parameters is called. The hand-built module + // spelled the same call in `CallNamed` operands against an inline table no + // binary fills. + let result = super::execute_source_with_natives( + "return full_state_clamp(52, min: 40, max: 50);", + &[( + "full_state_clamp", + NativeFunction::FullState(full_state_clamp), + crate::vm::NativeEntry::VARIADIC, + )], + ) + .expect("execute source"); assert_eq!(result.returns, vec![RuntimeVal::Int(50)]); - assert_eq!(result.state.stack[1], RuntimeVal::Nil); - assert_eq!(result.state.stack[2], RuntimeVal::Nil); - assert_eq!(result.state.stack[3], RuntimeVal::Nil); - assert_eq!(result.state.stack[4], RuntimeVal::Nil); - assert_eq!(result.state.stack[5], RuntimeVal::Nil); } #[test] @@ -224,7 +169,6 @@ fn execute_module_calls_runtime_callable_from_heap() { }; let caller_module = Module { functions: vec![entry], - natives: Vec::new(), globals: vec![GlobalSlot { name: "f".into() }], entry: 0, type_info: Default::default(), @@ -352,12 +296,9 @@ fn direct_full_state_native_named_map_uses_heap_map_source() { arity: 1, function: NativeFunction::FullState(full_state_named), }))); - let named = state - .heap - .alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([( - Arc::::from("increment"), - 37, - )])))); + let named = state.heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([(Arc::::from("increment"), 37)]), + ))); let mut ctx = VmContext::new_without_core_vm_builtins(); let result = call_runtime_value_runtime_named_map( @@ -416,6 +357,8 @@ fn direct_runtime_native_collects_after_heap_allocation() { state.heap.get(live), Some(HeapValue::String(value)) if value.as_ref() == "native-live" )); + // Handle 1: the string the native allocated before failing. Handle 0 is the + // callable itself, which the global keeps alive. assert!(state.heap.get(HeapRef::new(1)).is_none()); assert!(matches!( state.heap.get(match callable { @@ -504,7 +447,6 @@ fn execute_module_uses_global_slot_fact_for_get_and_set() { ); let module = Module { functions: vec![entry], - natives: Vec::new(), globals: vec![ GlobalSlot { name: "unused".into() }, GlobalSlot { name: "answer".into() }, @@ -549,7 +491,6 @@ fn execute_module_set_global_move_fact_consumes_source_register() { ); let module = Module { functions: vec![entry], - natives: Vec::new(), globals: vec![GlobalSlot { name: "stored".into() }], entry: 0, type_info: Default::default(), @@ -583,7 +524,6 @@ fn execute_module_set_global_without_move_fact_clones_source_register() { }; let module = Module { functions: vec![entry], - natives: Vec::new(), globals: vec![GlobalSlot { name: "stored".into() }], entry: 0, type_info: Default::default(), @@ -610,7 +550,6 @@ fn execute_module_falls_back_to_instr_global_slot_without_fact() { }; let module = Module { functions: vec![entry], - natives: Vec::new(), globals: vec![GlobalSlot { name: "answer".into() }], entry: 0, type_info: Default::default(), @@ -619,8 +558,9 @@ fn execute_module_falls_back_to_instr_global_slot_without_fact() { let result = execute_module_with_globals(&module, vec![RuntimeVal::Int(42)]).expect("execute module"); + // The slot came off the instruction: this module carries no global fact, + // and reading `answer` still found slot 0. assert_eq!(result.returns, vec![RuntimeVal::Int(42)]); - assert_eq!(result.state.inline_caches.global(0), Some(0)); } #[test] @@ -636,7 +576,6 @@ fn execute_caller_handler_catches_raise_from_runtime_callable() { }; let callee_module = Arc::new(Module { functions: vec![callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -661,7 +600,6 @@ fn execute_caller_handler_catches_raise_from_runtime_callable() { }; let caller_module = Module { functions: vec![entry], - natives: Vec::new(), globals: vec![GlobalSlot { name: "f".into() }], entry: 0, type_info: Default::default(), @@ -694,7 +632,6 @@ fn execute_module_calls_runtime_callable_with_named_args() { }; let callee_module = Arc::new(Module { functions: vec![callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -710,7 +647,7 @@ fn execute_module_calls_runtime_callable_with_named_args() { let entry = Function { consts: ConstPool { ints: vec![40, 2], - strings: vec!["y".to_string()], + strings: vec![alloc::sync::Arc::::from("y")], ..ConstPool::default() }, code: vec![ @@ -730,7 +667,6 @@ fn execute_module_calls_runtime_callable_with_named_args() { }; let caller_module = Module { functions: vec![entry], - natives: Vec::new(), globals: vec![GlobalSlot { name: "f".into() }], entry: 0, type_info: Default::default(), @@ -751,7 +687,7 @@ fn runtime_callable_error_keeps_shared_module_state() { let callee = Function { consts: ConstPool { ints: vec![41], - strings: vec!["boom".to_string()], + strings: vec![alloc::sync::Arc::::from("boom")], ..ConstPool::default() }, code: vec![ @@ -768,7 +704,6 @@ fn runtime_callable_error_keeps_shared_module_state() { }; let callee_module = Arc::new(Module { functions: vec![callee], - natives: Vec::new(), globals: vec![GlobalSlot { name: "counter".into() }], entry: 0, type_info: Default::default(), @@ -807,7 +742,7 @@ fn runtime_callable_native_error_collects_pending_heap_allocations() { } let callee = Function { - code: vec![Instr::abx(Opcode::LoadNative, 0, 0), Instr::abc(Opcode::Call, 0, 0, 0)], + code: vec![Instr::abx(Opcode::GetGlobal, 0, 0), Instr::abc(Opcode::Call, 0, 0, 0)], register_count: 1, param_count: 0, positional_param_count: 0, @@ -817,17 +752,23 @@ fn runtime_callable_native_error_collects_pending_heap_allocations() { }; let callee_module = Arc::new(Module { functions: vec![callee], - natives: vec![NativeEntry { - name: "native_alloc_then_error".to_string(), - arity: 0, - function: NativeFunction::Plain(native_alloc_then_error), + globals: vec![crate::vm::GlobalSlot { + name: "native_alloc_then_error".into(), }], - globals: Vec::new(), entry: 0, type_info: Default::default(), type_scope: Default::default(), }); - let mut state = RuntimeModuleState::new(HeapStore::new(), Vec::new()); + // The native lives in the callable's *own* state, as a global holding a + // `RuntimeNative` — the shape a loaded module has. An inline `NativeEntry` + // plus `LoadNative` is a mechanism nothing but these tests builds. + let mut heap = HeapStore::new(); + let native = RuntimeVal::Obj(heap.alloc(HeapValue::Callable(CallableValue::RuntimeNative { + name: Arc::::from("native_alloc_then_error"), + arity: 0, + function: NativeFunction::Plain(native_alloc_then_error), + }))); + let mut state = RuntimeModuleState::new(heap, vec![native]); state.heap.set_gc_threshold(1); let callable = RuntimeCallable::with_state( Arc::clone(&callee_module), @@ -860,7 +801,6 @@ fn direct_runtime_callable_restores_shared_state_stack_top() { }; let module = Arc::new(Module { functions: vec![callee], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: Default::default(), @@ -898,6 +838,46 @@ fn execute_source_runs_public_source_entry_on_new_vm() { assert_eq!(result.returns, vec![RuntimeVal::Int(42)]); } +/// A map's own methods win over a key of the same name — for every method, +/// not just the one with its own opcode. +/// +/// docs/semantics.md adjudicates "方法优先", and that was true of `len` alone: +/// the compiler emits a dedicated opcode for it, so it never reached the +/// dispatcher, where the key lookup ran *first*. `{"keys": 5, "z": 1}.keys()` +/// answered `5` and `{"is_empty": 5}.is_empty()` answered `5` — which of the +/// two you got depended on an implementation detail of the compiler. +/// +/// The shadowed key keeps an unambiguous spelling (`m["len"]`), and a callable +/// stored under a name no builtin uses is still called. +#[test] +fn a_map_method_is_not_shadowed_by_a_key_of_the_same_name() { + let result = execute_source( + r#" + let a = {"keys": 5, "z": 1}; + let b = {"values": 5, "z": 1}; + let c = {"len": 5, "z": 1}; + let d = {"is_empty": 5}; + let e = {"f": |x| { return x + 1; }, "n": 3}; + return [a.keys().len(), b.values().len(), c.len(), d.is_empty(), c["len"], e.f(4), e.n]; + "#, + ) + .expect("execute source"); + + let [RuntimeVal::Obj(handle)] = result.returns.as_slice() else { + panic!("expected one list return"); + }; + let HeapValue::List(TypedList::Mixed(values)) = result.state.heap.get(*handle).expect("result list") else { + panic!("expected mixed list return"); + }; + assert_eq!(values[0], RuntimeVal::Int(2), "keys() is the method, not the key"); + assert_eq!(values[1], RuntimeVal::Int(2), "values() is the method, not the key"); + assert_eq!(values[2], RuntimeVal::Int(2), "len() already was"); + assert_eq!(values[3], RuntimeVal::Bool(false), "is_empty() is the method"); + assert_eq!(values[4], RuntimeVal::Int(5), "the shadowed key is still readable"); + assert_eq!(values[5], RuntimeVal::Int(5), "a stored callable is still called"); + assert_eq!(values[6], RuntimeVal::Int(3), "and a plain key still reads"); +} + #[test] fn execute_source_uses_builtin_set_constructor_methods_and_iteration() { let result = execute_source( @@ -910,7 +890,7 @@ fn execute_source_uses_builtin_set_constructor_methods_and_iteration() { for value in s { total += value; } - return [s.len(), s.has(2), added, duplicate, removed, 3 in s, total, typeof(s)]; + return [s.len(), s.contains(2), added, duplicate, removed, 3 in s, total, typeof(s)]; "#, ) .expect("execute source"); @@ -941,7 +921,10 @@ fn execute_source_rejects_float_set_values() { "#, ) .expect_err("float set value should fail"); - assert!(err.to_string().contains("Float cannot be used as a key")); + assert!( + err.to_string().contains("Float cannot be a map key or set member"), + "{err}" + ); } #[test] @@ -963,19 +946,18 @@ fn execute_module_context_native_can_use_vm_context() { Ok(RuntimeVal::Int(value)) } - let module = Compiler::compile_source_module_with_natives( - "return add_seed(2);", - vec![NativeEntry { - name: "add_seed".to_string(), - arity: 1, - function: NativeFunction::Context(add_seed), - }], - ) - .expect("compile module"); + // Installed on the context and named as an external global: how a stdlib + // native reaches a program. Compiling one *into* the module was the + // `LoadNative` path, which no binary built. + let program = crate::syntax::parse_program_source("return add_seed(2);", Default::default()).expect("parse"); let mut ctx = crate::vm::VmContext::new_without_core_vm_builtins(); + ctx.install_runtime_builtin("add_seed", NativeFunction::Context(add_seed), 1); ctx.define_runtime_value("seed", RuntimeVal::Int(40), HeapStore::new()); - let result = execute_module_with_globals_and_ctx(&module, Vec::new(), &mut ctx).expect("execute module"); + // The program path, because that is what seeds a module's globals from the + // context (`seed_module_globals`); `execute_module_with_globals_and_ctx` + // takes the values from its caller and this one has none to give. + let result = crate::vm::execute_program_with_ctx(&program, &mut ctx).expect("execute program"); assert_eq!(result.returns, vec![RuntimeVal::Int(42)]); assert!(matches!( @@ -1105,6 +1087,12 @@ fn program_execute_installs_core_method_helper_by_default() { assert_eq!(result.display_first_return(), "red|blue"); } +/// `typeof` on a struct instance names the struct. +/// +/// It answered `Object` — the heap representation, which is not a type the +/// language has, and the same answer for every struct in the program. That made +/// `typeof` useless on exactly the values a program most wants to ask about. +/// `HeapValue::type_name` is the carrier of that rule and had the hole itself. #[test] fn execute_program_imports_typeof_as_runtime_native() { let tokens = crate::token::Tokenizer::tokenize( @@ -1118,5 +1106,23 @@ fn execute_program_imports_typeof_as_runtime_native() { let result = execute_program_with_ctx(&program, &mut ctx).expect("execute"); - assert!(matches!(result.first_return(), RuntimeVal::ShortStr(value) if value.as_str() == "Object")); + assert!(matches!(result.first_return(), RuntimeVal::ShortStr(value) if value.as_str() == "Box")); +} + +/// A `Set` is a map's key set, so it rejects what a map rejects. It used to +/// take a list as a member and compare it by *handle*, so the member could +/// never be found again and two equal lists both went in. +#[test] +fn execute_source_rejects_container_set_members_like_map_keys() { + for source in [ + "let s = Set(); s.add([1, 2]); return s;", + "let s = Set([[1, 2]]); return s;", + "let m = {}; m.set([1, 2], 3); return m;", + ] { + let err = execute_source(source).expect_err("a list is not a key"); + assert!( + err.to_string().contains("List cannot be a map key or set member"), + "{source} → {err}" + ); + } } diff --git a/core/src/vm/exec/format.rs b/core/src/vm/exec/format.rs deleted file mode 100644 index fd876a5d..00000000 --- a/core/src/vm/exec/format.rs +++ /dev/null @@ -1,182 +0,0 @@ -use super::*; - -pub(super) fn format_runtime_val(value: &RuntimeVal, heap: &HeapStore, depth: usize) -> String { - const MAX_DEPTH: usize = 8; - match value { - RuntimeVal::Nil => "nil".to_string(), - RuntimeVal::Bool(b) => b.to_string(), - RuntimeVal::Int(i) => i.to_string(), - RuntimeVal::Float(f) => f.to_string(), - RuntimeVal::ShortStr(s) => s.as_str().to_string(), - RuntimeVal::Obj(handle) => { - let Some(heap_val) = heap.get(*handle) else { - return "".to_string(); - }; - match heap_val { - HeapValue::String(s) => s.to_string(), - HeapValue::List(list) if depth < MAX_DEPTH => format_typed_list(list, heap, depth + 1), - HeapValue::List(_) => "[...]".to_string(), - HeapValue::Map(map) if depth < MAX_DEPTH => format_typed_map(map, heap, depth + 1), - HeapValue::Map(_) => "{...}".to_string(), - HeapValue::Set(set) if depth < MAX_DEPTH => format_runtime_set(set), - HeapValue::Set(_) => "Set([...])".to_string(), - HeapValue::Callable(callable) => format_callable(callable), - HeapValue::Object(obj) => { - if depth < MAX_DEPTH { - let mut out = String::new(); - out.push('<'); - out.push_str(obj.type_name()); - out.push_str(" {"); - let mut first = true; - for (key, value) in &obj.fields { - if !first { - out.push_str(", "); - } - first = false; - out.push_str(key); - out.push_str(": "); - out.push_str(&format_runtime_val(value, heap, depth + 1)); - } - out.push_str("}>"); - out - } else { - format!("<{} {{...}}>", obj.type_name()) - } - } - _ => "".to_string(), - } - } - } -} - -pub(super) fn format_callable(callable: &crate::val::CallableValue) -> String { - match callable { - crate::val::CallableValue::Closure { - function_index, - captures, - } => format!("", function_index, captures.len()), - crate::val::CallableValue::RuntimeNative { name, arity, .. } => { - if *arity == NativeEntry::VARIADIC { - format!("", name) - } else { - format!("", name, arity) - } - } - crate::val::CallableValue::Runtime(function) => { - format!( - "", - function.display_signature(), - function.capture_count() - ) - } - } -} - -pub(super) fn format_typed_list(list: &TypedList, heap: &HeapStore, depth: usize) -> String { - let mut out = String::new(); - out.push('['); - match list { - TypedList::Int(values) => append_display_items(&mut out, values.iter().copied()), - TypedList::Float(values) => append_display_items(&mut out, values.iter().copied()), - TypedList::Bool(values) => append_display_items(&mut out, values.iter().copied()), - TypedList::String(values) => append_display_items(&mut out, values.iter().map(|value| value.as_ref())), - TypedList::Mixed(values) => append_runtime_items(&mut out, values, heap, depth), - } - out.push(']'); - out -} - -pub(super) fn format_typed_map(map: &TypedMap, heap: &HeapStore, depth: usize) -> String { - let mut out = String::new(); - out.push('{'); - match map { - TypedMap::Mixed(entries) => { - let mut first = true; - for (key, value) in entries { - append_separator(&mut out, &mut first); - out.push_str(&format_map_key(key)); - out.push_str(": "); - out.push_str(&format_runtime_val(value, heap, depth)); - } - } - TypedMap::StringMixed(entries) => append_string_runtime_map_entries(&mut out, entries, heap, depth), - TypedMap::StringInt(entries) => append_string_display_map_entries(&mut out, entries), - TypedMap::StringFloat(entries) => append_string_display_map_entries(&mut out, entries), - TypedMap::StringBool(entries) => append_string_display_map_entries(&mut out, entries), - } - out.push('}'); - out -} - -pub(super) fn format_runtime_set(set: &RuntimeSet) -> String { - let mut out = String::from("Set(["); - let mut first = true; - for value in set.entries() { - append_separator(&mut out, &mut first); - out.push_str(&format_map_key(value)); - } - out.push_str("])"); - out -} - -pub(super) fn append_separator(out: &mut String, first: &mut bool) { - if !*first { - out.push_str(", "); - } - *first = false; -} - -pub(super) fn append_display_items(out: &mut String, values: impl IntoIterator) { - let mut first = true; - for value in values { - append_separator(out, &mut first); - out.push_str(&value.to_string()); - } -} - -pub(super) fn append_runtime_items(out: &mut String, values: &[RuntimeVal], heap: &HeapStore, depth: usize) { - let mut first = true; - for value in values { - append_separator(out, &mut first); - out.push_str(&format_runtime_val(value, heap, depth)); - } -} - -pub(super) fn append_string_runtime_map_entries( - out: &mut String, - entries: &FastHashMap, RuntimeVal>, - heap: &HeapStore, - depth: usize, -) { - let mut first = true; - for (key, value) in entries { - append_separator(out, &mut first); - out.push_str(key); - out.push_str(": "); - out.push_str(&format_runtime_val(value, heap, depth)); - } -} - -pub(super) fn append_string_display_map_entries( - out: &mut String, - entries: &FastHashMap, T>, -) { - let mut first = true; - for (key, value) in entries { - append_separator(out, &mut first); - out.push_str(key); - out.push_str(": "); - out.push_str(&value.to_string()); - } -} - -pub(super) fn format_map_key(key: &RuntimeMapKey) -> String { - match key { - RuntimeMapKey::Nil => "nil".to_string(), - RuntimeMapKey::Bool(b) => b.to_string(), - RuntimeMapKey::Int(i) => i.to_string(), - RuntimeMapKey::ShortStr(s) => s.as_str().to_string(), - RuntimeMapKey::String(s) => s.to_string(), - RuntimeMapKey::Obj(h) => format!("", h.index()), - } -} diff --git a/core/src/vm/exec/frame.rs b/core/src/vm/exec/frame.rs index 9891ad52..11a57beb 100644 --- a/core/src/vm/exec/frame.rs +++ b/core/src/vm/exec/frame.rs @@ -27,7 +27,7 @@ pub(super) struct CallFrame { pub(super) pc: usize, pub(super) frame_base: usize, pub(super) register_count: u16, - pub(super) captures: Arc>, + pub(super) captures: Option>>, /// `handler_stack.len()` at call time, truncated back to on pop (mirrors /// `call_closure_stack_args`'s `saved_handler_depth`). pub(super) handler_depth: usize, diff --git a/core/src/vm/exec/gc.rs b/core/src/vm/exec/gc.rs index b0d5e8be..c71da7ee 100644 --- a/core/src/vm/exec/gc.rs +++ b/core/src/vm/exec/gc.rs @@ -16,23 +16,35 @@ impl Executor { // Ancestor frames' captures (plan M2.5 sub-step ①: flattened LK→LK // calls no longer keep them alive implicitly on the Rust stack) must // be rooted explicitly here, same as the current frame's `captures`. - let frame_roots = self.frames.iter().flat_map(|frame| frame.captures.iter()); + let frame_roots = self + .frames + .iter() + .flat_map(|frame| frame.captures.iter().flat_map(|captures| captures.iter())); self.state - .gc_roots(self.captures.iter().chain(frame_roots).chain(handler_roots)) + .gc_roots( + self.captures + .iter() + .flat_map(|captures| captures.iter()) + .chain(frame_roots) + .chain(handler_roots), + ) .into_refs() } + /// The collection itself, reached only once a safepoint has decided there + /// is something to do. + /// + /// The test used to live *inside* this function while the function was + /// `#[cold]`, which put the outline boundary in the wrong place: every + /// safepoint made a real call into the cold section just to load two bools + /// and come back. A call/return pair passes two safepoints, so the empty + /// call benchmark spent ~3% of its time there. [`Executor::safepoint`] now + /// holds the test and this stays cold, which is what `#[cold]` is for. #[cold] - #[inline] - pub(super) fn collect_pending_garbage(&mut self) { - // Stress mode collects at every safepoint (not at allocation sites — - // fresh handles are only rooted once the handler writes them to a - // register), so a missed root fails deterministically in any test run. - if self.gc_pending || self.gc_stress { - let roots = self.root_refs(); - self.state.heap.collect(roots); - self.gc_pending = false; - } + fn collect_now(&mut self) { + let roots = self.root_refs(); + self.state.heap.collect(roots); + self.gc_pending = false; } pub(super) fn sync_heap_gc_threshold(&mut self) { @@ -47,21 +59,26 @@ impl Executor { /// and only fails if the *reachable* set is still over budget. #[cold] pub(super) fn force_collect(&mut self) { - let roots = self.root_refs(); - self.state.heap.collect(roots); - self.gc_pending = false; + self.collect_now(); } - /// GC safepoint run after allocation-heavy opcodes: reclaim pending garbage, - /// then enforce the process **byte** budget (plan M2.6, `LK_MAX_HEAP_BYTES`). - /// A non-allocating hot loop never reaches a safepoint, so it pays nothing; + /// GC safepoint run after allocation-heavy opcodes and at call boundaries: + /// reclaim pending garbage, then enforce the process **byte** budget (plan + /// M2.6, `LK_MAX_HEAP_BYTES`). + /// A non-allocating, non-calling hot loop never reaches a safepoint, so it + /// pays nothing; /// [`mem::over_limit`](crate::mem::over_limit) short-circuits to a single /// atomic load when the limit is unset. When over budget, collect once more /// (returning freed VM memory to the allocator) and abort execution (a hard /// sandbox stop, like fuel) only if the *reachable* footprint is still over. #[inline] pub(super) fn safepoint(&mut self) -> anyhow::Result<()> { - self.collect_pending_garbage(); + // Stress mode collects at every safepoint (not at allocation sites — + // fresh handles are only rooted once the handler writes them to a + // register), so a missed root fails deterministically in any test run. + if self.gc_pending || self.gc_stress { + self.collect_now(); + } if crate::mem::over_limit() { self.enforce_memory_limit()?; } @@ -110,7 +127,7 @@ mod tests { RuntimeVal::Obj(inactive_stack), ]; executor.state.stack_top = 2; - executor.captures = Arc::new(vec![RuntimeVal::Obj(capture)]); + executor.captures = Some(Arc::new(vec![RuntimeVal::Obj(capture)])); assert_eq!(executor.root_refs(), vec![global, stack, capture]); } diff --git a/core/src/vm/exec/handler.rs b/core/src/vm/exec/handler.rs index 98b370bf..12b5364a 100644 --- a/core/src/vm/exec/handler.rs +++ b/core/src/vm/exec/handler.rs @@ -17,6 +17,31 @@ impl core::fmt::Display for LanguageRaise { impl core::error::Error for LanguageRaise {} +/// `panic(msg)` — an abort, and the one raise `catch` refuses. +/// +/// The language has two ways to stop: `error(v)` is recoverable and `catch` +/// binds it, `panic(msg)` is not. That was the documented design and no host +/// implemented it: the desktop one called Rust's `panic!` (which works on a +/// desktop and is an unrecoverable trap in wasm and has no unwinder on bare +/// metal), while the web and bare hosts returned an ordinary error — which +/// `catch` catches, making `panic` recoverable there and not here. +/// +/// Being a distinct type is the whole mechanism: the unwinder checks for it +/// before consulting the handler stack, so no `try` can swallow it, on any +/// host, without anyone having to remember. +#[derive(Clone, Debug)] +pub struct LkPanic { + pub message: alloc::sync::Arc, +} + +impl core::fmt::Display for LkPanic { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.message.as_ref()) + } +} + +impl core::error::Error for LkPanic {} + /// A recoverable error carrying a first-class LK value. `error(v)` raises this /// and `pcall` extracts `value`, so an errored value round-trips as itself /// rather than a string — including heap objects (String/List/…), which are diff --git a/core/src/vm/exec/imports.rs b/core/src/vm/exec/imports.rs index a9c93cd6..a40e4f10 100644 --- a/core/src/vm/exec/imports.rs +++ b/core/src/vm/exec/imports.rs @@ -1,13 +1,11 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::{FastHashMap, fast_hash_map_new, fast_hash_set_new}; +use crate::util::value_map::{ValueMap, value_map_new}; use alloc::sync::Arc; -use anyhow::{Result, anyhow}; +use anyhow::{Result, anyhow, bail}; -use crate::val::{ - CallableValue, HeapStore, HeapValue, RuntimeMapKey, RuntimeObject, RuntimeSet, RuntimeVal, TypedList, TypedMap, -}; +use crate::val::{CallableValue, HeapStore, HeapValue, RuntimeObject, RuntimeSet, RuntimeVal, TypedList, TypedMap}; use super::{RuntimeCallable, runtime_value_to_callable_shared}; use crate::vm::{Module, RuntimeExport}; @@ -85,15 +83,9 @@ fn import_heap_value( source_module, source_state, )?), - HeapValue::Set(values) => HeapValue::Set(import_runtime_set( - values, - source_heap, - dest_heap, - source_module, - source_state, - )?), + HeapValue::Set(values) => HeapValue::Set(import_runtime_set(values)), HeapValue::Object(object) => { - let mut fields = fast_hash_map_new(); + let mut fields = value_map_new(); for (key, value) in &object.fields { fields.insert( Arc::clone(key), @@ -136,8 +128,32 @@ fn import_heap_value( } HeapValue::Task(value) => HeapValue::Task(Arc::clone(value)), HeapValue::Channel(value) => HeapValue::Channel(Arc::clone(value)), - HeapValue::Stream(value) => HeapValue::Stream(Arc::clone(value)), - HeapValue::StreamCursor(value) => HeapValue::StreamCursor(Arc::clone(value)), + // Same reason as the copy in `runtime_callable::copy_runtime_value_with` + // — a stream's callbacks and buffered values are handles into the heap + // that built it, and an id shared across heaps does not carry them. + // This is the path an *import* takes, and the REPL's, where each input + // is its own module: the pipeline came back with its filter silently + // skipped. + HeapValue::Stream(value) => { + if value.roots.iter().any(|root| matches!(root, RuntimeVal::Obj(_))) { + bail!( + "a stream cannot be imported from another module: this one's pipeline holds a \ + callback or a value that lives in the heap of the module that built it. Collect \ + it first (`stream.collect`) and pass the list, or build the stream on this side" + ); + } + HeapValue::Stream(Arc::clone(value)) + } + HeapValue::StreamCursor(value) => { + if value.roots.iter().any(|root| matches!(root, RuntimeVal::Obj(_))) { + bail!( + "a stream cursor cannot be imported from another module: this one reads from a \ + pipeline that lives in the heap of the module that built it. Drain it first and \ + pass the values" + ); + } + HeapValue::StreamCursor(Arc::clone(value)) + } HeapValue::Slice(value) => HeapValue::Slice(Arc::new(crate::val::SliceValue { source: import_runtime_value( &value.source, @@ -146,7 +162,6 @@ fn import_heap_value( source_module, source_state.clone(), )?, - kind: value.kind, start: value.start, len: value.len, })), @@ -177,24 +192,11 @@ fn import_heap_value( }) } -fn import_runtime_set( - values: &RuntimeSet, - source_heap: &HeapStore, - dest_heap: &mut HeapStore, - source_module: Arc, - source_state: alloc::sync::Arc>, -) -> Result { - let mut out = fast_hash_set_new(); - for key in values.entries() { - out.insert(import_runtime_map_key( - key, - source_heap, - dest_heap, - Arc::clone(&source_module), - source_state.clone(), - )?); - } - Ok(RuntimeSet::from_entries(out)) +/// A set crosses heaps as itself: its members are `RuntimeMapKey`s, and none of +/// those carries a heap handle — a long string is an `Arc` held inline, and +/// a container cannot be a member at all (see `RuntimeMapKey::from_value`). +fn import_runtime_set(values: &RuntimeSet) -> RuntimeSet { + values.clone() } fn import_typed_list( @@ -234,16 +236,10 @@ fn import_typed_map( ) -> Result { Ok(match values { TypedMap::Mixed(values) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in values { out.insert( - import_runtime_map_key( - key, - source_heap, - dest_heap, - Arc::clone(&source_module), - source_state.clone(), - )?, + key.clone(), import_runtime_value( value, source_heap, @@ -256,7 +252,7 @@ fn import_typed_map( TypedMap::Mixed(out) } TypedMap::StringMixed(values) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in values { out.insert( Arc::clone(key), @@ -277,42 +273,14 @@ fn import_typed_map( }) } -fn import_runtime_map_key( - key: &RuntimeMapKey, - source_heap: &HeapStore, - dest_heap: &mut HeapStore, - source_module: Arc, - source_state: alloc::sync::Arc>, -) -> Result { - Ok(match key { - RuntimeMapKey::Nil => RuntimeMapKey::Nil, - RuntimeMapKey::Bool(value) => RuntimeMapKey::Bool(*value), - RuntimeMapKey::Int(value) => RuntimeMapKey::Int(*value), - RuntimeMapKey::ShortStr(value) => RuntimeMapKey::ShortStr(*value), - RuntimeMapKey::String(value) => RuntimeMapKey::String(Arc::clone(value)), - RuntimeMapKey::Obj(handle) => { - match import_runtime_value( - &RuntimeVal::Obj(*handle), - source_heap, - dest_heap, - source_module, - source_state, - )? { - RuntimeVal::Obj(handle) => RuntimeMapKey::Obj(handle), - _ => unreachable!("object map key use must stay an object"), - } - } - }) -} - fn copy_slice(values: &[T]) -> Vec { let mut out = Vec::with_capacity(values.len()); out.extend_from_slice(values); out } -fn copy_string_map_values(values: &FastHashMap, T>) -> FastHashMap, T> { - let mut out = fast_hash_map_new(); +fn copy_string_map_values(values: &ValueMap, T>) -> ValueMap, T> { + let mut out = value_map_new(); for (key, value) in values { out.insert(Arc::clone(key), *value); } diff --git a/core/src/vm/exec/named_call.rs b/core/src/vm/exec/named_call.rs index ac2afb0b..91f0a13e 100644 --- a/core/src/vm/exec/named_call.rs +++ b/core/src/vm/exec/named_call.rs @@ -2,6 +2,7 @@ use crate::compat::prelude::*; use core::ops::Range; +use alloc::borrow::Cow; use anyhow::{Result, anyhow, bail}; use crate::{ @@ -131,7 +132,7 @@ impl Executor { let callee = *self .read(u8::try_from(window.callee.as_usize()).map_err(|_| anyhow!("call callee register overflow"))?)?; let RuntimeVal::Obj(handle) = callee else { - bail!("CallNamed callee is not callable"); + bail!("this value is not a function"); }; let callable = callable_target( known_target_kind, @@ -139,7 +140,7 @@ impl Executor { .heap .get(handle) .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?, - "CallNamed callee is not callable", + "this value is not a function", )?; match callable { CallableTarget::RuntimeNative { arity, function } => { @@ -151,7 +152,7 @@ impl Executor { ); } let native = NativeEntry { - name: "".to_string(), + name: Cow::Borrowed(""), arity, function, }; @@ -206,12 +207,15 @@ impl Executor { .functions .get(function_index as usize) .ok_or_else(|| anyhow!("function index {} out of bounds", function_index))?; - self.push_call_frame_named(function_index, function, captures, window, named_count)?; + self.push_call_frame_named(function_index, function, Some(captures), window, named_count)?; Ok(CallOutcome::Pushed(function_index)) } CallableTarget::Runtime(function) => { let args = self.call_args_stack_range(window)?; let named_start = args.end; + // Same as the positional path: only the executor can say which + // module a function among these arguments came from. + let caller_module = self.shared_module.clone(); let result = runtime_callable::call_runtime_callable_runtime_named_stack( function.as_ref(), &self.state.stack[args], @@ -219,6 +223,7 @@ impl Executor { named_start, named_count, &mut self.state.heap, + caller_module.as_ref(), ctx.as_deref_mut(), ); result diff --git a/core/src/vm/exec/program.rs b/core/src/vm/exec/program.rs index 1057eab6..904208ae 100644 --- a/core/src/vm/exec/program.rs +++ b/core/src/vm/exec/program.rs @@ -1,3 +1,4 @@ +use crate::compat::path::Path; #[cfg(not(feature = "std"))] use crate::compat::prelude::*; use alloc::sync::Arc; @@ -25,6 +26,9 @@ pub trait ProgramExec { fn execute(&self) -> Result; /// Type-checks and runs the program in `ctx`. fn execute_with_ctx(&self, ctx: &mut VmContext) -> Result; + /// As [`Self::execute_with_ctx`], with the directory the program was loaded + /// from so its own imports can be seeded into the checker. + fn execute_with_ctx_from(&self, ctx: &mut VmContext, base_dir: Option<&Path>) -> Result; } impl ProgramExec for Program { @@ -34,7 +38,27 @@ impl ProgramExec for Program { } fn execute_with_ctx(&self, ctx: &mut VmContext) -> Result { + self.execute_with_ctx_from(ctx, None) + } + + /// As [`ProgramExec::execute_with_ctx`], with the directory this program + /// was loaded from so its own imports can be seeded. + /// + /// Without the directory the checker cannot open the files this program + /// imports, so a name that crosses a module boundary — a `struct` returned + /// by a function in another file — is unknown to it. That was invisible + /// while an unknown name silently became `Type::Named`: the annotation + /// checked against nothing. The entry file has always been seeded (the CLI + /// does it); a module *loaded as an import* had not been, so it was the one + /// place where cross-file calls went unchecked entirely. + fn execute_with_ctx_from(&self, ctx: &mut VmContext, base_dir: Option<&Path>) -> Result { let mut type_checker = crate::typ::TypeChecker::new(); + #[cfg(feature = "std")] + if let Some(base_dir) = base_dir { + crate::typ::seed_imported_signatures(self, base_dir, &mut type_checker); + } + #[cfg(not(feature = "std"))] + let _ = base_dir; self.type_check(&mut type_checker)?; execute_program_with_ctx(self, ctx) } @@ -46,6 +70,21 @@ pub fn execute_program(program: &Program) -> Result { } pub fn compile_program_module_with_ctx(program: &Program, ctx: &mut VmContext) -> Result> { + compile_program_module_with_ctx_and_data_globals::<&str>(program, ctx, &[]) +} + +/// As [`compile_program_module_with_ctx`], naming the context globals that hold +/// *user data* rather than an imported module object. +/// +/// A plain program has none: everything it did not declare itself came from an +/// import. The REPL is the exception — its `xs` from an earlier line arrives as +/// a context global, and without this it compiles `xs.len()` as a module-member +/// read (see `Compiler::compile_module_with_globals_and_data`). +pub fn compile_program_module_with_ctx_and_data_globals>( + program: &Program, + ctx: &mut VmContext, + data_globals: &[S], +) -> Result> { let imports = collect_program_imports(program); let resolver = ctx.resolver().clone(); execute_imports(&imports, resolver.as_ref(), ctx)?; @@ -55,11 +94,15 @@ pub fn compile_program_module_with_ctx(program: &Program, ctx: &mut VmContext) - external_globals.push(name.clone()); } - let mut module = Compiler::compile_module_with_natives_and_globals(program, Vec::new(), external_globals)?; + let mut module = Compiler::compile_module_with_globals_and_data( + program, + external_globals, + data_globals.iter().map(|name| name.as_ref()), + )?; // The compiler has no idea which file it is compiling; the loader does, and // it put that on the context before handing the program over. Stamping here // is what gives this module's declared types an identity distinct from an - // identically-named type in any other module (`vm::TypeScope`). + // identically-named type in any other module (`val::TypeScope`). module.type_scope = ctx.type_scope().clone(); Ok(Arc::new(module)) } @@ -375,6 +418,813 @@ mod tests { assert!(matches!(dest_heap.get(imported), Some(HeapValue::String(value)) if value.as_ref() == "external")); } + /// `-x` — negation of anything that is not a literal. + /// + /// The language had no negation operator at all: `UnaryOp` held only + /// `Not`, and only the *lexer* could produce a negative number, by folding + /// `-5` into an `Int(-5)` token where it could tell an operand was + /// expected. So `-5` worked and `-x` was a syntax error in every position, + /// with `0 - x` as the workaround. That workaround is also not a + /// substitute: `0.0 - 0.0` is `+0.0` where `-(0.0)` is `-0.0`. + #[test] + fn negation_works_on_values_and_not_only_literals() { + let source = "let i = 7;\n\ + let f = 2.5;\n\ + let z = 0.0;\n\ + let neg = |v| -v;\n\ + return [-i, -f, -(-i), neg(i), -z, -9223372036854775808];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("scalars only"); + assert_eq!(items[0], RuntimeVal::Int(-7)); + assert_eq!(items[1], RuntimeVal::Float(-2.5)); + assert_eq!(items[2], RuntimeVal::Int(7)); + assert_eq!(items[3], RuntimeVal::Int(-7)); + // The zero's *sign* survives, which is the whole reason this is a real + // negation and not `0 - x`: the latter answers `+0.0` here. `==` cannot + // see the difference, so ask for the sign bit. + let RuntimeVal::Float(negative_zero) = items[4] else { + panic!("expected a float"); + }; + assert!( + negative_zero == 0.0 && negative_zero.is_sign_negative(), + "-0.0 should keep its sign, got {negative_zero}" + ); + // `i64::MIN`'s magnitude does not fit an `i64`, so the lexer still owns + // this one; it has to keep agreeing with the operator. + assert_eq!(items[5], RuntimeVal::Int(i64::MIN)); + } + + /// `if` produces a value, the way `match` always has. + /// + /// `let a = match c { … };` parsed and `let a = if c { … } else { … };` did + /// not, so the only way to *choose* a value was the C-style ternary — the + /// operator a language whose `if` is an expression does not need. Both now + /// lower to the same node, so they cannot drift apart. + #[test] + fn if_is_an_expression_that_yields_its_branch() { + let source = "let x = 5;\n\ + let size = if x > 3 { \"big\" } else if x > 1 { \"mid\" } else { \"small\" };\n\ + let doubled = if true { let t = x; t * 2 } else { 0 };\n\ + let missing = if false { 1 };\n\ + let pick = |v| if v > 0 { 1 } else { -1 };\n\ + // Truthiness, not `Bool`: `0` is truthy, only nil and false are not.\n\ + let zero_is_truthy = if 0 { \"yes\" } else { \"no\" };\n\ + return [size, doubled, missing, pick(-9), zero_is_truthy];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("results are heap objects"); + let text = |value: &RuntimeVal| -> String { + match value { + RuntimeVal::ShortStr(s) => s.as_str().to_string(), + RuntimeVal::Obj(h) => match outcome.state.heap().get(*h) { + Some(HeapValue::String(s)) => s.to_string(), + other => panic!("expected a string, got {other:?}"), + }, + other => panic!("expected a string, got {other:?}"), + } + }; + assert_eq!(text(&items[0]), "big"); + assert_eq!(items[1], RuntimeVal::Int(10)); + // No `else` means no value: `nil`, not a parse error. + assert_eq!(items[2], RuntimeVal::Nil); + assert_eq!(items[3], RuntimeVal::Int(-1)); + assert_eq!(text(&items[4]), "yes"); + } + + /// A zero-parameter closure is a value, so it goes wherever a value goes. + /// + /// The lexer decides whether `||` opens a closure or is a logical or by + /// looking at what precedes it — and it used to look at the previous + /// *character*, accepting only `= ( { , ; :`. A character cannot see a + /// keyword, so `return || 1;` lexed as an operator ("Unexpected token: + /// Or") while `let f = || 1;` was fine, and `[|| 1]` failed on the missing + /// `[`. It asks the same predicate `-5` asks now: is a value expected here. + #[test] + fn a_zero_parameter_closure_goes_where_a_value_goes() { + let source = "fn returned() { return || 1; }\n\ + fn takes(c) { return c; }\n\ + let bound = || 2;\n\ + let in_list = [|| 3];\n\ + let in_map = {\"f\": || 4};\n\ + let x = 1;\n\ + return [\n\ + returned()(), bound(), in_list[0](), in_map.f(), takes(|| 5)(),\n\ + // …and `||` between two operands is still the operator.\n\ + (x > 0 || x < 0) ? 6 : 0,\n\ + ];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("ints only"); + for (index, want) in [1, 2, 3, 4, 5, 6].iter().enumerate() { + assert_eq!( + items[index], + RuntimeVal::Int(*want), + "position {index} should have parsed" + ); + } + } + + /// The list's mutating methods all mutate, and hand back one of two + /// things. + /// + /// They used to disagree three ways: `push`/`set` changed the list, + /// `insert` copied it and returned a *new* one, and `remove_at` copied it + /// and returned a two-element `[updated, old]` — the only method in the + /// language shaped that way, whose "updated" list nobody was holding. So + /// `xs.push(v)` changed `xs` and `xs.insert(i, v)` did not, which is two + /// opposite answers to "does adding an element change this list". + /// + /// The rule now, across every container: a mutating method changes the + /// receiver, and answers either the container (so calls chain) or the + /// element it took out. A `Set` is the one exception, and for a reason — + /// `add`/`delete` have no separate element to hand back, so they report + /// whether the value was new or present. + #[test] + fn the_mutating_list_methods_agree() { + let source = "let xs = [1, 3];\n\ + let chained = xs.insert(1, 2);\n\ + let after_insert = xs.len();\n\ + let removed = xs.remove_at(0);\n\ + let after_remove = xs.len();\n\ + let strings = [\"a\", \"c\"];\n\ + strings.insert(1, \"b\");\n\ + let widened = [1, 2];\n\ + widened.insert(1, \"middle\");\n\ + let m = {};\n\ + m.set(\"a\", 1).set(\"b\", 2);\n\ + let chained_writes = [1, 2];\n\ + chained_writes.set(0, 9).set(1, 8);\n\ + return [\n\ + chained.len(), after_insert, removed, after_remove,\n\ + strings.len(), widened.len(), m.len(), chained_writes.get(0),\n\ + ];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("ints only"); + // `insert` answers the *receiver*, not a copy — so the chained value + // tracks the later `remove_at` and is two long, not three. + assert_eq!(items[0], RuntimeVal::Int(2)); + assert_eq!(items[1], RuntimeVal::Int(3), "insert changes the receiver"); + assert_eq!(items[2], RuntimeVal::Int(1), "remove_at answers the element it took"); + assert_eq!(items[3], RuntimeVal::Int(2), "…and the list is one shorter"); + assert_eq!(items[4], RuntimeVal::Int(3), "a string list inserts by content"); + // A value the representation cannot hold widens the list rather than + // failing — the same degradation `push` has always done. + assert_eq!(items[5], RuntimeVal::Int(3)); + // `set` answers the receiver too, on a map and on a list, so writes + // chain wherever they are written. + assert_eq!(items[6], RuntimeVal::Int(2)); + assert_eq!(items[7], RuntimeVal::Int(9)); + } + + /// `pop` takes the element off; `last` reads it. + /// + /// They were the same function under two names — same body, same declared + /// type, same doc sentence — so the language had two spellings of "peek" + /// and no way at all to remove the last element. A name every language + /// uses for "remove and return" must not quietly mean "read". + #[test] + fn pop_removes_and_last_only_looks() { + let source = "let ints = [1, 2, 3];\n\ + let texts = [\"ab\", \"cdefghijk\"];\n\ + let peeked = ints.last();\n\ + let after_peek = ints.len();\n\ + let popped = ints.pop();\n\ + let after_pop = ints.len();\n\ + let long = texts.pop();\n\ + let empty = [];\n\ + return [peeked, after_peek, popped, after_pop, long, texts.len(), empty.pop()];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("results are heap objects"); + assert_eq!(items[0], RuntimeVal::Int(3), "last() reads the final element"); + assert_eq!(items[1], RuntimeVal::Int(3), "…and leaves the list alone"); + assert_eq!(items[2], RuntimeVal::Int(3), "pop() returns the same element"); + assert_eq!(items[3], RuntimeVal::Int(2), "…and takes it off"); + // A heap string element comes back whole, and the list still shrinks. + assert_eq!(items[5], RuntimeVal::Int(1)); + assert_eq!(items[6], RuntimeVal::Nil, "popping an empty list is nil, not an error"); + } + + /// `?.` calls a method, which is most of what it is for. + /// + /// `OptionalAccess` is a *read*, and the compiler lowers it as an index — + /// so `s?.len()` indexed the string with the string `"len"` and failed at + /// runtime with "String index must be Int". The null-safe operator did not + /// work on the values it exists for; only field access on a struct or map + /// went through. It is rewritten at parse time now, the way postfix `!` + /// is, so the checker and the compiler both see ordinary constructs. + #[test] + fn optional_chaining_reaches_methods_and_stops_at_nil() { + let source = "let present = \"abcd\";\n\ + let m = {\"a\": \"xy\"};\n\ + let missing = if false { \"abc\" };\n\ + return [\n\ + present?.len(), m.get(\"a\")?.len(), m.get(\"z\")?.len(),\n\ + missing?.len(), missing?.len() ?? 0,\n\ + ];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("scalars only"); + assert_eq!(items[0], RuntimeVal::Int(4)); + assert_eq!(items[1], RuntimeVal::Int(2)); + // The call does not happen at all when the receiver is nil. + assert_eq!(items[2], RuntimeVal::Nil); + assert_eq!(items[3], RuntimeVal::Nil); + assert_eq!(items[4], RuntimeVal::Int(0)); + } + + /// An `if` with no `else` is a value that may be nil, not a contradiction. + /// + /// The missing branch is a synthesised `nil`, and the two arms were + /// constrained to be *equal* — so `let r = if c { "a" };` reported "Cannot + /// unify String with Nil" and the expression form could not do what the + /// statement form does. `c ? "a" : nil` reads the same way and now gets the + /// same answer: `String?`. + #[test] + fn an_if_without_else_is_optional_not_a_conflict() { + fn check(source: &str) -> Result<(), String> { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + program + .type_check(&mut crate::typ::TypeChecker::new()) + .map_err(|e| e.to_string()) + } + + for source in [ + "let c = true;\nlet r = if c { \"a\" };\n", + "let c = true;\nlet r: String? = if c { \"a\" };\n", + "let c = true;\nlet r = c ? \"a\" : nil;\n", + "let c = true;\nlet r = c ? nil : \"a\";\n", + // Both branches present and agreeing keeps the bare type. + "let c = true;\nlet r: String = if c { \"a\" } else { \"b\" };\n", + ] { + check(source).unwrap_or_else(|e| panic!("{source} should check, said: {e}")); + } + + let error = check("let c = true;\nlet r: String = if c { \"a\" };\n") + .expect_err("a branch that may not run makes the value optional"); + assert!(error.contains("String?"), "should say String?, said: {error}"); + } + + /// A `match` that can miss is typed as able to miss. + /// + /// LK's rule is that an unmatched `match` evaluates to `nil` — deliberate, + /// and tested. The *type* ignored it: the expression was typed as its + /// arms' type, so + /// + /// ```text + /// let r: String = match x { 1 => "one" }; // checked, held nil + /// r.len() // approved, failed at runtime + /// ``` + /// + /// A binding annotated `String` holding nil is the type system saying + /// something untrue. It says `String?` now, and the shapes that cannot + /// miss — a catch-all arm, or a `Bool` with both literals — keep the bare + /// type so the common cases do not grow a `?`. + #[test] + fn a_match_that_can_miss_is_typed_as_nullable() { + fn check(source: &str) -> Result<(), String> { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + program + .type_check(&mut crate::typ::TypeChecker::new()) + .map_err(|e| e.to_string()) + } + + for source in [ + "let x = 5;\nlet r: String = match x { 1 => \"one\" };\n", + "fn f(x: Int) -> String { return match x { 1 => \"one\" }; }\n", + ] { + let error = check(source).expect_err("a match that can miss is not a bare String"); + assert!(error.contains("String?"), "{source} should say String?, said: {error}"); + } + + for source in [ + // A catch-all arm always matches. + "let x = 5;\nlet r: String = match x { 1 => \"a\", _ => \"b\" };\n", + // A binding pattern is a catch-all too. + "let x = 5;\nlet r: String = match x { 1 => \"a\", other => \"b\" };\n", + // Both `Bool` literals cover every value of the type. + "let b = true;\nlet r: Int = match b { true => 1, false => 2 };\n", + // And the nullable type is writable when the miss is intended. + "let x = 5;\nlet r: String? = match x { 1 => \"one\" };\n", + ] { + check(source).unwrap_or_else(|e| panic!("{source} should check, said: {e}")); + } + } + + /// A type declaration's position in the file does not matter. + /// + /// Function signatures were hoisted and type declarations were not, which + /// nobody noticed while an undeclared name silently became `Type::Named`: + /// the annotation checked against nothing either way. The moment unknown + /// names became an error, `fn f() -> Point { … }` written above + /// `struct Point { … }` — the ordinary way to put the interesting function + /// first — started failing. + #[test] + fn a_type_declaration_can_come_after_its_use() { + for source in [ + "fn f() -> Point { return Point { a: 1 }; }\nstruct Point { a: Int }\nreturn f().a;\n", + "fn f(v: Point) -> Int { return v.a; }\nstruct Point { a: Int }\nreturn f(Point { a: 2 });\n", + "fn f(v: Int) -> U { return v; }\ntype U = Int;\nreturn f(1);\n", + "let s: Shown = 1;\ntype Shown = Int;\nreturn s;\n", + ] { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let mut checker = crate::typ::TypeChecker::new(); + program + .type_check(&mut checker) + .unwrap_or_else(|e| panic!("{source} should check, said: {e}")); + } + + // A name nothing declares is still an error, wherever it appears. LK + // has no generic parameters — `fn f(…)` does not parse — so a bare + // `T` is an undeclared name like any other. + for source in ["fn f(v: T) -> T { return v; }\n", "let x: Nope = 1;\n"] { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let error = program + .type_check(&mut crate::typ::TypeChecker::new()) + .expect_err("an undeclared type name is an error"); + assert!(error.to_string().contains("Unknown type"), "got: {error}"); + } + } + + /// A `type` alias works in every position, including across a module + /// boundary. + /// + /// It is a second *spelling*, not a second type. It worked in a binding + /// (`let x: U = 5`) and in a parameter (`fn f(v: U)`) and broke in exactly + /// one place — the return type — with "Cannot unify U with Int", because + /// the declared type went to the solver unresolved and the solver has no + /// registry to look a name up in. Aliases also never crossed a module + /// boundary at all: only `struct`s and `trait`s were seeded from an + /// imported file. + #[test] + fn a_type_alias_is_a_spelling_not_a_type() { + for source in [ + "type U = Int;\nlet x: U = 5;\nreturn x;\n", + "type U = Int;\nfn f(v: U) -> Int { return v; }\nreturn f(3);\n", + "type U = Int;\nfn f(v: Int) -> U { return v; }\nreturn f(3);\n", + "type U = Int;\nfn f(v: Int) -> U { return v; }\nfn g(v: Int) -> U { return f(v); }\nreturn g(3);\n", + "type Pair = List;\nfn f() -> Pair { return [1, 2]; }\nreturn f().len();\n", + ] { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let mut checker = crate::typ::TypeChecker::new(); + program + .type_check(&mut checker) + .unwrap_or_else(|e| panic!("{source} should check, said: {e}")); + } + + // …and a genuine mismatch is still one. + let source = "type U = Int;\nfn f(v: Int) -> U { return \"x\"; }\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let error = program + .type_check(&mut crate::typ::TypeChecker::new()) + .expect_err("a String is not an Int by another name"); + assert!(error.to_string().contains("Return type mismatch"), "got: {error}"); + } + + /// A misspelled type name is reported where it is written. + /// + /// `Type::Named` is the parser's answer for any identifier in type + /// position, so a typo became a type nothing declares — and the complaint + /// landed on the *value*: `let x: Strng = "a";` said "expected Strng, but + /// expression has type String", pointing away from the misspelling. A + /// signature was worse: `fn f(v: Nonexistent)` made the function + /// uncallable and blamed every caller. + #[test] + fn an_unknown_type_name_is_reported_at_the_annotation() { + fn check_error(source: &str) -> String { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let mut checker = crate::typ::TypeChecker::new(); + program + .type_check(&mut checker) + .expect_err("an undeclared type name is an error") + .to_string() + } + + // Every position an annotation can appear in. The bug repeated itself + // one position at a time — binding, then parameter, then return, then + // impl target, then trait method, then struct field — so the list is + // the point of the test. + for (source, expected) in [ + ("let x: Strng = \"a\";\n", "Unknown type 'Strng'"), + ("fn f(v: Nonexistent) { return 1; }\n", "Unknown type 'Nonexistent'"), + ("fn f() -> Bogus { return 1; }\n", "Unknown type 'Bogus'"), + ("let x: List = [1];\n", "Unknown type 'Nope'"), + ("let x: Map = {};\n", "Unknown type 'Nope'"), + ("struct P { a: Nope }\n", "Unknown type 'Nope'"), + ("trait T { fn f(self) -> Missing; }\n", "Unknown type 'Missing'"), + ("trait T { fn f(self, v: Bogus) -> Int; }\n", "Unknown type 'Bogus'"), + ( + "trait T { fn f(self) -> Int; }\nimpl T for Nonexistent { fn f(self) -> Int { return 1; } }\n", + "Unknown type 'Nonexistent'", + ), + ] { + let message = check_error(source); + assert!( + message.contains(expected), + "{source} should name the type, said: {message}" + ); + } + + // …and say what to write instead, when there is an obvious answer. A + // bare "Unknown type 'bool'" is accurate and useless: someone arriving + // from Rust or Python writes `bool`, `str`, `int` by reflex, and `f32` + // is a *decision* (one float type, spelled `Float` or `f64`) rather + // than an omission. + for (source, hint) in [ + ("let x: bool = true;\n", "did you mean `Bool`?"), + ("let x: int = 1;\n", "did you mean `Int`?"), + ("let x: Strng = \"a\";\n", "did you mean `String`?"), + ("struct Point { a: Int }\nlet p: Poimt = 1;\n", "did you mean `Point`?"), + ("let x: str = \"a\";\n", "LK spells that `String`"), + ("let x: f32 = 1.0;\n", "LK spells that `Float`"), + ] { + let message = check_error(source); + assert!(message.contains(hint), "{source} should suggest, said: {message}"); + } + + // A name with no near miss says nothing rather than guessing. + let far = check_error("let x: Zzzzz = 1;\n"); + assert!(!far.contains("did you mean"), "should not invent a suggestion: {far}"); + + // Declared names, builtins and documented runtime handles all pass. + for source in [ + "let x: Int = 1;\n", + "struct P { a: Int }\nlet p: P = P { a: 1 };\n", + "trait T { fn f(self) -> Int; }\nfn g(v: T) -> Int { return 1; }\n", + "let x: List = [];\n", + "let x: Map = {};\n", + ] { + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let mut checker = crate::typ::TypeChecker::new(); + program + .type_check(&mut checker) + .unwrap_or_else(|e| panic!("{source} should check, said: {e}")); + } + } + + /// The declared arity is the arity — one source, not two. + /// + /// Each dispatcher stated its own in a `bail!` guard, so a method could + /// accept a shape the checker rejected (or the reverse) and nothing said + /// so. Three had drifted by the time anyone compared them by hand: + /// `bytes.slice` (checker computed the wrong count for a named call), + /// `map.get` (runtime took a default, the table declared one parameter), + /// and `str.slice` (declared `end` required where every other sequence has + /// it optional). Dispatch checks the declaration now, so a guard that + /// disagrees is unreachable rather than quietly authoritative. + #[test] + fn a_methods_optional_arguments_are_the_declared_ones() { + let source = "let m = {\"a\": 1};\n\ + let text = \"abcd\";\n\ + let xs = [10, 20, 30];\n\ + return [\n\ + m.get(\"z\", 9), m.get(\"a\", 9),\n\ + text.slice(1).len(), text.slice(1, 3).len(), xs.slice(1).len(),\n\ + ];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("ints only"); + assert_eq!(items[0], RuntimeVal::Int(9), "an absent key takes the default"); + assert_eq!(items[1], RuntimeVal::Int(1), "a present key ignores it"); + assert_eq!(items[2], RuntimeVal::Int(3), "slice without an end runs to the end"); + assert_eq!(items[3], RuntimeVal::Int(2)); + assert_eq!(items[4], RuntimeVal::Int(2)); + } + + /// A `String` is a sequence, and reads like one. + /// + /// `List`, `Slice` and `Bytes` were unified on `first`/`last`/`get`/ + /// `slice`/`take`/`skip`/`index_of`; `String` — a sequence of characters, + /// which is what `len()` counts and `[i]` indexes — was left out. It had + /// `substring(start, length)` and `find` instead, and `substring` was the + /// reason this was more than tidiness: it took a *length* where every + /// `slice` takes an *end*, so `xs.slice(1, 3)` and `s.substring(1, 3)` + /// cut different windows from the same numbers. Both are gone now. + #[test] + fn a_string_reads_like_every_other_sequence() { + let source = "let s = \"h\u{e9}llo\";\n\ + return [\n\ + s.slice(1, 3), s.take(2), s.skip(2),\n\ + s.first(), s.last(), s.get(1),\n\ + ];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("strings only"); + let text = |value: &RuntimeVal| -> String { + match value { + RuntimeVal::ShortStr(s) => s.as_str().to_string(), + RuntimeVal::Obj(h) => match outcome.state.heap().get(*h) { + Some(HeapValue::String(s)) => s.to_string(), + other => panic!("expected a string, got {other:?}"), + }, + other => panic!("expected a string, got {other:?}"), + } + }; + // `slice` counts to an *end*, so this is two characters — the same + // window `[10, 20, 30, 40].slice(1, 3)` takes. + assert_eq!(text(&items[0]), "él"); + assert_eq!(text(&items[1]), "hé"); + assert_eq!(text(&items[2]), "llo"); + assert_eq!(text(&items[3]), "h"); + assert_eq!(text(&items[4]), "o"); + assert_eq!(text(&items[5]), "é"); + } + + /// `==`, `in`, and the constant folder answer the same question the same + /// way. + /// + /// There were three answers to "is `1` equal to `1.0`": + /// + /// ```text + /// println(1 == 1.0); → false (constant folder) + /// let a = 1; let b = 1.0; a == b; → true (runtime) + /// 1 in [1.0]; → false (typed-list `in`) + /// ``` + /// + /// The folder used `LiteralVal`'s derived `PartialEq` — structural, so two + /// variants are never equal — while contradicting its *own* ordering rule, + /// which promotes: `1 <= 1.0 && 1 >= 1.0` folded to `true`. And `in` + /// matched on the element's variant, so the answer depended on the list's + /// internal representation, which no program can see. + #[test] + fn equality_answers_the_same_whoever_asks() { + let source = "let a = 1;\n\ + let b = 1.0;\n\ + let ints = [1, 2];\n\ + let floats = [1.0, 2.0];\n\ + return [\n\ + 1 == 1.0, a == b, 1 <= 1.0 && 1 >= 1.0,\n\ + a in floats, b in ints, 1 in floats, 1.0 in ints,\n\ + 1.5 in ints, a in ints,\n\ + ];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of answers"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let answers = list.collect_owned().expect("bools only"); + let expected = [true, true, true, true, true, true, true, false, true]; + for (index, want) in expected.iter().enumerate() { + assert_eq!( + answers[index], + RuntimeVal::Bool(*want), + "answer {index} disagrees with the others" + ); + } + } + + /// A container is a container for `in`, whatever inferred it. + /// + /// `in`'s type check listed `List`/`Map`/`Set` and nothing else, so a + /// `String` (which contains substrings) and a `Tuple` (what a heterogeneous + /// list *literal* infers to) were rejected — while indexing, `len()` and + /// method dispatch took both. `"a" in "abc"` therefore worked as a folded + /// literal and was a type error one line later through a variable. + #[test] + fn in_accepts_every_container_the_rest_of_the_language_does() { + let source = "let text = \"abc\";\n\ + let mixed = [1, \"a\"];\n\ + return [\"b\" in text, \"z\" in text, \"a\" in mixed, 1 in mixed];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let mut checker = crate::typ::TypeChecker::new(); + program + .type_check(&mut checker) + .expect("a String and a Tuple are containers"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of answers"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let answers = list.collect_owned().expect("bools only"); + assert_eq!(answers[0], RuntimeVal::Bool(true)); + assert_eq!(answers[1], RuntimeVal::Bool(false)); + assert_eq!(answers[2], RuntimeVal::Bool(true)); + assert_eq!(answers[3], RuntimeVal::Bool(true)); + } + + /// The braced constructs agree on punctuation and on parentheses. + /// + /// Three rules used to differ for no reason any of them could explain: + /// `while` *required* parentheses around its condition while `if` and + /// `for` did not; and `match x { … }` / `unsafe { … }` as statements + /// *required* a trailing `;` while `if c { … }` refused one. Same shape on + /// the page, different punctuation. + #[test] + fn braced_constructs_agree_on_parentheses_and_semicolons() { + let source = "let seen = [];\n\ + let i = 0;\n\ + while i < 3 { i = i + 1; }\n\ + while (i < 6) { i = i + 1; }\n\ + match i { 6 => { seen = seen.concat([\"matched\"]); }, _ => {} }\n\ + unsafe { seen = seen.concat([\"unsafe\"]); }\n\ + if i == 6 { seen = seen.concat([\"if\"]); }\n\ + return [i, seen];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("results are heap objects"); + assert_eq!(items[0], RuntimeVal::Int(6), "both `while` forms should have run"); + let RuntimeVal::Obj(seen) = items[1] else { + panic!("expected the marker list"); + }; + let Some(HeapValue::List(seen)) = outcome.state.heap().get(seen) else { + panic!("expected the marker list"); + }; + assert_eq!(seen.len(), 3, "each statement after a closing brace should have run"); + } + + /// A braced construct ends a *statement*, never an operand. + /// + /// `match x { … } println("next");` is two statements; `return match x + /// { … } == nil;` is one comparison. Stopping at the brace in both places + /// would silently drop the `== nil` — an answer, not a syntax error. + #[test] + fn a_block_ends_a_statement_but_not_an_operand() { + let source = "let compared = match 99 { 1 => \"one\", _ => nil } == nil;\n\ + return compared;\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + assert_eq!(*outcome.first_return(), RuntimeVal::Bool(true)); + } + + /// An `if` *statement* keeps working, and an `else` that belongs to one is + /// still its own. + /// + /// The statement parser slices an expression up to the next top-level + /// `else`, which was correct while `else` could only close a statement. + /// Now it has to hand the `else` to an unmatched `if` inside the slice + /// instead — and only a genuinely dangling one ends the expression. + #[test] + fn an_if_statement_still_owns_its_own_else() { + let source = "let seen = [];\n\ + if 1 > 2 { seen = seen.concat([\"then\"]); } else { seen = seen.concat([\"else\"]); }\n\ + let nested = if true { if false { 1 } else { 2 } } else { 3 };\n\ + return [seen, nested];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list.collect_owned().expect("results are heap objects"); + let RuntimeVal::Obj(branch) = items[0] else { + panic!("expected the branch list"); + }; + let Some(HeapValue::List(branch)) = outcome.state.heap().get(branch) else { + panic!("expected the branch list"); + }; + assert_eq!(branch.len(), 1, "exactly one branch should have run"); + assert_eq!(items[1], RuntimeVal::Int(2)); + } + + #[test] + fn negating_a_non_number_is_a_type_error() { + let tokens = crate::token::Tokenizer::tokenize("-\"text\"").expect("tokenize"); + let expr = crate::ast::Parser::new(&tokens).parse().expect("parse"); + let error = crate::typ::TypeChecker::new() + .check_expr(&expr) + .expect_err("negating a String has no answer"); + assert!( + error.to_string().contains("numeric"), + "the error should say the operand is not numeric, said: {error}" + ); + } + + #[test] + fn long_string_elements_survive_every_read_path() { + // `ShortStr` inlines up to seven bytes. Every path that reads an + // element out of a `TypedList::String` used to assume that was always + // enough: the index fast path answered `Nil` for a longer element — + // making `xs[0]` disagree with `xs.first()` about the same list — and + // the slice path called `ShortStr::new(..).unwrap()` in the branch + // reached exactly when it returns `None`, so `xs[0..2]` panicked. + let source = "let xs = [\"aaaaaaaaaaaaaaaaaaaa\", \"bb\"];\n\ + let seen = [];\n\ + for x in xs { seen = seen.concat([x]); }\n\ + return [xs[0], xs.get(0), xs.first(), xs[0..1], seen];\n"; + let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); + let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); + let outcome = super::execute_program(&program).expect("run"); + + let RuntimeVal::Obj(handle) = *outcome.first_return() else { + panic!("expected a list of results"); + }; + let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { + panic!("expected a heap list"); + }; + let items = list + .collect_owned() + .expect("results are heap objects, not inline strings"); + + let long = |value: &RuntimeVal| -> String { + match value { + RuntimeVal::Obj(handle) => match outcome.state.heap().get(*handle) { + Some(HeapValue::String(text)) => text.to_string(), + other => panic!("expected a heap string, got {other:?}"), + }, + other => panic!("expected a heap string, got {other:?}"), + } + }; + assert_eq!(long(&items[0]), "aaaaaaaaaaaaaaaaaaaa", "xs[0]"); + assert_eq!(long(&items[1]), "aaaaaaaaaaaaaaaaaaaa", "xs.get(0)"); + assert_eq!(long(&items[2]), "aaaaaaaaaaaaaaaaaaaa", "xs.first()"); + } + fn compile_source(source: &str) -> crate::vm::Module { let tokens = crate::token::Tokenizer::tokenize(source).expect("tokenize"); let program = crate::stmt::StmtParser::new(&tokens).parse_program().expect("parse"); @@ -410,7 +1260,9 @@ mod tests { let Some(HeapValue::List(list)) = outcome.state.heap().get(handle) else { panic!("result handle must stay live in the outcome state"); }; - let items = list.collect_owned(); + let items = list + .collect_owned() + .expect("the result list holds no inline-limited strings"); assert_eq!(items[0], RuntimeVal::Int(7)); let RuntimeVal::Obj(text) = items[1] else { panic!("expected the long string element on the heap"); @@ -453,7 +1305,9 @@ mod tests { pairs.sort_by(|a, b| a.0.cmp(&b.0)); let expected: alloc::vec::Vec<(String, RuntimeVal)> = [("alpha", 1), ("beta", 2), ("gamma", 3)] .into_iter() - .map(|(key, value)| (format!("String({key:?})"), RuntimeVal::Int(value))) + // `ShortStr`, not `String`: the text decides the representation, and + // these five-character keys fit inline. + .map(|(key, value)| (format!("ShortStr({key:?})"), RuntimeVal::Int(value))) .collect(); assert_eq!(pairs, expected); } diff --git a/core/src/vm/exec/result.rs b/core/src/vm/exec/result.rs index fffee72c..286b124d 100644 --- a/core/src/vm/exec/result.rs +++ b/core/src/vm/exec/result.rs @@ -1,5 +1,6 @@ -use super::format::format_runtime_val; +use super::display::runtime_display_value; use super::*; +use crate::util::value_map::value_map_new; impl ProgramResult { pub fn first_return(&self) -> &RuntimeVal { @@ -8,7 +9,10 @@ impl ProgramResult { pub fn first_return_list(&self) -> Result<&TypedList> { let RuntimeVal::Obj(handle) = self.first_return() else { - bail!("first return is {:?}, expected list object", self.first_return().kind()); + bail!( + "first return is {}, expected list object", + self.first_return().type_name_in(&self.state.heap) + ); }; match self.state.heap.get(*handle) { Some(HeapValue::List(values)) => Ok(values), @@ -19,7 +23,10 @@ impl ProgramResult { pub fn first_return_map(&self) -> Result<&TypedMap> { let RuntimeVal::Obj(handle) = self.first_return() else { - bail!("first return is {:?}, expected map object", self.first_return().kind()); + bail!( + "first return is {}, expected map object", + self.first_return().type_name_in(&self.state.heap) + ); }; match self.state.heap.get(*handle) { Some(HeapValue::Map(values)) => Ok(values), @@ -30,17 +37,20 @@ impl ProgramResult { pub fn into_exports(self) -> RuntimeExport { let mut state = self.state; - let mut entries = fast_hash_map_new(); + let mut entries = value_map_new(); for (slot, value) in self.module.globals.iter().zip(state.globals.iter()) { - entries.insert(RuntimeMapKey::String(slot.name.clone()), *value); + entries.insert(RuntimeMapKey::from_shared(slot.name.clone()), *value); } let value = RuntimeVal::Obj(state.heap.alloc(HeapValue::Map(typed_map_from_entries(entries)))); + let mut module_state = RuntimeModuleState::new(state.heap, state.globals); + // The map just allocated is this module's value, and it lives in this + // module's heap with nothing else pointing at it. Without saying so, a + // collection driven from anywhere but `collect_runtime_export` frees it + // — see `RuntimeModuleState::export_root`. + module_state.set_export_root(value); RuntimeExport::new( value, - Arc::new(crate::compat::sync::Mutex::new(RuntimeModuleState::new( - state.heap, - state.globals, - ))), + Arc::new(crate::compat::sync::Mutex::new(module_state)), self.module, ) } @@ -59,6 +69,6 @@ impl ProgramResult { /// Format the first return value as a human-readable string for REPL/CLI display. pub fn display_first_return(&self) -> String { - format_runtime_val(self.first_return(), &self.state.heap, 0) + runtime_display_value(self.first_return(), &self.state.heap).unwrap_or_else(|_| "".to_string()) } } diff --git a/core/src/vm/exec/runners.rs b/core/src/vm/exec/runners.rs index 83b764dd..9a9c4b5f 100644 --- a/core/src/vm/exec/runners.rs +++ b/core/src/vm/exec/runners.rs @@ -6,7 +6,7 @@ use super::*; /// `v` itself, a `raise`/message raise binds the message string, and **any other /// runtime error** also binds its message. That last case is not an extra: the /// parse-time desugar ran the body under `pcall`, which catches every `Err`, so -/// `try { 1 / 0 } catch e` has always been caught even though `DivInt divisor is +/// `try { 1 % 0 } catch e` has always been caught even though `ModInt divisor is /// zero` is a plain `bail!` and not a raise at all. enum RaiseKind { Message(alloc::sync::Arc), @@ -152,7 +152,7 @@ impl Executor { } let saved_top = state.stack_top(); self.state = state; - self.captures = captures; + self.captures = Some(captures); self.shared_module = shared_module; self.reset_entry_frame(function.register_count); let arg_count = match seed_args(&mut self) { @@ -258,6 +258,7 @@ impl Executor { && !self.type_scope.is_same(&module.type_scope) { self.type_scope = module.type_scope.clone(); + self.struct_decls = module.type_info.structs.clone(); } let base_frame_depth = self.frames.len(); self.current_function_index = function_index; @@ -327,9 +328,16 @@ impl Executor { base_frame_depth: usize, ) -> Result { let mut errored_function = errored_function; + // A panic is not catchable, on any host. Checked before the handler + // stack rather than inside the classification below, so that both the + // same-frame case here and the unwinding loop underneath get it from + // one place. + if error.downcast_ref::().is_some() { + return Err(error); + } // First: a handler installed in the frame that actually faulted. Nothing // is popped in that case, so the loop below would never see it — this is - // the `try { 1 / 0 } catch e` shape, where the error is a plain `bail!` + // the `try { 1 % 0 } catch e` shape, where the error is a plain `bail!` // from the arithmetic opcode rather than a raise. if let Some(index) = self .handler_stack diff --git a/core/src/vm/exec/runtime_callable.rs b/core/src/vm/exec/runtime_callable.rs index 403e57ea..ce52c5d8 100644 --- a/core/src/vm/exec/runtime_callable.rs +++ b/core/src/vm/exec/runtime_callable.rs @@ -1,15 +1,15 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; use crate::compat::sync::Mutex; -use crate::util::fast_map::{FastHashMap, fast_hash_map_new, fast_hash_set_new}; +use crate::util::value_map::{ValueMap, value_map_new}; +use alloc::borrow::Cow; use alloc::sync::Arc; use anyhow::{Result, anyhow, bail}; use crate::{ val::{ - CallableValue, HeapRef, HeapStore, HeapValue, RuntimeMapKey, RuntimeObject, RuntimeSet, RuntimeVal, TypedList, - TypedMap, + CallableValue, HeapRef, HeapStore, HeapValue, RuntimeMapKey, RuntimeObject, RuntimeVal, TypedList, TypedMap, }, vm::{Module, NativeArgs, NativeEntry, RuntimeCallable, RuntimeModuleState, VmContext}, }; @@ -30,7 +30,8 @@ pub(crate) fn call_runtime_callable_test( args: &[RuntimeVal], ctx: &mut crate::vm::VmContext, ) -> Result> { - let state = take_runtime_callable_state(function)?; + let state = take_runtime_callable_state(function) + .map_err(|reason| reason.into_error(&function.module, function.function_index))?; let arg_count = checked_arg_count(args.len())?; let register_count = function .module @@ -53,6 +54,8 @@ pub(crate) fn call_runtime_callable_test( }, ) { Ok(result) => result, + // No crossing here, and so no copy: this helper hands the callee's own + // values straight to a test, which reads them against the callee's heap. Err(failure) => { let ExecFailure { error, state } = failure; commit_runtime_callable_state(function, state)?; @@ -64,6 +67,7 @@ pub(crate) fn call_runtime_callable_test( Ok(returns) } +#[allow(clippy::too_many_arguments)] pub fn call_runtime_callable_runtime_named_stack( function: &RuntimeCallable, positional: &[RuntimeVal], @@ -71,9 +75,12 @@ pub fn call_runtime_callable_runtime_named_stack( named_start: usize, named_count: u16, caller_heap: &mut HeapStore, + caller_module: Option<&Arc>, ctx: Option<&mut crate::vm::VmContext>, ) -> Result { - let state = take_runtime_callable_state(function)?; + let mode = crossing_mode(caller_module); + let state = take_runtime_callable_state(function) + .map_err(|reason| reason.into_error(&function.module, function.function_index))?; let function_meta = function .module .functions @@ -107,6 +114,7 @@ pub fn call_runtime_callable_runtime_named_stack( caller_heap, heap, frame, + &mode, )?; Ok(function_meta.param_count) }, @@ -114,12 +122,24 @@ pub fn call_runtime_callable_runtime_named_stack( Ok(result) => result, Err(failure) => { let ExecFailure { error, state } = failure; + // Same crossing as the return below, for the same reason. + let error = raised_value_into_caller_heap(error, &state.heap, caller_heap, &function.module); commit_runtime_callable_state(function, state)?; return Err(error); } }; let value = result.returns.first().cloned().unwrap_or(RuntimeVal::Nil); - let value = copy_runtime_value(&value, &result.state.heap, caller_heap)?; + // The way back is the same crossing as the way in, and the module is known + // here without asking anyone: it is the callee's own. Without this + // `fn make_adder(n) -> (Int) -> Int` could not hand its closure back — + // returning a function was refused while passing one had just started + // working. + let value = copy_runtime_value_with( + &value, + &result.state.heap, + caller_heap, + &ClosureCopy::Promote(Arc::clone(&function.module)), + )?; commit_runtime_callable_state(function, result.state)?; Ok(value) } @@ -130,7 +150,30 @@ pub fn call_runtime_callable_runtime( caller_heap: &mut HeapStore, ctx: Option<&mut crate::vm::VmContext>, ) -> Result { - call_runtime_callable_runtime_positional(function, RuntimePositionalArgs::Slice(args), caller_heap, ctx) + call_runtime_callable_runtime_positional(function, RuntimePositionalArgs::Slice(args), caller_heap, None, ctx) +} + +/// [`call_runtime_callable_runtime`] told which module the arguments come from. +/// +/// Only the executor knows that, and only it can say it: a *function* among +/// those arguments is a bare index into the caller's table, so without the +/// caller's module there is nothing to promote it against. Every other caller +/// (a stdlib HOF re-entering the VM, a test) passes `None` and keeps the old +/// refusal. +pub fn call_runtime_callable_runtime_from( + function: &RuntimeCallable, + args: &[RuntimeVal], + caller_heap: &mut HeapStore, + caller_module: Option<&Arc>, + ctx: Option<&mut crate::vm::VmContext>, +) -> Result { + call_runtime_callable_runtime_positional( + function, + RuntimePositionalArgs::Slice(args), + caller_heap, + caller_module, + ctx, + ) } pub fn call_runtime_value_runtime( @@ -235,7 +278,50 @@ pub fn call_trait_method( call_foreign_module_method(declaring, *function, name, pos, state, ctx) } crate::vm::MethodImpl::Imported(callable) => { - call_runtime_callable_runtime_positional(callable.as_ref(), pos, &mut state.heap, ctx) + // A method reached while its *own* module is the one executing does + // not borrow that module's state — it already has it. + // + // `take_runtime_callable_state` moves the shared state out of its + // mutex and leaves a `Default::default()` behind until the call + // returns, so the mechanism is non-reentrant by construction. And + // "a method that calls another method on `self`" re-enters by + // definition: the outer call took the state, the inner call took the + // empty shell, and the executor refused a module wanting 83 globals + // against a table of 0 — a message about globals for a program that + // never mentions one. Every cross-module `impl` was affected the + // moment one of its methods called another (trait default body, + // inherent method, inherent calling a trait method: all three). + // + // The `Local` arm one branch up already asks exactly this question + // for the same reason; this arm did not. + if let Some(executing) = module + && core::ptr::eq(Arc::as_ptr(&callable.module), executing as *const Module) + { + return call_closure_value( + callable.function_index, + Arc::clone(&callable.captures), + pos, + state, + Some(executing), + ctx, + ); + } + // Same problem one step further out: module A's method calls into B, + // and B calls back into A. A is not the module executing here (B + // is), so the branch above does not fire — but A's state is out on + // the stack, so borrowing it is impossible too. + // + // Borrowing is not the only way to run a foreign body, though. + // `call_foreign_module_method` exists for exactly this shape: it + // keeps the *current* heap and swaps in a global table of the + // declaring module's shape, so it needs the module, not the module's + // state. Taking that route makes A→B→A work, and a body that writes + // a global — the one thing the borrowed path could do and this one + // cannot — is refused there by name instead of corrupted. + if runtime_callable_module_is_executing(callable.as_ref()) { + return call_foreign_module_method(&callable.module, callable.function_index, name, pos, state, ctx); + } + call_runtime_callable_runtime_positional(callable.as_ref(), pos, &mut state.heap, None, ctx) } } } @@ -376,7 +462,7 @@ fn call_runtime_value_with_map_args( ) -> Result { let callee_root = callee; let RuntimeVal::Obj(handle) = callee else { - bail!("runtime callee is not callable"); + bail!("this value is not a function"); }; let callable = callable_target( None, @@ -384,7 +470,7 @@ fn call_runtime_value_with_map_args( .heap .get(handle) .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?, - "runtime callee is not callable", + "this value is not a function", )?; let Some(named_handle) = named else { return match callable { @@ -398,14 +484,14 @@ fn call_runtime_value_with_map_args( bail!("Native expects {} positional arguments, got {}", arity, pos_len); } let native = NativeEntry { - name: "".to_string(), + name: Cow::Borrowed(""), arity, function, }; call_runtime_native_positional(&native, pos, state, module, ctx, callee_root) } CallableTarget::Runtime(function) => { - call_runtime_callable_runtime_positional(function.as_ref(), pos, &mut state.heap, ctx) + call_runtime_callable_runtime_positional(function.as_ref(), pos, &mut state.heap, None, ctx) } }; }; @@ -428,7 +514,7 @@ fn call_runtime_value_with_map_args( bail!("Native expects {} positional arguments, got {}", arity, pos_len); } let native = NativeEntry { - name: "".to_string(), + name: Cow::Borrowed(""), arity, function, }; @@ -564,7 +650,7 @@ fn call_closure_value( let mut ctx = ctx; let mut callee = Executor::new(function.register_count); callee.state = core::mem::take(state); - callee.captures = captures; + callee.captures = Some(captures); let saved_top = callee.state.stack_top; let result = (|| { let new_base = saved_top; @@ -613,7 +699,7 @@ fn call_closure_value_typed_map( let mut ctx = ctx; let mut callee = Executor::new(function.register_count); callee.state = core::mem::take(state); - callee.captures = captures; + callee.captures = Some(captures); let saved_top = callee.state.stack_top; let result = (|| { let new_base = saved_top; @@ -660,9 +746,12 @@ fn call_runtime_callable_runtime_positional( function: &RuntimeCallable, pos: RuntimePositionalArgs<'_>, caller_heap: &mut HeapStore, + caller_module: Option<&Arc>, ctx: Option<&mut crate::vm::VmContext>, ) -> Result { - let state = take_runtime_callable_state(function)?; + let mode = crossing_mode(caller_module); + let state = take_runtime_callable_state(function) + .map_err(|reason| reason.into_error(&function.module, function.function_index))?; let function_meta = function .module .functions @@ -687,19 +776,31 @@ fn call_runtime_callable_runtime_positional( |executor| { let heap = &mut executor.state.heap; let frame = &mut executor.state.stack[..function_meta.register_count as usize]; - copy_runtime_positional_args_to_frame(function_meta, pos, caller_heap, heap, frame)?; + copy_runtime_positional_args_to_frame(function_meta, pos, caller_heap, heap, frame, &mode)?; Ok(function_meta.param_count) }, ) { Ok(result) => result, Err(failure) => { let ExecFailure { error, state } = failure; + // Same crossing as the return below, for the same reason. + let error = raised_value_into_caller_heap(error, &state.heap, caller_heap, &function.module); commit_runtime_callable_state(function, state)?; return Err(error); } }; let value = result.returns.first().cloned().unwrap_or(RuntimeVal::Nil); - let value = copy_runtime_value(&value, &result.state.heap, caller_heap)?; + // The way back is the same crossing as the way in, and the module is known + // here without asking anyone: it is the callee's own. Without this + // `fn make_adder(n) -> (Int) -> Int` could not hand its closure back — + // returning a function was refused while passing one had just started + // working. + let value = copy_runtime_value_with( + &value, + &result.state.heap, + caller_heap, + &ClosureCopy::Promote(Arc::clone(&function.module)), + )?; commit_runtime_callable_state(function, result.state)?; Ok(value) } @@ -711,7 +812,8 @@ fn call_runtime_callable_runtime_named_map_positional( caller_heap: &mut HeapStore, ctx: Option<&mut crate::vm::VmContext>, ) -> Result { - let state = take_runtime_callable_state(function)?; + let state = take_runtime_callable_state(function) + .map_err(|reason| reason.into_error(&function.module, function.function_index))?; let function_meta = function .module .functions @@ -743,19 +845,39 @@ fn call_runtime_callable_runtime_named_map_positional( }; let heap = &mut executor.state.heap; let frame = &mut executor.state.stack[..function_meta.register_count as usize]; - copy_runtime_positional_args_with_named_map_to_frame(function_meta, pos, named, caller_heap, heap, frame)?; + copy_runtime_positional_args_with_named_map_to_frame( + function_meta, + pos, + named, + caller_heap, + heap, + frame, + &ClosureCopy::Reject, + )?; Ok(function_meta.param_count) }, ) { Ok(result) => result, Err(failure) => { let ExecFailure { error, state } = failure; + // Same crossing as the return below, for the same reason. + let error = raised_value_into_caller_heap(error, &state.heap, caller_heap, &function.module); commit_runtime_callable_state(function, state)?; return Err(error); } }; let value = result.returns.first().cloned().unwrap_or(RuntimeVal::Nil); - let value = copy_runtime_value(&value, &result.state.heap, caller_heap)?; + // The way back is the same crossing as the way in, and the module is known + // here without asking anyone: it is the callee's own. Without this + // `fn make_adder(n) -> (Int) -> Int` could not hand its closure back — + // returning a function was refused while passing one had just started + // working. + let value = copy_runtime_value_with( + &value, + &result.state.heap, + caller_heap, + &ClosureCopy::Promote(Arc::clone(&function.module)), + )?; commit_runtime_callable_state(function, result.state)?; Ok(value) } @@ -769,12 +891,50 @@ fn commit_runtime_callable_state(function: &RuntimeCallable, next_state: Runtime Ok(()) } -fn take_runtime_callable_state(function: &RuntimeCallable) -> Result { - let mut state = function +/// Move a module's state out of its shared cell for the duration of one call. +/// +/// `Err` when the state is already out — i.e. this call re-enters a module that +/// is live further up the stack. The caller has to decide what that means; what +/// it must not do is run against the placeholder, which is an empty state that +/// looks perfectly valid and produces "module expected N globals, got 0" several +/// frames later. +fn take_runtime_callable_state(function: &RuntimeCallable) -> Result { + let mut cell = function.state.lock().map_err(|_| ReentrantModule::PoisonedLock)?; + if cell.borrowed_for_call { + return Err(ReentrantModule::AlreadyExecuting); + } + let taken = core::mem::take(&mut *cell); + cell.borrowed_for_call = true; + Ok(taken) +} + +/// Whether a call into this callable's module is already in progress. +fn runtime_callable_module_is_executing(function: &RuntimeCallable) -> bool { + function .state .lock() - .map_err(|_| anyhow!("RuntimeCallable state lock poisoned"))?; - Ok(core::mem::take(&mut *state)) + .map(|cell| cell.borrowed_for_call) + .unwrap_or(false) +} + +/// Why a module's state could not be taken. +enum ReentrantModule { + /// A call into this module is already in progress further up the stack. + AlreadyExecuting, + PoisonedLock, +} + +impl ReentrantModule { + fn into_error(self, module: &Module, function_index: u32) -> anyhow::Error { + match self { + Self::PoisonedLock => anyhow!("RuntimeCallable state lock poisoned"), + Self::AlreadyExecuting => anyhow!( + "function {function_index} of a module with {} globals was re-entered while that module was already \ + executing further up the call stack, and its state cannot be lent to two frames at once", + module.globals.len() + ), + } + } } #[cfg(test)] @@ -782,12 +942,22 @@ fn checked_arg_count(len: usize) -> Result { u16::try_from(len).map_err(|_| anyhow!("function arg count {} exceeds u16", len)) } +/// How a function value among the arguments is treated on the way across. +/// +/// With the caller's module known it is promoted to a callable that carries +/// that module; without it the copy has nothing to attach and refuses, which is +/// the old behaviour for every path that cannot say where the value came from. +fn crossing_mode(caller_module: Option<&Arc>) -> ClosureCopy { + caller_module.map_or(ClosureCopy::Reject, |module| ClosureCopy::Promote(Arc::clone(module))) +} + fn copy_runtime_positional_args_to_frame( function: &crate::vm::Function, pos: RuntimePositionalArgs<'_>, caller_heap: &HeapStore, callee_heap: &mut HeapStore, frame: &mut [RuntimeVal], + mode: &ClosureCopy, ) -> Result<()> { if frame.len() < function.param_count as usize { bail!( @@ -801,7 +971,7 @@ fn copy_runtime_positional_args_to_frame( if actual != expected { bail!("Function expects {} positional arguments, got {}", expected, actual); } - copy_runtime_positional_args_into_slots(pos, caller_heap, callee_heap, &mut frame[..expected]) + copy_runtime_positional_args_into_slots(pos, caller_heap, callee_heap, &mut frame[..expected], mode) } fn copy_runtime_positional_args_with_named_map_to_frame( @@ -811,6 +981,7 @@ fn copy_runtime_positional_args_with_named_map_to_frame( caller_heap: &HeapStore, callee_heap: &mut HeapStore, frame: &mut [RuntimeVal], + mode: &ClosureCopy, ) -> Result<()> { if frame.len() < function.param_count as usize { bail!( @@ -831,7 +1002,7 @@ fn copy_runtime_positional_args_with_named_map_to_frame( actual ); } - copy_runtime_positional_args_into_slots(pos, caller_heap, callee_heap, &mut frame[..positional_count])?; + copy_runtime_positional_args_into_slots(pos, caller_heap, callee_heap, &mut frame[..positional_count], mode)?; copy_typed_map_named_args_to_frame(function, named, caller_heap, callee_heap, frame) } @@ -840,24 +1011,25 @@ fn copy_runtime_positional_args_into_slots( caller_heap: &HeapStore, callee_heap: &mut HeapStore, slots: &mut [RuntimeVal], + mode: &ClosureCopy, ) -> Result<()> { match pos { RuntimePositionalArgs::Slice(values) => { for (slot, value) in slots.iter_mut().zip(values) { - *slot = copy_runtime_value(value, caller_heap, callee_heap)?; + *slot = copy_runtime_value_with(value, caller_heap, callee_heap, mode)?; } Ok(()) } RuntimePositionalArgs::ListHandle(handle) => { - copy_typed_list_arg_handle_to_slots(handle, caller_heap, callee_heap, slots) + copy_typed_list_arg_handle_to_slots(handle, caller_heap, callee_heap, slots, mode) } RuntimePositionalArgs::Prefixed { first, rest } => { let Some((first_slot, rest_slots)) = slots.split_first_mut() else { bail!("runtime positional argument frame is empty"); }; - *first_slot = copy_runtime_value(first, caller_heap, callee_heap)?; + *first_slot = copy_runtime_value_with(first, caller_heap, callee_heap, mode)?; for (slot, value) in rest_slots.iter_mut().zip(rest) { - *slot = copy_runtime_value(value, caller_heap, callee_heap)?; + *slot = copy_runtime_value_with(value, caller_heap, callee_heap, mode)?; } Ok(()) } @@ -865,8 +1037,8 @@ fn copy_runtime_positional_args_into_slots( let Some((first_slot, rest_slots)) = slots.split_first_mut() else { bail!("runtime positional argument frame is empty"); }; - *first_slot = copy_runtime_value(first, caller_heap, callee_heap)?; - copy_typed_list_arg_handle_to_slots(rest, caller_heap, callee_heap, rest_slots) + *first_slot = copy_runtime_value_with(first, caller_heap, callee_heap, mode)?; + copy_typed_list_arg_handle_to_slots(rest, caller_heap, callee_heap, rest_slots, mode) } } } @@ -876,6 +1048,7 @@ fn copy_typed_list_arg_handle_to_slots( caller_heap: &HeapStore, callee_heap: &mut HeapStore, slots: &mut [RuntimeVal], + mode: &ClosureCopy, ) -> Result<()> { match caller_heap .get(handle) @@ -883,7 +1056,7 @@ fn copy_typed_list_arg_handle_to_slots( { HeapValue::List(TypedList::Mixed(values)) => { for (slot, value) in slots.iter_mut().zip(values) { - *slot = copy_runtime_value(value, caller_heap, callee_heap)?; + *slot = copy_runtime_value_with(value, caller_heap, callee_heap, mode)?; } } HeapValue::List(TypedList::Int(values)) => { @@ -1064,6 +1237,7 @@ fn copy_named_stack_args_to_frame( caller_heap: &HeapStore, callee_heap: &mut HeapStore, frame: &mut [RuntimeVal], + mode: &ClosureCopy, ) -> Result<()> { if frame.len() < function.param_count as usize { bail!( @@ -1085,7 +1259,7 @@ fn copy_named_stack_args_to_frame( } for (slot, value) in frame.iter_mut().take(positional_count).zip(positional) { - *slot = copy_runtime_value(value, caller_heap, callee_heap)?; + *slot = copy_runtime_value_with(value, caller_heap, callee_heap, mode)?; } let mut seen = vec![false; function.param_count as usize - positional_count]; let named_end = named_start + named_count as usize * 2; @@ -1107,7 +1281,7 @@ fn copy_named_stack_args_to_frame( } offset }; - frame[positional_count + offset] = copy_runtime_value(&pair[1], caller_heap, callee_heap)?; + frame[positional_count + offset] = copy_runtime_value_with(&pair[1], caller_heap, callee_heap, mode)?; } if let Some(index) = seen.iter().position(|seen| !*seen) { @@ -1142,18 +1316,164 @@ pub fn runtime_value_to_callable_shared( None } +/// A function value crossing into another module, as a callable that carries +/// its own module. +/// +/// The problem this solves: a bare `Closure` is a `function_index` into *its +/// own* module's function table, so the moment it lands in another module it +/// indexes a different table — which is why the copy used to refuse it and +/// `apply(double, 5)` across two files did not work at all. +/// +/// The promoted callable holds the defining module, so the index means what it +/// meant. What it does *not* hold is that module's live state: the caller's +/// state belongs to a frame further down the Rust stack and cannot be taken +/// while it is running. So the callable gets a **private, empty** state — a +/// fresh heap that its arguments are copied into and its result copied out of, +/// which is exactly what every `RuntimeCallable` call already does. +/// +/// That is sound only if the function needs nothing else from its module, and +/// the one thing left is the globals. Hence the refusal below, with the same +/// analysis a cross-module trait dispatch uses +/// ([`crate::vm::analysis::function_global_use`]) — a function that reads a +/// module global would read `nil` here, and one that writes would write into a +/// table nobody will ever look at again. Both are wrong answers rather than +/// slow ones, so they are refused, by name. +fn promote_crossing_closure( + module: &Arc, + function_index: u32, + captures: &[RuntimeVal], + source_heap: &HeapStore, +) -> Result { + let global_use = crate::vm::analysis::function_global_use(module, function_index); + // A lambda has no name, and "`#3` cannot be passed out" would be useless — + // so an anonymous one is described by what it is instead. + let name = module + .functions + .get(function_index as usize) + .and_then(|function| function.debug_name.clone()) + .map_or_else( + || alloc::string::String::from("this lambda"), + |name| alloc::format!("`{name}`"), + ); + match global_use { + crate::vm::analysis::GlobalUse::Writes => bail!( + "{name} cannot be passed out of the module that defined it: it writes a module global. A function \ + that crosses a module boundary runs against a fresh state, so the write would land in a table \ + nobody reads again. Return the new value instead of storing it" + ), + // Not the same refusal as a write, and worth its own sentence: nothing + // is known to be wrong here, only unproven. `println` lands in this + // case — a builtin arrives through a register, and a call this walk + // cannot follow could reach anything, including a global. + crate::vm::analysis::GlobalUse::OpaqueCall => bail!( + "{name} cannot be passed out of the module that defined it: it makes a call this check cannot follow \ + (a builtin such as `println`, a function held in a variable, or a method), so it cannot be shown to \ + leave its module's globals alone — and a function that crosses a module boundary runs against a \ + fresh state where they are all nil. Do that work on this side of the boundary, or return the value \ + and let the caller print it" + ), + crate::vm::analysis::GlobalUse::Reads(reads) => { + if let Some(slot) = reads.first() { + let global = module + .globals + .get(*slot as usize) + .map(|slot| slot.name.to_string()) + .unwrap_or_else(|| alloc::format!("#{slot}")); + bail!( + "{name} cannot be passed out of the module that defined it: it reads the module global \ + `{global}`, and a function that crosses a module boundary runs against a fresh state where \ + that global is nil. Pass the value in as an argument instead" + ); + } + } + } + // The globals table is the module's shape, filled with nil: the executor + // checks the width on entry, and the analysis above has already proven that + // no slot is read. + let mut state = RuntimeModuleState { + globals: alloc::vec![RuntimeVal::Nil; module.globals.len()], + ..RuntimeModuleState::default() + }; + let mut copied = Vec::with_capacity(captures.len()); + for value in captures { + // The captures come along, into the callable's own heap: a promoted + // `|x| x + n` has to keep its `n`, and a capture that is itself a + // function of this module promotes the same way. + copied.push(copy_runtime_value_with( + value, + source_heap, + &mut state.heap, + &ClosureCopy::Promote(Arc::clone(module)), + )?); + } + Ok(HeapValue::Callable(CallableValue::Runtime(Arc::new( + RuntimeCallable::with_shared_captures( + Arc::clone(module), + function_index, + Arc::new(copied), + Arc::new(Mutex::new(state)), + ), + )))) +} + /// How a deep copy treats plain `Closure` values (`function_index` + /// captures, no module attached). -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone)] pub enum ClosureCopy { /// Reject: the destination may run a *different* module, where the bare - /// `function_index` would be meaningless (channel payloads, cross-VM - /// imports use the promote-to-`RuntimeCallable` path instead). + /// `function_index` would be meaningless, and the copy does not know which + /// module the value came from. A channel payload is the case left here. Reject, /// Copy structurally (`function_index` kept, captures deep-copied): only /// sound when the destination provably executes the *same* `Module` — /// the `spawn`/`go` snapshot is the use case. SameModule, + /// Promote to a [`RuntimeCallable`] carrying this module: the value is + /// crossing into another module, and a function that knows its own module + /// is callable from anywhere. + /// + /// This is what makes `apply(double, 5)` work across files. The promotion + /// is refused for a function whose reachable subtree touches its module's + /// globals — see [`promote_crossing_closure`] for why that is the line. + Promote(Arc), +} + +/// Bring a raised value into the caller's heap. +/// +/// The success path copies the *returned* value across the two heaps, because a +/// heap value is a handle and the callee's handles mean nothing on this side. A +/// raise carries a value the same way and was handed back untouched, so +/// `error([7, 8, 9])` crossing a module boundary arrived as a handle into a heap +/// the catch cannot read. The VM answered `heap object 88 out of bounds` — an +/// internal invariant, printed at the user — where the native build printed the +/// list. Int and short-string payloads were fine, which is why it survived: they +/// are stored inline and carry no handle at all. +/// +/// A payload that cannot cross (a bare closure) degrades to the message the +/// raise already rendered, rather than passing on a handle that will fault +/// later. +fn raised_value_into_caller_heap( + error: anyhow::Error, + callee_heap: &HeapStore, + caller_heap: &mut HeapStore, + module: &Arc, +) -> anyhow::Error { + let Some(raised) = error.root_cause().downcast_ref::() else { + return error; + }; + if !matches!(raised.value, RuntimeVal::Obj(_)) { + return error; + } + let rendered = Arc::clone(&raised.rendered); + match copy_runtime_value_with( + &raised.value, + callee_heap, + caller_heap, + &ClosureCopy::Promote(Arc::clone(module)), + ) { + Ok(value) => anyhow!(crate::vm::LkRaisedValue { value, rendered }), + Err(_) => anyhow!("{rendered}"), + } } pub fn copy_runtime_value( @@ -1161,7 +1481,7 @@ pub fn copy_runtime_value( source_heap: &HeapStore, dest_heap: &mut HeapStore, ) -> Result { - copy_runtime_value_with(value, source_heap, dest_heap, ClosureCopy::Reject) + copy_runtime_value_with(value, source_heap, dest_heap, &ClosureCopy::Reject) } /// Same-module deep copy: closures are copied structurally. See @@ -1171,14 +1491,14 @@ pub fn copy_runtime_value_same_module( source_heap: &HeapStore, dest_heap: &mut HeapStore, ) -> Result { - copy_runtime_value_with(value, source_heap, dest_heap, ClosureCopy::SameModule) + copy_runtime_value_with(value, source_heap, dest_heap, &ClosureCopy::SameModule) } fn copy_runtime_value_with( value: &RuntimeVal, source_heap: &HeapStore, dest_heap: &mut HeapStore, - mode: ClosureCopy, + mode: &ClosureCopy, ) -> Result { match value { RuntimeVal::Nil => Ok(RuntimeVal::Nil), @@ -1199,16 +1519,17 @@ fn copy_heap_value( value: &HeapValue, source_heap: &HeapStore, dest_heap: &mut HeapStore, - mode: ClosureCopy, + mode: &ClosureCopy, ) -> Result { Ok(match value { HeapValue::String(value) => HeapValue::String(Arc::clone(value)), HeapValue::Bytes(value) => HeapValue::Bytes(Arc::clone(value)), HeapValue::List(values) => HeapValue::List(copy_typed_list(values, source_heap, dest_heap, mode)?), HeapValue::Map(values) => HeapValue::Map(copy_typed_map(values, source_heap, dest_heap, mode)?), - HeapValue::Set(values) => HeapValue::Set(copy_runtime_set(values, source_heap, dest_heap, mode)?), + // No member carries a heap handle — see `imports::import_runtime_set`. + HeapValue::Set(values) => HeapValue::Set(values.clone()), HeapValue::Object(object) => { - let mut fields = fast_hash_map_new(); + let mut fields = value_map_new(); for (key, value) in &object.fields { fields.insert( Arc::clone(key), @@ -1231,7 +1552,28 @@ fn copy_heap_value( function_index, captures, }) => match mode { - ClosureCopy::Reject => bail!("cannot copy closure without module context"), + // The old text was "cannot copy closure without module context", + // which names a parameter of this function and nothing the program + // did. What the program did is hand a function to another module — + // as an argument to an imported function, or as a channel payload — + // and a bare closure is a `function_index` into *its own* module's + // table, meaningless once it lands anywhere else. + // + // The export direction already solves this: `import_runtime_export` + // promotes a crossing closure to a `RuntimeCallable`, which carries + // its module with it. The argument direction cannot yet, because the + // promotion also wants the caller module's shared state and the entry + // module has none — see the task tracking the module-bound callable + // that would close it. + ClosureCopy::Reject => bail!( + "a function value cannot be passed out of the module that defined it here (a channel payload). A \ + function carries an index into its own module's table, and this crossing does not record which \ + module that is. Passing a function *as an argument* to an imported function does work — send the \ + data through the channel and call the function on the other side" + ), + ClosureCopy::Promote(module) => { + return promote_crossing_closure(module, *function_index, captures, source_heap); + } ClosureCopy::SameModule => { let mut copied = Vec::with_capacity(captures.len()); for value in captures.iter() { @@ -1245,11 +1587,46 @@ fn copy_heap_value( }, HeapValue::Task(value) => HeapValue::Task(value.clone()), HeapValue::Channel(value) => HeapValue::Channel(value.clone()), - HeapValue::Stream(value) => HeapValue::Stream(value.clone()), - HeapValue::StreamCursor(value) => HeapValue::StreamCursor(value.clone()), + // A stream is an id into a process-global registry *plus* handles: its + // `roots` are heap references, and the pipeline the registry holds for + // that id keeps its `map`/`filter` callbacks as heap references too. + // Both belong to the heap that built them, and neither is rewritten by + // a copy — cloning the value handed the other side an id whose + // callbacks point into a heap it cannot read. What that produced + // depended on timing: plainly, `heap object 102 out of bounds`; with + // collection in between, the filter was silently skipped and + // `[1,2,3,4,5,6]` came back where `[16,25,36]` was asked for. + // + // Refused rather than repaired here, because repairing it means + // rewriting the *registry's* pipeline, which lives in the stdlib and + // which `core` must not reach into. See docs/semantics.md for the shape + // that would fix it. + HeapValue::Stream(value) => { + // `roots` is exactly the set of heap values the pipeline depends on + // — a `map`/`filter` callback, or elements of the list it was built + // from. A stream with none of them (`stream.range`, or a list of + // scalars) is just an id and crosses safely. + if value.roots.iter().any(|root| matches!(root, RuntimeVal::Obj(_))) { + bail!( + "a stream cannot be handed to another module or a task: this one's pipeline holds \ + a callback or a value that lives in the heap of the module that built it. Collect \ + it first (`stream.collect`) and pass the list, or build the stream on the other side" + ); + } + HeapValue::Stream(value.clone()) + } + HeapValue::StreamCursor(value) => { + if value.roots.iter().any(|root| matches!(root, RuntimeVal::Obj(_))) { + bail!( + "a stream cursor cannot be handed to another module or a task: this one reads from \ + a pipeline that lives in the heap of the module that built it. Drain it first and \ + pass the values" + ); + } + HeapValue::StreamCursor(value.clone()) + } HeapValue::Slice(value) => HeapValue::Slice(Arc::new(crate::val::SliceValue { source: copy_runtime_value_with(&value.source, source_heap, dest_heap, mode)?, - kind: value.kind, start: value.start, len: value.len, })), @@ -1270,24 +1647,11 @@ fn copy_heap_value( }) } -fn copy_runtime_set( - values: &RuntimeSet, - source_heap: &HeapStore, - dest_heap: &mut HeapStore, - mode: ClosureCopy, -) -> Result { - let mut out = fast_hash_set_new(); - for key in values.entries() { - out.insert(copy_runtime_map_key(key, source_heap, dest_heap, mode)?); - } - Ok(RuntimeSet::from_entries(out)) -} - fn copy_typed_list( values: &TypedList, source_heap: &HeapStore, dest_heap: &mut HeapStore, - mode: ClosureCopy, + mode: &ClosureCopy, ) -> Result { Ok(match values { TypedList::Mixed(values) => { @@ -1308,12 +1672,12 @@ fn copy_typed_map( values: &TypedMap, source_heap: &HeapStore, dest_heap: &mut HeapStore, - mode: ClosureCopy, + mode: &ClosureCopy, ) -> Result { Ok(match values { TypedMap::Mixed(values) => TypedMap::Mixed(copy_runtime_entries(values, source_heap, dest_heap, mode)?), TypedMap::StringMixed(values) => { - let mut out = fast_hash_map_new(); + let mut out = value_map_new(); for (key, value) in values { out.insert( Arc::clone(key), @@ -1334,8 +1698,8 @@ fn copy_slice(values: &[T]) -> Vec { out } -fn copy_string_map_values(values: &FastHashMap, T>) -> FastHashMap, T> { - let mut out = fast_hash_map_new(); +fn copy_string_map_values(values: &ValueMap, T>) -> ValueMap, T> { + let mut out = value_map_new(); for (key, value) in values { out.insert(Arc::clone(key), *value); } @@ -1343,38 +1707,17 @@ fn copy_string_map_values(values: &FastHashMap, T>) -> FastHas } fn copy_runtime_entries( - values: &FastHashMap, + values: &ValueMap, source_heap: &HeapStore, dest_heap: &mut HeapStore, - mode: ClosureCopy, -) -> Result> { - let mut out = fast_hash_map_new(); + mode: &ClosureCopy, +) -> Result> { + let mut out = value_map_new(); for (key, value) in values { out.insert( - copy_runtime_map_key(key, source_heap, dest_heap, mode)?, + key.clone(), copy_runtime_value_with(value, source_heap, dest_heap, mode)?, ); } Ok(out) } - -fn copy_runtime_map_key( - key: &RuntimeMapKey, - source_heap: &HeapStore, - dest_heap: &mut HeapStore, - mode: ClosureCopy, -) -> Result { - Ok(match key { - RuntimeMapKey::Nil => RuntimeMapKey::Nil, - RuntimeMapKey::Bool(value) => RuntimeMapKey::Bool(*value), - RuntimeMapKey::Int(value) => RuntimeMapKey::Int(*value), - RuntimeMapKey::ShortStr(value) => RuntimeMapKey::ShortStr(*value), - RuntimeMapKey::String(value) => RuntimeMapKey::String(Arc::clone(value)), - RuntimeMapKey::Obj(handle) => { - match copy_runtime_value_with(&RuntimeVal::Obj(*handle), source_heap, dest_heap, mode)? { - RuntimeVal::Obj(handle) => RuntimeMapKey::Obj(handle), - _ => unreachable!("object map key copy must stay an object"), - } - } - }) -} diff --git a/core/src/vm/exec/stack.rs b/core/src/vm/exec/stack.rs index 3a69426d..255056e3 100644 --- a/core/src/vm/exec/stack.rs +++ b/core/src/vm/exec/stack.rs @@ -1,18 +1,12 @@ -#[cfg(not(feature = "std"))] -use crate::compat::prelude::*; use core::ops::Range; use anyhow::{Result, bail}; -use alloc::sync::Arc; - use crate::{ - val::{HeapStore, HeapValue, RuntimeVal, TypedList}, + val::{RuntimeVal, TypedList}, vm::CallWindow, }; -use crate::vm::analysis::record_register_write_known_enabled; - use super::{Executor, ReturnValues}; impl Executor { @@ -44,9 +38,6 @@ impl Executor { #[inline] pub(super) fn write_stack_index(&mut self, index: usize, value: RuntimeVal) { self.state.stack[index] = value; - if self.collect_metrics { - record_register_write_known_enabled(); - } } #[inline] @@ -130,7 +121,7 @@ impl Executor { pub(super) fn read_register_list(&self, base: u8, count: u8) -> Result { let range = self.register_range(base, count, "register range")?; - Ok(typed_list_from_runtime_slots( + Ok(TypedList::from_runtime_values( &self.state.stack[range], &self.state.heap, )) @@ -138,10 +129,12 @@ impl Executor { pub(super) fn take_register_list(&mut self, base: u8, count: u8) -> Result { let range = self.register_range(base, count, "register range")?; - Ok(take_typed_list_from_runtime_slots( - &mut self.state.stack[range], - &self.state.heap, - )) + // Building and clearing are separate steps: the narrowing rule lives + // on `TypedList`, and emptying the registers afterwards is this + // caller's business, not the list constructor's. + let list = TypedList::from_runtime_values(&self.state.stack[range.clone()], &self.state.heap); + self.state.stack[range].fill(RuntimeVal::Nil); + Ok(list) } pub(super) fn take_return_values(&mut self, base: u8, count: u8) -> Result { @@ -159,6 +152,17 @@ impl Executor { Ok(range_start..range_start + count) } + /// Deliver a returned value into the caller's result window. + /// + /// One pass, not two: a callee that returns fewer values than the window + /// asks for leaves the rest nil, which is what a separate `fill` used to + /// provide — at the price of writing every slot twice on the overwhelmingly + /// common `ret_count == 1` path, which is every ordinary call. + /// + /// Not `#[inline]`: measured, inlining these two into `finish_return` cost + /// the empty-call benchmark 29ms → 50-90ms. Same lesson as the `#[cold]` on + /// `call_direct_function` — growing the code the dispatch loop has to hold + /// beats any instruction saved. pub(super) fn write_returns( &mut self, window: CallWindow, @@ -170,17 +174,25 @@ impl Executor { bail!("return range {}..{} out of bounds", start, start + count); } let range_start = self.frame_base + start; - let range_end = range_start + count; - self.state.stack[range_start..range_end].fill(RuntimeVal::Nil); - for (slot, value) in self.state.stack[range_start..range_end].iter_mut().zip(values) { - *slot = value; + let mut values = values.into_iter(); + for slot in &mut self.state.stack[range_start..range_start + count] { + *slot = values.next().unwrap_or(RuntimeVal::Nil); } Ok(()) } + /// Drop the argument registers of a completed call, so the values they held + /// stop being GC roots. + /// + /// A zero-argument call has nothing to clear, and that is the shape the + /// call benchmark is made of; the bounds check and the empty `fill` were + /// its whole cost. pub(super) fn clear_call_window_temps(&mut self, window: CallWindow, named_count: u16) -> Result<()> { - let start = window.arg_base().as_usize(); let count = window.arg_count as usize + named_count as usize * 2; + if count == 0 { + return Ok(()); + } + let start = window.arg_base().as_usize(); if start + count > self.register_count as usize { bail!("call temp range {}..{} out of bounds", start, start + count); } @@ -190,153 +202,3 @@ impl Executor { Ok(()) } } - -fn typed_list_from_runtime_slots(values: &[RuntimeVal], heap: &HeapStore) -> TypedList { - match runtime_slot_list_shape(values, heap) { - RuntimeSlotListShape::Mixed => { - let mut out = Vec::with_capacity(values.len()); - out.extend_from_slice(values); - TypedList::Mixed(out) - } - RuntimeSlotListShape::Int => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - let RuntimeVal::Int(value) = value else { - unreachable!("shape scan only returns Int for int slots"); - }; - out.push(*value); - } - TypedList::Int(out) - } - RuntimeSlotListShape::Float => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - let RuntimeVal::Float(value) = value else { - unreachable!("shape scan only returns Float for float slots"); - }; - out.push(*value); - } - TypedList::Float(out) - } - RuntimeSlotListShape::Bool => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - let RuntimeVal::Bool(value) = value else { - unreachable!("shape scan only returns Bool for bool slots"); - }; - out.push(*value); - } - TypedList::Bool(out) - } - RuntimeSlotListShape::String => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - match value { - RuntimeVal::ShortStr(value) => out.push(Arc::::from(value.as_str())), - RuntimeVal::Obj(handle) => match heap.get(*handle) { - Some(HeapValue::String(value)) => out.push(Arc::clone(value)), - _ => unreachable!("shape scan only returns String for string slots"), - }, - _ => unreachable!("shape scan only returns String for string slots"), - } - } - TypedList::String(out) - } - } -} - -fn take_typed_list_from_runtime_slots(values: &mut [RuntimeVal], heap: &HeapStore) -> TypedList { - match runtime_slot_list_shape(values, heap) { - RuntimeSlotListShape::Mixed => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - out.push(core::mem::take(value)); - } - TypedList::Mixed(out) - } - RuntimeSlotListShape::Int => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - let value = match core::mem::take(value) { - RuntimeVal::Int(value) => value, - _ => unreachable!("shape scan only returns Int for int slots"), - }; - out.push(value); - } - TypedList::Int(out) - } - RuntimeSlotListShape::Float => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - let value = match core::mem::take(value) { - RuntimeVal::Float(value) => value, - _ => unreachable!("shape scan only returns Float for float slots"), - }; - out.push(value); - } - TypedList::Float(out) - } - RuntimeSlotListShape::Bool => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - let value = match core::mem::take(value) { - RuntimeVal::Bool(value) => value, - _ => unreachable!("shape scan only returns Bool for bool slots"), - }; - out.push(value); - } - TypedList::Bool(out) - } - RuntimeSlotListShape::String => { - let mut out = Vec::with_capacity(values.len()); - for value in values { - let value = match core::mem::take(value) { - RuntimeVal::ShortStr(value) => Arc::::from(value.as_str()), - RuntimeVal::Obj(handle) => match heap.get(handle) { - Some(HeapValue::String(value)) => Arc::clone(value), - _ => unreachable!("shape scan only returns String for string slots"), - }, - _ => unreachable!("shape scan only returns String for string slots"), - }; - out.push(value); - } - TypedList::String(out) - } - } -} - -enum RuntimeSlotListShape { - Mixed, - Int, - Float, - Bool, - String, -} - -fn runtime_slot_list_shape(values: &[RuntimeVal], heap: &HeapStore) -> RuntimeSlotListShape { - if values.is_empty() { - return RuntimeSlotListShape::Mixed; - } - let mut shape: Option = None; - for value in values { - let next = match value { - RuntimeVal::Int(_) => RuntimeSlotListShape::Int, - RuntimeVal::Float(_) => RuntimeSlotListShape::Float, - RuntimeVal::Bool(_) => RuntimeSlotListShape::Bool, - RuntimeVal::ShortStr(_) => RuntimeSlotListShape::String, - RuntimeVal::Obj(handle) if matches!(heap.get(*handle), Some(HeapValue::String(_))) => { - RuntimeSlotListShape::String - } - _ => return RuntimeSlotListShape::Mixed, - }; - match (&shape, next) { - (None, next) => shape = Some(next), - (Some(RuntimeSlotListShape::Int), RuntimeSlotListShape::Int) - | (Some(RuntimeSlotListShape::Float), RuntimeSlotListShape::Float) - | (Some(RuntimeSlotListShape::Bool), RuntimeSlotListShape::Bool) - | (Some(RuntimeSlotListShape::String), RuntimeSlotListShape::String) => {} - _ => return RuntimeSlotListShape::Mixed, - } - } - shape.unwrap_or(RuntimeSlotListShape::Mixed) -} diff --git a/core/src/vm/exec/support.rs b/core/src/vm/exec/support.rs index 4fb31c4a..852c4b3a 100644 --- a/core/src/vm/exec/support.rs +++ b/core/src/vm/exec/support.rs @@ -3,10 +3,10 @@ use crate::compat::prelude::*; use alloc::sync::Arc; use core::ops::Range; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Result, bail}; use crate::{ - val::{HeapStore, HeapValue, RuntimeVal, TypedList}, + val::{HeapStore, RuntimeVal, TypedList}, vm::{ Function, Instr, Module, NativeArgs, NativeEntry, NativeFunction, NativeRuntime, Opcode, RuntimeModuleState, VmContext, @@ -74,56 +74,6 @@ pub(super) fn set_list_value(list: &mut TypedList, index: usize, value: RuntimeV Ok(()) } -pub(super) fn push_list_value(list: &mut TypedList, value: RuntimeVal, string_value: Option>) -> Result<()> { - match list { - TypedList::Mixed(values) if values.is_empty() => match (value, string_value) { - (RuntimeVal::Int(value), _) => *list = TypedList::Int(vec![value]), - (RuntimeVal::Float(value), _) => *list = TypedList::Float(vec![value]), - (RuntimeVal::Bool(value), _) => *list = TypedList::Bool(vec![value]), - (RuntimeVal::ShortStr(_) | RuntimeVal::Obj(_), Some(string_value)) => { - *list = TypedList::String(vec![string_value]); - } - (value, _) => values.push(value), - }, - TypedList::Mixed(values) => values.push(value), - TypedList::Int(values) => match value { - RuntimeVal::Int(value) => values.push(value), - value => { - let mut mixed = copy_numeric_list(values, RuntimeVal::Int); - mixed.push(value); - *list = TypedList::Mixed(mixed); - } - }, - TypedList::Float(values) => match value { - RuntimeVal::Float(value) => values.push(value), - value => { - let mut mixed = copy_numeric_list(values, RuntimeVal::Float); - mixed.push(value); - *list = TypedList::Mixed(mixed); - } - }, - TypedList::Bool(values) => match value { - RuntimeVal::Bool(value) => values.push(value), - value => { - let mut mixed = copy_numeric_list(values, RuntimeVal::Bool); - mixed.push(value); - *list = TypedList::Mixed(mixed); - } - }, - TypedList::String(values) => match string_value { - Some(value) => values.push(value), - None => bail!("internal error: typed string list push must be materialized before mutable borrow"), - }, - } - Ok(()) -} - -fn copy_numeric_list(values: &[T], wrap: impl Fn(T) -> RuntimeVal) -> Vec { - let mut mixed = Vec::with_capacity(values.len() + 1); - mixed.extend(values.iter().copied().map(wrap)); - mixed -} - fn copy_numeric_list_with_replacement( values: &[T], index: usize, @@ -341,50 +291,17 @@ fn map_native_error(native: &NativeEntry, result: Result) -> Result< result } -pub(super) fn heap_kind(value: &HeapValue) -> &'static str { - match value { - HeapValue::String(_) => "String", - HeapValue::Bytes(_) => "Bytes", - HeapValue::List(_) => "List", - HeapValue::Map(_) => "Map", - HeapValue::Set(_) => "Set", - HeapValue::Callable(_) => "Callable", - HeapValue::Task(_) => "Task", - HeapValue::Channel(_) => "Channel", - HeapValue::Stream(_) => "Stream", - HeapValue::StreamCursor(_) => "StreamCursor", - HeapValue::Slice(_) => "Slice", - HeapValue::Resource(resource) => resource.kind, - HeapValue::Object(_) => "Object", - HeapValue::UpvalCell(_) => "UpvalCell", - HeapValue::ErrorVal(_) => "Error", - } -} - impl Executor { #[inline] pub(super) fn read_int(&self, register: u8) -> Result { let index = self.stack_index(register)?; match &self.state.stack[index] { RuntimeVal::Int(value) => Ok(*value), - other => bail!("register {} expected Int, got {:?}", register, other.kind()), - } - } - - #[inline] - pub(super) fn read_number(&self, register: u8) -> Result { - let index = self.stack_index(register)?; - self.number_value(&self.state.stack[index]) - .map_err(|err| anyhow!("register {} expected Int or Float: {err}", register)) - } - - #[inline(always)] - pub(super) fn read_number_unchecked(&self, register: u8) -> f64 { - let index = self.stack_index_unchecked(register); - match &self.state.stack[index] { - RuntimeVal::Int(value) => *value as f64, - RuntimeVal::Float(value) => *value, - _ => panic!("register {} expected Int or Float", register), + other => bail!( + "register {} expected Int, got {}", + register, + self.value_type_name(other) + ), } } @@ -392,7 +309,7 @@ impl Executor { match value { RuntimeVal::Int(value) => Ok(*value as f64), RuntimeVal::Float(value) => Ok(*value), - other => bail!("got {:?}", other.kind()), + other => bail!("got {}", self.value_type_name(other)), } } @@ -669,6 +586,18 @@ impl Executor { bail!("jump before start of function") } + /// The call's shape: the function's own fact, else read off the + /// instruction. + /// + /// There used to be a third source between them — a cache in the module + /// *state*, keyed by pc alone. Keyed by pc alone across every function in + /// the module: two functions with a call at the same pc shared an entry, so + /// the second would have taken the first's call base and argument counts. + /// It never fired (the compiler records a fact for every call site it + /// emits, and artifact v4 serializes them, so the first branch always + /// wins), which is the only reason that was not a wrong answer waiting for + /// a program to find it. Measured across `examples/` and the bench: zero + /// reads reached it, while every call paid the write that filled it. #[inline] pub(super) fn call_fact_from_static_cache_or_instr( &mut self, @@ -679,10 +608,6 @@ impl Executor { if let Some(fact) = function.performance.call_site(self.pc).copied() && (named || fact.named_count == 0) { - self.state.inline_caches.set_call(self.pc, fact); - return fact; - } - if let Some(fact) = self.state.inline_caches.call(self.pc) { return fact; } let (positional_count, named_count) = if named { @@ -691,27 +616,26 @@ impl Executor { } else { (instr.c() as u16, 0) }; - let fact = PerfCallFact { + PerfCallFact { // A holds the call-window base. B is only 7 bits and would truncate call_base >= 128. call_base: instr.a() as u16, positional_count, named_count, target_kind: self.observe_call_target_kind(instr.a() as u16), - }; - self.state.inline_caches.set_call(self.pc, fact); - fact + } } + /// The global slot a `GetGlobal`/`SetGlobal` names. + /// + /// Same story as the call shape above: a pc-keyed state cache sat between + /// the fact and the instruction, shared by every function in the module. #[inline] - pub(super) fn global_slot_from_fact_cache_or_instr(&mut self, function: &Function, instr: Instr) -> u16 { - let slot = function + pub(super) fn global_slot_from_fact_or_instr(&mut self, function: &Function, instr: Instr) -> u16 { + function .performance .global_op(self.pc) .map(|fact| fact.slot) - .or_else(|| self.state.inline_caches.global(self.pc)) - .unwrap_or_else(|| instr.bx()); - self.state.inline_caches.set_global(self.pc, slot); - slot + .unwrap_or_else(|| instr.bx()) } #[inline(always)] diff --git a/core/src/vm/exec/value_ops.rs b/core/src/vm/exec/value_ops.rs index 938bebe4..e1f35f08 100644 --- a/core/src/vm/exec/value_ops.rs +++ b/core/src/vm/exec/value_ops.rs @@ -7,7 +7,7 @@ use anyhow::{Result, anyhow, bail}; use crate::val::{HeapValue, RuntimeVal, ShortStr, TypedList}; use crate::vm::{Module, VmContext}; -use super::{Executor, heap_kind}; +use super::Executor; impl Executor { pub(super) fn to_runtime_string(&self, register: u8) -> Result { @@ -28,13 +28,29 @@ impl Executor { if let Some(text) = self.try_runtime_display_show(&value, module, ctx)? { return Ok(text); } + // A container renders the way `print` renders it. It used to be an + // error — "object cannot be converted to string" — so + // + // println(xs) → [1,2] + // println("{}", xs) → [1,2] + // println("${xs}") → failed, at run time, after the type + // checker had approved it + // + // Three ways to print one value, two of which worked. The reason on + // record was a map's iteration order not being portable between the two + // backends — but the other two paths already print maps, so the rule + // was not buying that, and the AOT declines to *lower* an interpolated + // container anyway, which is where portability is actually decided. + if matches!(value, RuntimeVal::Obj(_)) { + return crate::vm::exec::display::runtime_display_value(&value, &self.state.heap); + } self.runtime_value_to_plain_string(&value) } fn runtime_value_to_plain_string(&self, value: &RuntimeVal) -> Result { match self.runtime_value_to_plain_string_maybe(value)? { Some(value) => Ok(value), - None => bail!("object cannot be converted to string: {:?}", value.kind()), + None => bail!("object cannot be converted to string: {}", self.value_type_name(value)), } } @@ -120,11 +136,17 @@ impl Executor { pub(super) fn string_split(&mut self, dst: u8, target: u8, delimiter: u8) -> Result<()> { let target = *self.read(target)?; let Some(target) = self.runtime_value_to_string(&target)? else { - bail!("StringSplit target must be string, got {:?}", target.kind()); + bail!( + "StringSplit target must be string, got {}", + self.value_type_name(&target) + ); }; let delimiter = *self.read(delimiter)?; let Some(delimiter) = self.runtime_value_to_string(&delimiter)? else { - bail!("StringSplit delimiter must be string, got {:?}", delimiter.kind()); + bail!( + "StringSplit delimiter must be string, got {}", + self.value_type_name(&delimiter) + ); }; let values = target .split(delimiter.as_ref()) @@ -137,35 +159,57 @@ impl Executor { pub(super) fn list_join(&mut self, dst: u8, target: u8, separator: u8) -> Result<()> { let target = *self.read(target)?; let RuntimeVal::Obj(handle) = target else { - bail!("ListJoin target must be list, got {:?}", target.kind()); + bail!("ListJoin target must be list, got {}", self.value_type_name(&target)); }; let separator = *self.read(separator)?; let Some(separator) = self.runtime_value_to_string(&separator)? else { - bail!("ListJoin separator must be string, got {:?}", separator.kind()); + bail!( + "ListJoin separator must be string, got {}", + self.value_type_name(&separator) + ); }; - let joined = match self - .state - .heap + // Every element is written the way the language writes it anywhere else. + // + // This used to raise "ListJoin list must contain only strings" for any + // carrier but `String` — so `[1, 2].join(",")` type-checked and then + // failed at run time, while `"${[1, 2]}"` had been printing `[1,2]` all + // along. The restriction was arbitrary in a language that renders every + // value, and it did not stay put: the AOT lowering refuses `join` on + // numeric carriers *because the VM refuses*, so one arbitrary rule became + // a second one in another back end. + // + // `display_runtime_value` is that one renderer, so there is no second + // spelling of "how does an Int look" to drift. The `String` carrier keeps + // its direct path: it is already what the renderer would produce (a bare + // string renders unquoted; only *inside* a container is it quoted), and + // it avoids an allocation per element. + let heap = &self.state.heap; + let joined = match heap .get(handle) .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { - HeapValue::List(TypedList::String(values)) => values + HeapValue::List(values) => join_typed_list(values, heap, separator.as_ref()), + // The other two sequence carriers. `join` reaching only `List` is + // the same shape the comment above describes — one carrier's + // arbitrary limit becoming the operator's. + HeapValue::Bytes(bytes) => bytes .iter() - .map(|value| value.as_ref()) + .map(|byte| crate::vm::display_runtime_value(&RuntimeVal::Int(i64::from(*byte)), heap)) .collect::>() .join(separator.as_ref()), - HeapValue::List(TypedList::Mixed(values)) => { - let mut parts = Vec::with_capacity(values.len()); - for value in values { - let Some(value) = self.runtime_value_to_string(value)? else { - bail!("ListJoin list must contain only strings"); - }; - parts.push(value.to_string()); - } - parts.join(separator.as_ref()) - } - HeapValue::List(_) => bail!("ListJoin list must contain only strings"), - other => bail!("ListJoin target must be list, got {:?}", heap_kind(other)), + // A window joins the range it windows, through the same function + // the list arm uses. + HeapValue::Slice(slice) => match slice.source { + RuntimeVal::Obj(source) => match heap.get(source) { + Some(HeapValue::List(values)) => { + let window = values.window(slice.start, slice.live_len(heap)); + join_typed_list(&window, heap, separator.as_ref()) + } + _ => String::new(), + }, + _ => String::new(), + }, + other => bail!("ListJoin target must be list, got {:?}", HeapValue::type_name(other)), }; self.write_string(dst, joined) } @@ -201,6 +245,13 @@ impl Executor { )) } + /// The executor's spelling of [`RuntimeVal::type_name_in`] — it has the + /// heap, so callers do not thread it through. + #[cold] + pub(super) fn value_type_name(&self, value: &RuntimeVal) -> &str { + value.type_name_in(&self.state.heap) + } + #[cold] pub(super) fn runtime_value_is_map(&self, value: &RuntimeVal) -> Result { let RuntimeVal::Obj(handle) = value else { @@ -223,6 +274,24 @@ impl Executor { RuntimeVal::Int(value) => Ok(value.to_string()), RuntimeVal::Float(value) => Ok(value.to_string()), RuntimeVal::ShortStr(value) => Ok(value.as_str().to_string()), + // A container renders the way `print` renders it — the same + // correction interpolation already took, and for the same reason. + // The comment above `runtime_value_to_display_string` counts three + // ways to print one value, two of which worked; `+` is a fourth, + // and it was the one still failing: + // + // println(xs) → [1,2] + // println("{}", xs) → [1,2] + // println("${xs}") → [1,2] + // println("" + xs) → failed, at run time + // + // A list operand never reaches here — a list wins over a string and + // the answer is a list — so what this changes is `Set`, `Bytes`, a + // window, a struct and a callable, none of which had any meaning + // under `+` at all. + // + // The other caller is the "X is not a function" message, where + // raising replaced the diagnostic with a worse one. RuntimeVal::Obj(handle) => match self .state .heap @@ -230,7 +299,7 @@ impl Executor { .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? { HeapValue::String(value) => Ok(value.to_string()), - other => bail!("object cannot be converted to string: {:?}", heap_kind(other)), + _ => crate::vm::exec::display::runtime_display_value(value, &self.state.heap), }, } } @@ -244,3 +313,42 @@ impl Executor { } } } + +/// `xs.join(sep)` over a list's elements, whatever carrier holds them. +/// +/// Its own function because three receivers need it — a list, a window over a +/// list, and (through its own byte rendering) `Bytes`. Every element is written +/// the way the language writes it anywhere else, through the one renderer, so +/// there is no second spelling of "how does an Int look". The `String` carrier +/// keeps its direct path: it is already what the renderer would produce (a bare +/// string renders unquoted; only *inside* a container is it quoted), and it +/// avoids an allocation per element. +fn join_typed_list(values: &TypedList, heap: &crate::val::HeapStore, separator: &str) -> String { + match values { + TypedList::String(values) => values + .iter() + .map(|value| value.as_ref()) + .collect::>() + .join(separator), + TypedList::Int(values) => values + .iter() + .map(|value| crate::vm::display_runtime_value(&RuntimeVal::Int(*value), heap)) + .collect::>() + .join(separator), + TypedList::Float(values) => values + .iter() + .map(|value| crate::vm::display_runtime_value(&RuntimeVal::Float(*value), heap)) + .collect::>() + .join(separator), + TypedList::Bool(values) => values + .iter() + .map(|value| crate::vm::display_runtime_value(&RuntimeVal::Bool(*value), heap)) + .collect::>() + .join(separator), + TypedList::Mixed(values) => values + .iter() + .map(|value| crate::vm::display_runtime_value(value, heap)) + .collect::>() + .join(separator), + } +} diff --git a/core/src/vm/gc.rs b/core/src/vm/gc.rs index 28ca3893..6bb76592 100644 --- a/core/src/vm/gc.rs +++ b/core/src/vm/gc.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use anyhow::{Result, anyhow}; +use anyhow::Result; use crate::val::{HeapRef, RuntimeVal}; @@ -84,17 +84,41 @@ impl RuntimeModuleState { // Values host (native) functions hold across re-entrant VM calls — // e.g. an HOF's accumulated callback results (see `host_roots`). roots.extend_values(&self.host_roots); + // The module's own export map: it lives in this heap and nothing in + // this heap points at it (see `export_root`). + roots.extend_values(self.export_root.iter()); roots } } impl RuntimeCallable { + /// Collect the heap this callable's *own* module owns. + /// + /// Reached from [`HeapStore::collect`](crate::val::HeapStore::collect) when + /// marking a heap that holds an imported function: the function's captures + /// live in the exporting module's heap, not in the one being marked, so + /// that heap has to be collected against its own roots. + /// + /// `try_lock`, not `lock`. This walk can arrive back at a state that is + /// already being collected further up the stack, and neither backing mutex + /// is re-entrant — `lock` would hang the process with no error and no + /// output. Today the import graph is a DAG (`ModuleResolver` rejects + /// circular imports by path), so that cannot happen; but nothing here + /// depends on that check, or would notice if it were relaxed. Skipping is + /// the conservative answer either way: the heap keeps its objects until the + /// collection already in progress, or the next one, reaches them. pub fn collect_garbage(&self) -> Result<()> { - let mut state = self - .state - .lock() - .map_err(|_| anyhow!("RuntimeCallable state lock poisoned"))?; - state.collect_garbage(self.captures.iter()); + self.collect_garbage_with_visited(&mut crate::val::CollectedModules::default()) + } + + /// The same, carrying the set of callables this cycle has already walked so + /// the module graph is not re-walked through every path into it — see + /// [`HeapStore::collect_with_visited`](crate::val::HeapStore::collect_with_visited). + pub fn collect_garbage_with_visited(&self, visited: &mut crate::val::CollectedModules) -> Result<()> { + let Some(mut state) = self.state.try_lock() else { + return Ok(()); + }; + state.collect_garbage_with_visited(self.captures.iter(), visited); Ok(()) } } @@ -153,4 +177,32 @@ mod tests { assert!(state.heap.get(global).is_some()); assert!(state.heap.get(dead).is_none()); } + + /// The export survives a collection driven from anywhere else, too. + /// + /// The test above passes the export value in as an extra root, which is + /// what `collect_runtime_export` does — and that path was always right. + /// Every *other* path was not: `RuntimeCallable::collect_garbage` collects + /// an imported module's heap with only the callable's captures as extras, + /// and the export map is not reachable from the globals (they hold the + /// values, not the map that collects them). Importing one module + /// transitively and then directly, under `LK_GC_STRESS=1`, then read a + /// handle past the end of a heap that had shrunk: `heap object 82 out of + /// bounds`, from `runtime_export_field`. + #[test] + fn the_export_root_survives_a_collection_with_no_extra_roots() { + let mut heap = HeapStore::new(); + let exported = heap.alloc(HeapValue::String(Arc::::from("exported"))); + let dead = heap.alloc(HeapValue::String(Arc::::from("dead"))); + let mut state = RuntimeModuleState::new(heap, Vec::new()); + state.set_export_root(RuntimeVal::Obj(exported)); + + state.collect_garbage([]); + + assert!( + state.heap.get(exported).is_some(), + "the module's own export map must be a root of its own heap" + ); + assert!(state.heap.get(dead).is_none(), "everything else is still collected"); + } } diff --git a/core/src/vm/hardware.rs b/core/src/vm/hardware.rs index fdcb22dc..910ff7f7 100644 --- a/core/src/vm/hardware.rs +++ b/core/src/vm/hardware.rs @@ -161,6 +161,239 @@ port_access! { port_in_u32, port_out_u32, u32, "eax"; } +/// The system-control instructions: descriptor tables, CR2/CR3, the TLB. +/// +/// Gated exactly like port I/O, and for the same reason stated at the top of +/// this file: the bare-metal x86 kernel *hosts this interpreter*, and a program +/// it loads off a disk reaches the same builtins the compiled kernel does. +/// Answering "unsupported" on the one architecture the machine actually is +/// would mean LK could describe a kernel but never run one on the only backend +/// that reaches the hardware. +/// +/// These duplicate the bodies in `lkrt/src/system.rs`, as `cpu_irq_save` here +/// already duplicates `lkrt/src/cpu.rs`. The two crates cannot share them: +/// `lkrt` must not depend on `lk-core`, and `lk-core` depending on `lkrt` would +/// close the loop the other way. What keeps the copies honest is that they are +/// each three lines of assembly with the instruction named in the function name. +#[cfg(all(not(feature = "std"), target_arch = "x86_64"))] +mod system { + /// The operand `lidt`/`lgdt` take: a limit and a base, packed. Built here + /// rather than by the caller — the layout is `#[repr(packed)]`, which no LK + /// type describes, and the CPU reads it only during the instruction. + #[repr(C, packed)] + pub(super) struct PseudoDescriptor { + pub(super) limit: u16, + pub(super) base: u64, + } +} + +/// One operand as a machine word. Gated with the instructions that read it — +/// on a hosted build every caller is compiled out, and CI builds with +/// `-D warnings`. +#[cfg(all(not(feature = "std"), target_arch = "x86_64"))] +fn word_operand(args: &NativeArgs<'_>, index: usize, name: &str) -> Result { + match args.get(index) { + Some(RuntimeVal::Int(value)) => Ok(*value), + _ => Err(anyhow!("{name} expects an integer as argument {}", index + 1)), + } +} + +fn system_refusal(name: &str) -> anyhow::Error { + anyhow!("{name} requires bare-metal execution on x86-64: no other target has this instruction") +} + +pub(super) fn cpu_load_idt(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let descriptor = system::PseudoDescriptor { + base: word_operand(&_args, 0, "cpu_load_idt")? as u64, + limit: word_operand(&_args, 1, "cpu_load_idt")? as u16, + }; + unsafe { + core::arch::asm!("lidt [{}]", in(reg) &descriptor, options(preserves_flags)); + } + return Ok(RuntimeVal::Nil); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_load_idt")) +} + +pub(super) fn cpu_load_gdt(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let descriptor = system::PseudoDescriptor { + base: word_operand(&_args, 0, "cpu_load_gdt")? as u64, + limit: word_operand(&_args, 1, "cpu_load_gdt")? as u16, + }; + unsafe { + core::arch::asm!("lgdt [{}]", in(reg) &descriptor, options(preserves_flags)); + } + return Ok(RuntimeVal::Nil); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_load_gdt")) +} + +/// Reloads CS and the data segments — the half of a GDT load that `lgdt` does +/// not do, because the segment registers hold cached descriptors. +pub(super) fn cpu_reload_segments(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let code = word_operand(&_args, 0, "cpu_reload_segments")? as u64; + let data = word_operand(&_args, 1, "cpu_reload_segments")? as u64; + unsafe { + // A far return, because CS cannot be written by `mov`: push the + // selector and the address to continue at, and `retfq` loads both. + // FS and GS are left alone — writing either zeroes its base. + core::arch::asm!( + "push {code}", + "lea {tmp}, [rip + 2f]", + "push {tmp}", + "retfq", + "2:", + "mov ds, {data:x}", + "mov es, {data:x}", + "mov ss, {data:x}", + code = in(reg) code, + data = in(reg) data, + tmp = lateout(reg) _, + ); + } + return Ok(RuntimeVal::Nil); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_reload_segments")) +} + +pub(super) fn cpu_load_task_register(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let selector = word_operand(&_args, 0, "cpu_load_task_register")? as u16; + unsafe { + core::arch::asm!("ltr {0:x}", in(reg) selector, options(nostack, preserves_flags)); + } + return Ok(RuntimeVal::Nil); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_load_task_register")) +} + +/// The address whose access caused the last page fault. Only the CPU writes it. +pub(super) fn cpu_read_cr2(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let value: u64; + unsafe { + core::arch::asm!("mov {}, cr2", out(reg) value, options(nostack, preserves_flags)); + } + return Ok(RuntimeVal::Int(value as i64)); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_read_cr2")) +} + +pub(super) fn cpu_read_cr3(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let value: u64; + unsafe { + core::arch::asm!("mov {}, cr3", out(reg) value, options(nostack, preserves_flags)); + } + return Ok(RuntimeVal::Int(value as i64)); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_read_cr3")) +} + +/// Raises a software interrupt, whatever its number is. +/// +/// The one x86 instruction whose operand a program cannot supply: `int` takes +/// its vector as an immediate, so a kernel that wants to raise a vector it +/// computed has nowhere to put it. The runtime answers that with a table of 256 +/// stubs — see `lkrt/src/isr.rs`, which does the same thing for the entry side — +/// and this is the interpreter reaching the same table. +/// +/// Without it a kernel written in this language cannot raise its own syscall or +/// reschedule vector, which is not a small gap: it is the difference between +/// defining an interrupt and merely handling one. +/// The symbol above, for a **test** binary. +/// +/// `lk-core`'s `no_std` face declares `lkrt_cpu_raise_interrupt` and does not +/// depend on the crate that defines it — sound in the bare-metal image, where +/// both are linked together, and unlinkable in a host test binary, where only +/// one of them is. `cargo test -p lk-core --no-default-features` therefore +/// could not link on x86_64 at all: +/// +/// ```text +/// rust-lld: error: undefined symbol: lkrt_cpu_raise_interrupt +/// ``` +/// +/// That is a CI step (`check.yml`, "lk-core builds and *tests* as no_std") and +/// a documented gate. `cargo build` with the same flags is green, because a +/// library has no link step — which is why running the build in its place hid +/// this. +/// +/// A stub rather than a `cfg(test)` arm inside the function: the shipped code +/// then stays the code the tests compile. Raising an interrupt from a host test +/// process is not a thing to do, so it does nothing. +#[cfg(all(test, not(feature = "std"), target_arch = "x86_64"))] +#[unsafe(no_mangle)] +extern "C" fn lkrt_cpu_raise_interrupt(_vector: i64) {} + +pub(super) fn cpu_raise_interrupt(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + // Declared, not depended on. `lk-core` must not have `lkrt` as a crate + // dependency — that boundary is what keeps the runtime free of the + // parser and the compiler — but on the one target where this means + // anything, both are linked into the same image and the symbol is simply + // there. A link-time reference is not an architectural edge. + unsafe extern "C" { + fn lkrt_cpu_raise_interrupt(vector: i64); + } + let vector = word_operand(&_args, 0, "cpu_raise_interrupt")?; + // SAFETY: the vector is bounds-checked inside, and a vector with no gate + // faults exactly as it would if a device had raised it. + unsafe { lkrt_cpu_raise_interrupt(vector as i64) }; + return Ok(RuntimeVal::Nil); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_raise_interrupt")) +} + +/// Switches address spaces, flushing the TLB in doing so. The code after it +/// must be mapped in the new space at the same address — which is why a kernel +/// is mapped into every one. +pub(super) fn cpu_write_cr3(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let value = word_operand(&_args, 0, "cpu_write_cr3")? as u64; + // No `nomem`: this invalidates every cached translation, so it orders + // against essentially all memory. + unsafe { + core::arch::asm!("mov cr3, {}", in(reg) value, options(nostack, preserves_flags)); + } + return Ok(RuntimeVal::Nil); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_write_cr3")) +} + +/// Drops one page's cached translation. The page table is not what the CPU +/// consults — the TLB is, and it does not notice a write behind it. +pub(super) fn cpu_invalidate_page(_args: NativeArgs<'_>) -> Result { + #[cfg(all(not(feature = "std"), target_arch = "x86_64"))] + { + let address = word_operand(&_args, 0, "cpu_invalidate_page")? as u64; + unsafe { + core::arch::asm!("invlpg [{}]", in(reg) address, options(preserves_flags)); + } + return Ok(RuntimeVal::Nil); + } + #[allow(unreachable_code)] + Err(system_refusal("cpu_invalidate_page")) +} + /// A full memory barrier. /// /// `fence(SeqCst)` rather than hand-written assembly: it is `mfence` on x86-64, diff --git a/core/src/vm/ir.rs b/core/src/vm/ir.rs index ed1dc100..e2466c23 100644 --- a/core/src/vm/ir.rs +++ b/core/src/vm/ir.rs @@ -5,7 +5,7 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use crate::util::fast_map::FastHashMap; +use crate::util::value_map::ValueMap; use alloc::sync::Arc; use core::fmt::Write as _; use core::mem::size_of; @@ -14,11 +14,9 @@ use anyhow::{Result, bail}; use crate::{ val::{RuntimeMapKey, ShortStr}, - vm::analysis::{FunctionAnalysis, PerformanceFacts}, + vm::analysis::PerformanceFacts, }; -use super::runtime::NativeEntry; - #[derive(Clone, Debug, PartialEq, Eq)] pub struct GlobalSlot { pub name: Arc, @@ -28,7 +26,12 @@ pub struct GlobalSlot { pub struct ConstPool { pub ints: Vec, pub floats: Vec, - pub strings: Vec, + /// `Arc`, not `String`: a constant string key inserted into a map + /// becomes an `Arc` there, and every insert used to allocate a fresh + /// one and free it when the map died. Sharing the pool's makes it a + /// refcount bump — `Arc::drop_slow` alone was 4.6% of a map-building + /// workload. Reads still hand out `&str`. + pub strings: Vec>, pub heap_values: Vec, } @@ -40,15 +43,15 @@ impl ConstPool { } pub fn push_float(&mut self, value: f64) -> Result { - push_const(&mut self.floats, value, "float") + push_const_by(&mut self.floats, value, "float", |a, b| a.to_bits() == b.to_bits()) } - pub fn push_string(&mut self, value: impl Into) -> Result { - push_const(&mut self.strings, value.into(), "string") + pub fn push_string(&mut self, value: impl AsRef) -> Result { + push_const(&mut self.strings, Arc::::from(value.as_ref()), "string") } pub fn push_heap_value(&mut self, value: ConstHeapValue) -> Result { - push_const(&mut self.heap_values, value, "heap value") + push_const_by(&mut self.heap_values, value, "heap value", const_heap_value_is_same) } #[inline] @@ -63,7 +66,14 @@ impl ConstPool { #[inline] pub fn string(&self, index: u16) -> Option<&str> { - self.strings.get(index as usize).map(String::as_str) + self.strings.get(index as usize).map(Arc::as_ref) + } + + /// The pooled string itself, for a caller that is about to *store* it — + /// a map key. See [`Self::strings`]. + #[inline] + pub fn shared_string(&self, index: u16) -> Option<&Arc> { + self.strings.get(index as usize) } #[inline] @@ -73,7 +83,21 @@ impl ConstPool { } fn push_const(values: &mut Vec, value: T, name: &str) -> Result { - if let Some(index) = values.iter().position(|existing| existing == &value) { + push_const_by(values, value, name, |existing, value| existing == value) +} + +/// Deduplication is *identity*, and for a float that is its bits. +/// +/// `PartialEq` is the wrong question here: `-0.0 == 0.0` is true and the two +/// are different values, so whichever literal a file wrote first swallowed +/// every later occurrence of the other. `println(-0.0); println(0.0);` printed +/// `-0` twice, and — because the sign of zero reaches division — +/// `println(1.0 / 0.0)` answered `-inf`. The answer depended on the spelling +/// and position of an unrelated line in the same file. +/// +/// Bit identity also merges two NaNs of the same payload, which `==` never did. +fn push_const_by(values: &mut Vec, value: T, name: &str, eq: impl Fn(&T, &T) -> bool) -> Result { + if let Some(index) = values.iter().position(|existing| eq(existing, &value)) { return Ok(index as u16); } let index = values.len(); @@ -84,6 +108,34 @@ fn push_const(values: &mut Vec, value: T, name: &str) -> Result Ok(index as u16) } +/// [`ConstRuntimeValue`] equality for pooling: identical to the derived one +/// except that floats compare by bits. See [`push_const_by`]. +fn const_value_is_same(left: &ConstRuntimeValue, right: &ConstRuntimeValue) -> bool { + match (left, right) { + (ConstRuntimeValue::Float(a), ConstRuntimeValue::Float(b)) => a.to_bits() == b.to_bits(), + (ConstRuntimeValue::Heap(a), ConstRuntimeValue::Heap(b)) => const_heap_value_is_same(a, b), + _ => left == right, + } +} + +/// [`ConstHeapValue`] equality for pooling. A container constant holds +/// [`ConstRuntimeValue`]s, so the float rule has to reach through it: `[0.0]` +/// and `[-0.0]` were the same pool entry too. +fn const_heap_value_is_same(left: &ConstHeapValue, right: &ConstHeapValue) -> bool { + match (left, right) { + (ConstHeapValue::List(a), ConstHeapValue::List(b)) => { + a.len() == b.len() && a.iter().zip(b).all(|(a, b)| const_value_is_same(a, b)) + } + (ConstHeapValue::Map(a), ConstHeapValue::Map(b)) => { + a.len() == b.len() + && a.iter() + .all(|(key, value)| b.get(key).is_some_and(|other| const_value_is_same(value, other))) + } + (ConstHeapValue::UpvalCell(a), ConstHeapValue::UpvalCell(b)) => const_value_is_same(a, b), + _ => left == right, + } +} + #[derive(Clone, Debug, PartialEq)] pub enum ConstRuntimeValue { Nil, @@ -98,7 +150,10 @@ pub enum ConstRuntimeValue { pub enum ConstHeapValue { LongString(Arc), List(Vec), - Map(FastHashMap), + /// Insertion-ordered: a map literal's entries reach the heap in the order + /// they were written, because that is the order the value iterates in + /// (`util::value_map`). + Map(ValueMap), UpvalCell(Box), } @@ -283,51 +338,64 @@ pub enum Opcode { CallDirect = 69, CallNamed = 70, LoadFunction = 71, - LoadNative = 72, - MakeClosure = 73, - LoadCapture = 74, - LoadCellVal = 75, - StoreCellVal = 76, - GetGlobal = 77, - SetGlobal = 78, - NewList = 79, - NewMap = 80, - NewRange = 81, - NewObject = 82, - GetIndex = 83, - SetIndex = 84, - GetIndexStrI = 85, - SetIndexStrI = 86, - GetFieldK = 87, - SetFieldK = 88, - GetList = 89, - ListPush = 90, - Len = 91, - ToIter = 92, - Contains = 93, - SliceFrom = 94, - MapRest = 95, - ToString = 96, - ConcatString = 97, - ConcatN = 98, - StringSplit = 99, - ListJoin = 100, - Raise = 101, - TryBegin = 102, - TryEnd = 103, - Wide = 104, + MakeClosure = 72, + LoadCapture = 73, + LoadCellVal = 74, + StoreCellVal = 75, + GetGlobal = 76, + SetGlobal = 77, + NewList = 78, + NewMap = 79, + NewRange = 80, + NewObject = 81, + GetIndex = 82, + SetIndex = 83, + GetIndexStrI = 84, + SetIndexStrI = 85, + GetFieldK = 86, + SetFieldK = 87, + GetList = 88, + ListPush = 89, + Len = 90, + ToIter = 91, + Contains = 92, + SliceFrom = 93, + MapRest = 94, + ToString = 95, + ConcatString = 96, + ConcatN = 97, + StringSplit = 98, + ListJoin = 99, + Raise = 100, + TryBegin = 101, + TryEnd = 102, + Wide = 103, /// Boxing-free method call: `a` = window base (receiver at `a`, args at /// `[a+1, a+1+c)`, result written to `a`), `b` = method-name string /// constant index, `c` = positional argument count. Replaces the /// `GetGlobal __lk_call_method` + `NewList` + `Call` sequence for /// positional method calls whose name constant index fits in `b`. - CallMethodK = 105, + CallMethodK = 104, /// `A = B as ` — see `CastTarget`. /// /// One opcode for every conversion rather than one per source/target pair: /// the source type is only known at runtime anyway, so a per-pair opcode /// would not save the dispatch on it. - CastTo = 106, + CastTo = 105, + /// `A = -B`. + /// + /// Not `0 - B`: the two differ on floats, where `-0.0` is a value distinct + /// from `0.0 - 0.0`, and negation is what the writer asked for. + Neg = 106, + /// `A = floor(B / C)` on two `Int`s — the fused form of + /// `math.floor(a / b)`. + /// + /// Exists because `/` yields a `Float`, so this idiom is the only way to + /// write integer division and it would otherwise cost a float divide plus + /// a native call. Floor, not truncation: `math.floor(-7 / 2)` is `-4`. + /// Non-`Int` operands divide as `f64` and floor the result, which is what + /// `math.floor` would have answered. + FloorDivInt = 107, } impl Opcode { @@ -409,41 +477,42 @@ impl Opcode { 69 => Some(Self::CallDirect), 70 => Some(Self::CallNamed), 71 => Some(Self::LoadFunction), - 72 => Some(Self::LoadNative), - 73 => Some(Self::MakeClosure), - 74 => Some(Self::LoadCapture), - 75 => Some(Self::LoadCellVal), - 76 => Some(Self::StoreCellVal), - 77 => Some(Self::GetGlobal), - 78 => Some(Self::SetGlobal), - 79 => Some(Self::NewList), - 80 => Some(Self::NewMap), - 81 => Some(Self::NewRange), - 82 => Some(Self::NewObject), - 83 => Some(Self::GetIndex), - 84 => Some(Self::SetIndex), - 85 => Some(Self::GetIndexStrI), - 86 => Some(Self::SetIndexStrI), - 87 => Some(Self::GetFieldK), - 88 => Some(Self::SetFieldK), - 89 => Some(Self::GetList), - 90 => Some(Self::ListPush), - 91 => Some(Self::Len), - 92 => Some(Self::ToIter), - 93 => Some(Self::Contains), - 94 => Some(Self::SliceFrom), - 95 => Some(Self::MapRest), - 96 => Some(Self::ToString), - 97 => Some(Self::ConcatString), - 98 => Some(Self::ConcatN), - 99 => Some(Self::StringSplit), - 100 => Some(Self::ListJoin), - 101 => Some(Self::Raise), - 102 => Some(Self::TryBegin), - 103 => Some(Self::TryEnd), - 104 => Some(Self::Wide), - 105 => Some(Self::CallMethodK), - 106 => Some(Self::CastTo), + 72 => Some(Self::MakeClosure), + 73 => Some(Self::LoadCapture), + 74 => Some(Self::LoadCellVal), + 75 => Some(Self::StoreCellVal), + 76 => Some(Self::GetGlobal), + 77 => Some(Self::SetGlobal), + 78 => Some(Self::NewList), + 79 => Some(Self::NewMap), + 80 => Some(Self::NewRange), + 81 => Some(Self::NewObject), + 82 => Some(Self::GetIndex), + 83 => Some(Self::SetIndex), + 84 => Some(Self::GetIndexStrI), + 85 => Some(Self::SetIndexStrI), + 86 => Some(Self::GetFieldK), + 87 => Some(Self::SetFieldK), + 88 => Some(Self::GetList), + 89 => Some(Self::ListPush), + 90 => Some(Self::Len), + 91 => Some(Self::ToIter), + 92 => Some(Self::Contains), + 93 => Some(Self::SliceFrom), + 94 => Some(Self::MapRest), + 95 => Some(Self::ToString), + 96 => Some(Self::ConcatString), + 97 => Some(Self::ConcatN), + 98 => Some(Self::StringSplit), + 99 => Some(Self::ListJoin), + 100 => Some(Self::Raise), + 101 => Some(Self::TryBegin), + 102 => Some(Self::TryEnd), + 103 => Some(Self::Wide), + 104 => Some(Self::CallMethodK), + 105 => Some(Self::CastTo), + 106 => Some(Self::Neg), + 107 => Some(Self::FloorDivInt), _ => None, } } @@ -473,7 +542,6 @@ impl Opcode { | Self::LoadHeapConst | Self::LoadCapture | Self::LoadFunction - | Self::LoadNative | Self::CallNamed | Self::BrEqIntI4 | Self::BrNeIntI4 @@ -752,7 +820,6 @@ pub fn decode_instr(bytes: &[u8]) -> Result> { pub struct Function { pub consts: ConstPool, pub code: Vec, - pub analyses: Vec, pub performance: PerformanceFacts, pub register_count: u16, pub param_count: u16, @@ -784,7 +851,6 @@ pub struct Function { #[derive(Clone, Debug, Default)] pub struct Module { pub functions: Vec, - pub natives: Vec, pub globals: Vec, pub entry: u32, /// Static `trait`/`impl` declarations (see [`super::TypeInfo`]). Produced @@ -792,8 +858,8 @@ pub struct Module { /// reconstruct it from bytecode. pub type_info: super::TypeInfo, /// Identity of this module as a *declarer of types* — see - /// [`super::TypeScope`]. - pub type_scope: super::TypeScope, + /// [`crate::val::TypeScope`]. + pub type_scope: crate::val::TypeScope, } impl Module { @@ -801,11 +867,10 @@ impl Module { pub fn single(function: Function) -> Self { Self { functions: vec![function], - natives: Vec::new(), globals: Vec::new(), entry: 0, type_info: super::TypeInfo::default(), - type_scope: super::TypeScope::anonymous(), + type_scope: crate::val::TypeScope::anonymous(), } } @@ -813,10 +878,6 @@ impl Module { pub fn entry_function(&self) -> Option<&Function> { self.functions.get(self.entry as usize) } - - pub fn native_index(&self, name: &str) -> Option { - self.natives.iter().position(|native| native.name == name) - } } pub fn disassemble_function(function: &Function) -> String { @@ -841,12 +902,6 @@ pub fn disassemble_module(module: &Module) -> String { let _ = writeln!(out, " g{slot} {}", global.name); } } - if !module.natives.is_empty() { - let _ = writeln!(out, ".natives"); - for (slot, native) in module.natives.iter().enumerate() { - let _ = writeln!(out, " n{slot} {} arity={}", native.name, native.arity); - } - } for (index, function) in module.functions.iter().enumerate() { let _ = writeln!(out, ".fn {index}"); out.push_str(&disassemble_function(function)); @@ -856,11 +911,62 @@ pub fn disassemble_module(module: &Module) -> String { #[cfg(test)] mod tests { - use crate::util::fast_map::fast_hash_map_new; - use crate::{val::RuntimeVal, vm::NativeFunction}; use super::*; + /// The opcode discriminants run 0..=N with no holes, and that is a + /// **performance** property, not tidiness. + /// + /// Removing `LoadNative` (an opcode no production path ever emitted) left a + /// hole at 72 and cost **9%** on the workload suite — measured three times + /// either side: 1.075 / 1.086 / 1.089 against a 0.991 / 0.986 baseline. + /// Renumbering the opcodes above it to close the hole put it back to + /// 0.994 / 0.987. The dispatch `match` lowers to a jump table only while the + /// discriminants are dense; one gap is enough to lose it. + /// + /// Nothing guarded this, and the next opcode removal would have paid the + /// same 9% with no test and no reviewer able to see why. Note the cost is + /// the *hole*, not the missing arm: the same removal with contiguous + /// numbering is free. + /// + /// Renumbering changes the artifact encoding, so it comes with a + /// `MODULE_ARTIFACT_VERSION` bump. + #[test] + fn opcodes_are_contiguous() { + assert_contiguous("Opcode", |value| Opcode::from_bits(value).map(|op| op as u8)); + // Both of these are decoded from a byte on a dispatch path too, and the + // rule is not about `Opcode` — it is about what a `match` on a dense + // integer lowers to. Guarding only the one that was measured would be + // guarding the incident rather than the property. + assert_contiguous("InstrFormat", |value| InstrFormat::from_bits(value).map(|f| f as u8)); + assert_contiguous("CastTarget", |value| CastTarget::from_u8(value).map(|c| c as u8)); + } + + /// Every byte the decoder accepts forms `0..=N`, **and** decodes to the + /// variant whose discriminant is that byte. + /// + /// The round trip is the load-bearing half. Each of these decoders is a + /// hand-written `match` on literals, so it mirrors the discriminants rather + /// than deriving from them — a first version of this test only checked + /// which bytes the decoder accepted, which is the mirror and not the thing. + /// It would have passed with `Sj = 40` and `4 => Some(Self::Sj)` side by + /// side: contiguous decode, sparse enum, and the jump table gone. + fn assert_contiguous(name: &str, decode: impl Fn(u8) -> Option) { + let decoded: Vec<(u8, u8)> = (0u8..=255) + .filter_map(|value| decode(value).map(|discriminant| (value, discriminant))) + .collect(); + assert!(!decoded.is_empty(), "{name}: nothing decodes at all"); + let expected: Vec<(u8, u8)> = (0..decoded.len() as u8).map(|value| (value, value)).collect(); + assert_eq!( + decoded, + expected, + "{name} must decode 0..={} onto the variants whose discriminants are those bytes — a \ + hole costs ~9% by breaking the dispatch jump table, and a decoder that disagrees with \ + the discriminants hides one", + decoded.len() - 1 + ); + } + #[test] fn abc_round_trips_opcode_format_and_registers() { let instr = Instr::abc(Opcode::AddInt, 1, 2, 255); @@ -986,7 +1092,7 @@ mod tests { #[test] fn const_pool_heap_values_can_represent_nested_containers() { - let mut entries = fast_hash_map_new(); + let mut entries = crate::util::value_map::value_map_new(); entries.insert( RuntimeMapKey::ShortStr(ShortStr::new("name").expect("short")), ConstRuntimeValue::Heap(Box::new(ConstHeapValue::LongString(Arc::::from( @@ -1035,11 +1141,6 @@ mod tests { fn disassembles_module_metadata() { let module = Module { functions: vec![Function::default()], - natives: vec![NativeEntry { - name: "native_add".to_string(), - arity: 2, - function: NativeFunction::Plain(|_, _runtime| Ok(RuntimeVal::Nil)), - }], globals: vec![GlobalSlot { name: Arc::::from("answer"), }], @@ -1052,7 +1153,42 @@ mod tests { assert!(text.contains(".module entry=0")); assert!(text.contains("g0 answer")); - assert!(text.contains("n0 native_add arity=2")); assert!(text.contains(".fn 0")); } } + +#[cfg(test)] +mod signed_zero_pool_tests { + use super::*; + + /// A constant pool entry's identity is its bits, not `==`. + /// + /// `-0.0 == 0.0` is true and the two are different values, so pooling by + /// equality made whichever literal a file wrote first swallow every later + /// occurrence of the other: `println(-0.0); println(0.0);` printed `-0` + /// twice, and the swallowed sign reached division — + /// `println(1.0 / 0.0)` answered `-inf`. The answer depended on the + /// spelling and position of an unrelated line in the same file. + #[test] + fn the_two_zeros_are_two_constants() { + let mut pool = ConstPool::default(); + let negative = pool.push_float(-0.0).expect("pooled"); + let positive = pool.push_float(0.0).expect("pooled"); + assert_ne!(negative, positive, "-0.0 and 0.0 are different constants"); + assert_eq!(pool.floats.len(), 2); + assert_eq!(pool.push_float(-0.0).expect("pooled"), negative, "and each still pools"); + assert_eq!(pool.push_float(0.0).expect("pooled"), positive); + + // The same rule one carrier deeper: a container constant holds these + // values, so `[0.0]` and `[-0.0]` were the same pool entry too. + let mut pool = ConstPool::default(); + let negative = pool + .push_heap_value(ConstHeapValue::List(vec![ConstRuntimeValue::Float(-0.0)])) + .expect("pooled"); + let positive = pool + .push_heap_value(ConstHeapValue::List(vec![ConstRuntimeValue::Float(0.0)])) + .expect("pooled"); + assert_ne!(negative, positive); + assert_eq!(pool.heap_values.len(), 2); + } +} diff --git a/core/src/vm/migration_guard.rs b/core/src/vm/migration_guard.rs index 41bbb8a6..e812bd34 100644 --- a/core/src/vm/migration_guard.rs +++ b/core/src/vm/migration_guard.rs @@ -16,6 +16,23 @@ const FORBIDDEN_TOKENS: &[(&str, &str)] = &[ "quickening", "runtime feedback/quickening must not return to the VM path", ), + // A value's *type* in a message needs the heap: `RuntimeVal::kind()` calls + // every handle `Object`, so `[1] * 2` said `Object` and `"ab" - 1` said + // `String` while `"aaaaaaaaaa" - 1` said `Object` — one type, two names, + // decided by whether the string fit in seven bytes. + // + // Forty-odd sites made that mistake because the wrong function had the + // right-sounding name. It is `scalar_type_name` now, and the honest one is + // `RuntimeVal::type_name_in(heap)` (or `Executor::value_type_name`). This + // token is what keeps the next site from reaching past them: a `kind()` that + // really wants the *representation* can say `repr_name` or compare the + // variant, neither of which matches here. + ( + ".kind()", + "name a value's type, not its representation: RuntimeVal::type_name_in(heap) \ + (or Executor::value_type_name). `.kind().scalar_type_name()` is the opt-in \ + for a site with no heap, and says so", + ), ("unsafe ", "LLVM-external VM/value code must stay safe Rust"), ("unsafe{", "LLVM-external VM/value code must stay safe Rust"), ("unsafe\n", "LLVM-external VM/value code must stay safe Rust"), @@ -35,6 +52,74 @@ fn vm_rewrite_guard_blocks_old_vm_compatibility_paths() { ); } +/// A whole module may not be exempted from dead-code analysis. +/// +/// `vm.rs` carried three of these — `analysis`, `analysis_queries`, `type_info`. +/// Two turned out to suppress nothing at all; the third was hiding eleven +/// counters that no longer had a caller, including ten that `lk coverage +/// --runtime` printed as data. A module-level allow is indiscriminate by +/// construction: it cannot distinguish "used only under `--features vm-profile`" +/// from "used by nobody, ever", so it silently absorbs the second forever. +/// +/// The shape that works is a `#[cfg(...)]` on the item, so the build that +/// doesn't need it doesn't compile it — then genuinely dead code surfaces on its +/// own. An item-level `#[allow(dead_code)]` with a reason is still fine; this +/// guard only rejects the blanket form on a `mod` declaration. +#[test] +fn no_module_is_blanket_exempted_from_dead_code() { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let mut violations = Vec::new(); + for root in [manifest_dir.join("src/vm.rs"), manifest_dir.join("src/vm")] { + collect_module_allows(&root, manifest_dir, &mut violations); + } + assert!( + violations.is_empty(), + "a module-level allow(dead_code) cannot tell an unused-under-this-cfg item from a dead one — \ + put a #[cfg(...)] on the items instead:\n{}", + violations.join("\n") + ); +} + +fn collect_module_allows(path: &Path, manifest_dir: &Path, violations: &mut Vec) { + let Ok(metadata) = fs::metadata(path) else { + return; + }; + if metadata.is_dir() { + let Ok(entries) = fs::read_dir(path) else { + return; + }; + let mut paths: Vec<_> = entries.flatten().map(|entry| entry.path()).collect(); + paths.sort(); + for entry in paths { + collect_module_allows(&entry, manifest_dir, violations); + } + return; + } + if path.extension().is_none_or(|ext| ext != "rs") { + return; + } + let Ok(source) = fs::read_to_string(path) else { + return; + }; + let lines: Vec<&str> = source.lines().collect(); + for (index, line) in lines.iter().enumerate() { + if !line.trim_start().starts_with("#[allow(dead_code") { + continue; + } + // Only an allow that lands on a `mod` declaration is indiscriminate; + // attributes intervening between the two are still the same attachment. + let attached = lines[index + 1..] + .iter() + .find(|next| !next.trim_start().starts_with("#[")) + .map(|next| next.trim_start()) + .unwrap_or(""); + if attached.starts_with("mod ") || attached.starts_with("pub mod ") || attached.starts_with("pub(crate) mod ") { + let display = path.strip_prefix(manifest_dir).unwrap_or(path).display(); + violations.push(format!("{display}:{}: {} on `{attached}`", index + 1, line.trim())); + } + } +} + fn collect_violations(path: &Path, manifest_dir: &Path, violations: &mut Vec) { let Ok(metadata) = fs::metadata(path) else { return; @@ -53,6 +138,15 @@ fn collect_violations(path: &Path, manifest_dir: &Path, violations: &mut Vec Vec { + // Carried across lines: a Rust string literal may span them with a + // trailing `\`, which is how a multi-line LK sample is written in a test. + let mut in_string = false; + source + .lines() + .map(|line| scannable_code(line, &mut in_string)) + .collect() +} + +fn scannable_code(line: &str, in_string: &mut bool) -> String { + let mut out = String::with_capacity(line.len()); + let mut escaped = false; + for ch in line.chars() { + if *in_string { + if escaped { + escaped = false; + } else if ch == '\\' { + escaped = true; + } else if ch == '"' { + *in_string = false; + } + out.push(' '); + continue; + } + if ch == '"' { + *in_string = true; + out.push(' '); + continue; + } + if ch == '/' && out.ends_with('/') { + out.pop(); + break; + } + out.push(ch); + } + out +} + +/// Whether a line that contains a forbidden token is nevertheless allowed. +/// +/// Only `.kind()` has exceptions, and they are the two spellings that are *not* +/// the mistake: naming the scalar limit out loud, and comparing variants +/// (`kind()` is what a representation check is for). +fn exempt(token: &str, line: &str) -> bool { + token == ".kind()" && (line.contains("scalar_type_name") || line.contains("RuntimeValKind")) +} diff --git a/core/src/vm/repl.rs b/core/src/vm/repl.rs index 5eccf441..edab909e 100644 --- a/core/src/vm/repl.rs +++ b/core/src/vm/repl.rs @@ -11,7 +11,7 @@ use crate::{ stmt::{Program, Stmt}, typ::TypeChecker, val::RuntimeVal, - vm::{Module, RuntimeExport, RuntimeModuleState, VmContext, execute_program_with_ctx}, + vm::{Module, RuntimeExport, RuntimeModuleState, VmContext}, }; /// Persistent VM state for interactive REPL execution. @@ -24,6 +24,23 @@ pub struct ReplVmSession { ctx: VmContext, type_checker: TypeChecker, persistent_names: BTreeSet, + /// Every `struct` the session has declared, in the order first seen — see + /// [`ReplVmSession::carry_struct_declarations`]. + struct_decls: Vec, + /// Default method bodies from every `trait` the session has declared, so a + /// later input's `impl` gets them — see `apply_carried_trait_defaults`. + trait_defaults: crate::compat::collections::HashMap>, + /// Where an import path is relative to, for the *type* side of an import. + /// + /// Every other entry point seeds the checker with the signatures an import + /// brings (`typ::seed_imported_signatures` — the CLI does it for a file, + /// the native compiler for a compile, and `execute_with_ctx_from` for a + /// module loaded as an import). The session did not, so `use { Pt } from + /// "lib";` bound the value and left the *type* unknown: `Pt { x: 1, y: 2 }` + /// was refused with "no type named `Pt` is declared here", by a message + /// that then suggested writing the import that had just been written. + #[cfg(feature = "std")] + base_dir: Option, } impl ReplVmSession { @@ -32,9 +49,20 @@ impl ReplVmSession { ctx, type_checker, persistent_names: BTreeSet::new(), + struct_decls: Vec::new(), + trait_defaults: crate::compat::collections::HashMap::new(), + #[cfg(feature = "std")] + base_dir: None, } } + /// Where this session's import paths are relative to — the working + /// directory, for the REPL. + #[cfg(feature = "std")] + pub fn set_base_dir(&mut self, base_dir: std::path::PathBuf) { + self.base_dir = Some(base_dir); + } + pub fn ctx(&self) -> &VmContext { &self.ctx } @@ -44,17 +72,127 @@ impl ReplVmSession { } pub fn execute_program(&mut self, program: &Program) -> Result { + // A trait's default bodies are copied into the impls that leave them out + // during parsing, over one program's statements. An input that declares + // the `impl` without the `trait` beside it never saw them. + let carried_defaults; + let program = if self.trait_defaults.is_empty() { + program + } else { + let mut owned = program.clone(); + crate::stmt::trait_defaults::apply_carried_trait_defaults(&mut owned.statements, &self.trait_defaults); + carried_defaults = owned; + &carried_defaults + }; let mut next_type_checker = self.type_checker.clone(); + let carried = self.carried_function_types(program); + // The signatures and types this input's imports bring, before checking + // it — see `base_dir`. + #[cfg(feature = "std")] + if let Some(base_dir) = self.base_dir.as_deref() { + crate::typ::seed_imported_signatures(program, base_dir, &mut next_type_checker); + } program.type_check(&mut next_type_checker)?; + restore_carried_function_types(&mut next_type_checker, carried); let (runtime_program, declared_names) = repl_runtime_program(program, &self.persistent_names)?; - let result = execute_program_with_ctx(&runtime_program, &mut self.ctx)?; + // The session's own bindings are user data, not module objects: without + // saying so, `xs` from an earlier line is indistinguishable from an + // imported `math`, and `xs.len()` compiles to an index read keyed by + // `"len"` (`compile_program_module_with_ctx_and_data_globals`). + let data_globals = self.persistent_names.iter().cloned().collect::>(); + let module = crate::vm::compile_program_module_with_ctx_and_data_globals( + &runtime_program, + &mut self.ctx, + &data_globals, + )?; + let module = self.carry_struct_declarations(module); + let result = crate::vm::execute_compiled_module_with_ctx(module, &mut self.ctx)?; self.type_checker = next_type_checker; self.persistent_names.extend(declared_names); + self.trait_defaults + .extend(crate::stmt::trait_defaults::trait_defaults_of(&program.statements)); + self.record_struct_declarations(&result.module); self.sync_result_globals(result) } + /// Give this input's module the `struct` declarations earlier inputs made. + /// + /// A field's *declaration order* travels with the type, and both paths that + /// build an instance read it from the module being executed + /// (`exec::container::declared_type` and `__lk_make_struct`). Every REPL + /// input is its own module, so a struct declared on one line and built on + /// the next had no declaration to order by and fell back to the field map's + /// own iteration: + /// + /// ```text + /// > struct Reading { zebra: Int, apple: Int, mango: Int, … } + /// > Reading { zebra: 1, apple: 2, mango: 3, … } + /// Reading{apple:2,fig:6,kiwi:4,mango:3,pear:5,zebra:1} + /// ``` + /// + /// Written on one line, or in a file, or across a real `use`, the same + /// value prints in declaration order. This makes the session behave like + /// the file: a declaration stays visible to the inputs after it. + fn carry_struct_declarations(&self, module: Arc) -> Arc { + // A redeclaration in *this* input wins — it is what the source being run + // says, exactly as a later `struct` in a file would. + let missing: Vec = self + .struct_decls + .iter() + .filter(|decl| !module.type_info.structs.iter().any(|own| own.name == decl.name)) + .cloned() + .collect(); + if missing.is_empty() { + return module; + } + let mut module = module; + Arc::make_mut(&mut module).type_info.structs.extend(missing); + module + } + + /// Keep what this input declared, for the inputs after it. + fn record_struct_declarations(&mut self, module: &crate::vm::Module) { + for decl in &module.type_info.structs { + match self.struct_decls.iter_mut().find(|held| held.name == decl.name) { + Some(held) => *held = decl.clone(), + None => self.struct_decls.push(decl.clone()), + } + } + } + + /// The recorded types of functions this program does *not* declare. + /// + /// After checking a program the checker applies the solved substitutions to + /// everything it has recorded — right for one program, and wrong for a + /// sequence of them. An unannotated parameter's type is a derivation from + /// the body rather than a claim by the source (see `FunctionSig::annotated`), + /// and this turned one input's derivation into a claim binding every later + /// input: `fn f(x) { return x; }` then `f(1)` left `f` as `(Int) -> Int`, so + /// `f("a")` on the next line answered "Cannot unify Int with String". The + /// same three lines in a file are fine, because there the substitution pass + /// runs once with every call site already contributing to it. + /// + /// A function the program *does* declare is left alone: its definition and + /// this input's uses were checked together, exactly as in a file. + fn carried_function_types( + &self, + program: &Program, + ) -> Vec<(String, crate::typ::FunctionSig, Option)> { + let declared_here = declared_function_names(program); + self.type_checker + .declared_function_names() + .into_iter() + .filter(|name| !declared_here.contains(name.as_str())) + .filter_map(|name| { + let sig = self.type_checker.get_function_sig(&name)?.clone(); + let local = self.type_checker.get_local_type(&name).cloned(); + Some((name, sig, local)) + }) + .collect() + } + fn sync_result_globals(&mut self, result: crate::vm::ProgramResult) -> Result { let display_first_return = (!result.first_return_is_nil()).then(|| result.display_first_return()); let returns = result.returns; @@ -97,6 +235,44 @@ impl ReplExecutionResult { } } +fn restore_carried_function_types( + checker: &mut TypeChecker, + carried: Vec<(String, crate::typ::FunctionSig, Option)>, +) { + for (name, mut sig, local) in carried { + // A function from an earlier input is, quite literally, in another + // module: every input is compiled as one. So it carries the same rule a + // real import does — a named parameter's default cannot be filled from + // here — and saying that at check time is better than the run time's + // `missing required named argument`, about a parameter that is not + // required. + sig.origin = crate::typ::SigOrigin::Imported; + checker.add_function_sig(name.clone(), sig); + if let Some(local) = local { + checker.add_local_type(name, local); + } + } +} + +/// The names of functions a program declares at its top level, including the +/// constructor a `struct` brings with it. +fn declared_function_names(program: &Program) -> BTreeSet { + fn item(stmt: &Stmt) -> &Stmt { + match stmt { + Stmt::Attributed { item, .. } => item, + other => other, + } + } + program + .statements + .iter() + .filter_map(|stmt| match item(stmt.as_ref()) { + Stmt::Function { name, .. } => Some(name.clone()), + _ => None, + }) + .collect() +} + fn repl_runtime_program(program: &Program, existing_names: &BTreeSet) -> Result<(Program, BTreeSet)> { let mut active_names = existing_names.clone(); let mut declared_names = BTreeSet::new(); @@ -186,6 +362,8 @@ fn flush_global_stmt(name: String) -> Stmt { Stmt::Define { name: name.clone(), value: Box::new(Expr::Var(name)), + // Synthesised to flush a REPL global; it corresponds to no source text. + span: None, } } @@ -271,6 +449,20 @@ mod tests { assert_eq!(result.returns, vec![RuntimeVal::Int(3)]); } + /// A binding from an earlier input is an *external* global to the module + /// compiled for this one — indistinguishable from an imported `math` unless + /// the session says otherwise. Without that, `xs.len()` compiled to an + /// index read keyed by `"len"` and every method call on a REPL binding + /// failed: `xs.push(1)`, `s.upper()`, `m.get(k)`. + #[test] + fn repl_method_call_on_earlier_binding_dispatches_as_method() { + let mut session = new_session(); + execute(&mut session, "xs := [1, 2];").expect("define list"); + let result = execute(&mut session, "return xs.len();").expect("method call across inputs"); + + assert_eq!(result.returns, vec![RuntimeVal::Int(2)]); + } + #[test] fn repl_preserves_heap_backed_values() { let mut session = new_session(); diff --git a/core/src/vm/resolver.rs b/core/src/vm/resolver.rs index 503a27ca..760e53cb 100644 --- a/core/src/vm/resolver.rs +++ b/core/src/vm/resolver.rs @@ -244,15 +244,16 @@ impl ModuleResolver { } pub fn resolve_source_runtime(&self, src: &str) -> Result { - self.resolve_source_runtime_with_base(src, None, crate::vm::TypeScope::anonymous()) + self.resolve_source_runtime_with_base(src, None, crate::val::TypeScope::anonymous()) } fn resolve_source_runtime_with_base( &self, src: &str, base_dir: Option, - type_scope: crate::vm::TypeScope, + type_scope: crate::val::TypeScope, ) -> Result { + let seed_dir = base_dir.clone(); let program = parse_program_source( src, ParseOptions { @@ -263,7 +264,10 @@ impl ModuleResolver { .map_err(|e| anyhow!(e.to_string()))?; let resolver = Arc::new(self.clone()); let mut ctx = VmContext::new().with_resolver(resolver).with_type_scope(type_scope); - let result = program.execute_with_ctx(&mut ctx)?; + // The loaded module's own directory, so *its* imports are seeded too: + // a type crossing one more module boundary is still a type this file + // names. + let result = program.execute_with_ctx_from(&mut ctx, seed_dir.as_deref())?; Ok(result.into_exports()) } @@ -356,11 +360,11 @@ impl ModuleResolver { } // The normalized path is this module's type identity: the compiler has // no idea what file it is compiling, so the loader is the only place - // that can supply it (`vm::TypeScope`). + // that can supply it (`val::TypeScope`). resolver.resolve_source_runtime_with_base( &src, path.parent().map(Path::to_path_buf), - crate::vm::TypeScope::from_path(&path.to_string_lossy()), + crate::val::TypeScope::from_path(&path.to_string_lossy()), ) } } @@ -385,7 +389,43 @@ fn runtime_export_field(module: &RuntimeExport, name: &str) -> Result;` is declared, and + // got told it was not. + if module + .shared_module() + .type_info + .traits + .iter() + .any(|decl| decl.name == name) + { + return Err(anyhow!( + "'{name}' is a `trait`, and a trait has no constructor to bind, so it cannot be imported as \ + a name. Import the type that implements it instead — the `impl` travels with the type." + )); + } + return Err(anyhow!( + "'{name}' is not an export of this module — no value, and no `struct` by that name to bind a \ + constructor for. A `trait` and a `type` alias are both compile-time only and neither can be \ + imported as a name." + )); + } + Err(anyhow!("'{}' is not an export of this module", name)) } pub fn execute_imports(imports: &[ImportStmt], resolver: &ModuleResolver, env: &mut VmContext) -> Result<()> { @@ -450,7 +490,7 @@ pub fn execute_imports(imports: &[ImportStmt], resolver: &ModuleResolver, env: & // module `main` never named, so dispatch failed outright ("Object has no // method"). Registering the resolver's whole loaded set closes that: it is // already the transitive closure, and scope-keyed entries mean the extra - // modules cannot clobber anything (see `vm::TypeScope`). + // modules cannot clobber anything (see `val::TypeScope`). #[cfg(feature = "std")] for module in resolver.loaded_file_modules() { env.register_imported_types(&module)?; @@ -525,7 +565,6 @@ mod tests { fn test_parent_module_item_import_binds_child_namespace() -> Result<()> { use crate::{ module::{ModuleProvider, RuntimeNativeExport, runtime_export_from_plain_native_entries}, - util::fast_map::fast_hash_map_from_iter, val::{HeapStore, HeapValue, TypedMap}, vm::{NativeArgs, NativeRuntime}, }; @@ -548,7 +587,7 @@ mod tests { let mut heap = HeapStore::new(); let file = crate::vm::import_runtime_export(&file, &mut heap)?; let value = RuntimeVal::Obj(heap.alloc(HeapValue::Map(TypedMap::StringMixed( - fast_hash_map_from_iter([(Arc::::from("file"), file)]), + crate::util::value_map::value_map_from_iter([(Arc::::from("file"), file)]), )))); Ok(RuntimeExport::from_value(value, heap)) } @@ -598,6 +637,95 @@ mod tests { assert!(resolver.resolve_file_path(&rel.to_string_lossy()).is_err()); } + /// A method in an imported module may call another method on `self`. + /// + /// `take_runtime_callable_state` moves a module's shared state out of its + /// mutex for the duration of a call and leaves `Default::default()` behind, + /// so the mechanism cannot be re-entered — and a method calling another + /// method on `self` re-enters by definition. The outer call took the state, + /// the inner call took the empty shell, and the executor refused "a module + /// expecting 83 globals against a table of 0" for a program that never + /// mentions a global. All three shapes below were broken; each works when + /// the same code sits in one file, which is what made it a cross-module bug + /// rather than a dispatch bug. + #[test] + fn an_imported_method_may_call_another_method_on_self() -> Result<()> { + let cases = [ + // A trait default body reaching the impl's own method. + ( + "trait Area { fn area(self) -> Int; fn twice(self) -> Int { return self.area() * 2; } }\n\ + impl Area for Sq { fn area(self) -> Int { return self.side * self.side; } }", + "twice", + ), + // An inherent method reaching another inherent method. + ( + "impl Sq { fn area(self) -> Int { return self.side * self.side; } \n\ + fn twice(self) -> Int { return self.area() * 2; } }", + "twice", + ), + // An inherent method reaching a trait method. + ( + "trait Area { fn area(self) -> Int; }\n\ + impl Area for Sq { fn area(self) -> Int { return self.side * self.side; } }\n\ + impl Sq { fn twice(self) -> Int { return self.area() * 2; } }", + "twice", + ), + ]; + for (index, (impls, method)) in cases.iter().enumerate() { + let temp = tempfile::tempdir()?; + let dep = temp.path().join("shape.lk"); + std::fs::write( + &dep, + format!("struct Sq {{ side: Int }}\n{impls}\nfn make(n: Int) -> Sq {{ return Sq {{ side: n }}; }}\n"), + )?; + let mut resolver = ModuleResolver::new(); + resolver.set_base_dir(temp.path().to_path_buf()); + let value = execute_import_source( + &format!("use {{ make }} from \"./shape.lk\";\nreturn make(3).{method}();\n"), + Arc::new(resolver), + )?; + assert_eq!(value, RuntimeVal::Int(18), "case {index}"); + } + Ok(()) + } + + /// Module A's method may call into B and have B call back into A. + /// + /// The step past `an_imported_method_may_call_another_method_on_self`: there + /// the re-entered module *was* the one executing, so the call could simply + /// use the live state. Here it is not — B is — and A's state is out on the + /// stack, so neither borrowing it nor reusing the current one is right. + /// + /// Borrowing was never the only way to run a foreign body, though. + /// `call_foreign_module_method` keeps the current heap and swaps in a global + /// table shaped like the declaring module's, so it needs A's *module*, not + /// A's state. Before that, the re-entering call got the empty placeholder + /// and the failure surfaced as "module expected 84 globals, got 0". + #[test] + fn a_module_may_be_re_entered_through_another_module() -> Result<()> { + let temp = tempfile::tempdir()?; + std::fs::write(temp.path().join("b.lk"), "fn helper(x) { return x.base() + 100; }\n")?; + std::fs::write( + temp.path().join("a.lk"), + "use { helper } from \"./b.lk\";\n\ + struct A { v: Int }\n\ + impl A {\n\ + fn base(self) -> Int { return self.v; }\n\ + fn viab(self) -> Int { return helper(self); }\n\ + }\n\ + fn make(n: Int) -> A { return A { v: n }; }\n", + )?; + let mut resolver = ModuleResolver::new(); + resolver.set_base_dir(temp.path().to_path_buf()); + let value = execute_import_source( + "use { make } from \"./a.lk\";\nreturn make(5).viab();\n", + Arc::new(resolver), + )?; + + assert_eq!(value, RuntimeVal::Int(105)); + Ok(()) + } + /// `..` is allowed as a way to reach a sibling directory of the same package, /// not as a way out of it. The boundary is the package root (nearest /// `Lk.toml`), or the importing file's directory when there is no manifest. @@ -873,4 +1001,41 @@ mod tests { assert_eq!(result?, RuntimeVal::Int(21)); Ok(()) } + + /// An imported type can be constructed: `module.Type { … }`. + /// + /// A module exports *values*, and a `struct` declaration is not one, so a + /// module that declared a type could not let its users make one — every + /// such module hand-wrote a `make`. It cannot simply be allowed either: a + /// type's identity carries its defining module (`TypeScope`), and a `Pt` + /// built in the importer is not the `Pt` that `impl … for Pt` was + /// registered against. + /// + /// So the *defining* module builds it: `stmt::struct_ctors` puts a + /// named-parameter constructor beside every `struct`, and the literal is + /// parse-time sugar for a call to it. This pins all three things that made + /// it worth doing: the fields, the trait method, and the declaration-order + /// display. + #[test] + fn an_imported_type_can_be_constructed_by_the_module_that_owns_it() -> Result<()> { + let temp = tempfile::tempdir()?; + std::fs::write( + temp.path().join("types.lk"), + "struct Pt { x: Int, y: Int }\n trait Norm { fn norm(self) -> Int; }\n impl Norm for Pt { fn norm(self) -> Int { return self.x + self.y; } }\n", + )?; + let program = crate::syntax::parse_program_source( + "use \"types\";\nlet p = types.Pt { x: 3, y: 4 };\nreturn [p.x, p.norm(), \"${p}\"];\n", + crate::syntax::ParseOptions { + base_dir: Some(temp.path().to_path_buf()), + ..crate::syntax::ParseOptions::default() + }, + ) + .expect("program should parse"); + let mut resolver = ModuleResolver::new(); + resolver.set_base_dir(temp.path().to_path_buf()); + let mut ctx = crate::vm::VmContext::new().with_resolver(alloc::sync::Arc::new(resolver)); + let result = crate::vm::ProgramExec::execute_with_ctx(&program, &mut ctx)?; + assert_eq!(result.display_first_return(), r#"[3,7,"Pt{x:3,y:4}"]"#); + Ok(()) + } } diff --git a/core/src/vm/runtime.rs b/core/src/vm/runtime.rs index 6f1e0cd9..014c2bd6 100644 --- a/core/src/vm/runtime.rs +++ b/core/src/vm/runtime.rs @@ -1,6 +1,7 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; use crate::compat::sync::{Mutex, MutexGuard}; +use alloc::borrow::Cow; use alloc::sync::Arc; use anyhow::{Result, anyhow}; @@ -42,12 +43,39 @@ pub struct RuntimeModuleState { /// deterministically). Hosts push via `host_root_push`/`host_roots_extend` /// and restore their `host_roots_mark` on every exit path. pub(crate) host_roots: Vec, + /// This module's own exported value — the map of its top-level names. + /// + /// It lives in *this* heap, and nothing else here points at it: the globals + /// hold the individual values, not the map that collects them. So a + /// collection of this heap driven from anywhere other than + /// [`collect_runtime_export`] freed it, and the next import read a handle + /// past the end of a heap that had shrunk under it — + /// `heap object 82 out of bounds` from `runtime_export_field`. Reproduced by + /// importing one module transitively and then directly, under + /// `LK_GC_STRESS=1`. + pub(crate) export_root: Option, /// Live LK call depth. Lives in the shared state (not the executor) so it /// keeps accumulating across native→VM re-entries (pcall, stdlib HOFs, the /// Tier 1 bridge), which each construct a fresh executor: the runaway- /// recursion cap (see `Executor::max_call_depth`) cannot be reset by /// routing recursion through a native boundary. pub(crate) call_depth: usize, + /// Set on the placeholder that [`super::exec::take_runtime_callable_state`] + /// leaves in the mutex while the real state is out on a call. + /// + /// A module's state is *moved out* of its `Arc>` for the duration + /// of a call and put back on return, so nothing can enter that module again + /// in the meantime — and a method calling another method on `self`, or + /// module A calling B which calls back into A, does exactly that. What the + /// re-entering call found was `Default::default()`, which is + /// indistinguishable from a real state that happens to be empty, so the + /// failure surfaced far away as "module expected 83 globals, got 0" — a + /// sentence about globals for programs that mention none. + /// + /// This flag makes "in use" a thing the shell says about itself. It is never + /// true of a state a program is running against: `take` clears it on the + /// value it hands out and sets it only on what it leaves behind. + pub(crate) borrowed_for_call: bool, } impl RuntimeModuleState { @@ -62,10 +90,17 @@ impl RuntimeModuleState { inline_caches: InlineCaches::default(), pending_raise_root: None, host_roots: Vec::new(), + export_root: None, call_depth: 0, + borrowed_for_call: false, } } + /// Record this state's own module export as a root of its heap. + pub(crate) fn set_export_root(&mut self, value: RuntimeVal) { + self.export_root = Some(value); + } + /// Pin (or clear) the first-class error value currently unwinding so it is /// treated as a GC root until `pcall` recovers it (plan M2.2). pub fn set_pending_raise_root(&mut self, value: Option) { @@ -108,6 +143,16 @@ impl RuntimeModuleState { self.heap.collect(self.root_refs(extra_roots)); } + /// The same, carrying the set of module heaps this cycle has walked. + pub fn collect_garbage_with_visited<'a>( + &mut self, + extra_roots: impl IntoIterator, + visited: &mut crate::val::CollectedModules, + ) { + let roots = self.root_refs(extra_roots); + self.heap.collect_with_visited(roots, visited); + } + pub fn heap(&self) -> &HeapStore { &self.heap } @@ -457,6 +502,25 @@ impl<'a> NativeArgs<'a> { } } + /// The same call, with its positional arguments replaced. + /// + /// The named arguments come along unchanged, which is the point: the + /// stdlib export macro merges named arguments into the positional slots so + /// a body can read them by index, and a body that reads them *by name* must + /// still find them. `string.replace` does both — its `all` default depends + /// on whether `pattern`/`with` arrived by name — so dropping them here + /// would quietly change what a call means. + #[inline] + pub fn with_values<'b>(&self, values: &'b [RuntimeVal]) -> NativeArgs<'b> + where + 'a: 'b, + { + NativeArgs { + values, + named: self.named, + } + } + #[inline] pub const fn new_with_named_stack( values: &'a [RuntimeVal], @@ -641,7 +705,15 @@ impl NativeFunction { #[derive(Clone, Debug)] pub struct NativeEntry { - pub name: String, + /// Borrowed for the names that are compile-time constants. + /// + /// This field is read only by `bail!` — it names the native in an arity or + /// window error. It was a `String`, and calling a native *through a value* + /// (`let f = typeof; f(x)`, or any bare stdlib global) builds an entry per + /// call to carry the function and arity to the helper that runs it, so a + /// two-million-iteration loop allocated and freed the literal + /// `""` two million times for a message it never printed. + pub name: Cow<'static, str>, pub arity: u16, pub function: NativeFunction, } @@ -657,7 +729,6 @@ impl NativeEntry { #[cfg(test)] mod tests { - use crate::util::fast_map::fast_hash_map_from_iter; use alloc::sync::Arc; use crate::val::{HeapStore, HeapValue, RuntimeVal, TypedMap}; @@ -727,10 +798,9 @@ mod tests { assert_eq!(seen, vec![("flag".to_string(), RuntimeVal::Bool(false))]); let mut heap = HeapStore::new(); - let named_handle = heap.alloc(HeapValue::Map(TypedMap::StringInt(fast_hash_map_from_iter([( - Arc::::from("limit"), - 7, - )])))); + let named_handle = heap.alloc(HeapValue::Map(TypedMap::StringInt( + crate::util::value_map::value_map_from_iter([(Arc::::from("limit"), 7)]), + ))); let native_args = NativeArgs::new_with_named_map_handle(&args, named_handle, 1); assert_eq!(native_args.named_len(), 1); let mut seen = Vec::new(); diff --git a/core/src/vm/ssa.rs b/core/src/vm/ssa.rs deleted file mode 100644 index 16c77cef..00000000 --- a/core/src/vm/ssa.rs +++ /dev/null @@ -1,723 +0,0 @@ -use crate::compat::collections::HashMap; -#[cfg(not(feature = "std"))] -use crate::compat::prelude::*; - -mod escape; -pub mod pipeline; - -use crate::{ - expr::Expr, - operator::{BinOp, UnaryOp}, - val::LiteralVal, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ValueId(u32); - -impl ValueId { - fn new(raw: u32) -> Self { - Self(raw) - } - - pub fn index(self) -> usize { - self.0 as usize - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct BlockId(u32); - -impl BlockId { - const ENTRY: BlockId = BlockId(0); - - pub const fn entry() -> Self { - BlockId::ENTRY - } - - fn new(raw: u32) -> Self { - BlockId(raw) - } - - pub fn index(self) -> usize { - self.0 as usize - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ParamId(pub usize); - -#[derive(Debug, Clone)] -pub struct SsaFunction { - pub entry: BlockId, - pub blocks: Vec, - pub params: Vec, -} - -impl SsaFunction { - pub fn block(&self, id: BlockId) -> Option<&SsaBlock> { - self.blocks.get(id.index()) - } -} - -#[derive(Debug, Clone)] -pub struct SsaBlock { - pub id: BlockId, - pub statements: Vec, - pub terminator: Option, -} - -impl SsaBlock { - fn new(id: BlockId) -> Self { - Self { - id, - statements: Vec::new(), - terminator: None, - } - } -} - -#[derive(Debug, Clone)] -pub struct SsaStatement { - pub result: ValueId, - pub value: SsaRvalue, -} - -#[derive(Debug, Clone)] -pub struct PhiOperand { - pub block: BlockId, - pub value: ValueId, -} - -#[derive(Debug, Clone)] -pub enum SsaRvalue { - Const(LiteralVal), - Param(ParamId), - Binary { - op: BinOp, - lhs: ValueId, - rhs: ValueId, - }, - Unary { - op: UnaryOp, - operand: ValueId, - }, - List(Vec), - Map(Vec<(ValueId, ValueId)>), - StructLiteral { - name: String, - fields: Vec<(String, ValueId)>, - }, - Call { - target: SsaCallTarget, - positional: Vec, - named: Vec<(String, ValueId)>, - }, - Phi { - sources: Vec, - }, -} - -#[derive(Debug, Clone)] -pub enum SsaTerminator { - Return { - value: ValueId, - }, - Branch { - cond: ValueId, - then_block: BlockId, - else_block: BlockId, - }, - Jump { - target: BlockId, - }, - Unreachable, -} - -#[derive(Debug, Clone)] -pub enum SsaCallTarget { - Named(String), - Value(ValueId), -} - -#[derive(Debug)] -pub struct SsaLoweringError { - msg: String, -} - -impl core::fmt::Display for SsaLoweringError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "SSA lowering failed: {}", self.msg) - } -} - -impl core::error::Error for SsaLoweringError {} - -pub fn lower_expr_to_ssa(expr: &Expr) -> Result { - let mut ctx = LoweringContext::new(); - let value = ctx.lower_expr(expr)?; - ctx.finish_return(value); - Ok(ctx.finish()) -} - -struct LoweringContext { - blocks: Vec, - params: Vec, - param_indices: HashMap, - next_value: u32, - current_block: BlockId, -} - -#[derive(Debug, Clone, Copy)] -enum ShortCircuitKind { - And, - Or, -} - -impl LoweringContext { - fn new() -> Self { - Self { - blocks: vec![SsaBlock::new(BlockId::ENTRY)], - params: Vec::new(), - param_indices: HashMap::new(), - next_value: 0, - current_block: BlockId::ENTRY, - } - } - - fn current_block_mut(&mut self) -> &mut SsaBlock { - let idx = self.current_block.index(); - self.blocks - .get_mut(idx) - .expect("lowering context always has an active block") - } - - fn current_block_id(&self) -> BlockId { - self.current_block - } - - fn switch_to_block(&mut self, id: BlockId) { - self.current_block = id; - } - - fn create_block(&mut self) -> BlockId { - let id = BlockId::new(self.blocks.len() as u32); - self.blocks.push(SsaBlock::new(id)); - id - } - - fn block_mut(&mut self, id: BlockId) -> &mut SsaBlock { - self.blocks - .get_mut(id.index()) - .expect("block should exist in SSA function") - } - - fn set_terminator(&mut self, block: BlockId, terminator: SsaTerminator) { - let block_ref = self.block_mut(block); - if block_ref.terminator.is_some() { - panic!("attempted to reassign terminator for block {:?}", block); - } - block_ref.terminator = Some(terminator); - } - - fn ensure_jump(&mut self, block: BlockId, target: BlockId) { - if self.blocks[block.index()].terminator.is_none() { - self.blocks[block.index()].terminator = Some(SsaTerminator::Jump { target }); - } - } - - fn alloc_value(&mut self) -> ValueId { - let id = ValueId::new(self.next_value); - self.next_value += 1; - id - } - - fn lower_expr(&mut self, expr: &Expr) -> Result { - match expr { - Expr::Literal(v) => Ok(self.emit_const(v.clone())), - Expr::Var(name) => Ok(self.emit_param(name.clone())), - Expr::Unary(op, inner) => { - let operand = self.lower_expr(inner)?; - Ok(self.emit_unary(op.clone(), operand)) - } - Expr::Bin(lhs, op, rhs) => { - let lhs_val = self.lower_expr(lhs)?; - let rhs_val = self.lower_expr(rhs)?; - Ok(self.emit_binary(op.clone(), lhs_val, rhs_val)) - } - Expr::Conditional(cond, then_expr, else_expr) => self.lower_conditional(cond, then_expr, else_expr), - Expr::And(lhs, rhs) => self.lower_short_circuit(lhs, rhs, ShortCircuitKind::And), - Expr::Or(lhs, rhs) => self.lower_short_circuit(lhs, rhs, ShortCircuitKind::Or), - Expr::Call(name, args) => { - let positional = self.lower_expr_list(args)?; - Ok(self.emit_call(SsaCallTarget::Named(name.clone()), positional, Vec::new())) - } - Expr::CallExpr(callee, args) => { - let callee_val = self.lower_expr(callee)?; - let positional = self.lower_expr_list(args)?; - Ok(self.emit_call(SsaCallTarget::Value(callee_val), positional, Vec::new())) - } - Expr::CallNamed(callee, positional_args, named_args) => { - let callee_val = self.lower_expr(callee)?; - let positional = self.lower_expr_list(positional_args)?; - let named = self.lower_named_args(named_args)?; - Ok(self.emit_call(SsaCallTarget::Value(callee_val), positional, named)) - } - Expr::List(items) => { - let values = self.lower_expr_list(items)?; - Ok(self.emit_list(values)) - } - Expr::Map(entries) => { - let entries = self.lower_map_entries(entries)?; - Ok(self.emit_map(entries)) - } - Expr::StructLiteral { name, fields } => { - let fields = self.lower_struct_fields(fields)?; - Ok(self.emit_struct_literal(name.clone(), fields)) - } - Expr::Paren(inner) => self.lower_expr(inner), - other => Err(SsaLoweringError { - msg: format!("unsupported expression form for SSA lowering: {other:?}"), - }), - } - } - - fn lower_conditional( - &mut self, - cond: &Expr, - then_expr: &Expr, - else_expr: &Expr, - ) -> Result { - let cond_val = self.lower_expr(cond)?; - let pivot_block = self.current_block_id(); - let then_block = self.create_block(); - let else_block = self.create_block(); - let merge_block = self.create_block(); - - self.set_terminator( - pivot_block, - SsaTerminator::Branch { - cond: cond_val, - then_block, - else_block, - }, - ); - - self.switch_to_block(then_block); - let then_val = self.lower_expr(then_expr)?; - self.ensure_jump(then_block, merge_block); - - self.switch_to_block(else_block); - let else_val = self.lower_expr(else_expr)?; - self.ensure_jump(else_block, merge_block); - - self.switch_to_block(merge_block); - Ok(self.emit_phi(vec![(then_block, then_val), (else_block, else_val)])) - } - - fn lower_short_circuit( - &mut self, - lhs: &Expr, - rhs: &Expr, - kind: ShortCircuitKind, - ) -> Result { - let lhs_val = self.lower_expr(lhs)?; - let pivot_block = self.current_block_id(); - let rhs_block = self.create_block(); - let merge_block = self.create_block(); - let (then_block, else_block) = match kind { - ShortCircuitKind::And => (rhs_block, merge_block), - ShortCircuitKind::Or => (merge_block, rhs_block), - }; - - self.set_terminator( - pivot_block, - SsaTerminator::Branch { - cond: lhs_val, - then_block, - else_block, - }, - ); - - self.switch_to_block(rhs_block); - let rhs_val = self.lower_expr(rhs)?; - self.ensure_jump(rhs_block, merge_block); - - self.switch_to_block(merge_block); - let sources = match kind { - ShortCircuitKind::And => vec![(rhs_block, rhs_val), (pivot_block, lhs_val)], - ShortCircuitKind::Or => vec![(pivot_block, lhs_val), (rhs_block, rhs_val)], - }; - Ok(self.emit_phi(sources)) - } - - fn lower_expr_list(&mut self, exprs: &[Box]) -> Result, SsaLoweringError> { - let mut lowered = Vec::with_capacity(exprs.len()); - for expr in exprs { - lowered.push(self.lower_expr(expr)?); - } - Ok(lowered) - } - - fn lower_named_args( - &mut self, - named_args: &[(String, Box)], - ) -> Result, SsaLoweringError> { - let mut lowered = Vec::with_capacity(named_args.len()); - for (name, expr) in named_args { - let value = self.lower_expr(expr)?; - lowered.push((name.clone(), value)); - } - Ok(lowered) - } - - fn lower_map_entries( - &mut self, - entries: &[(Box, Box)], - ) -> Result, SsaLoweringError> { - let mut lowered = Vec::with_capacity(entries.len()); - for (key, value) in entries { - let key_id = self.lower_expr(key)?; - let value_id = self.lower_expr(value)?; - lowered.push((key_id, value_id)); - } - Ok(lowered) - } - - fn lower_struct_fields( - &mut self, - fields: &[(String, Box)], - ) -> Result, SsaLoweringError> { - let mut lowered = Vec::with_capacity(fields.len()); - for (name, expr) in fields { - let value = self.lower_expr(expr)?; - lowered.push((name.clone(), value)); - } - Ok(lowered) - } - - fn emit_const(&mut self, value: LiteralVal) -> ValueId { - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::Const(value), - }); - id - } - - fn emit_param(&mut self, name: String) -> ValueId { - let param = match self.param_indices.get(&name) { - Some(id) => *id, - None => { - let id = ParamId(self.params.len()); - self.params.push(name.clone()); - self.param_indices.insert(name.clone(), id); - id - } - }; - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::Param(param), - }); - id - } - - fn emit_unary(&mut self, op: UnaryOp, operand: ValueId) -> ValueId { - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::Unary { op, operand }, - }); - id - } - - fn emit_binary(&mut self, op: BinOp, lhs: ValueId, rhs: ValueId) -> ValueId { - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::Binary { op, lhs, rhs }, - }); - id - } - - fn emit_list(&mut self, elements: Vec) -> ValueId { - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::List(elements), - }); - id - } - - fn emit_map(&mut self, entries: Vec<(ValueId, ValueId)>) -> ValueId { - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::Map(entries), - }); - id - } - - fn emit_struct_literal(&mut self, name: String, fields: Vec<(String, ValueId)>) -> ValueId { - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::StructLiteral { name, fields }, - }); - id - } - - fn emit_call(&mut self, target: SsaCallTarget, positional: Vec, named: Vec<(String, ValueId)>) -> ValueId { - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::Call { - target, - positional, - named, - }, - }); - id - } - - fn emit_phi(&mut self, sources: Vec<(BlockId, ValueId)>) -> ValueId { - let mut operands = Vec::with_capacity(sources.len()); - for (block, value) in sources { - operands.push(PhiOperand { block, value }); - } - let id = self.alloc_value(); - self.current_block_mut().statements.push(SsaStatement { - result: id, - value: SsaRvalue::Phi { sources: operands }, - }); - id - } - - fn finish_return(&mut self, value: ValueId) { - let block = self.current_block_id(); - self.set_terminator(block, SsaTerminator::Return { value }); - } - - fn finish(self) -> SsaFunction { - SsaFunction { - entry: BlockId::ENTRY, - blocks: self.blocks, - params: self.params, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{expr::Expr, operator::BinOp, val::LiteralVal}; - - #[test] - fn lowers_simple_binary_expression() { - let expr = Expr::Bin( - Box::new(Expr::Var("a".into())), - BinOp::Add, - Box::new(Expr::Literal(LiteralVal::Int(1))), - ); - - let func = lower_expr_to_ssa(&expr).expect("lowering should succeed"); - assert_eq!(func.entry, BlockId::ENTRY); - assert_eq!(func.params, vec!["a".to_string()]); - assert_eq!(func.blocks.len(), 1); - - let block = &func.blocks[0]; - assert_eq!(block.statements.len(), 3); - - let const_stmt = block - .statements - .iter() - .find(|stmt| matches!(stmt.value, SsaRvalue::Const(_))); - assert!(const_stmt.is_some(), "expected constant statement"); - - let param_stmt = block - .statements - .iter() - .find(|stmt| matches!(stmt.value, SsaRvalue::Param(_))); - assert!(param_stmt.is_some(), "expected parameter statement"); - - let bin_stmt = block - .statements - .iter() - .find(|stmt| matches!(stmt.value, SsaRvalue::Binary { .. })); - assert!(bin_stmt.is_some(), "expected binary statement"); - - match block.terminator.as_ref() { - Some(SsaTerminator::Return { value }) => { - assert_eq!(value.index(), (block.statements.len() - 1)); - } - other => panic!("expected return terminator, got {other:?}"), - } - } - - #[test] - fn rejects_unsupported_expression() { - let expr = Expr::NullishCoalescing(Box::new(Expr::Var("lhs".into())), Box::new(Expr::Var("rhs".into()))); - let err = lower_expr_to_ssa(&expr).expect_err("nullish coalescing lowering is not yet supported"); - assert!( - err.to_string().contains("unsupported expression"), - "unexpected error: {err}" - ); - } - - #[test] - fn lowers_conditional_expression_into_multiple_blocks() { - let expr = Expr::Conditional( - Box::new(Expr::Var("flag".into())), - Box::new(Expr::Literal(LiteralVal::Int(1))), - Box::new(Expr::Literal(LiteralVal::Int(2))), - ); - - let func = lower_expr_to_ssa(&expr).expect("lowering should succeed"); - assert_eq!(func.params, vec!["flag".to_string()]); - assert_eq!(func.blocks.len(), 4); - - let entry = &func.blocks[BlockId::entry().index()]; - let (then_block, else_block) = match entry.terminator.as_ref() { - Some(SsaTerminator::Branch { - cond, - then_block, - else_block, - }) => { - assert_eq!(cond.index(), 0); - (then_block, else_block) - } - other => panic!("expected branch terminator, got {other:?}"), - }; - - let then_block_data = &func.blocks[then_block.index()]; - assert!(matches!( - then_block_data.statements.first().map(|stmt| &stmt.value), - Some(SsaRvalue::Const(LiteralVal::Int(1))) - )); - - let else_block_data = &func.blocks[else_block.index()]; - assert!(matches!( - else_block_data.statements.first().map(|stmt| &stmt.value), - Some(SsaRvalue::Const(LiteralVal::Int(2))) - )); - - let merge_block = func.blocks.last().expect("expected merge block"); - assert_eq!(merge_block.statements.len(), 1); - match &merge_block.statements[0].value { - SsaRvalue::Phi { sources } => { - assert_eq!(sources.len(), 2); - let mut source_blocks: Vec<_> = sources.iter().map(|operand| operand.block.index()).collect(); - source_blocks.sort_unstable(); - assert_eq!(source_blocks, vec![then_block.index(), else_block.index()]); - } - other => panic!("expected phi in merge block, got {other:?}"), - } - assert!(matches!(merge_block.terminator, Some(SsaTerminator::Return { .. }))); - } - - #[test] - fn lowers_named_call_with_list_and_map_arguments() { - let expr = Expr::Call( - "combine".into(), - vec![ - Box::new(Expr::List(vec![ - Box::new(Expr::Literal(LiteralVal::Int(1))), - Box::new(Expr::Literal(LiteralVal::Int(2))), - ])), - Box::new(Expr::Map(vec![( - Box::new(Expr::Literal(LiteralVal::Int(0))), - Box::new(Expr::Literal(LiteralVal::Int(42))), - )])), - ], - ); - - let func = lower_expr_to_ssa(&expr).expect("lowering should succeed"); - let entry = &func.blocks[BlockId::entry().index()]; - - let list_stmt = entry - .statements - .iter() - .find(|stmt| matches!(stmt.value, SsaRvalue::List(_))) - .expect("expected list statement"); - if let SsaRvalue::List(elements) = &list_stmt.value { - assert_eq!(elements.len(), 2); - } - - let map_stmt = entry - .statements - .iter() - .find(|stmt| matches!(stmt.value, SsaRvalue::Map(_))) - .expect("expected map statement"); - if let SsaRvalue::Map(entries) = &map_stmt.value { - assert_eq!(entries.len(), 1); - } - - let call_stmt = entry - .statements - .iter() - .find(|stmt| matches!(stmt.value, SsaRvalue::Call { .. })) - .expect("expected call statement"); - if let SsaRvalue::Call { - target, - positional, - named, - } = &call_stmt.value - { - match target { - SsaCallTarget::Named(name) => assert_eq!(name, "combine"), - other => panic!("expected named call target, got {other:?}"), - } - assert_eq!(positional.len(), 2); - assert!(named.is_empty()); - } - } - - #[test] - fn lowers_short_circuit_and_expression() { - let expr = Expr::And(Box::new(Expr::Var("lhs".into())), Box::new(Expr::Var("rhs".into()))); - - let func = lower_expr_to_ssa(&expr).expect("lowering should succeed"); - assert_eq!(func.params, vec!["lhs".to_string(), "rhs".to_string()]); - assert!(func.blocks.len() >= 3, "expected entry, rhs, and merge blocks"); - - let entry = &func.blocks[BlockId::entry().index()]; - let (rhs_block, merge_block) = match entry.terminator.as_ref() { - Some(SsaTerminator::Branch { - cond, - then_block, - else_block, - }) => { - assert_eq!(cond.index(), 0); - (then_block, else_block) - } - other => panic!("expected branch terminator, got {other:?}"), - }; - - let rhs_block_data = &func.blocks[rhs_block.index()]; - assert!( - rhs_block_data - .statements - .iter() - .any(|stmt| matches!(stmt.value, SsaRvalue::Param(ParamId(1)))), - "rhs block should contain rhs param read" - ); - - let merge_block_data = &func.blocks[merge_block.index()]; - let phi_stmt = merge_block_data - .statements - .iter() - .find(|stmt| matches!(stmt.value, SsaRvalue::Phi { .. })) - .expect("expected phi statement in merge block"); - if let SsaRvalue::Phi { sources } = &phi_stmt.value { - assert_eq!(sources.len(), 2); - let mut source_blocks: Vec<_> = sources.iter().map(|operand| operand.block.index()).collect(); - source_blocks.sort_unstable(); - assert_eq!(source_blocks, vec![BlockId::entry().index(), rhs_block.index()]); - } - - assert!(matches!( - merge_block_data.terminator, - Some(SsaTerminator::Return { .. }) - )); - } -} diff --git a/core/src/vm/ssa/escape.rs b/core/src/vm/ssa/escape.rs deleted file mode 100644 index 91061855..00000000 --- a/core/src/vm/ssa/escape.rs +++ /dev/null @@ -1,341 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::compat::prelude::*; -use core::cmp::max; - -use crate::vm::analysis::{EscapeClass, EscapeSummary}; - -use super::{SsaCallTarget, SsaFunction, SsaRvalue, SsaStatement, SsaTerminator, ValueId}; - -pub fn analyze(func: &SsaFunction) -> EscapeSummary { - let capacity = value_capacity(func); - if capacity == 0 { - return EscapeSummary { - return_class: EscapeClass::Trivial, - escaping_values: Vec::new(), - }; - } - - let mut classes = vec![EscapeClass::Trivial; capacity]; - let mut base_classes = vec![EscapeClass::Trivial; capacity]; - let return_values = collect_return_values(func); - - let mut changed = true; - while changed { - changed = false; - - for &ret in &return_values { - if mark_value(&mut classes, ret, EscapeClass::Escapes) { - changed = true; - } - } - - for block in &func.blocks { - for stmt in &block.statements { - let slot = stmt.result.index(); - let new_class = classify_rvalue(stmt, &classes); - base_classes[slot] = base_classes[slot].join(new_class); - if mark_slot(&mut classes, slot, new_class) { - changed = true; - } - - if mark_call_inputs_escape(stmt, &mut classes) { - changed = true; - } - - if classes[slot].is_escaping() && propagate_escape_to_operands(stmt, &mut classes) { - changed = true; - } - } - } - } - - let mut summary = EscapeSummary { - return_class: EscapeClass::Trivial, - escaping_values: Vec::new(), - }; - - for &ret in &return_values { - let class = base_classes.get(ret.index()).copied().unwrap_or(EscapeClass::Trivial); - summary.return_class = summary.return_class.join(class); - } - - for (idx, class) in classes.into_iter().enumerate() { - if class.is_escaping() { - if matches!(base_classes.get(idx), Some(EscapeClass::Trivial)) { - continue; - } - summary.mark_escaping(idx); - } - } - - summary -} - -fn value_capacity(func: &SsaFunction) -> usize { - let mut max_idx: Option = None; - for block in &func.blocks { - for stmt in &block.statements { - max_idx = Some(max(max_idx.unwrap_or(0), stmt.result.index())); - match &stmt.value { - SsaRvalue::Unary { operand, .. } => { - max_idx = Some(max(max_idx.unwrap_or(0), operand.index())); - } - SsaRvalue::Binary { lhs, rhs, .. } => { - max_idx = Some(max(max_idx.unwrap_or(0), lhs.index())); - max_idx = Some(max(max_idx.unwrap_or(0), rhs.index())); - } - SsaRvalue::List(values) => { - for value in values { - max_idx = Some(max(max_idx.unwrap_or(0), value.index())); - } - } - SsaRvalue::Map(entries) => { - for (key, value) in entries { - max_idx = Some(max(max_idx.unwrap_or(0), key.index())); - max_idx = Some(max(max_idx.unwrap_or(0), value.index())); - } - } - SsaRvalue::StructLiteral { fields, .. } => { - for (_, value) in fields { - max_idx = Some(max(max_idx.unwrap_or(0), value.index())); - } - } - SsaRvalue::Call { - positional, - named, - target, - } => { - for value in positional { - max_idx = Some(max(max_idx.unwrap_or(0), value.index())); - } - for (_, value) in named { - max_idx = Some(max(max_idx.unwrap_or(0), value.index())); - } - if let SsaCallTarget::Value(val) = target { - max_idx = Some(max(max_idx.unwrap_or(0), val.index())); - } - } - SsaRvalue::Phi { sources } => { - for operand in sources { - max_idx = Some(max(max_idx.unwrap_or(0), operand.value.index())); - } - } - SsaRvalue::Const(_) | SsaRvalue::Param(_) => {} - } - } - - if let Some(SsaTerminator::Return { value }) = &block.terminator { - max_idx = Some(max(max_idx.unwrap_or(0), value.index())); - } - } - - max_idx.map(|idx| idx + 1).unwrap_or(0) -} - -fn classify_rvalue(stmt: &SsaStatement, classes: &[EscapeClass]) -> EscapeClass { - match &stmt.value { - SsaRvalue::Const(_) => EscapeClass::Trivial, - SsaRvalue::Param(_) => EscapeClass::Local, - SsaRvalue::Unary { operand, .. } => classes.get(operand.index()).copied().unwrap_or(EscapeClass::Local), - SsaRvalue::Binary { lhs, rhs, .. } => classes - .get(lhs.index()) - .copied() - .unwrap_or(EscapeClass::Local) - .join(classes.get(rhs.index()).copied().unwrap_or(EscapeClass::Local)), - SsaRvalue::List(values) => values.iter().fold(EscapeClass::Local, |acc, value| { - acc.join(classes.get(value.index()).copied().unwrap_or(EscapeClass::Local)) - }), - SsaRvalue::Map(entries) => entries.iter().fold(EscapeClass::Local, |acc, (key, value)| { - let key_class = classes.get(key.index()).copied().unwrap_or(EscapeClass::Local); - let value_class = classes.get(value.index()).copied().unwrap_or(EscapeClass::Local); - acc.join(key_class).join(value_class) - }), - SsaRvalue::StructLiteral { fields, .. } => fields.iter().fold(EscapeClass::Local, |acc, (_, value)| { - acc.join(classes.get(value.index()).copied().unwrap_or(EscapeClass::Local)) - }), - SsaRvalue::Call { .. } => EscapeClass::Escapes, - SsaRvalue::Phi { sources } => sources.iter().fold(EscapeClass::Trivial, |acc, operand| { - acc.join( - classes - .get(operand.value.index()) - .copied() - .unwrap_or(EscapeClass::Local), - ) - }), - } -} - -fn mark_call_inputs_escape(stmt: &SsaStatement, classes: &mut [EscapeClass]) -> bool { - match &stmt.value { - SsaRvalue::Call { - target, - positional, - named, - } => { - let mut changed = false; - if let SsaCallTarget::Value(value) = target { - changed |= mark_value(classes, *value, EscapeClass::Escapes); - } - for arg in positional { - changed |= mark_value(classes, *arg, EscapeClass::Escapes); - } - for (_, value) in named { - changed |= mark_value(classes, *value, EscapeClass::Escapes); - } - changed - } - _ => false, - } -} - -fn propagate_escape_to_operands(stmt: &SsaStatement, classes: &mut [EscapeClass]) -> bool { - match &stmt.value { - SsaRvalue::Const(_) | SsaRvalue::Param(_) => false, - SsaRvalue::Unary { operand, .. } => mark_value(classes, *operand, EscapeClass::Escapes), - SsaRvalue::Binary { lhs, rhs, .. } => { - mark_value(classes, *lhs, EscapeClass::Escapes) | mark_value(classes, *rhs, EscapeClass::Escapes) - } - SsaRvalue::List(values) => { - let mut changed = false; - for value in values { - if mark_value(classes, *value, EscapeClass::Escapes) { - changed = true; - } - } - changed - } - SsaRvalue::Map(entries) => { - let mut changed = false; - for (key, value) in entries { - if mark_value(classes, *key, EscapeClass::Escapes) { - changed = true; - } - if mark_value(classes, *value, EscapeClass::Escapes) { - changed = true; - } - } - changed - } - SsaRvalue::StructLiteral { fields, .. } => { - let mut changed = false; - for (_, value) in fields { - if mark_value(classes, *value, EscapeClass::Escapes) { - changed = true; - } - } - changed - } - SsaRvalue::Call { - target, - positional, - named, - } => { - let mut changed = false; - if let SsaCallTarget::Value(value) = target - && mark_value(classes, *value, EscapeClass::Escapes) - { - changed = true; - } - for arg in positional { - if mark_value(classes, *arg, EscapeClass::Escapes) { - changed = true; - } - } - for (_, value) in named { - if mark_value(classes, *value, EscapeClass::Escapes) { - changed = true; - } - } - changed - } - SsaRvalue::Phi { sources } => { - let mut changed = false; - for operand in sources { - if mark_value(classes, operand.value, EscapeClass::Escapes) { - changed = true; - } - } - changed - } - } -} - -fn collect_return_values(func: &SsaFunction) -> Vec { - let mut values = Vec::new(); - for block in &func.blocks { - if let Some(SsaTerminator::Return { value }) = &block.terminator { - values.push(*value); - } - } - values -} - -fn mark_value(classes: &mut [EscapeClass], value: ValueId, target: EscapeClass) -> bool { - let slot = value.index(); - if let Some(class) = classes.get_mut(slot) { - let joined = class.join(target); - if *class != joined { - *class = joined; - return true; - } - } - false -} - -fn mark_slot(classes: &mut [EscapeClass], slot: usize, target: EscapeClass) -> bool { - if let Some(class) = classes.get_mut(slot) { - let joined = class.join(target); - if *class != joined { - *class = joined; - return true; - } - } - false -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::expr::Expr; - use crate::val::LiteralVal; - use crate::vm::ssa::lower_expr_to_ssa; - - #[test] - fn constant_expression_stays_trivial() { - let expr = Expr::Literal(LiteralVal::Int(42)); - let func = lower_expr_to_ssa(&expr).expect("lowering"); - let summary = analyze(&func); - assert_eq!(summary.return_class, EscapeClass::Trivial); - assert!(summary.escaping_values.is_empty()); - } - - #[test] - fn returning_list_marks_escape() { - let expr = Expr::List(vec![Box::new(Expr::Literal(LiteralVal::Int(1)))]); - let func = lower_expr_to_ssa(&expr).expect("lowering"); - let summary = analyze(&func); - assert_eq!(summary.return_class, EscapeClass::Escapes); - assert!(!summary.escaping_values.is_empty()); - } - - #[test] - fn call_arguments_escape() { - let expr = Expr::Call("foo".to_string(), vec![Box::new(Expr::Var("x".to_string()))]); - let func = lower_expr_to_ssa(&expr).expect("lowering"); - let summary = analyze(&func); - assert_eq!(summary.return_class, EscapeClass::Escapes); - assert!(!summary.escaping_values.is_empty()); - } - - #[test] - fn phi_joins_escape_information() { - let expr = Expr::Conditional( - Box::new(Expr::Var("flag".to_string())), - Box::new(Expr::List(vec![Box::new(Expr::Literal(LiteralVal::Int(1)))])), - Box::new(Expr::Literal(LiteralVal::Int(0))), - ); - let func = lower_expr_to_ssa(&expr).expect("lowering"); - let summary = analyze(&func); - assert_eq!(summary.return_class, EscapeClass::Escapes); - } -} diff --git a/core/src/vm/ssa/pipeline.rs b/core/src/vm/ssa/pipeline.rs deleted file mode 100644 index 39ccc299..00000000 --- a/core/src/vm/ssa/pipeline.rs +++ /dev/null @@ -1,242 +0,0 @@ -#[cfg(not(feature = "std"))] -use crate::compat::prelude::*; -use alloc::sync::Arc; - -use crate::operator::BinOp; -use crate::vm::alloc::{AllocationRegion, RegionPlan}; -use crate::vm::analysis::{FunctionAnalysis, PerfContainerFact, PerfValueFact, PerfValueKind, PerformanceFacts}; - -use crate::expr::Expr; -use crate::vm::ssa::{SsaFunction, SsaRvalue, escape, lower_expr_to_ssa}; - -/// Run the SSA pipeline (lowering + escape analysis) for an expression. -pub fn analyze_expr(expr: &Expr) -> Option { - match lower_expr_to_ssa(expr) { - Ok(ssa) => Some(run_analyses(ssa)), - Err(_) => None, - } -} - -fn run_analyses(ssa: SsaFunction) -> FunctionAnalysis { - let escape = escape::analyze(&ssa); - let region_plan = Arc::new(build_region_plan(&escape)); - let perf = build_performance_facts(&ssa, &escape); - FunctionAnalysis { - ssa: Some(ssa), - escape, - region_plan, - perf, - } -} - -fn build_performance_facts(ssa: &SsaFunction, escape: &crate::vm::analysis::EscapeSummary) -> PerformanceFacts { - let mut facts = PerformanceFacts::default(); - let mut escaping = vec![false; value_capacity(ssa)]; - for &value in &escape.escaping_values { - if escaping.len() <= value { - escaping.resize(value + 1, false); - } - escaping[value] = true; - } - - for block in &ssa.blocks { - for stmt in &block.statements { - let value_id = stmt.result.index(); - facts.ensure_value(value_id); - let value_facts = infer_rvalue_facts(&stmt.value, &facts); - let escape_class = if escaping.get(value_id).copied().unwrap_or(false) { - crate::vm::analysis::EscapeClass::Escapes - } else { - crate::vm::analysis::EscapeClass::Trivial - }; - facts.values[value_id] = PerfValueFact { - kind: value_facts.kind, - escape: escape_class, - move_preferred: !escape_class.is_escaping(), - must_clone: escape_class.is_escaping(), - }; - if let Some(list) = value_facts.list { - facts.set_value_list_fact(value_id, list); - } - if let Some(map) = value_facts.map { - facts.set_value_map_fact(value_id, map); - } - } - } - - facts -} - -#[derive(Debug, Clone, Copy, Default)] -struct InferredValueFacts { - kind: PerfValueKind, - list: Option, - map: Option, -} - -fn infer_rvalue_facts(value: &SsaRvalue, facts: &PerformanceFacts) -> InferredValueFacts { - match value { - SsaRvalue::Const(value) => InferredValueFacts { - kind: PerfValueKind::from_literal(value), - ..InferredValueFacts::default() - }, - SsaRvalue::Param(_) | SsaRvalue::Unary { .. } => InferredValueFacts::default(), - SsaRvalue::Binary { op, lhs, rhs } => { - let lhs = facts.value(lhs.index()).map(|fact| fact.kind).unwrap_or_default(); - let rhs = facts.value(rhs.index()).map(|fact| fact.kind).unwrap_or_default(); - let kind = match op { - BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Mod - if lhs == PerfValueKind::Int && rhs == PerfValueKind::Int => - { - PerfValueKind::Int - } - op if op.is_arith() && (lhs == PerfValueKind::Float || rhs == PerfValueKind::Float) => { - PerfValueKind::Float - } - op if op.is_cmp() => PerfValueKind::Bool, - _ => PerfValueKind::Unknown, - }; - InferredValueFacts { - kind, - ..InferredValueFacts::default() - } - } - SsaRvalue::List(values) => { - let list = PerfContainerFact { - value_kind: join_value_kinds( - values - .iter() - .filter_map(|value| facts.value(value.index()).map(|fact| fact.kind)), - ), - known_len: Some(values.len()), - adoptable: values.is_empty(), - }; - InferredValueFacts { - kind: PerfValueKind::List, - list: Some(list), - ..InferredValueFacts::default() - } - } - SsaRvalue::Map(entries) => { - let map = PerfContainerFact { - value_kind: join_value_kinds( - entries - .iter() - .filter_map(|(_, value)| facts.value(value.index()).map(|fact| fact.kind)), - ), - known_len: Some(entries.len()), - adoptable: entries.is_empty(), - }; - InferredValueFacts { - kind: PerfValueKind::Map, - map: Some(map), - ..InferredValueFacts::default() - } - } - SsaRvalue::StructLiteral { .. } | SsaRvalue::Call { .. } => InferredValueFacts::default(), - SsaRvalue::Phi { sources } => InferredValueFacts { - kind: join_value_kinds( - sources - .iter() - .filter_map(|source| facts.value(source.value.index()).map(|fact| fact.kind)), - ), - ..InferredValueFacts::default() - }, - } -} - -fn join_value_kinds(kinds: impl IntoIterator) -> PerfValueKind { - let mut iter = kinds.into_iter(); - let Some(first) = iter.next() else { - return PerfValueKind::Unknown; - }; - iter.fold(first, PerfValueKind::join) -} - -fn value_capacity(ssa: &SsaFunction) -> usize { - ssa.blocks - .iter() - .flat_map(|block| block.statements.iter().map(|stmt| stmt.result.index())) - .max() - .map(|idx| idx + 1) - .unwrap_or(0) -} - -fn build_region_plan(summary: &crate::vm::analysis::EscapeSummary) -> RegionPlan { - let mut plan = RegionPlan::default(); - for &value in &summary.escaping_values { - ensure_len(&mut plan.values, value + 1); - plan.values[value] = AllocationRegion::Heap; - } - plan.return_region = if summary.return_class.is_escaping() { - AllocationRegion::Heap - } else { - AllocationRegion::ThreadLocal - }; - plan -} - -fn ensure_len(vec: &mut Vec, len: usize) { - if vec.len() < len { - vec.resize_with(len, Default::default); - } -} - -#[cfg(test)] -mod tests { - #[cfg(not(feature = "std"))] - use crate::compat::prelude::*; - use crate::{expr::Expr, val::LiteralVal, vm::analysis::PerfValueKind}; - - use super::analyze_expr; - - #[test] - fn performance_facts_record_list_container_shape() { - let analysis = analyze_expr(&Expr::List(vec![ - Box::new(Expr::Literal(LiteralVal::Int(1))), - Box::new(Expr::Literal(LiteralVal::Int(2))), - ])) - .expect("analysis"); - let ssa = analysis.ssa.as_ref().expect("ssa"); - let value_id = ssa.blocks[ssa.entry.index()] - .statements - .last() - .expect("list statement") - .result - .index(); - - assert_eq!( - analysis.perf.value(value_id).map(|fact| fact.kind), - Some(PerfValueKind::List) - ); - let list = analysis.perf.value_list(value_id).expect("list fact"); - assert_eq!(list.value_kind, PerfValueKind::Int); - assert_eq!(list.known_len, Some(2)); - assert!(!list.adoptable); - } - - #[test] - fn performance_facts_record_map_container_shape() { - let analysis = analyze_expr(&Expr::Map(vec![( - Box::new(Expr::Literal(LiteralVal::from_str("answer"))), - Box::new(Expr::Literal(LiteralVal::Int(42))), - )])) - .expect("analysis"); - let ssa = analysis.ssa.as_ref().expect("ssa"); - let value_id = ssa.blocks[ssa.entry.index()] - .statements - .last() - .expect("map statement") - .result - .index(); - - assert_eq!( - analysis.perf.value(value_id).map(|fact| fact.kind), - Some(PerfValueKind::Map) - ); - let map = analysis.perf.value_map(value_id).expect("map fact"); - assert_eq!(map.value_kind, PerfValueKind::Int); - assert_eq!(map.known_len, Some(1)); - assert!(!map.adoptable); - } -} diff --git a/core/src/vm/type_info.rs b/core/src/vm/type_info.rs index feee865c..08d654fc 100644 --- a/core/src/vm/type_info.rs +++ b/core/src/vm/type_info.rs @@ -1,4 +1,5 @@ -//! Static type declarations carried from the compiler to every back end. +//! Static `trait`/`impl`/`struct` declarations carried from the compiler to +//! every back end. //! //! # Why this exists //! @@ -18,6 +19,10 @@ //! [`TypeInfo`] is that knowledge kept in its structured form and carried //! through `ModuleArtifact`, so a back end reads it instead of rebuilding it. //! +//! A value's own type identity is **not** here: see [`crate::val::DeclaredType`]. +//! The two halves shared this file and referenced each other **not once** — +//! which is how the value half ended up under `vm/` unnoticed. +//! //! # Representation notes //! //! Types are stored as their `Type::display()` text rather than a structured @@ -50,126 +55,8 @@ #[cfg(not(feature = "std"))] use crate::compat::prelude::*; -use alloc::sync::Arc; use serde::{Deserialize, Serialize}; -/// Identity of the module that *declares* a named type. -/// -/// # Why a declared type needs more than its name -/// -/// `struct Point` in `a.lk` and `struct Point` in `b.lk` are different types. -/// The runtime used to disagree: an object carried only `"Point"` and the -/// dispatch table was keyed by that bare string, so whichever module registered -/// last owned the name for the whole context — `a.mk(1).tag()` returned `b`'s -/// answer. The same missing half made a *transitive* import fail outright: the -/// importer collected impls one level deep, so a value built by a module its -/// own dependency imported had no reachable methods at all. -/// -/// Both are the same hole: identity lived in a name, and a name is only unique -/// inside one module. -/// -/// # Why the declaring module is the right scope -/// -/// A struct literal can only name a type declared in the same compilation unit -/// — an imported struct is not constructible (`Point { .. }` in the importer is -/// "Unknown struct 'Point'") and not nameable in an annotation. So the module -/// executing the construction *is* the module that declared the type, and -/// stamping the object at construction needs no extra compiler plumbing. -/// -/// # Representation -/// -/// The normalized source path for a file module, so the identity is stable -/// across processes and can ride in a `ModuleArtifact`. Modules with no file -/// behind them (the entry program, `eval`-style sources, tests) get -/// [`TypeScope::anonymous`], which is distinct from every path and from other -/// anonymous scopes only by being the single scope of that run — good enough, -/// because nothing can import them. -#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -pub struct TypeScope(Arc); - -impl TypeScope { - /// The scope of a module loaded from `path` (already normalized by the - /// resolver). - pub fn from_path(path: &str) -> Self { - Self(Arc::::from(path)) - } - - /// The scope of a module with no file behind it. - pub fn anonymous() -> Self { - Self(Arc::::from("")) - } - - /// The one scope shared by every `impl` whose target is a **builtin** type - /// (`impl Doubler for Int`). - /// - /// A builtin type is not declared by anybody, so it has no declaring module - /// to be scoped to and every module means the same `Int`. Filing those - /// impls per-module would be wrong in the other direction: the receiver is - /// a bare `5` with no module attached, so the lookup could never find them. - /// - /// TODO(coherence): two modules that both `impl Doubler for Int` still - /// collide here, last registration winning, because a global type genuinely - /// admits only one impl. Rejecting the overlap needs an orphan rule, which - /// is a language decision rather than a dispatch fix. - pub fn builtin() -> Self { - Self(Arc::::from("")) - } - - /// Whether this is the shared scope for builtin types (see - /// [`Self::builtin`]), which admits only one impl per trait. - pub fn is_builtin(&self) -> bool { - self.0.as_ref() == "" - } - - pub fn as_str(&self) -> &str { - &self.0 - } - - /// Pointer identity — the same scope value, not merely an equal one. - /// - /// Every module hands out clones of one `Arc`, so this answers "still the - /// same module?" without a string compare. The executor asks that on every - /// activation, which is why it is worth not spelling `==` there. - #[inline] - pub fn is_same(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } -} - -impl Default for TypeScope { - fn default() -> Self { - Self::anonymous() - } -} - -/// The full identity of a declared type: which module declared it, and its -/// name. Neither half identifies a type on its own. -/// -/// Kept as one heap-allocated value that instances share by `Arc`, rather than -/// as two fields on every object. `RuntimeObject` is the largest `HeapValue` -/// variant and therefore sets the size of *every* heap cell — list, map, string -/// and all — so widening it by a second fat pointer measurably slowed programs -/// that contain no structs at all (~1.3% on the workload suite). One thin -/// pointer instead of the previous bare `Arc` name makes objects smaller -/// than they were before scoping. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct DeclaredType { - pub scope: TypeScope, - pub name: Arc, -} - -impl DeclaredType { - pub fn new(scope: TypeScope, name: Arc) -> Self { - Self { scope, name } - } -} - -impl core::fmt::Display for TypeScope { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str(&self.0) - } -} - /// One `trait` declaration: the method names it requires and their declared /// types (as display text). #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] @@ -221,7 +108,11 @@ pub struct ImplMethod { /// One `impl Trait for Type` block. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ImplDecl { - pub trait_name: String, + /// `None` for an inherent `impl Type { … }`: the methods belong to the + /// type, not to a trait it satisfies. Dispatch never needed the trait — it + /// keys on the target type — which is why an *empty* trait plus an impl of + /// it was the workaround before the syntax existed. + pub trait_name: Option, /// Target type as display text (the key both back ends dispatch on). pub type_name: String, pub methods: Vec, @@ -236,13 +127,50 @@ pub struct ImplDecl { pub struct TypeInfo { pub traits: Vec, pub impls: Vec, + /// Each `struct` this module declares, with its field names in declaration + /// order — what `display` prints them in. See [`DeclaredType::fields`]. + #[serde(default)] + pub structs: Vec, +} + +/// One `struct` declaration: its name and its fields, in order. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct StructDecl { + pub name: String, + /// Fields in declaration order. + pub fields: Vec, +} + +/// One field of a `struct`. +/// +/// The *type* is carried alongside the name because a declared field type is +/// the only thing that says what a field read produces. Without it every +/// `p.count` was a boxed `Dyn` to native lowering however plainly the +/// declaration said `count: Int` — so the arithmetic around it boxed too, and +/// a loop variable fed from a field could not stay an integer at all. +/// +/// A field with no annotation has `None`, which is the same `Any` it always +/// was. The text is `Type::display()`, read back with `Type::parse` — the +/// convention `TraitDecl` and `ImplDecl` already use. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct StructFieldDecl { + pub name: String, + pub ty: Option, } impl TypeInfo { - /// Whether the module declared no traits or impls — the common case, kept - /// cheap so callers can skip work entirely. + /// Whether the module declared no traits, impls or structs — the common + /// case, kept cheap so callers can skip work entirely. pub fn is_empty(&self) -> bool { - self.traits.is_empty() && self.impls.is_empty() + self.traits.is_empty() && self.impls.is_empty() && self.structs.is_empty() + } + + /// The fields of a `struct` this module declares, in declaration order. + pub fn struct_fields(&self, name: &str) -> Option<&[StructFieldDecl]> { + self.structs + .iter() + .find(|decl| decl.name == name) + .map(|decl| decl.fields.as_slice()) } /// The declaration of whichever impl method compiled to `function`. diff --git a/core/src/vm/verify.rs b/core/src/vm/verify.rs index 40d07278..540c2281 100644 --- a/core/src/vm/verify.rs +++ b/core/src/vm/verify.rs @@ -195,6 +195,7 @@ impl FunctionVerifier<'_> { | Opcode::SubInt | Opcode::MulInt | Opcode::DivInt + | Opcode::FloorDivInt | Opcode::ModInt | Opcode::AddMulInt | Opcode::Add2Int @@ -268,6 +269,7 @@ impl FunctionVerifier<'_> { | Opcode::ToIter | Opcode::ToString | Opcode::Not + | Opcode::Neg | Opcode::IsNil | Opcode::IsList | Opcode::IsMap @@ -388,19 +390,6 @@ impl FunctionVerifier<'_> { )); } } - Opcode::LoadNative => { - self.check_reg(pc, "a", instr.a())?; - let index = instr.bx() as usize; - if index >= self.module.natives.len() { - return Err(self.fail( - pc, - format_args!( - "LoadNative index {index} out of bounds ({} natives)", - self.module.natives.len() - ), - )); - } - } Opcode::MakeClosure => { self.check_reg(pc, "a", instr.a())?; let callee_index = instr.b() as usize; diff --git a/docs/aot/aot-gaps-and-lkrt.md b/docs/aot/aot-gaps-and-lkrt.md index df2d95b3..5dd04282 100644 --- a/docs/aot/aot-gaps-and-lkrt.md +++ b/docs/aot/aot-gaps-and-lkrt.md @@ -202,3 +202,2293 @@ LLVM 会把 `xs.slice(..)`、`xs.sort()` 等**就地**操作 lower 成 `src == d 导致个别测试断言(`select i1` 来自 `@lk_i64_decimal_len`、`call i32 @strcmp` 来自 map helper)其实在测样板而非被测程序。下沉删除这些定义后,应改断被测 lowering 自身产物 (如 bool 常量返回折叠成静态串经 `@lk_str_fmt` 打印;模板比较分解为 `icmp eq i64`)。 + +## 9. 已落地:impl 方法里的 `self` 带类型出身(2026-07-30) + +**`self` 在 `impl T { … }` 的方法里就是一个 `T`。** + +devirtualization 此前只认一个来源的类型出身:`NewObject` 写进 +`ssa.struct_types` 的那份(`lower_call.rs`)。而 impl 方法里的接收者是**参数**, +它一辈子见不到 `NewObject`,于是 `lower_trait_method_k` 两条路都不匹配 —— +静态那条要 `struct_types` 有记录,动态那条要接收者是 `Dyn`。落到通用分发, +报 `an operand at pc 1 is a str where a i64 is required`。 + +被挡住的是**「一个方法建立在这个类型的其它方法之上」**这个形状,而方法多半 +就是这么写的;trait 的默认方法体更是**只能**这么写(它不能提字段名,不然对 +别的实现者就不成立)。所以在这条修好之前,一个有意义的 trait 默认实现示例 +根本进不了 `examples/`(coverage 门禁要求每个 example 全原生降低)。 + +做法:`function.rs` 在给参数建 SSA 值时,若参数 0 的类型是 `MapStrDyn` 且这个 +函数是某个 impl 块的方法(`TraitEnv::impl_owner`,`impls` 表的逆),就把该类型 +写进 `struct_types`。 + +**一个函数登记在两个类型名下时不给答案。** 编译器可以共享函数体,而一份被复制 +进两个 impl 的默认方法恰好就是两段一模一样的体。这时随便答一个会把 +`self.other()` devirt 到**错的** impl —— 那是错答案,不是拒绝。`impl_owner` +因此在发现歧义时返回 `None`。 + +### 同日续:**没人调用的 impl 方法**也不能用 `I64` 兜底 + +上面那条修完之后 `t4` 形状还是拒,报的是 `in `B::base`: an operand at pc 1 is a +str where a i64 is required` —— 而 `B::base` 在那个程序里**从来没被调用过**。 + +两件事凑在一起:每个 impl 方法都是降低的 root(trait 的每条臂都必须存在), +而没有调用点的参数类型走 `param_ty` 的默认值 `I64`。于是一个没人调用的方法 +按"参数是整数"降低,体里一读字段就炸,整个模块跟着掉回 Tier 0 —— 起因是一个 +谁也没调的方法。 + +`param_ty` 现在对 impl 方法的参数 0 给 `MapStrDyn`:`self` 是结构体实例, +调用点说什么都不改变这件事,**包括一个调用点都没有的时候**。 + +同一个洞还有另一半:`self` 之外的参数。`fn add(self, x: String)` 没人调用时 +`x` 同样默认成 `I64` —— 而声明里就写着 `String`,`FunctionData` 却没有把参数 +类型带下来。与其把声明一路传下去,更正确的修法是**根本不给它降低**:impl +方法之所以是 root,是因为 trait 分发经注册表到达它们、调用扫描看不见;而一个 +**没有任何调用点提到其名字**的方法,分发也到不了。`CallMethodK` 是唯一的方法 +调用 opcode 且方法名取自常量池,所以"哪些方法名会被调用"是可以精确算出来的 +(`called_method_names`)。 + +**例外必须显式列出。** `show` 由显示点(`"${value}"`)到达,没有任何 +`CallMethodK` 提它 —— 把它从 root 里剪掉会留下悬空 callee,模块直接 MIR +验证失败(`examples/syntax/macros.lk` 当场变红)。所以有一份 +`IMPLICIT_METHOD_HOOKS`,`lower_method::apply_show` 的查表和 root 计算**用的 +是同一个常量**,而不是两处各写一个 `"show"`。以后再加隐式钩子,只有一个地方 +要改。 + +这条也是上面那条能生效的前提:诊断此前指不到人。impl 方法从来没有 +`debug_name`,所有关于它们的 AOT 报错都是光秃秃的 `an operand at pc 1 …`; +现在它们叫 `Type::method`(`compile_impl_method_function_indexed`)。**是这个 +名字直接指出了真凶**——在那之前我一直在错的函数上找。 + +## 10. 已落地:bundle 把依赖的 `impl` 也搬过来(2026-07-30) + +编译时 bundle(`use "../general/fib"`)把依赖的**函数**、globals、常量都搬进了 +合并 artifact,唯独没搬 `type_info.impls`。于是合并出来的 artifact 手里有一个 +导入 `impl` 的**函数体**,却没有"它们实现了什么"这条记录 —— AOT 的 trait 环境 +(`trait_env_prescan`,读的就是 `type_info.impls`)看不见它们,所以**每一个** +跨模块方法调用都掉出原生子集,而同样的代码写在定义方模块里降低得好好的。 + +现在 `impl` 声明跟着搬,方法索引用同一张 remap 重写。一个类型在两个 bundle +模块里同名实现会**报错而不是择一** —— VM 靠 `TypeScope` 把它们分得开,bundle +分不开,按这个 bundler 其余地方的规矩:说出原因,不要替人选。 + +配合上一条(构造函数返回值带类型出身),`types.Pt { x: 3, y: 4 }.norm()` 现在 +全原生。`examples/syntax/use_forms.lk` 覆盖了它,coverage 57 → 58。 + +## 11. 已落地:类型名跨函数边界(2026-07-30) + +**根子是"类型名只从 `NewObject` 那一个地方流出来"。** 那条唯一的出口在函数 +边界上断掉,于是 `make(3, 4).norm()` 的接收者无类型可用,方法调用掉出 +devirtualize 路径 —— 同一个模块里也一样。这是这一族的第四个入口(前三个: +`self` 参数、没人调用的方法、构造函数返回值)。 + +`SigInfer::ret_structs` 记每个函数**返回的结构体名**,在返回点由 +`ssa.struct_types` 读出、跨返回点求交(两个不同结构体、或有一个返回不是结构体 +→ 不给答案,因为只有时候对的名字会 devirt 到错的 impl),再由两个调用发射器 +(`lower_user_call` / `emit_call_with_args`)在结果上播下。它进了定点的 +snapshot,所以先于被调方降低的调用方会在下一趟拿到。 + +**顺带删掉一个特例**:上一条我给构造函数的返回值按 `$new` 名字播过出身;通用 +规则把它包住了(构造函数的体就是 `return S { … }`,`NewObject` 出身直接得到 +`ret_structs = "S"`)。一个机制取代一个命名约定。 + +基准 geomean 1.006x,无回退。 + +### 仍然开着的一个,**不是**跨模块特有的 + +**结构体在模板串里显示**:`"${p}"` 不降低,本地同样。`ToString` 走 +`apply_display_show`,只有注册了 `show` 的类型有出路;没有 `show` 的结构体在 VM +里有默认显示(`Pt{x:3,y:4}`,按声明序),native 没有对应物。 + +**试过并撤回的捷径(2026-07-30,别再走同一条)**:在降低点把渲染**内联展开**。 +降低点确实什么都知道 —— 类型名在 `ssa.struct_types`,字段序在 +`type_info.structs` —— 所以渲染可以写成"常量片段 + 每字段一次 +`dyn.display_quoted`"的拼接链,不需要任何运行时表。顶层结构体逐字正确,连 +`P{name:"a, b",n:-3,ok:true,f:1.5}` 的引号和负号都对。 + +**但嵌套结构体给错答案**:字段里的结构体在运行时只是个 `str→Dyn` map, +`dyn.display_quoted` 把它渲染成 `{"ok":true,…}`(hash 序),而 VM 给 +`P{name:…}`。而"这个字段会不会装结构体"在降低点**判定不了** —— 字段的类型不在 +手上,值是 `Dyn`。也就是说这条捷径是一个我检测不出来的错答案生成器,所以撤回, +恢复成响亮拒绝。 + +**嵌套不可回避,所以运行时表也不可回避。** 已按这条落地(同日): + +- **每个声明的 struct 都拿到 type id**,不再只有带 impl 的那些 —— id 也是 + `display` 找类型名和字段序的钥匙,没有方法的结构体照样要打印。 +- **entry 前奏把类型描述交给运行时**:每个类型一次 `obj_ty.begin(tid, name)`, + 之后每个字段一次 `obj_ty.field(tid, name)`。用调用序列而不是静态数据表, + 是因为这样 codegen 不需要任何新东西 —— 用的都是 ABI 已有的 `I64`/`StrPtr`。 +- **lkrt 的 display 认这个标记**:`DYN_MAP` 且有标记且类型有描述 → 渲染 + `Name{f:v,…}`,值走同一个 display,于是**嵌套自然递归**。没有标记的 map 仍然 + 按 map 渲染(它的序是布局的序,故意不在原生子集里)。 +- **`to_display_str` 的 `MapStrDyn` 从"拒绝"改成走 `dyn.display*`**。 + +逐字核对过的形状:字符串字段带引号(`P{name:"a, b",…}`)、负数、Float、空 +结构体 `E{}`、嵌套结构体、结构体里装列表、`println(p)` 与 `"${p}"` 两条路径。 +差分语料里那条用例**专门钉住嵌套**,因为那正是上面那条捷径栽的地方。基准 +geomean 1.001x。 + +### 同日续:模板插值里的容器,是照着一条退休的裁决在拒绝 + +结构体列表 `"${[P{v:1}]}"` 追下去发现:`ToString`/`ConcatString`/`ConcatN` 都传 +`containers: false`,而那是 `docs/semantics.md` 里一条**已经过时**的裁决 —— +"`ToString`/模板插值是标量 only,容器是响亮失败"。VM 早就不是那样了 +(`"${xs}"` 就是 `[1,2,3]`,`"m=${m}"` 就是 `m={"k":1}`),只有这一侧还在照办。 + +后果是:任何模板里带 list 或结构体列表的程序都掉回 VM。**答案一致,只是慢**, +所以差分门禁不会红 —— 是手写探针撞上的。四处 `false` 改成 `true`,文档那条改 +正,差分语料补上。基准 geomean 1.013x。 + +留在原生子集外的只剩 map(hash 迭代序不可移植)和 Set,各有自己的理由。 + +## 12. 形状扫描:门禁看不见的回落(2026-07-30) + +上一条("照着退休的裁决拒绝")说明了一类门禁**结构上**看不见的缺口:两个后端 +答案一致,只有 native 慢。差分测试锁"双方一致",coverage 锁"每个 example 全 +降低",两者都不问"这个形状本来该不该降低"。 + +所以手写了两轮扫描:**参数类型 × `try`**(9 种)与**常见语言形状**(20 个)。 +前者一发命中最后一个缺口(`Float` 参数),后者找出五处,其中已修: + +- **`[1,2,3].index_of(2)`** —— VM 在每种序列上都有 `index_of`,降低只在 `Str` + 上有。补 `list_h/i64_index_of`(未命中给 nil,`Int?` 的 boxed 形式)。 + +**扫描里踩的一次**:同一批里 `[1,2,3].join("-")` 也拒绝,我以为是同类缺口, +补了 `i64_join` 让它降低 —— 然后**逐字比对 VM 才发现 VM 自己是拒绝的** +("ListJoin list must contain only strings",与 Python 一致)。原注释和守卫都是 +对的,我的改动会让 native 答出 VM 拒绝的东西。已撤回,并在方法臂旁写明为什么 +`join` 不在 `index_of` 旁边。 + +**教训**:扫描给出的是"native 拒绝"的清单,不是"缺口"的清单。每一条都要先问 +VM 怎么答 —— 对照的基准永远是 VM,不是"看起来该能行"。 + +### 仍开着的三处(见任务 #56/#57 与下) + +- **`StoreCellVal`**:闭包写它捕获的变量(`let add = |v| { acc = acc + v; };`)。 + 可变捕获编译成 cell,cell 的写入还没有原生降低。 +- **`chan.new` + `send`/`recv`**:`register r2 is read at pc 2 before any + definition`。 +- **`"hi".bytes()`**:`Call` 不可降低。 + +## 13. 闭包改自己捕获的变量(2026-07-30) + +`let add = |v| { acc = acc + v; };` —— 一个累加闭包,也就是闭包这件事本身最 +常见的用法 —— 让**整个程序**掉回 VM。 + +捕获走的是隐藏尾参:调用点把 cell 的**当前内容**取出来传进去。对只读的捕获 +这是对的,对写的捕获则是"写没有落点",于是 `StoreCellVal` 那条臂上写着"a +by-value capture parameter has no write-back path"直接拒绝。 + +要的载体其实早就有:`Ty::Cell`(`rt.cell_new/get/set`),`try` 体对外层局部量 +赋值就是靠它跨边界的。缺的只是**判据** —— 哪个捕获需要它。 + +判据没有去字节码上猜寄存器出身(`LoadCapture` 落到哪个寄存器、`Move` 传到 +哪),而是用降低本身已有的收敛回路,和 `dyn_rets`/`try_body_params` 同一个 +办法:体降低到那条赋值,发现捕获是按值来的,就把 `(函数, 捕获下标)` 记进 +`SigInfer::cell_captures` 并请求重试;下一趟调用方看到这条事实,seed 一个 +`rt.cell_new`、按 `Ty::Cell` 传、调用后 `cell_get` 读回父函数的槽。事实来自 +"体真的降低到了那里",不来自猜。 + +一个坑:`param_obs` 跨趟只增不清,所以第一趟按值观测到的 `I64` 会和 `Cell` +join 成 `Dyn`,调用点连 cell 指针都塞不进去。因此记事实的同时要把那个参数槽 +**pin** 成 `Ty::Cell`(`SigInfer::require_cell_capture`)。 + +只读捕获仍按值传 —— 一个只读的捕获被拖进 cell 是白付一次装箱。 + +**还没通的**:内层 lambda 写外层 lambda 的捕获(捕获链要一级级传下去),见 +todos #87。 + +### 13.1 嵌套闭包(2026-07-30) + +内层 lambda 写外层 lambda 的捕获: + +```lk +let total = 0; +let outer = |v| { + let inner = |w| { total = total + w; }; // 写的是 outer 捕获的东西 + inner(v); +}; +``` + +`MakeClosure` 只认 `GlobalRef::Cell(cid)`(父函数自己有的 cell)。这里父函数是把 +它当**捕获参数**拿着的,没有 cid 可指,`ssa.read` 于是在那个寄存器上找不到值, +报 "register r2 is read at pc 2 before any definition" —— 整个程序回落。 + +加了 `ClosureCapture::CellParam(k)`:父的第 k 个捕获再传下去。父的捕获已经是 +`Ty::Cell` 时**指针直接穿过去** —— 父子共用一个 cell,正是 VM 的语义;还不是 +cell 时,子的需求**往上传**:调用点把它记到父身上并请求重试,于是 +`cell_captures` 在整条链上收敛。三层也是这么通的。 + +`spawn`、`try` 区域、以及被擦除的闭包环境这三处一开始不解析 `CellParam`,标了 +TODO;2026-08-17 补齐(见 §26)。 + +## 14. `base64` / `hex` / `url`(2026-07-30) + +`String -> String` 的那半边有了原生实现:`base64.encode`、`hex.encode`、 +`url.encode_component`、`url.decode_component`。lkrt 用**与 stdlib 模块同一个 +crate**(`base64`、`hex`),所以文本逐字节相同 —— 和 `datetime` 用 chrono、 +`json` 用 serde_json 是同一个理由。 + +`url.encode_component` 是先修了才镜像的:它和 `decode_component` 不往返(编码是 form +编码、解码只撤 `%XX`),见 `docs/semantics.md`。所以 lkrt 里那份是手写的百分号编码, +和 stdlib 里手写的那份同一套未保留集。 + +`base64.decode` / `hex.decode` 给 `Bytes`,原生没有那个承载类型(见 §13 之前的 +Bytes 一节 / todos),继续回落。 + +## 15. `Bytes` 是一个原生值(2026-07-30) + +`Bytes` 以前在原生一侧**没有承载类型**,于是 `"hi".bytes()`、`bytes` 模块的每个 +成员、`base64.decode` / `hex.decode` 出现任意一个,整个程序回落。 + +`Ty::Bytes` 是个不透明指针句柄,和 `List`/`Map`/`Set` 同形 —— 十个接点(MIR 的 `Ty` +与渲染、codegen 的类型映射、lower 的两张"这是句柄"表、显示、相等、`len` 快路、 +`GetIndex`、方法表、模块表)。它必须是**独立的类型**而不是裸句柄整数,因为显示和相等 +都要知道它是字节:`println(b)` 是 `Bytes([104,105])` 而不是一个指针,`==` 比内容。 + +**lkrt 里曾经有两个 `Bytes`。** 另一个是 `tcp`/`fs` 用的**一次性** host 句柄 +(`HandleKind::Bytes`,用 `take_bytes` 读,读走就没了)。对"读一次 socket 然后解一次 +码"是对的,对**值**是错的:`bytes.len(b)` 之后再 `bytes.to_string_utf8(b)`,第二次就 +找不到句柄了。所以 `tcp.read` / `fs.read` 现在都给 arena 句柄,一次性那套(资源变体、 +两个访问器、`bytes.to_string_utf8`/`bytes.free` 两条 ABI 项)整套删掉 —— 让它们分开的 +理由消失了,留着就是个陷阱。`fs.read` 因此也第一次拿到了降低行:它一直有 ABI 项而没有 +行,因为没有类型可给。 + +`bytes.from_list` / `to_list` 也接上了(2026-07-30):`List` 在 lkrt 里就是 +`Vec` 的 arena 句柄,和 `Bytes` 同形,所以两个方向都只是一次转换。不是字节的值 +**raise**,和 stdlib 模块一样 —— 一个不是字节的"字节"是个错误,不是要静默截断的东西。 +`bytes` 模块的十个成员现在全部原生。 + +## 16. 闭包调另一个闭包(2026-07-30) + +```lk +let f = |x| x + 1; +let g = |x| f(x) * 2; // 以前:整个程序回落 +``` + +组合两个 lambda 是"有 lambda"这件事本身最主要的用途。`f` 被捕获,所以编译器把它放进 +一个 **cell**,而进那个 cell 的是一个**降低期的引用**,不是值 —— `StoreCellVal` 去读 +SSA 值,读不到。 + +两半: + +1. `Ssa::cell_refs` —— 整个内容就是一个可调用引用的 cell。存进去时记下引用、不写槽; + 读出来时把引用还回去。一个 cell 只能有一个引用,同时又被赋别的东西就拒绝(回落), + 而不是猜后面那次读想要哪个意思。 +2. `SigInfer::ref_captures` —— 被调方那侧。引用在这里没有运行时表示,所以那个捕获仍然 + 占着 ABI 槽位(一个死的 `0`),**意思**走这张表。由调用方发现并请求重试,和 + `cell_captures` 同一个回路。 + +**只收无捕获的可调用**(`Lambda` / `UserFn`)。`Closure(fidx, caps)` 里的 `ValueId` 属于 +建造它的那个函数,在读 cell 的人那里什么也不指 —— 记下来就等于把不存在的操作数递给 +读者。它拒绝,而且是**故意**拒绝,不是碰巧。 + +### 16.1 捕获环境全是静态引用时,整个擦掉 + +`[1,2,3].map(|x| f(x))` 还差一步:那个 lambda 实参**有**一个捕获(被捕获的 `f`),而 +列表 HOF 的类型化快路(`i64_map_fn` 等)只认无捕获的可调用 —— 它调回调时只递一个元素, +再没有别的。 + +但是:一个捕获环境**全是静态引用**的闭包,运行时什么都不需要传 —— 它就等价于一个裸 +函数引用。所以 `SigInfer::captures_all_static` 为真时,`lower_function` 干脆不声明那些 +参数,`MakeClosure` 直接给 `GlobalRef::Lambda`,于是各处(包括 HOF 快路)都把它当普通 +函数引用看。 + +**全有或全无**,这是有意的:混合环境需要在某一个下标上留个洞,而每个调用点都得同意洞 +在哪。死槽位那种形式本来就把混合情形处理对了,只是白费一个寄存器。 + +还没通的一种:`let n = 2; let f = |x| x * n; let g = |x| f(x) + 1;` —— 被调的那个自己 +带捕获,正是上面故意拒绝的那条。 + +## 17. `try` 体里的 `return`(2026-07-30 调查,未实现) + +```lk +fn f(n: Int) -> Int { + try { return n * 2; } catch e { return -1; } // 整个程序回落 +} +fn g(n: Int) -> Int { + let v = try { n * 2 } catch e { -1 }; return v; // 原生 +} +``` + +同一个意思两种写法,一种慢三倍。拒绝的理由在 `try_region.rs` 里写着:body 被外联成 +一个函数,里面的 `return` 会变成"从 body 返回"而不是"从外层函数返回",而"然后返回" +这个协议还没有。 + +**做法(2026-07-30 已实现)**:body 本来就有两条通道 —— 返回值走 `LkDyn`,raise 走 +trampoline 的 outcome。加的是第三个信号,用的是现成的**输出 cell** 机制 +(`try_body_cells`:父建 cell、当额外实参传进去、调用后读回): + +1. 多一对输出 cell:一个"是否返回了"的标志,一个返回值。只给**体里真的有 return** + 的 region 加(`SigInfer::try_body_returns`),别的 region 传的东西一个不变。 +2. body 侧:`Return v` 降低成 `cell_set(flag, 1); cell_set(value, box v)`,然后正常 + 返回,让 trampoline 报"没有 raise"。 +3. 父侧:ok 边不再直接去 fallthrough,而是去一个**检查块**:读标志,真就去一个 + **返回块**,假就转发到原来的 fallthrough。 + +第 3 步本来的顾虑是"新块会成为 fallthrough / handler 的新前驱,phi 要重排"。**不需要**: +检查块转发时用的是 `args_to(区域块, fallthrough)` —— 也就是区域块本来要传的那一份实参。 +目标的 phi 操作数仍然记在区域块名下,而这里正是读它的地方。所以插入是局部的。 + +**"体里每条路都 return"也通了。** 我一开始把它读成一条独立的拒绝:那种 region 在字节码 +里没有跳过 handler 的 `Jmp`,看着像"没有 ok 边"。它不是 —— ok 边照样存在,只是**恒定** +走返回那一支。当时看到的崩溃来自两个自己的 bug(一份 python 补丁在第二处断言失败时整份 +没写盘;`Return` 那条臂用 `continue` 跳过了循环末尾存指令的那行),不是 CFG 的性质。 +拒绝去掉之后六种"每条路都 return"的形状全部原生。 + +教训是具体的:**一个自造的 bug 会长得很像一条语言性质**。当"这里需要一条新规矩"这个 +念头是从崩溃里冒出来的,先把崩溃归零再判断。 + +这条**曾被当作** `defer` 在 raise 路径上跑的前置条件。它做完之后,那个改动也做了、也 +能跑,然后**因为另一个代价被撤回**:把整个函数体包进 `try`,会让体里赋值的每个寄存器都 +变成区域的**输出 cell**,而寄存器复用意味着那是其中大多数;cell 往返对标量有定义,对 +**容器句柄故意没有**(`unbox_from_dyn` —— 读回成错的类型化句柄是错答案,不是拒绝)。于是 +`examples/syntax/defer.lk` 当场掉出原生。详见 `core/src/stmt/defer.rs`。 + +所以真正的前置条件是**容器句柄的 cell 往返**,不是这一条。 + +其中**一半随即做了**(2026-07-30):本来就装箱的容器按指针往返 —— `dyn.from_list` / +`from_map` 只是给句柄打个 tag,`as_list` / `as_map` 查 tag 后把同一个指针还回来,身份和 +它捎带的修改都在。于是 `List` 和 `Map` 现在能跨区域。 + +**类型化容器随后也通了**(同日):它们要的是**保身份的 cell** —— 句柄原样停在 +`DYN_RAW` 这个 tag 下,不装箱,所以拿回来的是同一个指针。两端(`cell_get` / +`cell_get_raw`)都查 tag,所以"把裸的当装箱的读"是**响亮失败**,而不是一个 `Vec` +被当成 `Vec` 走 —— 这条是这个设计敢做的前提。判据只有一处 +(`function::cell_is_raw`),调用方(seed 和读回)和体(每次赋值时写)读的是同一个函数, +两边不可能各说各话。 + +覆盖到:类型化 list、类型化 map、`Set`、`Bytes`。 + +### 17.1 第二个区域(同日,一条独立的旧账) + +上面记下的"容器区域 + 第二个区域仍然回落"查清了,而且**先于**容器跨区域就有:两个 dyn +容器区域一样回落。它跟容器无关,是**归因**的问题。 + +体在自己的帧里写了寄存器又没捎回来,父帧那一份就被 poison;之后谁读到它,失败信息 +(`UndefinedOperand`)正是定点用来发现"哪些寄存器需要 cell"的那条线索 —— 这一步是对的, +它把"区域之后还有人读吗"这个活性问题变成了向 SSA 提问,而不是给每个 opcode 写一张读操作数表。 + +坏在这条线索**只说了寄存器号,没说是谁毒的**。消费端于是把 cell 派给函数里**每一个** +区域。而寄存器号是会被复用的:第一个体拿 r2 当过草稿(存 `[2]` 这个字面量),第二个变量 +`b` 也正好分到 r2。第一个区域于是收到一个 r2 的 cell —— 可它在**自己的区域开始处从来没有 +定义过 r2**,做种就得在 pc 0 之前读它,于是整个函数回落。也就是说:**给一个函数加第二个 +`try`,会让第一个 `try` 丢掉降低**,而两个都单独写时各自都好。 + +修法是让 poison 记住是谁毒的(`poisoned: Vec>>`,存 body id),错误带着它 +走,消费端就不用猜了。没有归因的 `UndefinedOperand` 是一次普通的未定义读,cell 修不了它 —— +那条瞎猜的兜底一并删掉,覆盖率一个没掉,说明它从来没干过活。 + +判据仍然是"读到了才算",没有变成预测。`several_try_regions_share_a_function` 钉住五种形状 +(容器区域接标量区域、三种类型三个区域、区域进循环、两个区域都 raise、函数里两个区域串起来)。 + +## 17.2 map 字面量与 map 显示(2026-07-30) + +两个洞在同一处碰头,而且互相遮掩。 + +**一、`NewMap` 没有降低。** 值不全是常量的字面量 —— `{"k": a}`、`{"a": f(3)}` —— +走的是 `NewMap`,而它根本没有降低,于是"从算出来的东西拼一条记录"这种再普通不过的 +程序整个掉回 VM。对应的 list 拼写 `[a, a + 1]` 一直是原生的,这正是它没被发现的原因: +两种字面量读起来一样,只有一种能编译。 + +修法是走**和常量 map 完全同一条路**:`lit_new` / `lit_set` 按字面量序累积装箱的键值对, +`lit_finish_<形状>` 转成类型化表示。于是这里的形状判据只需要**照抄常量那几条臂** +(并透过它们照抄 VM 的 `typed_map_from_entries`),而不是长出第二套会各自漂移的分类。 + +**二、显示类型化 map 照着一条退休的裁决拒绝。** 原话是"map 的顺序是底层 hash 迭代序, +两个运行时不共享"。这条**先于 `lkrt/src/vm_mirror.rs`** —— 而那个模块存在的全部意义就是 +让两边共享它,`lit_protocol_matches_vm_iteration_order` 直接拿 `lk-core` 比对。更明显的是, +`MapStrDyn` 那条臂早就放行了:裁决对一个 map 类型解除,对其余的留着,于是 +`println({"a": 1})` 让程序丢掉降低,`println({"a": 1, "b": "x"})` 不会。 + +现在字符串键的三种(`str_i64` / `str_f64` / `str_bool`)按载体自身的迭代序在 lkrt 里渲染, +**不重建** —— 顺序这个问题因此只问一次。 + +**三、顺带撞出来的错答案。** `println([m])` 打的是 `{"a":1}`,而 VM 打 `[{"a":1}]`。 +`NewList` 的任何一条臂都装不下类型化 map,于是**什么都没物化**,目标寄存器只剩下 +ArgList 那一半视图(`NewList` 同时也是方法调用的实参窗口),读到它的调用就把元素当成了 +列表本身。补了装箱之外,还给 `NewList` 加了一条兜底:非空却没有任何一条臂物化出句柄, +是**回落**,不是静默拆包。覆盖率一个没掉。 + +**四、整数键:一条潜伏的错答案,顺手挖出来修掉。** VM 对非字符串键**不做第二阶段** +—— `typed_map_from_entries` 直接返回 `Mixed`,那就是 stage-1 那张表 —— 而 +`lit_finish_i64_i64` / `i64_f64` 又 rehash 进 `FxMap`:哈希不是一回事 +(`i64` 对 `RtKey::Int(i64)`),插入序也不是。`{1: 1.5, 2: 2.5}` VM 迭代 `2,1`, +native 迭代 `1,2`。 + +它当时没人看得见(整数键 map 的 display 和 `.keys()` 都不降低),所以是**潜伏**的 —— +但"潜伏"的意思是:下一个给整数键 map 降低迭代的人会拿到一个错答案,而且没有任何东西 +会告诉他。所以修的是载体,不是绕开它: + +- `vm_mirror::IntKey` 按 `RtKey::Int` 哈希(判别式写死成常量而不是现构一个 32 字节的 + 枚举,`int_key_hashes_like_the_mirror_enum` 负责说这两个是同一个); +- `LitBuilder` 除了 stage-1 表还记一条**字面量序**。这不是冗余:字符串键有 stage 2, + 所以它的 finisher 迭代表;非字符串键**没有** stage 2,它的 finisher 必须重放字面量的 + 插入序列 —— 在那里迭代表就等于多跑了一个 VM 没跑过的阶段。 +- `int_lit_protocol_matches_vm_iteration_order` 拿 `lk-core` 的 + `typed_map_iteration_int_keys` 逐条比对,五组键(含 64 个键、逼出多次扩容)。 + +于是整数键的 display 也进了子集,字面量和逐个赋值两条路都钉在差分里。 + +## 17.3 装箱不能改变表示(2026-07-30) + +`DYN_RAW` 的注释早就把这条规矩写下来了 —— 类型化容器的装箱是逐元素转换,往返一趟 +拿回来的是另一个容器 —— 但**类型化 map 的装箱一直在违反它**:`str_i64_to_dyn` +把 map 重建成 `str -> Dyn` 的,按迭代序往一张新表里插。 + +新表由**不同的插入序列**填成,布局就不一样。只要历史里有过删除,两张表的迭代序 +就分岔,于是 `println([m])` 打出的条目顺序是 VM 永远不会给的 —— **错答案,不是回落**。 +它有两条到达路径:结构体字段持有 map(长期存在),和 map 进 list / map +(补可装箱元素时新加的)。没有删除时两张表的布局重合,所以它藏得住。 + +改法是照着那条规矩来:类型化 map **原地装箱**,标签说明它是哪个载体(五个载体, +五个标签)。重建只剩一处 —— `typed_map_keyed`,它唯一的消费者是相等,而相等与顺序 +无关,这一点写在那个函数头上。 + +顺带,相等因此要能**跨表示**比较:`{"a": 1} == {"a": 1.0}` 是同一张 map 的两种 +拼写,标签不同只是存储细节。所以 `dyn_eq_inner` 在标签相等检查**之前**先处理 +"两边都是 map" 这一类,并且仍然先比结构体标记。 + +整数键 map 顺势全通了(显示 / 相等 / 进容器 / 迭代),形状矩阵(13 种值 × 9 种 +操作)因此 **172/172**。 + +## 18. `task.join_all`(2026-07-30) + +变参,所以**没有哪一行能描述它** —— 一行只有一个 arity。三种拼写里, +`join_all(a, b)` 和 `join_all(a)` 是同一个循环(逐个 `rt.task_await`,推进一个 dyn +列表),现在原生;`join_all([a, b])` 传的是一个 `List` 句柄,长度只有运行时才知道, +需要 lkrt 里的一个循环而不是在这里展开 —— 那一种仍然回落。 + +元素显示是这条的验收点:dyn 列表对字符串的引号必须和 VM 的类型化列表**逐字**一样 +(`["x","y z"]`),混合类型也一样(`[1,"s"]`)。差分语料里两种都在。 + + +## 19. 不可达块(2026-08-05) + +不可达的字节码块曾让**整个函数**拒绝降低。这类块没有前驱,`Ssa::read_recursive` +的 `preds.is_empty()` 分支直接答 `UndefinedOperand`;而它同时是后继块的前驱,于是 +把定义从后继的交集里也抹掉 —— 报出来的寄存器往往是函数**自己的形参**: + + fn h(n: Int) -> Int { + if n > 0 { return 1; } else { return 2; } + let z = n + 1; // 不可达 + return z; + } + → MIR lowering: register r4 is read at pc 12 before any definition + +字节码也可以来自 `.lkm` 文件,后端不能建立在"前端从不发不可达代码"这个前提上。 +`lower_function` 因此在算完边之后从块 0 做一次可达性:不可达块不贡献前驱边,不被 +降低,在 MIR 里留一个空块、终结子指向自己。 + +同一处还有两个相邻的洞: + +- **隐式返回块的分配条件不全。** 它只在有显式跳转越过末尾时才分配,而函数末尾 + 可以本来就没有 Ret(全臂都 `return` 的 match 之后不再补隐式返回),于是 + `block_of(code_len)` panic。现在末块没有 exit 时也分配。 +- **落到末尾与返回值冲突时报得不清楚。** 一条路径返回值、另一条落到末尾(答 nil), + 过去会生成 `-> i64` 函数里的 `ret void`,由 Cranelift 验证器报错。这与两条 + `return` 互相矛盾是同一件事,现在同样答 `ReturnTypeConflict`,干净回落。 + +门禁在 `cli/tests/aot_differential_test.rs` 的 `differential_control_flow`: +`match_arms_return`、`code_after_a_total_if`、`binding_arm_catches_nil`。 + +## 20. 列表字面量的载体也可以被后续 push 推翻(2026-08-05) + +`let xs: List = [1, 2]; xs.push("a");` 此前在原生侧回落: + + MIR lowering: an operand at pc 3 is a str where a i64 is required + +VM 的做法是把 `TypedList::Int` 就地拓宽成 `Mixed`;原生的 `Vec` 做不到这一步。 +原来记的判据是"别的别名按静态类型直接读这块内存",在容器改成不变(docs/semantics.md +「可变容器不再是协变的」)之后不成立了 —— 不变意味着同一个值在每个名字上的元素类型 +相同。剩下的问题只是**载体在构造时就定了**。 + +空 `[]` 早就有这条通路:降低时抛可重试的 `EmptyListGuessWrong`,定点记下字面量的 pc, +下一轮把它建成 Dyn 列表。同质字面量与空字面量在这件事上没有区别 —— 两者的元素类型都 +是一个后续 push 可以推翻的判断 —— 所以现在共用同一条通路,名字也按这个含义改了: + +| 原名 | 现名 | +| --- | --- | +| `Unsupported::EmptyListGuessWrong` | `Unsupported::ListElemTypeContradicted` | +| `Ssa::dyn_empty_pcs` | `Ssa::dyn_list_pcs` | +| `Ssa::empty_guess` | `Ssa::literal_list_ty` | +| `FnSigs::dyn_empty_lists` | `FnSigs::dyn_lists` | + +两处字面量都记录并都认这个标记:常量列表(`LoadHeapConst`)和寄存器窗口列表 +(`NewList`)。被推翻的那个从构造起就是 `list_h.dyn_new` + 逐元素 `to_dyn` + `dyn_push`。 + +门禁在 `cli/tests/aot_differential_test.rs` 的 `differential_lists`: +`widened_after_a_typed_literal`(Int/Float/Str 三种载体各推翻一次)、 +`widened_from_a_register_window`(变量元素、循环里 push)。 + +### 20.1 map 载体同理(同日) + +`let m: Map = {"a": 1}; m["b"] = "x";` 与空 map 的同一形状都回落。这两条 +与列表那条是同一件事在另一个载体族里,而 map 侧一条通路都没有 —— 连"空字面量猜错就 +重试"都只在列表侧存在。 + +补齐三处: + +- **存储臂。** `SetIndex` 上没有 `Ty::MapStrDyn` 的臂,所以就算把 map 建成 Dyn 载体, + 也没有东西能存进去(`SetFieldK` 那侧本来就有,给结构体字段用)。 +- **字面量记录。** 空 `{}` 与非空字面量都记进 `literal_carrier`,并都认 + `dyn_literal_pcs`:被推翻的那个用 `map_h.str_dyn_new` / `lit_finish_str_dyn` 重建。 +- **报重试而不是直接拒。** `SetFieldK` 与 `SetIndex` 的类型化 map 臂原来答 + `TypeMismatch`,现在先问 `carrier_contradicted`。 + +"谁的载体被推翻了"这条判断从 push 那侧的闭包提成一个函数 `carrier_contradicted`, +列表与 map 共用 —— 一条规则,一处实现。名字也再改了一轮以覆盖两族: +`ListElemTypeContradicted` → `LiteralElemTypeContradicted`,`dyn_list_pcs` → +`dyn_literal_pcs`,`literal_list_ty` → `literal_carrier`,`dyn_lists` → `dyn_literals`。 + +门禁:`differential_maps` 的 `widened_after_a_typed_literal`、 +`widened_from_an_empty_literal`。 + +## 21. 静态上只可能 raise 的操作:发 raise 还是回落(2026-08-06 裁决) + +`let xs = [1]; xs[5].len();` —— `xs[5]` 的静态类型是元素类型 `Int`,`len()` 作用在整数 +上只可能 raise。VM 报 "`len()` works on a String, List, Map, Set, Bytes or Slice, got …"; +原生侧 `Opcode::Len` 的 `_` 分支答 `TypeMismatch`,整个模块回落。 + +仓库里有一条相反方向的先例:`SetIndex` 上的 Float 键 —— "no map carrier accepts one, +so the store can only raise. Emitting the raise keeps the rest of the program native — +refusing sent the whole thing back to the VM to produce the same error." + +**这里不照做。** 差别在消息的份数: + +- Float 键那处是**一句**固定文本,复制一份、两端各写一次是可控的。 +- 类型错误是**每个方法一族**:`len` / `push` / `slice` / `sort` / … 每个都有自己的措辞, + 还带被拒类型的渲染。复制它们等于把"被捕获的错误消息就是 stdout"(见 + docs/semantics.md 的两端逐字裁决)这条约束铺到几十处,每一处都会漂。 +- 回落这条路产生的是 **VM 自己**那句消息,按构造就是对的,永远不会漂。 + +而收益是负的:这个形状的程序执行到那一行就死,"其余部分留在原生"没有意义 —— 与 +Float 键不同,那条可以在一个正常程序里被数据触发。覆盖率门禁 60/60,语料里也没有程序 +命中这条。 + +所以规矩是:**只可能 raise 的操作,当它的消息是固定一句时发 raise,当它属于一个按类型 +措辞的族时回落。** 前者省下的是程序其余部分的速度,后者省下的是两端消息不一致。 + +## 22. bundler 要合并的不只是 `impl`,还有 `struct` 声明(2026-08-06) + +`use "geo"; println(geo.P { x: 4 })` 原生打 `{"x":4}`,VM 打 `P{x:4}`。 + +链条:`trait_env_prescan` 给**每个已声明的 struct** 发一个 type id,`NewObject` +只在有 id 时发 `map_h.obj_mark`,而 lkrt 的显示靠这个标记去查类型名与字段序。 +CLI 的 AOT bundler 把 dep 的 `type_info.impls` 重编号后并进了 merged artifact +(§见 `cli/src/main.rs` 里那段注释:没有它,跨模块方法调用整段掉出原生子集), +却没有并 `type_info.structs`。于是导入来的类型拿不到 id,标记不发,显示退回 +到载体本身 —— 一个 str-dyn map。 + +方法分发看不出来:它读的是编译期的 `ssa.struct_types`,那条路从 `NewObject` +的类型名常量拿名字,与运行时标记无关。所以 `a.norm()` 一直是对的,只有输出 +是错的,而且只在类型声明在另一个文件时。**没有任何测试覆盖"跨文件构造 + +显示"这一格**,尽管跨文件构造和跨文件方法调用各自都有测试。 + +合并规则与 `impls` 那条一致:同名而字段不同则拒绝编译。VM 按 `TypeScope` 把 +两个同名类型分得开,bundle 只有一张 id 表,分不开。 + +同一天的相邻改动让这条从"命名空间写法独有"变成"两种写法都会踩": +`use { P } from "geo"` 此前在 AOT 侧绑不到任何东西(`bundles[b].fns` 里只有 +`P$new`,没有 `P`),整个程序回落到 Tier 0,反而打对了。补上 `$new` 回退之后 +它开始原生降低,也就开始踩这条。 + +## 23. 类型化列表的拓宽:VM 就地变 `Mixed`,原生 raise(2026-08-06 实测) + +```lk +fn sink(v: Any) -> Int { v.push(7); return 0; } +fn main() -> Int { + let a: List = [1]; + let s: List = ["q"]; + sink(a); sink(s); + println("${a} ${s}"); + return 0; +} +main(); +// VM: [1,7] ["q",7] native: Error: runtime type error +``` + +**VM 对,原生错。** 判据是逐类探出来的,不是读实现读出来的: + +| 写法 | VM | native | +| --- | --- | --- | +| `s.push(7)`(字面量,`s: List`) | 检查期拒(#194) | 同 | +| `let v: Any = 7; s.push(v)` | `["q",7]` | `["q",7]` | +| 单一载体流进 `sink(v: Any)` | `["q",7]` | 回落 | +| **两种载体**流进同一个 `sink(v: Any)` | `["q",7]` | **raise** | + +第二行说明"拓宽"就是这门语言的运行时规矩,所以第四行是原生侧的缺陷。 + +链条:`dyn.list_push` → `lklist::typed_list_push`,它用 `dyn_as_i64` / +`as_f64` / `as_str` 转换元素,不合型就 `raise_str("runtime type error")`。VM 的 +`TypedList` 则就地拓宽成 `Mixed`,所有别名都看得见。 + +### 四条便宜的路都实测否掉了 + +1. **降低期一律拒绝 `Ty::Dyn` 接收者的 push**。覆盖率仍 60/60,扫描仍 + `identical=61`,分歧变回落 —— 但打掉了 + `clif_differential_test::a_boxed_typed_list_is_the_same_list` 的 + `writes_cross_the_box_both_ways`(`c[0].push(9)`,推入类型匹配、本来正确)。 +2. **精确的静态规则**。要区分 `c[0]`(`List`)和 `sink` 的 `v`(`Any`), + 需要接收者的 **LK 静态类型**,而 AOT 只看到 `Ty::Dyn`;编译器在 `ListPush` + 处没有元素类型的 fact,加一条要动 artifact 版本。 +3. **装箱时重建成 `Vec`**。回到 §"类型化列表装箱是重建"修掉的那条, + 同样打掉 `writes_cross_the_box_both_ways`。 +4. **lkrt 里就地拓宽**。句柄是 `*mut Vec`,而每个别名持有自己那份 tag; + 要让拓宽对所有别名可见,tag 必须在**对象里**而不在 `LkDyn` 副本里 —— + 那就是表示层改造本身。 + +所以修法唯一:让类型化列表的载荷能改 kind 而句柄保持有效(三个 +`DYN_TLIST_*` tag 已经存在,缺的是那层间接和 lkrt 里 29 处读点)。任何修法 +必须保住 `writes_cross_the_box_both_ways`。 + +## 24. 方法名常量先进池子:上限从"第 129 个方法调用"回到"256 个方法名"(2026-08-06) + +130 个 struct、各一个方法、`main` 里逐个 `Sk { x: k }.mk()` —— `lk compile` 报 +"the call at pc 1293 is not natively lowerable"。128 个可以,130 个不行。 + +反汇编: + + 1287 LoadString r8 #257 ← 方法名常量下标 257 + 1293 Call r10 r10 r3 ← 通用 Call,不是 CallMethodK + +`CallMethodK` 是 abc 形式(7 位 opcode + 8 位 A + 1 位 K + 8 位 B + 8 位 C, +32 位已占满),`b` 装方法名的常量下标,只有 8 位。`lower_dynamic_method_call` +里 `name_const <= u8::MAX` 不成立就退回 `__lk_call_method` 的通用调用 —— +那条 AOT 降低不了,于是整个程序回落。编译器注释把这称作 "(pathological)"。 + +**它不是病态输入。** 常量池是**按函数**的,结构体名、字段名、方法名共用一个; +130 个 struct 字面量先占掉 260 个格子,方法名就被挤过了 255。逐类分离过, +单独哪一维都不封顶:同一方法调 400 次、普通函数调 1000 次、一个类型 200 个 +方法、200 个类型同一方法名 —— 全都降低。 + +修法不动编码:降低函数体(以及顶层入口)**之前**,先把这个体里调用到的方法名 +压进它的常量池。方法名下标落在 0..N,门槛变成编码本身说的那个数 —— +一个函数里 256 个不同方法名。实测悬崖从 129 移到 250~260,两端答案一致。 + +`a_function_may_call_two_hundred_distinct_methods` 断言的是**指令**不是能否 +编译:退回那条路照样产出可运行的程序,只有 opcode 说得清走的是哪条。 +反向验过:去掉预压,它红。 + +geomean 0.987x。 + +## §25 包依赖也进 bundle(2026-08-06) + +`examples/lk-example-workspace/apps/demo/src/main.lk` 是 VM/原生扫描里**唯一**的回落。 +它 `use mathlib;`(一个工作区依赖)然后调 `mathlib.double(n)`;bundler 只认**文件**导入 +(`use "./m.lk"`),包导入不进队,于是那次调用落到 `lower_module`,而它只认 stdlib —— +整程序退到 Tier 0 的 VM bundle,约 3 倍慢,没有任何提示。 + +包依赖就是一个 `.lk` 文件,它产生的绑定与文件导入同形,所以按同一条路走: + +1. `package_import_modules`(CLI):`PackageGraph::discover` 把每一种指向包的拼写解析成 + `(绑定名, 入口文件)`,和文件导入一起入队。四种拼写都答:`use dep;`、`use dep as n;`、 + `use { item } from dep;`、`use * as ns from dep;` —— 后两种没有自己的模块对象绑定, + 所以 bundle 按**模块名**做键,与降低那侧查它的方式一致。 +2. `ImportEnv::build`(降低):四条臂都先查 bundle,查不到才按 stdlib 的读法绑定 —— + `Module` / `ModuleAlias` / `Namespace` 绑成文件命名空间,`Items` 把条目绑成合并后的 + 函数下标(与文件那条分支同款,含 `S$new` 回退)。缺这一步时 bundle 建好了却没人查。 + +扫描从 `identical=61 diverged=1 fallback=1` 变成 `identical=62 diverged=1 fallback=0`, +门禁期望值同步更新(`scripts/vm_native_sweep.sh`、`docs/testing.md`)。 + +## §26 `try` 区域:闭包入参、cell 入参、嵌套(2026-08-17) + +三件事一起做,因为它们是同一个问题的三面 —— region body 被外联成独立函数,于是 +"外层帧里的什么东西能跨过边界"这个问题要对每一类东西单独答一次。 + +**闭包入参。** lambda 在原生侧没有运行时表示,它是编译期 `GlobalRef`,所以 +`try { r = inner(); }` 里的 `inner` 没有可 marshal 的机器字,body 读它报 +`ReferenceAsValue`。按擦除 lambda 实参的老办法走:**身份走编译期** +(`SigInfer::try_body_lambdas`,body 把寄存器 seed 成 ref),**环境走运行时** +(每个捕获一个字,capture 顺序)。两侧都按 `try_body_params` 顺序走,所以布局不用 +写在任何地方。 + +**cell 入参。** 被任意闭包捕获的变量是 cell:寄存器持 `GlobalRef::Cell`,内容在虚拟 +slot 里。region 只要**读**一下这种变量就拒绝(body 的 `LoadCellVal` 找不到 ref)。 +这不是罕见形状 —— 函数里任何 lambda 提到的参数都是,生成语料里 191 个嵌套 region +程序只有 2 个能原生化。改成跨**运行时 cell**:父从 slot 建一个,body 把它当 +capture parameter 收(`inst::global` 本来就会用 `rt.cell_get`/`cell_set` 读写这种), +父在 region 后把 slot 读回来。父自己已经持指针时(它本身是 body 或闭包)直接传下去, +三层帧同一个 cell。 + +**嵌套。** `try` 里的 `try` 原来直接拒。放开只要两步:`scan` 只认本函数**自己**的 +region(内层属于 body,body 自己被扫时才轮到它),外联循环改成在**增长的**表上走。 +但放开之后暴露了三处静默错答,每一处都是"某个东西只在指令循环里被处理": + +1. 内层 region 的写回发生在 **terminator** 里,而"把改过的寄存器镜像进本体自己的 + cell"只在指令循环里做 —— 内层 body 的赋值就这么丢了。抽出 `mirror_cells`, + terminator 之后再跑一次,`rebound` 同理。 +2. 本体自己的 cell,内层 region 写了也要带回来。原来的判据是"region 之后有人读", + 而那个读者在**上一帧**,本体自己不读。所以 `try_body_cells[本体]` 直接并进内层 + region 的 cell 集合。 +3. 内层 body 的 `return` 经 return channel 交给本体,而本体自己**也**是 body 时, + 那个 `return` 还得再往上一层交。check block 原来直接 `Term::Ret`,于是值被读出来 + 又丢掉(body 自己的返回类型是 `Nil`)。 + +还有一个不是静默的:trampoline 的 arity switch `default` 是 `__builtin_trap()`,而 +预算只数了 inputs + cells,没数 return channel 的两个 cell,也没把 lambda 入参按 +capture 数展开。七个 input 加一个 return 的 body 编译、链接、然后第一次进 region 就 +`SIGILL`。预算改成精确计数,codegen 侧再加一道 `LK_TRY_MAX_ARGS` 拒绝,于是那个 +`trap` 从构造上不可达。 + +**cell 入参是有类型的。** cell 本身是动态类型的,读出来是 `Dyn`,而 `Dyn` 算术没有 +降低 —— 于是"被捕获的变量在 region 里参与运算"这个最常见的形状还是回落 +(`if (p0 % 5 == 0)`,只因为函数里某个 lambda 提到了 `p0`)。父在建 cell 时把**内容 +类型**记下来(`try_body_cell_input_tys`),body 的读按那个类型 unbox,写的时候类型对不上 +就把这一项并到 `Dyn` 重试 —— 两端不可能对同一个 cell 持两种意见。三处 unbox +(region 输出 cell、return channel、cell 入参的读)合成一个 `unbox_cell_value`,`Bool` +的窄化不会在其中两处记得、第三处忘掉。 + +这一步把原生化从 176/836 提到 306/919,同时**暴露了上一条留下的一个静默错答**: +跨进 region 的闭包,它的 `Cell` 捕获原来是按值快照的,而 body 之后会通过自己的运行时 +cell 写同一个变量 —— `try { a = a * 2; a = clo(); }` 原生答 `3`,VM 答 `6`。改成: +闭包的捕获若命中本 region 的 cell 入参,就**拿那个 cell**(`require_cell_capture` 把 +callee 的捕获钉成 `Ty::Cell`,收敛回路照旧)。为此 region 入参的 marshal 拆成两遍 —— +先建 cell,再解析 lambda 环境 —— 每个入参的机器字按位置收集,最后按 `try_body_params` +顺序摊平,所以布局仍然是 body 走的那个。 + +**`performance` facts 按 pc 重基,不再整个丢掉。** 外联本来把 facts 全清空,理由是 +"pc 会重基,读到错位的 fact 比没有更糟"。但这条流水线只读两张表 —— `for_loops` +(`cfg::exit_of`)和 `key_ops`(`inst::container`)—— 而 body 的 pc 就是父的 pc 减 +`body_start`,所以重基是一次**切片**,切片不会产生错位。两张表里也都没有 pc +(`PerfForLoopFact` 的跳转是偏移量)。代价是具体的:`for` 循环**必须**有 fact,于是 +region 里一句普通的 `for i in 0..n` 就整程序回落 —— 那是生成语料里剩下最多的一类。 +其余的表是 VM 执行器的,而外联出来的 body 从不被 VM 执行(它只存在于本 crate 自己的 +函数表里),保持 default。 + +门禁:随机生成的含 `try` 程序,六批不同种子共 1688 个,0 处分歧;facts 重基后原生化 +从 306/919 提到 494/1066(约 46%)。fuzzer 加了嵌套 region + 闭包入参的形状; +`examples/syntax/try_catch.lk` 把三种形状钉进覆盖率门禁。剩下的回落主要是 trampoline +的 8 字上限和 cell 之外的 `Dyn` 操作数,都是诚实的拒绝。 + +## §27 cell 内容类型推广到闭包捕获,以及两处静默错答(2026-08-17) + +§26 给 region 的 cell 入参记了内容类型;闭包**自己的**可变捕获有同样的毛病 —— +`clo` 一旦捕获变成 cell(赋值给它,或者把它交给 region 就会),`return p0 + a` 里的 +`a` 读出来就是 `Dyn`,整条算术没有降低。同一个概念推广成 `cell_capture_tys`: +建 cell 的那一侧(它知道类型)记下来,callee 按它 unbox,写的时候类型不合就并到 `Dyn` +重试。join 必须**单调**(`join_cell_content`):定点的前几遍看到的是**临时**类型 +(callee 的返回类型在它自己被降低一次之前就是默认的 `I64`),直接覆盖会让协议每遍都翻, +snapshot 永不收敛,预算耗尽 —— `examples/syntax/closure.lk` 直接不再原生化。 + +放开之后暴露了两处**静默错答**,两处都不是这次引入的,是这次才够得着: + +1. **`unwrap_or` 吞掉了发现通道。** `acc.push(b)` 读 `b` 失败时,容器载体那条 + "literal 猜错了" 的拒绝会**顶替**原始错误 —— 而 `UndefinedOperand` 不是类型错误, + 它是**发现**,region 的写回 cell(`try_body_extra_cells`)正是按这个变体收集的。 + 于是 `try { try { b = clo(); } catch { } } catch { }` 之后的 `acc.push(b)` 读到的是 + 进 region 之前的 `b`,原生、无回落、无提示;而 `let t = b; acc.push(t);` —— + 中间多一条 `Move` 的同一个程序 —— 是对的。改成 `keep_discovery`:载体答案只顶替 + *类型*失败。 + +2. **外层 body 少报了自己重绑了什么。** 内层 region 的写回要等内层有 cell 才发生, + 而外层 body 的 `try_body_rebound` 是在那之前就报出去的 —— 报完之后它就是权威的, + 再也回不来。改成把内层 body 自己的报告并进来(传递闭包)。注意**不能**用语法扫描来并: + 那个扫描取每条指令的 `a`,于是 region 只是**改**了一下的容器(`ListPush a=receiver`) + 会被算作重绑,定点给它发 cell,而 cell 的往返恰恰会丢掉那次修改 —— 两种写法都试过, + 语法那种让 `examples/syntax/closure.lk` 彻底不再原生化。 + +门禁:六批随机语料(约 1340 个含 `try` 的程序)0 处分歧;coverage 60/60;十个 fuzz +种子;`examples/syntax/try_catch.lk` 把这两处静默错答各钉了一条。 + +## §28 双寄存器载体按两个字过 region 边界(2026-08-18) + +`try` 写在 `for x in <一个 list>` 里面,整程序回落 —— 而这是最普通不过的形状,随手写的 +第一个探针就是它。原因:list 的循环变量是**载体**(元素读是带边界检查的,类型是 +`Maybe`;异构 list 则是 `Dyn`),而 region 的入参走 trampoline 的 `long long` 缓冲区, +`crosses_as_word` 对两寄存器的类型答"不能"。 + +两条看起来能走的路都是错的: + +- **在边界上 unwrap**:`UnwrapMaybeX` 在缺失时 abort,而 body 可能只写了 `x ?? default` + —— 那就把一个会答 42 的程序变成 abort。`examples/syntax/try_catch.lk` 的 + `defaulted(false)` 就是这个见证。 +- **装箱成 `Dyn` 过去**:`Dyn` 自己也是两寄存器。 + +正确做法是按它本来的样子过去:**一个载体占两个寄存器,就走两个字**,到对面再拼回来 +(`Inst::CarrierWord` / `Inst::CarrierFromParts`)。两条指令都取**裸的两半**而不是用 +`MaybeValue`/`MaybePresent` 这类**解释**性的访问器 —— 要活着过去的是比特,而且"取两半、 +按同样顺序放回去"这件事**不可能把约定搞反**,也就没有约定要维护。`MaybeF64` 的值半边是 +`f64`,按位 bitcast 进出。 + +`crosses_as_two_words` 写成没有 `_` 臂的 match:漏一个类型会被劈成不存在的两半,多一个 +会让 body 多绑一个参数。预算按 2 计。 + +顺带把"过不去"的诊断从 "an operand at pc N has a type outside the natively lowerable +subset" 改成指名道姓的 `OperandType`(`is a dyn where a machine word is required`)—— +是哪个类型过不去,本来就是这个答案的全部内容。 + +(写这条时发现的另一个缺口 —— 字符串 list 循环 —— 见 §29。) + +## §29 载体接收者先 unwrap(2026-08-18) + +`for s in ["ab","cde"] { s.len(); }` 整程序回落,**不带 `try` 也一样**。原因:list 的元素 +读是带边界检查的,循环变量类型是 `Maybe`,而 `Opcode::Len` 和 `CallMethodK` 都用 +`ssa.read` 拿接收者 —— 拿到一个两寄存器的载体,查不到对应的 `len` 实现就拒。 + +改成 `read_scalar`,它先 unwrap。判据不是"方便",是**与 VM 一致**:VM 里 +`m["zz"].len()` 会 raise(可被 catch),而 `lkrt_maybe_*_unwrap` 走的也是 +`raise_str`,不是 abort —— 两边同样是可捕获的 raise。`examples/syntax/for_loop_patterns.lk` +两向都钉了:present 的答长度,absent 的被 `catch` 接住。 + +顺带一条教训:这条的回归覆盖**最初写进了 `closure.lk`**,结果那个文件整体不再原生化 +(`opcode CallDirect (at pc 2)`),而两段代码**各自单独**都能原生化 —— 是和文件里已有的 +`spawn` 段互相作用。覆盖率门禁要求 61/61,所以例子加在哪里不是随便的:**加完当场跑一次 +`AOT_COVERAGE_REQUIRE_FULL=1`**,别假设"能编译的两段拼起来还能编译"。 + +## §30 闭包作为运行时值(2026-08-18 调查,**未落地** —— 有一个已知的错答) + +原生侧每一个闭包都是**编译期事实**:降低知道某个寄存器指的是哪个函数,于是调用去虚拟化、 +捕获变成隐藏的尾部实参。这覆盖了"建出来就调用"的闭包,也**只**覆盖那个。把闭包塞进 list、 +放进结构体字段、从分支里返回,都没有编译期答案,全部报 +`the closure in rN is a compile-time reference, not a runtime value`。 + +十个常见形状里六个回落:list / map / 结构体字段 / 循环里 push / 从分支返回 / 返回一个 +包住参数闭包的闭包。能过的四个都是能**静态解析**的(擦除或特化)。 + +### 设计(已验证可行) + +关键在于 `spawn` 已经证明了这条路:它按地址调用一个 lambda,而那个 lambda 的签名被降低 +**强制成全 `Dyn`**,所以一个 arity switch 能调到任何一个。闭包值就是同一件事,只是环境跟在 +指针旁边而不是当隐藏实参 —— 而追加环境的顺序**正好就是**原生签名已有的顺序(可见参数, +然后捕获)。 + +- **lkrt**:`DYN_CLOSURE` 标签 + `LkClosure { code, params, fn_index, env: Vec }`; + `closure_new` / `closure_call` / `closure_arity`。环境按 `OwnedVal` 深拷贝(闭包按定义 + 比建它的帧活得久),调用时再 materialize 进调用方 arena —— 和 goroutine 的快照同一套。 + `fn_index` 只为了 display 能打出解释器那句 ``:两个索引是同一个数 + (都是 `module.functions` 里的位置),而**值闭包不会是擦除克隆**,克隆是唯一被重编号的。 +- **发现是按需的**:`Unsupported::ReferenceAsValue` 带上 lambda 下标,定点收进 + `SigInfer::value_lambdas` 并重试。只建不存的闭包因此继续去虚拟化,一分钱不多花。 +- **`param_ty` 对 value_lambda 一律答 `Dyn`**,参数和捕获都是。 +- **在定义点物化**(`MakeClosure` / `LoadFunction`),不是在每个读取点:寄存器从此持一个 + 普通 `Dyn`,list 字面量、结构体字段、间接调用都不需要知道这件事。 +- callee 是 `Dyn` 的 `Call` 走 `rt.closure_call`。 + +### 为什么没落地 + +`let fs = []; for i in 0..2 { fs.push(|| 7); } println(fs.len());` **编译通过但答 +`runtime error`**,VM 答 2。同样的 push 放在循环**外面**是对的;循环里 push 一个真正的 +`Dyn`(`src[0]`,src 是异构 list)也是对的 —— 所以既不是空 list 载体重试的既有机制坏了, +也不是捕获的问题(无捕获的 `|| 7` 一样错)。是物化本身和循环的相互作用,原因未查明。 + +一个会**静默答错**的形状比一个回落坏得多,所以这一整块回退了,只留下这份设计。 + +### 2026-08-18 续:错答的原因查到了,另外两件事也量出来了 + +**① 错答不在闭包,在 `ListPush`。** `LK_AOT_DUMP_MIR=1` 直接给出答案: + +``` +v0 = call list_h.i64_new() +v11 = call rt.closure_new(...) +v13 = call dyn.as_i64(v11) // <-- 这里 + call list_h.i64_push(v12, v13) +``` + +`[]` 的载体被猜成 `ListI64`,而往里 push 一个 `Dyn` **不会**触发载体重试 —— 因为 +`read_typed_scalar` 对 `(Dyn, I64)` 是**静默 unbox**(`dyn.as_i64`),读根本没失败,而 +`carrier_contradicted_here_or_at_callers` 只在读失败时才被问。`dyn.as_i64` 拿到任何非整数 +都会 raise,于是编译出来的程序答 `runtime type error`。 + +这对**声明过**的 `List` 是对的(类型检查器已经保证了元素是 Int),对**猜出来**的 `[]` +是错的。修法是在 `ListI64|ListF64|ListStr` 三个 push 臂里,读之前先问一次:值是 `Dyn` 且 +载体是猜的,就直接返回矛盾,让定点把字面量重建成 `Dyn` list;载体是声明的则答 `None`, +照旧 unbox。改完那个复现立刻对了。 + +**这个修**单独拿出来是**够不着的**:要触发它,需要"静态类型看起来是标量、运行时却是 `Dyn`" +的值,而这正是闭包值才有的组合(静态类型是函数,运行时是 `Dyn`)。所以它必须和闭包值一起 +落地,不能单独提交 —— 一段无法触发的防御性检查配一段它自己证明不了的 bug 叙述,比没有更糟。 + +**② 物化必须与编译期引用并存,不能替换它。** 「某个 lambda 会逃逸」是**函数**的属性,由它的 +某一个用法发现;但它**别的**用法可能恰好是能静态解析的那些,而那些要的是引用。把引用换成值 +让 `examples/syntax/closure.lk` 丢了降低:`xs.filter(|x| …)` 的类型化 HOF 路径读的是引用, +换掉之后那条臂就没了。`ssa.write` 会清掉 `builtin_regs`,所以引用要在 write **之后**补回去。 +代价是每个逃逸 lambda 的构造点多一次可能没人用的 `closure_new`。 + +**③ 「一个寄存器同时挂引用和值」这个形状本身是错的 —— 这是 2026-08-18 第三轮的结论。** + +先说③走到哪:把「值 = 原 lambda 的一个全 `Dyn` 签名**克隆**」(复用 `pending_clones`, +原函数签名不动,于是类型化 HOF 路径不受影响)这一步做完之后,覆盖率回到 61/61 无回归, +`[|x| x+1, |x| x*2]` 和循环里 push 都原生化并与 VM 一致。中间还修掉两处: + +- `Move` 只搬引用不搬值 —— 而编译器在每个 `Call` 前都把 callee `Move` 进调用窗口, + 于是值刚物化就够不着了(`GlobalRef::ArgList` 早就有同款"双视图"补丁,就在旁边)。 +- `lower_dyn_call` 要用 `read_scalar` 读 callee:从 list 里迭代出来的闭包是 `Maybe`, + 把载体交给 `rt.closure_call` 会答"value is not callable" —— 对一个确实可调用的值。 + +**然后随机差分扫描找到了第三种错法,而且是致命的那种**: + +```lk +let fs = []; +fs.push(|x| x + 2); +fs.push(|x| x * 7); +let t = 0; +for f in fs { t = t + f(2); } +``` + +降低成 `dyn_push(v0, dyn.from_list(v0))` —— **list 把自己 push 进了自己**,两次。VM 答 18, +原生答 `value is not callable`。方法实参的窗口读到的是接收者,不是 lambda。 + +三次失败(`Move`、迭代出来的载体、方法实参窗口)是**同一个根因的三个面**:让一个寄存器 +同时有"编译期引用"和"运行时值"两种含义,就要求**每一处搬运、每一处读取**都知道该带哪一个, +而它们并不知道。补一处就冒出下一处。 + +**下次换设计:在需要值的那个消费点物化,而不是在定义点。** 这样寄存器永远只有一种含义, +`Move`/窗口/迭代全都不用改。消费点是可以枚举的,而且正是今天报 `ReferenceAsValue` 的那些: +容器存入、结构体字段、分支里的 `return`、间接调用。做法是给这些位置换一个 +`read_value(ssa, insts, sig, reg, …)` 帮手 —— 它在读到 lambda 引用时就地发 `closure_new`。 +`Ssa` 拿不到 `insts` 是当初没这么做的原因,但消费点是**在** `insts` 在手的地方。 + +`①` 的载体修(`ListPush` 里 `Dyn` 值撞上猜出来的载体要先矛盾)仍然成立、仍然够不着, +仍然要和这件事一起落地。 + +## §31 引用与值的不变量是**单向**的(2026-08-18,第四轮的产出) + +`Ssa::write` 会清掉那个寄存器的 `builtin_regs` 条目 —— 值遮住引用。反过来没有:直接 +`builtin_regs.insert` **不清** `current_def`,而 `read_slot` 是**先看 `current_def`**。 +于是一个从"值"回收成"引用"的寄存器,读回来的是**过期的值**。 + +这半条不变量本来就该在,和闭包值无关,所以单独落地了:`Ssa::bind_ref` 是记录引用的唯一入口, +它清定义;六个文件里的 `builtin_regs.insert` 全部改走它。`GlobalRef::ArgList` 是唯一例外, +而且是**故意**的 —— 它是一个已物化句柄的*视图*,不是"没有值的名字",两半本来就该同时活着 +(`NewList` 和 `Move` 里那段"双视图"注释说的就是它)。漏掉这个例外会让参数包整个失效: +`the argument pack in r0 is a compile-time reference`,一次扫描里 14 个程序掉到 3 个。 + +**它修掉了一个已经在跑的静默错答(2026-08-18 补,第五轮)。** 反汇编一看就清楚: + +``` +0000 LoadHeapConst r1 #0 ; [] +0001 Move r0 r1 ; fs = r0 +0002 MakeClosure r1 … ; lambda,落在刚才装 list 的那个槽 +0003 ListPush r0 r1 +``` + +字节码**复用寄存器**,于是 lambda 的 `MakeClosure` 正好落在 `[]` 字面量刚用过的槽上。 +`read_slot` 先看 `current_def`,而记录引用不清它,于是 push 读回来的是**那个 list**, +把它 push 进了自己。`let fs = []; fs.push(|x| x + 1);` 就这样编译通过并且答错: +`typeof(fs[0])` 原生答 `List` 而 VM 答 `Function`,`println(fs)` 原生一路递归到爆栈。 +`len()` 两边都是 1,所以从数字上看不出来 —— 这也是它一直没被发现的原因。 + +第四轮先做了 `bind_ref` 但**转换漏了三处换行写法**(`ssa.builtin_regs\n.insert(...)`), +`MakeClosure` 的零捕获早返回恰好是其中之一 —— 也就是恰好是这个 bug 的现场。补完之后这个程序 +从"编译并答错"变成"诚实回落"。`aot/lower/src/tests.rs` 两条单测把规则和它的例外都钉住了。 + +### 第四轮闭包值走到哪 + +消费点物化(§30 的结论)按计划做了:`read_value` 是"我这里需要一个值"的唯一入口, +容器字面量、`ListPush`、间接调用三处接上,寄存器全程只有一种含义。十个形状里六个原生化, +覆盖率不回归。但 `fs.push(|x| x + 2)` 仍然降低成 `dyn_push(v0, dyn.from_list(v0))` —— +**push 进去的还是 list 自己**。`bind_ref` 装上之后这条**没变**,所以寄存器复用不是它的原因, +下次要查的是:`fs.push()` 到底走的是不是 `Opcode::ListPush`(`fs.push(1)` 走的是, +MIR 可证),如果不是,那它走的是哪条路、那条路怎么读实参。 + +## §32 闭包值落地了(2026-08-18,第五轮) + +§30 的设计原封不动地成立,挡路的从来不是它 —— 是 §31 那个不变量。修掉之后这条路直接通了。 + +**发现是按需的。** 需要值的那次读报 `ReferenceAsValue`,它带上 lambda 下标,定点收下来。 +只建不调用的闭包因此继续去虚拟化,一分钱不多花。 + +**值形式是一个克隆,不是那个 lambda 本身。** 闭包值经运行时的一个 arity switch 调用,所以 +它必须是全 `Dyn` 签名;而同一个 lambda 的**其它**用法往往正是能静态解析的那些,类型化 HOF +路径按类型化签名取它的地址。把原函数钉成 `Dyn` 会让 `examples/syntax/closure.lk` 丢掉降低。 +所以原函数不动,值形式是同一份 body 的第二份拷贝 —— 擦除克隆用的就是这套机制 +(`pending_clones`)。 + +**物化在消费点,不在定义点**(`read_value`)。寄存器因此**永远只有一种含义**,`Move`、调用 +窗口、迭代一行都不用改。§30 ③ 记的三种错法全是"一个寄存器两种含义"的后果。 + +**运行时**(`lkrt/src/lkclosure.rs`)照搬 `spawn` 已经证明过的形状:环境走同一个 +`spawn_args_new`/`push` 块,调用时**追加**到实参后面 —— 而那正好就是原生签名已有的顺序 +(可见参数,然后捕获)。环境按 `OwnedVal` 深拷贝(闭包按定义比建它的帧活得久),每次调用 +再 materialize 进调用方 arena。`fn_index` 只为 display 与解释器一字不差 +(``),而且带的是**原**下标 —— 克隆是这条流水线自己的记账,程序观察不到。 + +**消费点是可以逐个接的,而且互不干扰(2026-08-18 续)。** 又接了三处:map 字面量的值、 +`NewObject` 的字段值、以及普通调用与具名调用的实参(结构体字面量 `H { f: |x| … }` 走的是 +后者)。于是十个形状里八个原生化。剩下两个不是"闭包"的问题,是**调用点的拼法**: +`m["inc"](3)` 和 `h.f(2)` 走的是 `CallMethodK`(VM 有一条"属性里放着一个可调用值"的路), +而 `let f = m["inc"]; f(3);` / `let g = h.f; g(2);` 两种写法现在都原生化。同一个语义两种拼法, +一种快一种回落 —— **已接**(见下)。 + +*键*不走 `read_value`:可调用的东西不是这门语言的 map 键,那条读保持原样。门禁:coverage 62/62(新增 `examples/syntax/closure_value.lk`)、随机闭包语料 +350 个程序全原生 0 分歧、try 语料三批 0 分歧、容器惯用法 150/150、八个 fuzz 种子、 +workspace、clippy `--all-targets`、no_std。fuzzer 的生成器也加了这一类形状。 + +## §33 属性里的可调用值(2026-08-18) + +`m["inc"](3)` / `h.f(2)` 走 `CallMethodK`,而 `let f = m["inc"]; f(3);` 走 `Call`。两种拼法 +一个语义,原来只有后者原生化。现在前者接到 `rt.closure_call_property`:在 +`lower_method_dispatch` 那个**大 match 的最后**,等所有真方法臂都拒绝之后才轮到它 —— 所以 +它不可能遮住任何方法。十个形状里十个,其中八个原生化;剩两个是"调用一个调用的返回值" +(`pick(true)(5)`),那是另一件事。 + +**它有自己的运行时入口,而不是给 `closure_call` 加个参数**,理由只有一个:**miss 的措辞**。 +map 是唯一一个"没找到"有两种原因的接收者,解释器把两半都说出来 +(`a Map has no method \`x\`, and this map has no key \`x\` holding a function either`), +而 `closure_call` 只会说 "value is not callable"。同一个 `catch` 里拿到两种字符串就是分歧, +所以 `lkrt_closure_call_property` 带上名字,自己发那句一模一样的话。 +`examples/syntax/closure_value.lk` 把这句话逐字钉住了。 + +发现它靠的是**顺手探一下 miss**:功能本身的十个形状全绿,是问"那不存在的方法呢"才露出来的。 +加一条新的 raise 路径时,**它答错话**和它答对值一样要探。 + +## §34 从分支返回的闭包(2026-08-18) + +`pick(true)(5)` 回落,报的是调用点 `opcode Call not lowerable` —— 但根因在**返回点**。 + +`Ret` 那条臂里,`ret_closure_candidate` 一旦匹配就**无条件** `return Err`,不管摘要有没有真的 +记下来。摘要记下来时那样做是对的:调用点自己用实参把闭包造出来,这个 body 根本不发射,所以 +这里没有东西可返回。但两个 return 的函数**记不成摘要**,于是它也被同一条 `Err` 拒掉了 —— +那在"闭包还不能当值"的年代是唯一的答案,现在不是了。 + +改成只在**真的记下摘要**时才 `Err`,否则落到 `read_value`,返回一个闭包值。调用点那边的 +`peek` 守卫本来就认 `Dyn`,于是 `pick(true)(5)` 一起通了。 + +十个形状里九个原生化。剩下的 `twice(|a| a + 3)(1)` 是"返回一个捕获了**函数参数**的闭包", +它需要参数位置上的闭包值一路传下去,是下一件事。 + +教训:**报错的位置不一定是原因的位置。** 调用点说"这个 Call 降不了",而它降不了是因为被调用的 +东西没有类型;类型没有是因为返回点先拒了。查这类问题要沿着数据流往回走一步。 + +## §35 全静态环境被擦掉了,而值需要它(2026-08-18,**修的是自己两天前发的错答**) + +```lk +let add = |x| x + 1; +let fs = [|y| add(y) * 10]; +println(fs[0](2)); +``` + +VM 答 30,原生答 `value is not callable` —— 而这是 §32/§33/§34 发出去之后就存在的分歧。 + +原因:一个环境**全是静态引用**的 lambda 在运行时不带任何东西,所以 `MakeClosure` 把它记成 +一个光秃秃的 `Lambda`(`captures_all_static`)。对"就地解析那些引用"的调用来说这是对的; +对一个**值**来说不对 —— 它的克隆照样有那么多捕获参数,而环境是空的,于是它读过头、 +调用了读到的东西。 + +改法:物化时发现 `captures` 为空而 `capture_count > 0`,就按 `capture_count` 把环境按 +`ClosureCapture::StaticRef` 重建,再由下面那个循环逐个从 `sig.ref_captures` 里把被引用的 +callable **递归物化成值**。 + +**这一条是自己的语料没覆盖到的类**:随机闭包语料从来不生成"lambda 捕获另一个 lambda", +所以 200/200 全绿而分歧还在。语料和 fuzzer 的生成器都补了这一类。 + +教训:**一个功能"十个形状全过"不代表它对**,它只代表那十个形状对。新功能引入的**新组合维度** +(这里是"捕获的东西本身是不是同类值")要单独列一遍,而不是等它出现在随机语料里。 + +## §36 闭包的身份(2026-08-18) + +VM 里闭包按**引用**比较:`let g = f` 是同一个对象,写法相同的两个 lambda 是两个对象。 +把 lambda 在**每个使用点**现场物化(§32 的做法)会给每次读取造一个新句柄,于是 + +```lk +let f = |x| x + 1; +let fs = [f, f]; +fs[0] == fs[1] // 解释器 true,编译后 false +``` + +三处一起改才对: + +| 位置 | 改动 | +| --- | --- | +| `inst/call.rs::bind_lambda` | 一个被当作值使用的 lambda 在**定义点**物化一次(`sig.value_lambdas` 已经记录了这件事),寄存器从此持有句柄 | +| `lkdyn.rs::dyn_eq_inner` | `DYN_CLOSURE` 按 payload 指针比较 | +| `lkdyn.rs::contains_eq` | 把"按句柄比较"改成**默认**分支,只排除 `DYN_RAW`。原来是枚举包含的 tag,这正是 `Set`/`Bytes`/窗口/typed map 各自漏掉过一次的原因 | + +定义点物化的代价是这个 lambda 的调用不再去虚拟化,只有程序真的传递它时才付。 + +三个连带的洞,都是"引用变成值"之后别处的判断依据没了: + +1. **列表 HOF 的快路径**要求寄存器里是 `GlobalRef::Lambda`。物化之后没有了,`xs.map(f)` + 整个模块都掉到通用路径(而通用路径对它根本没有降级),`examples/general/sort_search.lk` + 与 `examples/syntax/closure.lk` 一起从门禁里掉出来。补法是 `Ssa::closure_fidx`: + 环境为空的闭包值记住它命名的函数,`lambda_at` 两种写法都认。 +2. **空 `[]` 的载体猜测**。`b.push(f)` 原本走的是"寄存器里是 lambda 引用"那一支,那一支会报 + `LiteralElemTypeContradicted` 把字面量拓宽成 `dyn`。物化之后寄存器里是普通 `Dyn`,落到标量支, + `read_typed_scalar` 用 `dyn.as_i64` 把闭包**拆箱**了——程序照样编译链接,运行时在它唯一要存的 + 值上抛错。补法是 `Ssa::closure_values`:闭包值永远不是标量,拆箱请求直接拒,调用方据此拓宽。 +3. **`try` 区域的 lambda 输入**。`sig.try_body_lambdas` 是写一次就长期有效的表(两侧靠它对齐), + 而同一个 lambda 后来变成值之后这条记录不再成立,两个事实互相矛盾,定点无法收敛——区域体一直 + 索要一个发现分支已经给过的值。`function.rs::try_body_lambda` 在读出时按 `value_lambdas` 过滤。 + +## §37 不能做键的值要说清是什么(2026-08-18) + +`vm_mirror::key_from_dyn` 的兜底分支对所有非键 tag 都抛"Float cannot be a map key or set member", +所以 `Set(["ab".bytes()])` 和 `Set([closure])` 都自称 Float。解释器有两句话:浮点一句,其余一句带类型名; +`Set(...)` 与 `set.add()` 还各自加一个前缀。现在按解释器分:`key_from_dyn_in(v, context)`。 + +带类型载体的 map **不**走这个函数(键是拆箱存的),所以另加了 `dyn.as_key_i64` / `dyn.as_key_str` +两个 ABI:先按键的可用性拒绝、再拆箱。`m[|x| x] = 1` 原来答"runtime type error"。 + +## §38 回调是值时的三个 fold(2026-08-18) + +`list_h.dyn_map_fn` 等三个 helper 收的是**函数地址**,只有降级时知道寄存器命名哪个 lambda 才有。 +`xs.map(fs[0])`、以及回调从参数进来的写法,拿到的是 `DYN_CLOSURE`,于是加了对应的 +`dyn_map_closure` / `dyn_filter_closure` / `dyn_reduce_closure`。 + +filter 的判定按解释器来(`core_methods::list_filter`):`Bool` 取自身、`nil` 为假、其余为真。 +`*_fn` 那条路做不到这件事——它在编译期就要求回调返回 `Bool`——而闭包的返回类型这里不知道。 + +`lkrt_closure_call` 拆成"取参数块"和"调用"两步(`call_with`),这三个 fold 每个元素只造一个 +`Vec`,不再为了让同一个函数马上拆开而先造一个参数块。 + +## §39 按函数索引的表必须一起增长(2026-08-18,**影响面最大的一个**) + +`SigInfer` 里有八张按函数索引的并行数组(`param_obs` / `ret_types` / `ret_known` / +`lambda_params` / `specialized` / `plain_called` / `ret_closures` / `ret_closure_poisoned`)。 +`funcs` 在三个地方增长:`try` 体外联、lambda 实参特化、闭包值克隆。三处各自 push 自己关心的 +那几张,**集合不同**:外联只 push 前三张。 + +于是模块里只要有一个 `try`,`lambda_params.len()` 就比 `param_obs.len()` 少, +而特化用的是 `let clone = sig.param_obs.len()`——`lambda_params.push(identity)` 落在了 +`clone - 1` 上,记到了别人头上。可观察到的现象: + +```lk +try { … } catch e { … } +fn ap(xs, f) { return xs.map(f); } +ap([1, 2], |x| x + 1) // 模块里有 try,这一行就不再原生化 +``` + +现在只有 `SigInfer::push_function` 一个入口,一次给八张表各追加一格,并 `debug_assert!` 长度一致。 +教训与 snapshot 元组那条相同:**并行结构的增长点必须只有一个**,否则漏掉一处是静默的。 + +## §40 被程序写过的槽位就是程序的(2026-08-18) + +`GetGlobal` 先按**名字**解析:内建函数名、`module::member`、stdlib 模块名。导入绑定那一段已经 +写了正确的规则——"只有槽位从未被写过时才用导入的含义"——但名字那一段没有这个前提。 +于是 `SetGlobal` 只能反过来兜:凡是写一个名字被识别的槽位就拒绝整个程序,否则后面的读会解析成 +过时的模块含义。 + +代价是 14 个普通变量名(`time` `env` `hash` `iter` `os` `io` `net` `math` `fs` `bytes` +`regex` `task` `process` `encoding`)一旦被函数读到,整个程序就掉出原生路径: + +```lk +let time = [30, 45, 60]; +fn first() -> Int { return time[0]; } // 有这一行就不原生化 +``` + +改法是把导入那一段的规则提到名字那一段之前:`prescan_shadowed_globals` 语法扫出所有被 +`SetGlobal` 写过的槽位,被写过的槽位不再按名字解析,`SetGlobal` 那边的名字检查随之删掉。 +顺带 `let len = 42` 这类遮蔽内建函数名的写法也一起原生化了。 + +语法扫描而不是"是否已观察到写入":同一趟里读可能先于写降级,按观察顺序回答会随趟次变化。 + +## §41 字段的声明类型一直被扔掉(2026-08-18) + +`struct P { count: Int }` 的实例是一个字符串键 map,字段读是 `map_h.str_dyn_get`,结果是装箱的 +`Dyn`。声明说了 `Int`,而**没有任何东西把这句话带到降级这一层**——`StructDecl` 只存字段名。 +于是 `p.count + 1` 两边都装箱、走 `dyn.add`,`p.count >= 0` 走 `dyn.ge`。 + +**按声明类型给字段读定型这件事不成立,当天就撤回了。** LK 是渐进类型:一个无类型参数可以 +往声明为 `Int` 的字段里写字符串,解释器允许: + +```lk +struct A { v: Int } +fn poison(p) { p["v"] = "s"; } +let a = A { v: 1 }; +poison(a); +println(a.v); // 解释器 "s";按声明类型拆箱的编译版抛 runtime type error +``` + +列表元素那一侧同样:`fn add(xs) { xs.push(B { … }); }` 能把一个 `B` 推进 `List`, +类型检查器看不到。所以**声明的字段类型不是运行期保证**,不能用来给读定型。 +要让它成立,得在动态字段写入处按声明类型做运行期检查——那是语言语义的改动,单独一项。 + +留下来的是**按下标读**,这一条成立,而且性能本来就在这里: + +| 形状 | 哈希查找 | 按下标 | +| --- | --- | --- | +| `for i in 0..3e6 { total += s.a; }` | 0.32s | 0.06s | + +**这两个数字是调试版 lkrt 的**(见 §43 的计时陷阱),所以 5 倍这个**比值**成立, +"~107ns 一次字段读"这个绝对值不成立。声明的字段序随模块走(`StructDecl` 现在带序),下标是编译期常量; +**字段名仍然一起传下去并比较**,因为顺序不是保证——从 hybrid 桥或别处建的实例可能是别的顺序, +比不上就退回按键查找。这一条不依赖任何类型假设,所以是安全的。 + +改动:`StructDecl.fields` 从 `Vec` 变成 `Vec`(名字 + 声明类型文本, +沿用 `TraitDecl` / `ImplDecl` 的 `Type::display()` 约定),artifact 版本 17 → 18。 +AOT 侧 `TraitEnv::struct_field_tys` 记 `(结构体名, 字段名) → Ty`,字段读之后按它拆箱。 + +只拆**标量**(`Int` / `Float` / `String`)。容器字段的载体不是声明能钉住的(`List` 可以是 +任何一种列表表示),猜一个载体正是错答的来源。`Bool` 也留在外面:`dyn.as_bool` 返回 ABI 的 `I64`, +而 MIR 的 `Ty::Bool` 是 codegen 会 `uextend` 的一位值,只改类型不加那次比较过不了 Cranelift 校验。 + +结构体身份也跟着值走了两步:`Ssa::list_elem_struct` 记"这个列表的元素都是结构体 N", +元素读把身份传给结果;`Ssa::inherit_provenance` 在 phi 处继承(此前只有猜测载体 `literal_carrier` +有这个待遇,三张表现在在同一处继承,免得再加第四张时漏掉)。 + +循环头的 phi 也补上了(同日)。Braun 算法里循环体在 header 封口**之前**降级,所以等所有边到齐 +再继承等于身体永远看不到这件事。改成**创建时**从已填充的那个前驱种下(`seed_provenance`)—— +`phi_ty` 给类型定型用的正是同一个前驱,同样是乐观的——操作数到齐时校验 +(`verify_seeded_provenance`),被某条边推翻就报可重试的 `Unsupported::PhiProvenance`, +下一趟对这个槽位不再种(`no_phi_provenance`)。这是 `dyn_loop_phis` 对**类型**做的同一件事。 + +于是 `while cursor >= 0 { walked += nodes[cursor].value; cursor = nodes[cursor].next; }` +整段原生化,字段读是 `dyn.as_i64` + `int.add` / `icmp.ge`,不再是 `dyn.add` / `dyn.ge`。 +`examples/syntax/struct.lk` 钉住了这个形状。 + +注意 snapshot 元组:新字段**追加在末尾**(索引 23),不是插在中间。第一版插在索引 5, +把后面每一项都错位成比较别的东西——那正是那段注释警告的事。 + +## §42 循环里的 format 模板(2026-08-18) + +`"{}".format(x)` 在编译期展开,所以模板必须是常量。而字节码编译器会把**循环不变的字面量提到 +循环外**(`vm/compiler/loop_consts.rs`),于是循环体内那个模板是一个 **phi 参数**,不是字面量本身。 +按 SSA 值查 `const_strs` 什么也查不到,写在循环里的每一个 `"{}".format(x)` 都掉出原生路径: + +```lk +let s = ""; +for i in 0..n { s = s + "[{}]".format(i); } // 整段回退 +``` + +`Ssa::reg_const_str` 早就为这件事写好了——"Recovers `println` format strings the compiler's +loop-literal cache hoisted out of the loop body"——但它从**寄存器**出发,而 `format` 这一处手里 +只有已经读出来的 SSA 值。补了 `Ssa::const_str_value`:值查不到就找它是哪个 phi 的参数, +改按那个 phi 的寄存器走同一个回溯。`println` 那条路一直是对的,`format` 这条不是。 + +## §43 map 字面量的两段式构建是遗留的(2026-08-18) + +一个 24 项的 map 字面量,建 10 万次(**调试版 lkrt**,见下面那条): + +| | 时间 | +| --- | --- | +| 24 元素的**列表**字面量 | 0.08s | +| 24 项的 map 字面量(两段式) | 1.79s | +| 同上,直接建进载体 | 1.14s | + +> **计时陷阱:调试版 CLI 的 `lk compile` 链接的是调试版 lkrt。** +> `native_executable::lkrt_staticlib_path` 在 `target//` 及其 `deps/` 里找归档, +> `target/debug/lk` 找到的就是调试版的。上面那组数字因此把绝对开销放大了数倍—— +> 同一个 lkrt 两边比较的**相对**结论仍然成立,绝对值不成立。 +> +> 用 `--profile dist` 的 CLI 重测,100 万次: + +| | 每次构建 | 每项 | +| --- | --- | --- | +| 24 元素列表字面量 | 0.38µs | ~16ns | +| 24 项 map 字面量 | 2.1µs | ~88ns | +| 24 字段结构体 | 3.0µs | ~125ns | + +所以 map 比列表贵 **5.5 倍**、结构体贵 8 倍,不是调试版数字暗示的 20 倍。 + +两段式是这样的:`lit_new` 建一个 `RtKey` 键的中间 map,每个键和值都装箱后 `lit_set` 进去, +再由 `lit_finish_str_i64` 之类遍历它、把键 `to_owned()` 一遍插进真正的载体。 +**两倍的哈希插入、两倍的键分配,外加每项一次装箱。** + +第二段存在的理由是"按 VM 的 stage-1 **哈希序**重放进 stage 2"——那是 map 迁到 IndexMap 之前的事。 +现在两边都是插入序,按写的顺序直接插就是同一个结果。而载体形状本来就是**编译期**选定的 +(`lit_finish_str_i64` 这个名字就是降级时挑的),所以中间那一步没有任何信息是必需的。 + +寄存器窗口(`NewMap`)和常量(`LoadHeapConst`)两条路都改成直接建。`lit_*` 保留给形状在运行期才知道的 +用法(`MapRest`、解码器)。 + +两条量过但**没有**收益、已撤回的尝试,记下来免得再试:预留容量(`with_capacity`,1.54s → 1.53s), +以及按声明类型给字段读定型(见 §41)。 + +下一层的结构性问题还在。50 万次 × 24 项,`--profile dist`: + +| 每项 | | +| --- | --- | +| 列表元素 | ~15ns | +| **整数**键 map 项 | ~48ns | +| **字符串**键 map 项 | ~90ns | +| 覆盖一个已有的字符串键 | ~8ns | + +三条读法:哈希+探测只值 ~8ns;map 结构本身比列表多 ~33ns;字符串键再多 ~42ns, +那是**每项一次 String 分配**(以及之后一次释放)。 + +试过并撤回的两条:预留容量(1.10s → 1.07s,在这台机器的噪声里),以及把键换成 +`Cow<'static, str>` 让常量键不分配——后者能省掉那 ~42ns,但需要一个"这个指针是常量、活得够久"的 +ABI 约定,写错就是 UB 而不是错答。为 45% 换这个,不划算。 + +**记录表示**才是该走的路:声明的结构体按声明序存一个值向量,字段下标编译期已知 +(读的一侧 `str_dyn_get_at` 已经在按下标走了),一个字段能落到列表元素那个量级,而且不存键、 +没有借用指针的问题。代价是构造、读写、display、相等、迭代、spread 都要跟着改, +声明不可达的结构体(跨模块、宿主建的)还得保留 map 形式。 + +## §44 打包对"调用参数上的方法"判得太粗(2026-08-18) + +打包会拒绝"可能通过容器参数写入"的模块——打包把调用方的容器**按引用**交过去,而 VM 给每个模块 +一份拷贝,写入的可见性因此不同。判定里,**调用参数上的一个用户方法**一律算作写:字节码里没有类型, +名字看不出它做什么。 + +代价是一个 trait 最典型的用法就打不了包: + +```lk +// shape.lk +trait Area { fn area(self) -> Int; } +impl Area for Sq { fn area(self) -> Int { return self.s * self.s; } } +fn describe(v: Area) -> Int { return v.area(); } +``` + +只要模块里有 `describe` 这一个函数,整个模块拒绝打包,**它导出的每个名字都不再解析**—— +连 `Sq { … }` 这种与 trait 无关的构造也一起掉出原生路径。 + +方法体就在同一个模块里,而同一个定点本来就在判断"它的接收者安不安全"。所以改成查那个结果: +一个方法名的**所有** impl 体的第 0 号参数都安全,这次调用就不算写。 + +改这一条要先补另一条,否则不成立:污点必须**穿过容器读**。此前只有 `Move` 传递污点, +于是 `fn contains(self) { self.items.push(x); }` 里,`self.items` 之后的 push 落在一个 +"没有污点"的寄存器上,方法看起来没碰 `self`。`cli/tests/bundle_container_parameter_test.rs` +里正好有这个用例,它抓住了第一版的错误。现在 `GetFieldK` / `GetIndex` / `GetList` / +`GetIndexStrI` 也传污点,读出来的东西属于被读的容器。 + +## §45 那条 callable-property 分支从来没跑过(2026-08-18) + +`m.thing()`(`thing` 是 map 里一个装着函数的条目)的降级分支,对 `Ty::Dyn` 接收者发的是 +`dyn.map_get`——**ABI 里没有这个名字**。于是整个模块过不了 MIR 校验,那是模块级的回退, +所以从来没有人看见过它:这条路一次都没跑过。 + +打包放宽之后它才被够到。补上之后立刻暴露了它本来就错:`c.name.upper()` 读的是结构体字段, +接收者因此是 `Dyn`,而这条分支是最后兜底的,于是把 `upper` 当成"map 里的可调用条目"去找, +答了 `a Map has no method \`upper\``——解释器答的是 `A`。**这是错答,不是回退。** + +正确的做法是这条分支**不接受装箱接收者**:装箱值的运行期类型才决定 `upper` 是哪个方法, +静态这一侧不知道,就该拒绝、回退,而不是猜一个。`Ty::MapStrDyn`(确实是 map)保留。 + +教训:一个从未成功过的分支,和一个不存在的分支,外部看起来一样。补一个缺失的 ABI 名字之前, +先问它当初为什么缺。 + +两条后续,免得再靠运气发现: + +- `Unsupported::InvalidMir` 现在**带上校验错误本身**。此前只说"没通过 MIR 校验", + 真正的原因(`UnknownAbi { module: "dyn", name: "map_get" }`)藏在一个没人会设的 + `LK_AOT_DEBUG_FAILURES` 后面。整模块回退的诊断不该需要开关。 +- `aot/lower/tests/abi_names.rs` 扫源码:`AbiRef::new( … )` 里每一个**当作名字用**的字符串 + 字面量都必须在 schema 里。名字可以由条件式选出来 + (`AbiRef::new("dyn", if name == "keys" { "map_keys" } else { … })`),所以两个位置都读, + 只跳过 `==` / `!=` 的操作数。把 `str_dyn_get` 改回 `map_get` 试过,这个测试确实会红。 + +## §46 把方法分发表逐条对着解释器跑一遍(2026-08-18) + +§45 之后的自然一步:`lower_method_dispatch` 声称能降级 63 个方法名,把它们**逐个**在 +解释器和原生上跑一遍比对(接收者:String / List / Map / Set / Bytes,以及同一批经过 +无类型参数、接收者变成装箱值的版本)。两条错答: + +- `m.clear()` 原生答 `nil`,解释器答**那个 map 本身**(同一个句柄,已清空)。列表那条紧挨着, + 写的是"值就是接收者",map 这条却造了个 nil 出来。 +- `m.remove(k)` 原生**接受**并返回删掉的值,而解释器根本没有这个方法(map 只有 `delete`)。 + 这个方向更糟:一个解释器拒绝运行的程序跑起来了,而且只在一个后端上。 + +装箱接收者那一批 0 分歧。 + +把范围再推一层——**空接收者、越界与负下标、以及会改接收者的方法返回什么**——又出两条: + +- `Set.clear()` 也答 `nil`。list 和 map 各自修过一次,set 这条落下了:同一个约定分散在三个分支里, + 改的时候没有一处会提醒还有第三处。 +- `[1,2,3].chunk(0)`:解释器抛 `list.chunk() size must be positive`,原生把这句**打到 stderr**, + 再抛 `runtime type error`。于是解释和能被 `catch` 到的句子是两个不同的字符串。 + `rt_eprintln!` + `raise_str("runtime type error")` 这个组合全仓只此一处,已改成消息本身就是错误。 + +stdlib 模块那一面(`math` / `string` / `iter` / `bytes`,约 70 个调用)0 分歧。 + +两条收尾,免得这类东西再靠临时脚本发现: + +- **这份比对留了下来**:`examples/stdlib/method_surface.lk` 把 85 个用例(9 类接收者, + 含空容器与越界下标)各打印方法名、结果、以及**调用之后的接收者**。示例语料本来就被 + VM/native 差分门禁逐个跑两遍,所以这份文件就是那次比对本身,变成常驻的。 +- **`clear` 的约定收成一条**:此前三个分支各写一遍"值是接收者",其中两个写错成 `nil`, + 而那条约定只写在 list 那个分支的注释里——另外两处的读者不会看到。现在一个分支加一个 + `clear_helper(ty)` 查表,答案只说一次。 + +做法本身值得留着:能降级的方法名是从降级表的 match 分支里正则抽出来的,所以"表里写了什么" +和"实际跑起来是什么"是对着的——这正是 §45 那种"写了但从没跑过"的反面。 + +## §47 `try` 里跳出外层循环:最大的一块 try 回退(2026-08-18) + +随机 try 语料 231 个程序,128 个原生化。剩下的 103 个里 **52 个是同一条原因**—— +`the try region at pc N cannot be outlined: the body jumps out of the region`。逐形状确认: + +| 形状 | | +| --- | --- | +| `for … { try { … } catch … }`,body 里没有跳转 | 原生 | +| 同上,body 里 `return`(从外层函数返回) | 原生 | +| `try { for … { break; } }`(循环整个在 try 里) | 原生 | +| `for … { try { … break; … } catch … }` | **回退** | +| `for … { try { … continue; … } catch … }` | **回退** | + +也就是说:`try` 体里的 `break` / `continue` **属于包住 `try` 的那个循环**时,区域没法外联—— +外联出来的函数没有那个循环可跳。 + +`return` 这条已经解决了,而且用的正是可以照搬的办法:body 多收两个 cell(一个标志、一个值), +写完就返回,调用方在检查块里看标志决定是否把返回值再往上递(`SigInfer::try_body_returns`)。 +把标志从布尔换成**结果码**(0 落空 / 1 return / 2 break / 3 continue),body 那侧就齐了。 + +难的是调用方那一侧:检查块要跳到**外层循环**的 break 目标或 latch,所以外联时得知道这两个 pc。 +`try_region::scan` 现在只判断"目标落在区域外就拒绝",要改成"落在外面的哪里"—— +外层循环的出口、latch,还是别处(那才是真拒绝)。这需要 `cfg` 那边给出循环结构。 + +**已解决,见 §48。** 没有在这一轮动手:控制流改错正是会产生静默错答的那一类,值得单独一轮从头做, +而不是在一段很长的会话末尾开工。 + +## §48 `break` / `continue` 跳出外层循环:接上 §47 那条通道(2026-08-19) + +§47 写的办法照做了,标志从布尔换成结果码(0 落空 / 1 return / `2 + k` 第 k 个出口), +但**调用方那一侧比 §47 设想的简单**:检查块不需要知道"外层循环的 break 目标和 latch" +在哪里,因此也不需要 `cfg` 给出循环结构。它只需要知道 body 跳到了**哪个 pc**—— +那个 pc 在父函数里本来就是一个块,把它记成区域所在块的后继,phi 操作数就由既有的 +SSA 构造顺手算出来,检查块用 `args_to` 读回去。一个 `try` 里的 `break` 在父函数里 +不是什么特殊东西,只是某个块的终结指令有四个后继而不是两个。 + +body 那一侧的做法值得单独记:**跳转一直到最后都还是跳转**。每个不同的目的地在 body 的 +`Return0` 之后得到一条一指令的 trailer,跳过去的那些 `Jmp` 被改写成指向它。于是 leader +查找、CFG、SSA 构造一路上 `break` 都是普通跳转,只有到了 trailer 才变成一次写标志。 +另一种写法——按指令种类拦截跳转本身——要为每种终结指令各写一遍改写,而它漏掉的那一种 +会静默错答。 + +只接受无条件 `Jmp`。`break` / `continue` 编译出来就是这个:前面的条件是它自己的融合分支, +目标在 body **内部**。条件分支只有一条边离开区域的形状会明确报错,不做近似。 + +### 语料测量(生成 121 个 `try` 程序,每个一个函数) + +| | 原生化 | 唯一阻塞原因 | +| --- | --- | --- | +| 改之前 | 2 / 121 | `break`/`continue` 属于外层循环(118 条) | +| 改之后 | 108 / 121 | `too many values cross the region boundary`(13 条) | + +VM/native 逐行比对:50 个种子 × 5 个程序,0 分歧。61 程序 sweep、覆盖率门禁(65/65)、 +四个种子的生成式差分、`cargo test --workspace --all-features` 全绿。 + +### 一个静默错答,以及现在拦住它的东西 + +区域是按**块的 leader** 而不是 `TryBegin` 的 pc 查的,于是 +`while c { i = i + 1; try { … } }` 找不到任何出口:body 声明了五个参数,调用点只传四个。 +这不是链接错误——trampoline 拿的是 body 的**地址**,再按 switch 选中的元数强转—— +所以它跑起来了,并解引用了第五个寄存器里恰好剩下的东西。 + +`clif.rs` 现在把 try-region 调用的字数和被调 body 声明的字数对一遍。这条检查此前不存在, +而且没有别的东西能替代它:签名不经过 Cranelift 的类型检查。 + +## §49 try body 自己读参数缓冲,元数上限随之消失(2026-08-19) + +§48 之后唯一剩下的阻塞原因是 `too many values cross the region boundary`(13/121)。 +`LK_TRY_MAX_ARGS = 8` 不是 ABI 约束,是 `lkrt/src/try_trampoline.c` 里一段手写 switch +的长度:它把 body 的地址强转成九种 `(long long, …)` 原型之一,`default` 是 +`__builtin_trap()`,所以降级那一侧必须先算好字数再拒绝。 + +关键事实是**调用方本来就把参数摊在一个栈缓冲里**——`lkrt_rt_try_region` 收到的就是 +那个 `argv`。switch 做的事情只是把已经在内存里的字再装回寄存器。于是让 body 直接收 +`argv` 指针、按偏移把自己的参数读出来: + +- `body_signature`(`aot/codegen/src/clif.rs`)给 try body 一个 `(i64) -> ()` 的签名。 + 哪些函数是 try body 不是记下来的,是**从 `Inst::TryRegionCall` 反推的**—— + 成为 try body 的唯一条件就是有人这样调它,所以没有第二处可以对不上。 + 同一个函数如果还被普通 `CallFn` 调用,两处签名必然有一处是错的,直接报错。 +- 每个参数正好一个机器字(carrier 在 `function.rs` 里已经拆成两个 `I64`,`F64` 声明成 + `I64`),这一点在 `body_signature` 里**检查**而不是假定:缓冲没有办法表示"这一格是两个字"。 +- 读出来一律按 `i64` 读,再按声明类型收窄(`Bool` 在 Cranelift 里是 `i8`)。 + 按声明宽度直接读会依赖机器把哪一端放在前面。 +- switch、`LK_TRY_MAX_ARGS`(两份)、以及 trampoline 的 `argc` 参数一起删掉。 + +结果:同一份 121 程序语料 **121/121 原生化**;一个跨 8 个输入 + 多个 cell + 出口通道的 +区域,body 声明了 **29 个参数**,和解释器逐行一致。50 个种子 × 5 个程序 0 分歧, +覆盖率门禁 65/65,61 程序 sweep、五个种子的生成式差分、 +`cargo test --workspace --all-features` 全绿。VM 不受影响(改动只在 `aot/` 和 `lkrt/`)。 + +顺带被这条检查拦住的一类:参数类型是 `Nil` 的 body。`signature_of` 会给它 0 个 Cranelift +参数,而调用方照样推一个字进缓冲——以前是静默错位,现在直接拒绝。 + +## §50 生成式差分自己数不出原生化条数(2026-08-19) + +`cli/tests/aot_fuzz_differential_test.rs` 的下限断言是 +`compared * 4 >= cases`——而 `compared` 数的是**编译成功**的程序。 +回退到 Tier 1 桥或 Tier 0 VM bundle 的程序照样编译成功、照样跑、照样和解释器一致, +所以这条断言在"每一个生成的程序都退回 VM 上跑"的情况下**一动不动**。 +差分比对按定义看不见降级回归:两边答案本来就该一样。 + +现在多数一个数:stderr 里既没有 `Tier 1 hybrid` 也没有 `falling back` 的, +才算完全原生化。打印出来,并加第二条下限 `fully_native * 5 >= compared`。 +下限取五分之一,远低于实测(六个种子上是 40 里的 13–19):生成器本来就会故意产出 +不可降级的 hybrid helper,真实比例是生成器的属性而不是门禁;要拦的是塌方, +塌方会掉到接近零,而不是缓慢漂移。 + +同一轮里给生成器加了 §48 那个形状:`try` 体里的 `break` / `continue` 属于外层循环, +循环种类(`for` 区间 / `while`)是抽的——`continue` 在两种循环里落点不同, +`while` 是往回跳到条件,而第一版实现正好把这一种写错了。 + +## §51 "raise 前必须放掉 runtime borrow"这条规矩,现在自己会说话(2026-08-19) + +`lkrt/src/panic.rs` 顶上写着一条硬规矩:raise 会 `_longjmp` 过所有 Rust 帧, +所以**不能在 `with_runtime` 的借用还活着的时候 raise** —— `RefMut` 不会析构, +借用标志一直是置位的。 + +逐个查了一遍,今天这条规矩是成立的,而且成立的方式很整齐: + +| 位置 | 怎么保证的 | +| --- | --- | +| `net.rs` / `host.rs` / `io.rs` / `encoding.rs` 等 | 全部包在 `abi::raising` / `status` 里:闭包返回 `Result`,`?` 是普通返回,析构照跑,raise 发生在闭包**外面** | +| `chan.rs` | 直接调 `raise_str`,但每处前面都有显式 `drop(state)`;`own()`(会 raise)在取锁**之前**调用 | +| `lkdyn.rs` 的 41 处 raise | `with_obj_type_marks` / `with_struct_types` 的闭包全是纯 map 操作,raise 都在闭包返回之后 | +| `panic.rs` 自己 | 只碰自己的 `RefCell` | + +问题不在有没有错,而在**这条规矩只由"读代码时小心"来保证**。违反了也不会当场炸: +炸的是**下一次** runtime 操作,可能在任意远的地方,报 "already mutably borrowed", +读起来像是那段无关代码的 bug。 + +所以 `raise_current` 现在先问一句 `state::runtime_borrow_is_live()`,是就打印 +"这是 lkrt 的 bug,raise 的那个入口必须先放掉 runtime borrow" 然后 abort。 +代价可以忽略:raise 路径本来就在做 `CString` 分配和一次 `longjmp`。 + +配一个测试(`a_live_runtime_borrow_is_visible_to_the_raise_path`),因为这个谓词唯一 +可能的失效方式就是恒返回 `false` —— 那样它不报错、不碍事、也不再是检查。 +真正要拦的那件事没法从测试里触发(按设计它会 abort 整个进程)。 + +## §52 优化过的 `lk` 从来没被任何门禁跑过,里面有两个 bug(2026-08-19) + +起因是想给 channel 做个基准,顺手用 `--profile dist` 的 `lk` 编了个 `try` 程序。 +它没编出来 —— **编译器自己 panic 了**。 + +### 一:`debug_assert_eq!` 里藏着副作用 + +```rust +debug_assert_eq!(body_index, sig.push_function(Vec::new(), Ty::Nil)); +``` + +release 构建里 `debug_assert_eq!` 丢掉的是**整个表达式**,`push_function` 那次调用 +一起没了。于是签名表不为 try body 长出那一行,下一趟 +`sig.ret_types[body_index]` 越界 panic。 + +也就是说:**任何含 `try` 的程序,在任何优化构建的 `lk` 上都编不过**,一直如此。 +debug 构建是好的 —— 而所有门禁用的都是 debug 构建。 + +顺手把全仓 20 处 `debug_assert*` 逐个看了一遍,其余全是纯谓词,没有第二处。 + +### 二:刷新的是 debug 归档,链接的是旁边那个 + +改完 panic,生成的可执行文件死在 `SIGILL`。 + +`native_executable.rs` 里 `lkrt_staticlib_path()` 先无条件 `build_lkrt_staticlib()` +再搜索。注释写得很清楚,这次无条件重建是为了防止链到**陈旧**的归档 +(工具链换过之后两份 libstd 撞 `rust_eh_personality`)。但重建写死了 +`cargo build -p lkrt-cabi`(dev)和 `target/debug`,而搜索取的是**这个二进制自己 +所在目录**旁边的那份。于是对 `--release` / `--profile dist` 的 `lk` 来说, +它刷新的归档不是它链接的归档 —— 那件它要防的事情正好发生了: +dist 构建链到了前一天的 lkrt,里面 `lkrt_rt_try_region` 还是三参数带元数 switch 的旧签名 +(见 §49),`__builtin_trap()` 就是那声 `SIGILL`。 + +改成按**自己所在目录对应的 profile** 重建(`cargo_profile_of`:`deps` 上跳一级, +`debug` 目录对应 `dev` profile,其余同名),并且那句 +"building lkrt staticlib (one-time)…" 现在报的是真的 profile,也不再撒谎说"一次性"。 + +### 门禁 + +`check.yml` 新增一步:`cargo build --release -p lk-cli --features aot`, +用它跑同一份覆盖率门禁(65/65),再把 `try_catch.lk` 和 `concurrency_demo.lk` +的解释器输出与原生输出逐字节比一遍。 + +用 release 不用 dist:dist 多的是全量 LTO,为的是这里不测的性能数字,却要多花几分钟; +真正会**静默**改变行为的两件事 —— `debug_assertions` 关掉、优化打开 —— 两个 profile 是一样的。 + +## §53 一个没有 `select` 的 channel 程序,一半时间花在 select 的唤醒上(2026-08-19) + +`send`/`recv` 各 300k 次的原生程序,`perf` 的结果: + +| | 占比 | +| --- | --- | +| `syscall`(futex) | 40% | +| `lkrt::chan::notify_selects` | 18% | +| `lkrt::chan::channel` | 12% | +| `SipHash`(registry 的默认 hasher) | 2% | + +程序里**一个 `select` 都没有**。三处都改了,同一个基准 0.14s → 0.016s(每次操作 +230ns → 26ns,**8.75×**)。 + +### 一:没有 select 在等的时候不广播 + +`notify_selects()` 每次 send/recv 都取一把进程全局互斥锁、加一、`notify_all`。 +现在先读一个 `BLOCKED_SELECTS: AtomicUsize`,为零直接返回。 + +跳过是安全的,靠的是顺序:通知方走到这里时**已经**放开了自己 channel 的 `state` 锁, +改动已经发布;而 `select` 是在**轮询之前**就把计数加上去的。所以如果这里读到零, +那次加一在 `SeqCst` 全序里排在后面,它之后的轮询要取 channel 锁, +就一定看得到刚才那次改动。要么通知方叫醒它,要么它根本不会睡。 + +代价是 `select` 里的 raise 会跳过 RAII 的减一(longjmp 不跑析构)。所以顺手把 +`select$block` 里所有会 raise 的事情——arm 形状校验、`own` 深拷贝、以及 +`channel(id)` 解析——全挪到轮询循环**之前**的预处理里,循环里只剩一处 +"send on closed channel",那一处显式 `drop(parked)`。 +把 channel 解析提前还顺带把全局 registry 锁从轮询循环里拿掉了(原先每轮每条 arm 一次)。 + +### 二:没人在等的时候不 futex + +`Condvar::notify_one` 在 Linux 上无论有没有人 park 都会发 `futex_wake`。 +`ChanState` 现在带 `recv_waiters` / `send_waiters`,只在锁里读写,所以是精确的: +等待方在 `wait` 释放锁**之前**加一,通知方持锁读到零就意味着没人在等、 +也没人能在不经过这把锁的情况下开始等。 + +### 三:registry 是个稠密整数表,不该是 HashMap + +id 来自一次 `fetch_add`,而且从不删除(channel 活到进程结束,和 lkrt 其余部分一样)。 +`Mutex>` + 默认 hasher 换成 `RwLock>>>`, +按 `id - 1` 索引:一次读锁 + 一次边界检查。`Option` 是因为 id 在插入取锁之前就发出去了, +两个线程同时建 channel 可能乱序到达。 + +### 语料补了一条:真的会 park 的 `select` + +`examples/syntax/select.lk` 里此前每一个 `select` 都有一条**已经就绪**的 arm, +所以从来没有真正阻塞过——而阻塞正是上面这三条改动唯一会出错的地方, +出错的形式是**挂住**,不是答错。现在加了两个 producer 对着无缓冲 channel 喂 50 轮、 +主线程用无 default 的 `select` 收 100 次:总数确定,交错不确定。 +另外本地跑了多生产者/多消费者压测(无缓冲 + 小缓冲 + close 唤醒阻塞接收方) +原生 20 次、阻塞 `select` 20 次,无挂起无错答。 + +## §54 `try` 在闭包里读不到闭包捕获的东西(2026-08-19) + +写 §53 的压测时随手写了 `spawn(|| { try { got = recv(c); } catch e { … } })`, +它不原生化。缩下来是这么一条: + +```lk +fn outer(k: Int) -> Int { + let f = || { let got = 0; try { got = k + 1; } catch e { got = 0 - 1; } return got; }; + return f(); +} +``` + +`try` 的 body 被外联成一个自己的函数,而 `outline` 给它 `capture_count == 0`。 +body 里的 `LoadCapture 0` 于是去查 body 自己的 capture 列表——那里面只有区域的 +cell 输入。要么查不到(报 `BadConst`,今天就是这样),要么 cell 输入够多、 +**查到另一个**。也就是说这条不只是覆盖缺口,它还是一条潜在的静默错答。 + +由于捕获正是闭包的意义所在,这条实际上等于:**`try` 写在任何有捕获的闭包里都不原生化**。 +`spawn(|| { try { … } })` —— 一个自己处理错误的 goroutine —— 正好是最常见的写法。 + +### 改法 + +body 的 capture 列表**就是**外层函数的:在 `fn_params` 里(输出 cell 之后、出口通道 +之前)按位置声明外层的每一个捕获,并让它们占据 capture 索引 `0..n`, +后面才是这个区域自己的 cell 输入。索引 `k` 必须还是索引 `k`,所以是**按位置、 +无条件**传的:一个静态已知的捕获也占一格,传一个死的字——和普通调用点上 +`ClosureCapture::StaticRef` 已经在做的交易一样。同时把外层的 `ref_captures` +和 cell 内容类型按新索引复制过去,这样 body 里对捕获变量的算术还是有类型的, +不会退回 `Dyn`。 + +两种宽度都支持:一个字直接传;两寄存器的 carrier(`Dyn` / `Maybe`)拆成两个字、 +在 body 入口拼回去——和区域输入用的是同一套 `entry_carriers`。 + +一条明确的拒绝:**外层是 goroutine body 时不做**。那里捕获的当前值在线程私有的槽里, +不在参数里,把参数传下去会传成 spawn 那一刻的值,把之后所有写入都藏起来。 + +### 数 + +生成 60 个"闭包捕获 1..4 个变量(Int / String / List),`try` 里读它们"的程序: + +| | 原生化 | 分歧 | +| --- | --- | --- | +| 改之前 | **0 / 60** | — | +| 改之后 | **60 / 60** | 0 | + +第一版漏了一件事,是 §49 那条元数检查当场抓住的:`Dyn` 捕获是两个字, +`body_signature` 报 "a try body parameter is wider than a machine word"。 +那条检查写下来不到一天就付清了。 + +### 还没做的 + +body 里**写**捕获变量(`StoreCellVal` 打到一个按值传的捕获参数上)仍然拒绝, +消息是既有的 "no write-back path"。按值捕获本来就没有回写路径, +要做就得让区域的写入反过来把外层的捕获提升成 cell(`require_cell_capture` +现在只对函数自己的 body 起作用)。是拒绝而不是错答,单独一轮做。 + +## §55 越界读出来的 nil,编译后会炸(2026-08-19) + +顺着 §54 往下查一条"闭包捕获的容器进 `try` 被拒",落到 `to_dyn` 拒绝 `MaybeI64`。 +拒绝本身只是覆盖缺口,但顺手写的差分语料把一件更严重的事情翻了出来: + +```lk +let xs = [3, 1, 4]; +let out = []; +out.push(xs[9]); // 解释器:塞进一个 nil。原生:Error: runtime error +``` + +**同一个程序,解释器跑完,编译版本死掉。**四种落点,四条都是: + +| 写法 | 解释器 | 原生(改之前) | +| --- | --- | --- | +| `out.push(xs[oob])` | 追加 nil | raise | +| `m[k] = xs[oob]` | 存 nil | raise | +| `xs[oob] == 4` / `!= 4` | `false` / `true` | raise | +| `"[" + xs[oob] + "]"` | `[nil]` | raise | + +原因是同一个:这些位置都用 `read_scalar` 读值,而 `read_scalar` 会把 +"可能是 nil"**断言成**"一定在",不在就 raise。那在**要一个数**的地方是对的 +(解释器那里也报错),在别的地方全是错的 —— 列表、映射、相等比较、字符串拼接 +都收得下 nil,解释器也确实收下了。 + +静态上什么都看不出来:`Maybe` 窄化成 `Int`,而 `Int` 正是载体想要的类型。 + +### 改法 + +1. `to_dyn` 学会可空载体。此前它和 `to_dyn_any` 是两个函数,区别只有"拒不拒绝载体", + 而除了调用参数的编组,所有地方都拿的是**拒绝**的那个。两者已合并成一个: + 装箱本来就是"这个值在 VM 里是什么"的那一刻,而一个载体在 VM 里就是 nil 或者负载。 +2. `ListDyn` 的 push 和 `MapStrDyn` 的存,改成读原始值再装箱,不再断言。 +3. 相等比较:任一侧是可空载体时按 nil 比。`(Maybe, Int)` 这条常见形状走 + `present AND 相等`,多一条 `and` 而不是两次装箱加一次调用 —— + `xs[i] == k` 在搜索循环里就是这个形状。其余装箱交给 `dyn.eq`。 + **只管相等**:有序比较对 nil 在解释器里也是错误,断言在场失败的是同一批程序。 +4. 字符串拼接:`Str + 可空载体` 并入既有的 `Str + Dyn` 分支,`dyn.add` 按 VM 的 + 第四条规则显示拼接,缺席就是 `nil`。 +5. **类型化容器**是唯一真收不下 nil 的地方。往 `List` / `Map` 里 + 存一个可空值,现在报成"这个载体的字面量被推翻了"——就是既有的定点机制, + 下一趟会把它重建成 Dyn 载体。此前是静默接受、运行时 raise。 + +### 数 + +生成 80 个"越界元素落到各种位置"的程序(present 与 absent 两种上界都跑): +改之前 13 条原生化里 **13 条分歧**;改之后 32 条原生化,**0 分歧**。 +覆盖率门禁 65/65、61 程序 sweep、五个种子的生成式差分、 +`cargo test --workspace --all-features`、以及 §48/§54 两份语料全绿。 + +语料补在 `examples/syntax/null_coalescing.lk`:四种落点各一条, +外加一条"算术上确实该报错"的对照。这一类此前**一条覆盖都没有**。 + +### 还没做的:报错文字 + +用 nil 做算术时两边都 raise,但话不一样:解释器说 +`Add expected numbers or strings, got Nil and Int`,原生说 `runtime error` +(`lkrt_maybe_i64_unwrap`)。同一个程序 `try { xs[9] + 1 } catch e { e }` +在两个后端拿到两个字符串。 + +`lkrt_dyn_add` 那一处已经修好(它是这一族里唯一还写着 "runtime type error" 的, +`sub`/`mul`/`div`/`mod` 早就用 `binary_type_error` 指名操作数了)。 +但真正走到的是 `read_scalar` 的断言,而 `read_scalar` 有 **94 个调用点**, +都不知道自己在为哪个运算符读值。要按 VM 的逐运算符措辞对上,得把上下文串下去 —— +单独一轮做,不在这一轮顺手改。 + +## §56 被 catch 到的那句话,两个后端终于一样(2026-08-19) + +§55 结尾记了一条没做的:nil 参与算术时两边都 raise,但话不一样 —— +解释器说 `Add expected numbers or strings, got Nil and Int`,原生说 `runtime error`。 +当时的判断是"`read_scalar` 有 94 个调用点,把上下文串下去代价太大"。 + +那个判断错在**串错了东西**。要串的不是上下文,是**那句话本身**, +而且只要串到真正知道它的那几个地方。 + +### 做法 + +- lkrt 多一个入口:`lkrt_rt_maybe_guard(present, message)`,`present == 0` 就 raise + `message`。**一个**入口,不分类型 —— 它只看那一位,值由 `maybe.value` 自己取。 +- `convert::read_scalar_saying(..., message)`:可空载体走 guard + `maybe.value`, + 其余类型原样交给 `read_scalar`。94 个调用点一个没动。 +- `inst/scalar.rs` 的算术与有序比较两处,在读出**两侧的声明类型之后**构造那句话, + 作为常量 intern 进去。运算符和两个操作数的类型名在那里都是静态已知的, + 而这正是解释器那句话里的全部内容 —— 所以 lkrt 里**没有**一张消息表可以和执行器漂移。 +- 两侧**都**可空是静态句子唯一给不对的情况(第二个操作数在不在只有运行时知道), + 那一种装箱交给 `dyn.*` 按值格式化。 + +措辞是**逐个探出来的**,不是从源码读的:5 个算术运算符 × 4 个有序比较 × 左右两侧, +共 18 条,逐条对着解释器跑。三种句式:`+` 和 `-` 各自说自己收什么,其余共用一句。 + +### 结果 + +18 条逐字节一致。`try { xs[9] + 1 } catch e { e }` 两边都是 +`Add expected numbers or strings, got Nil and Int` —— 这是个**值**,不是诊断细节。 + +`examples/syntax/null_coalescing.lk` 里加了 9 条断言(含两侧都 nil 的那条), +所以这条一致性此后由差分门禁盯着。 + +### 顺带 + +- lkrt 里那句"这匹配 VM,VM 在 nil 参与算术时**halt**"是**假的** —— 解释器 + raise 的是一个可以被 catch 的值。注释已改,并指向新的 guard。 +- **没有性能代价**,量过:同一 profile 的两个二进制,8000 万次 + `for x in xs { s = s + x }`,改前 0.17s,改后 0.17s。guard 是"看一位、冷调用", + 和原先的 unwrap 一样;值由纯 extract 取出。 + +### 顺手补完的落点 + +§55 那一族又扫了两轮(每种落点单独一个程序,VM 与严格原生逐字节比): +`out[0] = xs[越界]`(Dyn 列表的**下标赋值**,push 修了它没修)是第五条错答,已修; +`Map` / `Map` / `List` 的下标赋值补上了载体推翻检查。 +现在两轮扫描 30 种落点,0 分歧,剩下的全是 NOLOWER(拒绝,安全): +列表字面量里的 nil、`insert`、`contains`、`join`、`Map` 的存。 + +## §57 nil 也要能被**构造**出来,不只是被存进去(2026-08-19) + +§56 结尾列的五条 NOLOWER,逐条用"同一个落点、值不可空 / 值可空"两个程序对照, +结论一致:**五条全都是"不可空就降级,可空就拒绝"**,也就是同一个根因的第五、六、七个分支。 + +- **列表字面量**(`[xs[9], 1]`):Dyn 列表那条路有一张**元素类型白名单**, + 上面没有可空载体。这张表旁边就写着一条注释,记的是上一次同样的错误 + ——"漏一个元素类型在这里是错答,不是回退"(当时漏的是类型化 map)。 + 这次漏的是四个可空载体。加上去,`join` 那条一起好了(它就是走字面量)。 +- **`insert`**:那条 arm 的值参数类型不受限,类型化列表分支把载体**原样**递给 + `i64_insert`。拦住它的是 ABI 里的参数个数不匹配 —— 拦是拦住了,但报的不是问题本身。 + 改成报"载体字面量被推翻",这既是更好的话,也是定点重建成 Dyn 列表的信号: + 于是这条从**拒绝变成了降级**。`push` 早就这么做了,`insert` 是同一个存,晚一个方法。 + +同一份 80 程序语料,原生化 **32 → 80**(全部),分歧仍然 0。 + +### 剩下的,以及为什么剩下 + +- `contains` / `count` / `index_of` 在**类型化**列表上,arm 要求精确类型, + 可空 needle 不匹配 → 拒绝。这一条其实有个更好的答案:`List` 里永远 + 不可能有 nil,所以"缺席就是 false",不必把列表重建成 Dyn。留着。 +- **`Map` 存非同类值根本没有载体**。`{"a": 1, "b": "s"}` 能降级(`MapStrDyn`), + `{1: "a", 2: 3}` 不能 —— 载体集合里有 `MapStrDyn` 却没有 `MapI64Dyn`, + 这个不对称是任意的,而 `{1: "a"}` 是一句普通的程序。 + lkrt 那边 `StrDynMap = FxMap` 是真的只认字符串键, + 所以补这一条要新增一个运行时 map 类型加一整套 ABI 入口 —— 是个单独的活。 + +## §58 空的 `catch` 让"body 每条路都离开"这个判断变成假的(2026-08-19) + +把 `defer` 和控制流的组合逐个探了一遍(8 种形状,VM 与严格原生逐字节比), +出来一条错答。缩下来 `defer` 根本没参与: + +```lk +fn f(n: Int) -> Int { try { if n > 0 { return 7; } } catch e { } return 5; } +f(0) // 解释器 5,原生 Error: runtime type error +``` + +`f(1)`(走 return 那条)两边都对,错的是**没有** return 的那条。 + +MIR 里看得很清楚:检查块算出了结果码,然后 `br` 到 return 块 —— **一次都没测**。 +于是 `dyn.as_i64` 去读那个还是 nil 的值 cell。 + +原因是 `always_leaves = region_handler == region_fallthrough`。这个条件真正检测的是 +"编译器没有发出跳过 handler 的那条 `Jmp`",而**handler 是空的**时候也没有 —— +跳过空的东西没有东西可发。两件事被读成了一件。 + +改法是把这个特判整个删掉:**结果码永远测**。body 真的每条路都离开时,码 0 不会出现, +那条假边不可达 —— 合法,而且是死的。代价是一次比较,发生在一个刚从调用返回的位置上。 + +### 为什么生成式差分没抓到 + +它生成的每一个 `catch` 体里都有语句。已经补了一条"空 handler + 只有一条路 return"的形状 +(两个上界都跑:return 的那条和不 return 的那条,后者才是错的), +语料也在 `examples/syntax/try_catch.lk` 里留了一份。 + +### 同一轮的其它探测 + +- **数值与浮点边界** 22 条(i64 最小值、回绕、除零、负数取模、NaN、-0、 + 1e-320、大整数与浮点的字符串化、移位、按位取反):全部一致。 +- **字符串与 Unicode** 24 条(多字节索引/切片/反转、emoji、`straße`.upper()、 + 负下标、越界、空分隔符、`\u{a0}` 的 trim、Unicode 比较):全部一致。 + +这两族没有发现问题,记在这里是为了下次不用再探一遍。 + +## §59 最后一趟的可重试发现,第三种漏了(2026-08-20) + +上面那条类型错误修好之后,`fn f(v) { … } f([]); f(5);` 还是不原生化,报 +"empty list literal(s) at pc [4] were mis-guessed (retried as Dyn)" —— +一句**定点内部的话**漏到了用户面前,而且重试并没有发生。 + +`lower_bundled` 结尾那段注释已经把道理写清楚了:定点收敛 → `refine_signatures` 跑一次 → +最后一趟按细化后的签名降级,而**只有在最后一趟才出现**的发现没有下一趟可去, +所以那里专门收集了一批可重试的失败再走一遍。收集的名单上有 +`DynLoopPhi` 和 `ParamCarrierContradicted`,**没有** `LiteralElemTypeContradicted` +(也没有 `PhiProvenance`)。 + +空的 `[]` 的元素类型是靠"往后找第一次 push"猜的;函数里根本没有 push 时没有任何证据, +猜的就是默认载体。而"这个参数是 Dyn"正是 `refine_signatures` 才定下来的, +于是矛盾**只在最后一趟可见**,而那里没人听。 + +两种都补进名单了。`f([]); f(5)` 现在原生化,和解释器一致。 + +顺带记一条**诊断本身的毛病**:`carrier_contradicted` 在句柄不是已知字面量时, +会把 `literal_carrier` 里**所有**同形状的 pc 都报出来 —— 所以 `f([1]); f(5)` +也报"empty list literal",尽管那里根本没有空字面量。这条没改, +它只是把话说得不准,不影响重试(重试的是同一批 pc)。 + +## §60 在装不下 nil 的容器里找 nil(2026-08-20) + +§57 结尾列的第一条:`contains` / `count` / `index_of` 在**类型化**列表上遇到可空 +needle 就拒绝降级。类型化的那些 arm 都精确匹配 needle 的类型,`Maybe` 一条也不匹配。 + +当时记的判断是对的:`List` 里永远不可能有 nil,所以"缺席就是没找到", +**不必**把列表重建成 Dyn 载体 —— 接收者没有问题,重建它会把一次查找变成一次分配。 + +做法是在方法分发的入口拦一次:把可空 needle 拆成 `present` 和 `value`, +拿 `value` 走原来的类型化 arm,再按方法的"没找到"是什么去 `select`: +`contains` 是 `false`,`count` 是 `0`,`index_of` 是 nil。 +缺席那条路上 `value` 是没人写过的值,所以它的答案是被**丢掉**的,不是被相信的。 + +`Inst::Select` 本来就按分量选(Cranelift 没有聚合 select),所以 `index_of` 返回的 +`Dyn` 载体和标量走同一条路,三个方法一套代码。 + +八种组合(`List` / `List` / `List` / `Set` / Dyn 列表 × 三个方法) +逐个对着解释器跑,全部一致;语料进了 `examples/syntax/null_coalescing.lk`。 + +## §61 只读一个容器参数,却被判成写(2026-08-20) + +`bare-metal-x86` 在这个分支上编不出来,两条独立的原因,都早于本轮: + +### 一:`lkrt` 不带 `std` 编不过 + +`lkdyn.rs` 无条件写着 `crate::lkclosure::…`,而 `lkclosure` 是 `#[cfg(feature = "std")]` 的 +(闭包值走 channel 的深拷贝模型,那个模型和线程在一起)。x86 内核链的是不带 std 的 `lkrt`, +于是整个 crate 编不过。四处引用(渲染一个闭包,以及 `map`/`filter`/`reduce` 的闭包回调) +连同它们在 `lib.rs` 的重导出一起加了 cfg —— 没有 `lkclosure` 就不可能存在闭包值, +这几处本来就不可达。 + +### 二:`drivers/text` 被判成"写过容器参数" + +那个文件里就写着:"`font` is only ever read, which is what keeps this module bundlable"。 +判定它的是 `module_may_mutate_a_parameter` —— 一个刻意过近似的污点分析。 +过近似的代价按注释的说法是"白白不打包一个模块",但 `compile object:` **没有回退**, +所以代价其实是**构建失败**。 + +两处过近似: + +- **通过寄存器的间接调用**把每个被污染的实参都判成"被留住了"。而 LK 的运算符是 desugar + 成调用的:`(bits >> shift) & 1` 就是 `__lk_shr` 和 `__lk_bit_and` 两次调用。于是 + "从容器参数里读出一个元素再移位"看起来和"交给一个不认识的函数"一模一样。 + 现在:如果被调寄存器能被指名、而且指的是一个证明只读的内置(全部运算符 desugar, + 加上 `typeof` / `assert*` / `print*` / `panic` / `error`),就不判。 + 指名要沿着 `Move` 传递 —— 被调者是被 `Move` 进调用窗口基址的 —— 而且全局槽位要按 + 编译器的 fact 解析,指令里的 `bx` 是执行器自己都不信的占位符。 +- **污点从不失效**。它只在传播的地方(`Move` 和容器读)被移除,所以一个曾经装过 + "从参数里读出的元素"的寄存器,在之后被普通算术覆盖之后**仍然是脏的** —— + 而字节码复用寄存器很凶。现在一批"把刚算出来的值写进 `a`"的操作码会清掉它。 + +两处都往**安全**的方向列举,不做推断:漏判一次改写是错答,多判一次只是拒绝。 +守卫本身的测试(`clif_differential_test` 里"写过容器参数的模块不打包")仍然绿。 + +### 还没通的 + +x86 内核现在过了打包这一关,停在下一个:`print_file` 里一处方法在那个接收者类型上 +还没有原生降级。那是另一条,没在这一轮做。 + + +## §62 装箱 map 按 `String` 索引,而 nil 和 Bool 也是键(2026-08-20) + +解释器的 map 键有四种:`nil`、Bool、Int、String。原生这边没有任何表示装得下前两种 —— +装箱载体 `StrDynMap = FxMap`,类型化载体按 `String` 或 `i64`。 + +**读**这一侧已经补齐:`lkrt_dyn_get` 对任意键种做查找,miss 给 nil,非键种交给 +`key_from_dyn` 按解释器的措辞 raise。**写**这一侧补不了:`m[nil] = 1` 解释器给 +`{nil:1}`,原生没有地方放。 + +现状(2026-08-20 起):**不再是错答,是覆盖缺口**。键的静态类型不是 `Str` / `I64` +时,降级层一律拒绝,整程序回落 VM —— 正确,慢。下面记的那些复现,今天两端一致。 + +### 范围比"nil 和 Bool"大(2026-08-20 复测) + +不止空字面量的猜测,也不止 nil/Bool。任何**跨种类的键**写进类型化 map 载体**曾经**是错答, +因为运行期拆箱 `dyn.as_key_str` / `dyn.as_key_i64` 拒绝其他种类,而解释器不拒绝 —— +LK 的 map 同时收 nil / Bool / Int / String,载体是原生这边的表示选择,程序没要求过。 + + fn put(m: Any, k: Any) { m[k] = 1; return m; } + println(put({"a": 1}, "b")); // 两端 {"a":1,"b":1} + println(put({"a": 1}, 7)); // VM {"a":1,7:1} / 原生 raise "runtime type error" + +第二行的 map 是**显式字面量**,没有任何猜测参与。默认设置(允许回退)下同样错。 + +顺带:开着 hybrid 时错法不同 —— 桥接返回一个非字符串键的 map 会 +`bridged return kind not yet marshalable: map with non-string key Int(7)`, +同样是解释器答得出来的程序在原生二进制里中止。 + +### 拒绝这条路的实测代价(修正) + +此前这里写着"拒绝擦除的键会把 fuzz 原生化比例从 12/60 打到 10/60"。**那是错的**, +是被一个不稳的下限断言骗了:同一个种子在**未改动的树**上就是 10/60。按 300 例重测, +加不加这条拒绝都是 **76/300**,fuzz 代价为零(下限断言本身已改成只在大样本上生效)。 + +真实代价只有一处,而且已经消掉:`examples/syntax/closure_value.lk` 里 +`try { m[f] = 1; }` 故意拿一个闭包当键,期待运行期按解释器的措辞拒绝 —— 那一条 +**降级是对的**(两端都 raise),不该被守卫拦下。闭包这个事实活在 +`ssa.closure_values` 里,是按函数的 SSA 状态,过不了 region 边界。 + +现在它跨得过去了:`SigInfer::try_body_closure_inputs` 记 `(body, 寄存器)`, +写在实参编排处、读在 body 的形参播种处 —— 和 `try_body_param_tys` 同一条路。 +闭包句柄是 `Dyn`,**走的是两字载体那条分支**,不是单字那条;只补单字那条时 +守卫照样拦,而且例子的报错一模一样。 + +### 已修的部分(2026-08-20) + +键的静态类型是 `Ty::Dyn`、且不是已知闭包时,写入拒绝降级,整程序回落 VM。 +实测:coverage 66/66 不变,fuzz 原生化 76/300 与基线**逐个相等**。 +差分语料 `a_map_key_of_any_kind_answers_or_declines` 钉住"两端一致"。 + +**仍未修**:这些形状现在是回退,不是原生。要让它们原生化,还是需要下面这个表示改动。 + +### 为什么没有改表示 + +`StrDynMap` 换成 `FxMap` 是唯一的正解,顺带把"装箱 map 是字符串键" +这个特例连同各处的 `str_key` / `key_from_dyn` 转换层一起删掉。迭代序不是障碍: +`FxMap` 是 `IndexMap`,顺序按插入,与键类型无关。 + +障碍是**查找要分配**。`map.get(&str_key(text))` 对超过 7 字节的键会构造 +`RtKey::String(String)` —— 而这正是结构体字段读取那条路径,它此前专门优化过 +(`str_dyn_get_at` 按位置读,注释记着一次哈希查找实测 ~107ns 是整个循环的开销)。 +结构体字段名超过 7 字节很常见。 + +绕开分配需要一个借用形式的键(`Equivalent for &str`),它的 `Hash` 必须与 +`RtKey::ShortStr` / `RtKey::String` 逐位一致,包括 7 字节那道分界。那是一份会在 +沉默中漂移的副本 —— 正是 [[lkrt-mirror-drift]] 那一类。 + +### 第四次撞上它:hybrid 桥接会中止(2026-08-20) + +`lk-api` 的 `marshal_map` 把 VM 的 map 搬成原生装箱 map,遇到非字符串键 +`hybrid_die`。于是一个**桥接函数返回混合键 map** 的程序,在默认设置下直接中止: + + fn put(m: Any, k: Any) { m[k] = 1; return m; } // 这个函数降级不了 → 走桥 + println(put({"a": 1}, 7)); + → lk hybrid bridge: bridged return kind not yet marshalable: map with non-string key Int(7) + +写入那一侧已经靠"降级层拒绝"修掉了(见上),桥接这一侧修不掉:它是运行期性质, +而桥的资格是编译期定的,返回值又是动态的 —— 不可能静态排除"会返回 map 的函数"。 +这条是**目前仅剩的错答**,而且默认可达。 + +### 实际动手之后的清单(2026-08-20,已回滚) + +把 `StrDynMap` 换成 `FxMap` 试了一次,`cargo build -p lkrt` 报 26 处。 +比预估的"换个类型"大,因为**共享的辅助件按 `String` 写死**: + +- `set_str_key(map: &mut FxMap, ...)` —— 同时服务 str_i64 / str_f64 / + str_bool / str_dyn 四个载体,装箱那个要拆出自己的 setter。 +- `map_iter_family!` 宏 —— 四次实例化共用,里面 `boxed_str_key(k: &String)`, + 装箱载体需要按键种装箱的版本。 +- `boxed_map_keyed` / `str_dyn_map_mirrored` 的两段式镜像,以及 `chan.rs` 四处、 + `vm_mirror.rs` 两处。 +- 显示要按键种渲染(`{nil:1,true:2}`),不能再直接打字符串。 + +**先量再改**:换类型之后 `map.get(text)` 变成 `map.get(&str_key(text))`,超过 7 字节 +的键每次查找会构造 `RtKey::String(String)`。要不要做借用键(`Equivalent for +&str`,附 hash 一致性测试)**应该由性能门禁回答**,而不是预先假设 —— 结构体字段读取 +走的是 `str_dyn_get_at` 的按位置路径,根本不查哈希,所以这条分配可能无关紧要。 +先做朴素版本、跑 `bench/run_workload_bench.sh`,再决定要不要那份镜像。 + +规模与此前记下的 `MapI64Dyn` 载体相当。 + +## §63 原生这边换了表示的值,有四种办法被看出来(2026-08-20) + +降级层有两处把值换成别的东西:**流物化成列表**(有限来源 + 纯 lambda), +**channel / task 表示成 `i64` 句柄**。两处的理由都是"看不出区别"。四种办法能看出来: + +| 办法 | 解释器 | 换表示之后 | +| --- | --- | --- | +| `typeof(v)` | `Stream` / `Channel` | `List` / `Int` | +| `println(v)` | `` / `` | `[0,1,2]` / `1` | +| `v == 1` | `false` | **`true`** | +| trait 分派 | `impl … for Stream` | `impl … for List` | + +第三行是最糟的:**一个 channel 等于整数 1**。 + +前三种已经拦住(`Ssa::disguised_values`):`typeof`、显示、以及"一边有标记另一边没有" +的比较,都拒绝降级而不是照表示回答。trait 分派同此。 + +### 逃逸只对流拦 + +装箱进容器、跨到类型化参数、被返回 —— 这三种都会把标记丢掉,把裸表示交给会照它 +回答的代码。对流拦了(`Ssa::escape_is_visible`),实测代价为零。对句柄**没拦**: +channel 天天被传进函数、被塞进 `select` 的列表,拦下来当场让两个例子不再原生化 +(`concurrency_demo.lk`、`select.lk`)。 + +**残留**:`println([c])` 打印的是 `[1]` 而不是 `[]`。 + +### `stream.from_list` 标不了 + +`from_list` 和 `collect` 都是**值直通**:结果与入参共用同一个 SSA 值。所以标记 +`from_list` 的结果等于同时标记了调用方的那个列表 —— `println(xs)` 会被拒;标记 +`collect` 的结果等于标记了一个**确实是列表**的东西 —— `collected == [...]` 会被拒。 +两个都试过,两个都当场把 `stream_demo.lk` 或它的比较打掉。 + +标记一个直通值需要它有自己的值可标,而 MIR 没有 copy 指令能造一个。 +`stream.range` 构造新句柄,标了。 + +### 正解,以及句柄那一半已经做了(2026-08-20) + +两处都是"表示不同"而不是"跟踪不到位"。channel / task 现在**带自己的 tag** +(`DYN_CHAN` / `DYN_TASK`)而不是裸 `i64` id,四种观察方式自然都对了,逃逸也不再丢 +信息 —— 跟踪代码随之删掉。`println([c])` 现在打 `[]`。 + +改动的清单是**上一次加 tag 时的足迹**:`rg DYN_CLOSURE` 列出五处必须学会新 tag 的 +地方(`kind_name`、`dyn_eq_at`、`display_into`、排序 rank、channel 深拷贝),外加 +trait 分派码。哪一处漏了都是沉默的错答,而这份清单是现成的。 + +顺带修好一处**解释器自相矛盾**:一个 channel 穿过 channel 之后,`==` 说它不是原来 +那个,而往它 `send` 又会到达原来那个。堆对象比的是句柄,而 channel 的身份是 id。 +现在按 id 比。 + +**流那一半也做了**(同日):`DYN_STREAM`,payload 是物化出来的 dyn-list 句柄。 +四种观察方式全对,`stream.from_list(xs)` 也不再把 `xs` 一起标上 —— 因为**箱子本身 +就是一个独立的值**,这正是"标记直通值"做不到的那件事。 + +`stream.collect` 取出里面的列表,`stream.map` / `filter` / `take` / `skip` / `chain` +取出、操作、再装回去;`chain` 的第二个参数也是流,一并拆。 + +代价与收益都量了:跟踪那套守卫此前让 trait-位置扫描里 7/35 回退,现在 +**35/35 既一致又原生**。跟踪代码全部删除。 + +## §64 结构体实例和 map 共用一个载体,"不知道"必须当成"可能是结构体"(2026-08-21) + +`NewObject` 编译成 `Map`,所以一个 `MapStrDyn` 字要么是 map,要么是 +结构体实例,MIR 类型区分不了。解释器那边是两个不同的堆值,map 的**集合**操作 +落在结构体上一律 raise。原生这边按载体作答: + +| 操作 | 解释器 | 原生(修复前) | +| --- | --- | --- | +| `v.len()` | ``E `len()` has no answer for P`` | `ok 2` | +| `v.is_empty()` | `E P has no method 'is_empty'` | `ok false` | +| `v.keys()` | `E P has no method 'keys'` | `["ok ","p","q"]` | +| `"p" in v` | `E Contains haystack object is not searchable: "P"` | `ok true` | + +类型检查器挡住了写明类型的接收者,所以这四条只在**擦除**位置出现:`fn f(v: Any)`, +或者两个调用点分别传结构体和 map 让参数事实归零的时候。§62 里的 `p.m["b"] = 2` +是同一类:两个后端一起答错,因为它们共用前端。 + +修复分两层。装箱那层(`Ty::Dyn` 接收者)由运行时判定:`dyn.len_of`、 +`dyn.contains`、`dyn.map_pairs`/`keys`/`values`、`dyn.map_has`、`dyn.map_delete` +读 arena 类型标记,标记非零就按解释器的原话 raise。非 map 的 tag 一次比较就返回, +只有装箱 map 付一次查表。 + +有类型那层不能用运行时判定:`map_h.str_dyn_len`/`str_dyn_has` 是热路径, +每次调用加一次带锁查表不可接受。改成编译期证明,而且是**正向**的: +`ssa.struct_facts` 从 `HashMap` 变成 +`HashMap`,`StructFact` 只有 `Struct(name)` 和 `PlainMap` +两个成员,**没有 Unknown**——不在表里就是不知道,集合操作在不知道的地方拒绝下降。 +方向选反了就是错答:如果"缺省 = 普通 map",漏记一处事实就多一个错答;现在漏记 +一处只是多一次回退。 + +事实的传播点就是原来那张表的足迹:`seed_provenance` / `inherit_provenance` / +`verify_seeded_provenance` 三处(phi 按"每条非自环边都一致"合并,`Struct(P)` 与 +`PlainMap` 不一致就归零),跨函数的 `param_structs` / `ret_structs`,跨 `try` 区域的 +`try_body_struct_inputs`。`PlainMap` 目前在三个 map 字面量构造点写入。 + +把剩下的观察点按同一张表走了一遍——相等、显示、`typeof`、下标读写、`get`、 +`values`、`clear`、`delete`、装进列表——只多出一处:`for k in v` 在原生这边 +按 map 迭代,把字段当成 pair 发给循环,解释器那边是 +`ToIter target object is not iterable: "P"`。两层各补一处:`ToIter` 的 +`MapStrDyn` 分支要 `PlainMap` 证明,`dyn.to_iter` 读类型标记。 + +覆盖率没有掉:门禁 71/71,VM/原生扫描 73 一致 1 允许分歧,300 例模糊测试通过。 +`a_map_that_is_one_still_lowers_its_collection_methods` 钉住反面——参数位置和 +循环头上的普通 map 仍然全原生下降。 + +## §65 结构体的身份存在别的线程读不到的地方(2026-08-21) + +`typeof(p)`、`println(p)`、trait 派发、字段声明检查,原生这边都靠两张表: +`id → 名字/字段` 的注册表(生成的入口序言写一次)和 `句柄 → id` 的标记表 +(每次构造写一条)。两张都是 `thread_local!`。任务跑在别的线程上,两张都读不到: + +| 程序 | 解释器 | 原生(修复前) | +| --- | --- | --- | +| 结构体经 channel 送进 task,在里面 `typeof` | `P` | `Map` | +| 同上,`"" + v` | `P{p:1,q:2}` | `{"p":1,"q":2}` | + +三处改动: + +1. **标记进句柄**。`StrDynMap` 从 `FxMap` 的别名变成一个带 + `type_id` 字段的结构体(`Deref`/`DerefMut` 到原来的 map,所以调用点不动)。 + 标记表整张删掉——顺带把 `typeof`、派发、声明检查上的一次哈希查表变成一次取字段。 +2. **注册表改成进程级**。std 下 `Mutex>`,裸机维持 spin。所有调用者 + 本来就是"把要的东西拷出闭包再 raise"(raise 会 longjmp 过 drop,守卫跨越它 + 就永远不解锁),这条纪律现在是注释里写明的前提。 +3. **过 channel 的深拷贝要带上 id**。`OwnedVal::Map` 只搬条目,收到的一端就是 + 一张普通 map;加一个 `i64` 字段,`materialize` 写回去。 + +把 lkrt 里每一处 `is_map_tag` 逐条过了一遍(共 26 处),又挖出六条:`+` 合并、 +`-` 去键(两种形状)、`.clear()`、`.get(k, default)`。前四条只要在 map 分支上加 +"不是结构体实例",就自然掉进原有的报错——`kind_name` 早就会把结构体叫 `P`,所以 +文案自动对上(`Add expected numbers or strings, got P and Map`)。后两条按接收者 +拒绝。合法留下的只有字段读写(`dyn.field`/`dyn.index_set`)和相等(相等本来就比 +类型标记)。 + +`.clear()` 那条还暴露出 §64 的守卫写窄了:它只在**已知是结构体**时拒绝,而两个 +调用点分别传结构体和 map 的参数根本没有事实——正是错答所在。守卫改成 +`MapStrDyn` 接收者必须**证明**是 map。改完 `examples/syntax/pattern_matching.lk` +掉了下降:`{ k: v, ..rest }` 产出的 `rest` 也是一个没标记的新 map,补上。同时 +`str_dyn_without` 的拷贝把 `type_id` 清零——少一个字段的结构体不再是那个结构体。 + +`a_struct_keeps_its_name_across_a_task` 钉四条:过 channel、被闭包捕获、嵌套加 +列表、从任务里送回来。 + +同一趟里 §64 的 `PlainMap` 证明缺了三个产出点——map 合并、`dyn.sub` 的 map 结果、 +以及**所有** stdlib 行的 `Map` 返回值。三条 `PureCranelift` 差分用例 +因此拒绝下降;覆盖率门禁和 VM/原生扫描都没看见(它们允许回退)。stdlib 那条写在 +通用行下降的收尾处,而不是逐行写,这样新加一行不会漏。 + +## §66 `?.` 整个操作符没有原生下降,因为 `IsNil` 不认识容器(2026-08-21) + +`a?.f` 降低成:先把结果置 nil,`IsNil` 测接收者,不是 nil 才去取字段。原生这边 +`IsNil` 的"永远不是 nil"那一支只列了标量(`I64` / `F64` / `Bool` / `Str`),没列 +容器句柄——于是: + +| 写法 | 修复前 | +| --- | --- | +| `p?.field`(`p` 是结构体) | 整个程序掉回解释器 | +| `m?.k`(`m` 是 map) | 同上 | +| `m?.missing` | 同上 | +| `z?.f`(`z` 是 nil) | 同上 | +| `list?.len()` | 原生(方法调用走另一条路) | + +**接收者根本不可能是 nil 的那几种也一样掉**,因为拒绝发生在类型上,不在值上。 +补上列表 / map / set / bytes / slice 各种句柄即可,它们的结论和标量一样是常量 +`false`。 + +找这个拒绝点花的时间比修它长,原因是 `Unsupported::OperandType` 的显示把它自己 +携带的 `want` / `got` 丢掉了,只印 pc。顺手改成印出来—— +"an operand at pc 9 is a str where a i64 is required" 比 +"has a type outside the natively lowerable subset" 少猜一轮。定位最终靠 +`lk coverage --disassemble` 数到第 6 条指令是 `IsNil r6 r0`。 + +`examples/syntax/null_coalescing.lk` 补了五条断言(结构体字段、map 键、缺失键、 +nil 接收者、链式),覆盖率门禁从此看着它。 + +## §67 任务里的 raise 杀掉整个进程,而解释器把它交给 await(2026-08-21) + +raise 交给的是最近的 `try` 帧,而那个帧栈是**线程局部**的。spawn 出来的任务从一个 +空栈开始,所以任务体里的 raise 找不到任何处理者,走了"未捕获"那条路——打印并 +`exit(1)`。解释器那边是把错误当成任务的**结果**,谁 await 谁收到: + +| 程序 | 解释器 | 原生(修复前) | +| --- | --- | --- | +| `try { task.await(t) } catch e { … }`,t 里除零 | `caught modulo by zero`,继续跑 | `Error: modulo by zero`,退出 1 | +| 任务失败但没人 await | 静默,程序照常结束 | 同上,进程死 | + +修法:任务体在自己的 `try` 帧里跑。Cranelift 和 Rust 都不能发 `setjmp`,所以能活过 +跳转的那个帧必须是 C 的——`try_trampoline.c` 里加一个 `lkrt_rt_try_thunk(thunk, +state)`,和已有的 `lkrt_rt_try_region` 同一套协议,只是接的是闭包而不是降低出来的 +函数体。任务槽从 `JoinHandle` 变成 `JoinHandle` +(`Returned` / `Raised`),`task.await` 拿到 `Raised` 就在**自己**这条线程上重抛 +(值先 materialize 进本线程的 arena——它是在任务那条线程的 arena 里建的)。 + +两处纪律写在代码上:参数在进保护区**之前**就 materialize 好(raise 会跳过中间每一个 +Rust drop),以及失败的任务会漏掉一个装参数的小 `Vec`——上界是失败任务的个数, +所以留着而不是为它绕一层 thread-local。 + +计时器那条路不走保护区:它的注释早就写明它不 raise(关闭的 channel 不是计时器要报 +的错),直接给 `TaskOutcome::Returned`。 + +`a_task_hands_its_raise_to_its_awaiter` 钉三条:await 捕获、无人 await 时静默、 +正常返回不受影响。 + +## §68 桥回来的结构体过不去,而那正是默认配置(2026-08-21) + +Tier 1 桥把 VM 端算出的值 marshal 回原生端,`marshal_value` 有 Nil/Bool/Int/Float/ +Str/List/Map 七支——**没有 Object**。所以一个被桥接的函数只要**返回**结构体,程序 +就死: + +``` +lk hybrid bridge: bridged return kind not yet marshalable: P +``` + +而 `LK_AOT_HYBRID=1` 是默认。响亮失败,不是错答,但整整一类程序在默认配置下跑不了。 + +已有的 hybrid 测试里其实有一个返回结构体的桥接函数(`mkp(7)`),但它**把结果丢掉了** +——没人用的值不会被 marshal,所以这个洞一直看不见。给那行加上 +`println(typeof(p)); println(p);`,测试当场变红。 + +补法:`marshal_object` 把字段建成 `str -> Dyn` map,再按**名字**打类型标记。id 是 +降低期分配的,只有运行时注册表能把名字换成 id,所以 lkrt 加一个 +`lkrt_lkmap_obj_mark_by_name`,并进桥的构造器表(lk-api 从不链接 lkrt,只通过 +wrapper 的 C 构造器拿函数指针——所以表和 wrapper 的签名要一起改)。名字在原生端 +不认识时不打标记,答案就是一张普通 map——和"声明够不到的结构体"在原生端本来的 +答案一致。 + +把桥的**返回类型**逐条走了一遍(22 种:各种标量、长短字符串、五种列表、五种 map +含整数键、Set、Range、Bytes、结构体、嵌套结构体、闭包),补完 Object 之后全部通过。 + +同一趟里清掉一条**过期的守卫**:`marshal_list` 开头写着"有类型的字符串列表在 VM 里 +带引号显示、`ListDyn` 不带,所以不能转",于是 `hybrid_die`——而它下面几行已经写好了 +正确的转换,被这条守卫变成死代码。两件事都不成立了:`println(["a","b"])` 和同一个 +列表标成 `List` 在两个引擎上都答 `["a","b"]`(显示早就统一了),而且四种造字符串 +列表的写法(字面量、`push`、`split`、`chars`)过桥都到不了那条守卫。守卫删掉,转换 +留下,注释改成现在为真的那句。 + +顺带记一条门禁缺口:每一条 AOT 门禁都钉 `LK_AOT_HYBRID=0`(它们量的是纯原生), +所以**用户实际拿到的那套配置从来没有被扫过**。`vm_native_sweep.sh --hybrid` 是 +那一趟:74 个一致、1 个允许分歧、0 个编译失败——比纯原生那趟还多一个 +(workspace 那个示例纯原生编不了,桥接编得了)。 diff --git a/docs/aot/native-stdlib.md b/docs/aot/native-stdlib.md index 87efa8a6..c340825b 100644 --- a/docs/aot/native-stdlib.md +++ b/docs/aot/native-stdlib.md @@ -136,5 +136,5 @@ LK user code or `lk-stdlib`; that keeps parser/compiler/VM code out of the final binary. `lk-aot-lower` is a compile-time crate and may depend on both `lk-core` and `lk-stdlib`; the CLI only connects the AOT path when the `aot` feature is -enabled. A Tier 1 hybrid binary additionally links `liblk_api.a` for the +enabled. A Tier 1 hybrid binary additionally links `liblk_api_cabi.a` for the bridge (see [`tier1-hybrid.md`](./tier1-hybrid.md)). diff --git a/docs/aot/tier1-hybrid.md b/docs/aot/tier1-hybrid.md index b7ac9535..5022bcd4 100644 --- a/docs/aot/tier1-hybrid.md +++ b/docs/aot/tier1-hybrid.md @@ -47,7 +47,7 @@ native and executes only the unsupported ones on the VM, inside one binary. statically typed scalar arguments. The serialized artifact is embedded by the *link wrapper* (exactly like Tier 0 embeds source today), never by the IR. The VM enters at link time: a hybrid executable links `liblkrt.a` - *and* `liblk_api.a`. + *and* `liblk_api_cabi.a`. 4. **Eligibility — a reachable non-entry function `f` may be marked VM-executed instead of failing the module when:** @@ -188,7 +188,7 @@ removed the "all call sites must agree on a parameter's type" restriction. VM-executed callees; `.ll` snapshot tests (still nothing links). 4. **CLI hybrid link**: when the lowered module has `vm_functions`, emit the wrapper (artifact JSON + `lk_hybrid_init` registration), link - `liblkrt.a` + `liblk_api.a`; end-to-end demo + hand-written differential + `liblkrt.a` + `liblk_api_cabi.a`; end-to-end demo + hand-written differential cases (native-with-bridge == VM, stdout + exit code). 5. **Gate hardening**: teach the generative fuzz to emit eligible-but- unsupported callees so hybrid binaries join the seeded differential and diff --git a/docs/concurrency.md b/docs/concurrency.md index 8dad6d2a..dc53002c 100644 --- a/docs/concurrency.md +++ b/docs/concurrency.md @@ -33,16 +33,39 @@ Channels are the one shared thing (Arc-backed, not copied); the *values* sent through them are deep copies. ```lk -let c = chan(8); // capacity 8; chan(0) = unbounded +let c = chan(8); // capacity 8; chan(0) = unbuffered; negative raises send(c, v); // blocking; raises once c is closed let v = recv(c); // blocking; raises once c is closed AND drained use chan as ch; +let d = ch.new(8); // the module spelling of chan(…) — `use` shadows it +ch.send(d, v); // the module spells the blocking pair too +let w = ch.recv(d); ch.try_send(c, v); // -> Bool (false = full, not an error); closed raises let v = ch.try_recv(c); // -> value | nil when empty; closed+drained raises ch.close(c); // Go close: buffered values stay receivable -ch.is_closed(c); ch.len(c); ch.capacity(c); +ch.is_closed(c); ch.len(c); ch.capacity(c); // capacity = what was asked for ``` +**The module spelling is the complete one.** All nine operations are on +`chan.…`; the bare globals are only the Go-shaped core: + +| operation | bare global | `chan.…` | +| --- | --- | --- | +| construct | `chan(n)` | `chan.new(n)` | +| blocking send / recv | `send(c, v)` / `recv(c)` | `chan.send` / `chan.recv` | +| `try_send` / `try_recv` | — | yes | +| `close` / `is_closed` | — | yes | +| `len` / `capacity` | — | yes | + +This sentence used to claim every operation had both spellings, which was +false for six of the nine — see `stdlib::globals_test`, which now pins the set. +The six are module-only on purpose: `close`, `len` and `capacity` are names a +program is likely to want for itself, and taking them as globals buys nothing, +because the module spelling reaches every operation. The blocking pair is the +exception because it was the one *missing* half — before it existed, +`use chan;` shadowed the `chan` global and left no way to send at all without +falling back to unqualified names. + Failure semantics follow the v2 error model (see `docs/semantics.md`): errors **raise** and are caught with try/catch — there are no `[ok, value]` status pairs. Closing follows Go: `close` marks the channel and drops the diff --git a/docs/macros.md b/docs/macros.md index b21db6a4..7ad2e828 100644 --- a/docs/macros.md +++ b/docs/macros.md @@ -31,7 +31,7 @@ unless!(x == 9 { | kind | 匹配 | |------|------| -| `expr` | 表达式 | +| `expr` | 表达式,**包括另一个宏调用**(`twice!(twice!(1))`) | | `stmt` | 语句 | | `block` | `{ ... }` 块 | | `item` | 顶层项(fn/struct/…) | @@ -42,6 +42,18 @@ unless!(x == 9 { | `ty` | 类型 | | `path` | 路径 | +`expr` 收得下宏调用是 2026-07-30 补的。展开是 token 级的,捕获那一刻内层调用 +还是 `Id ! ( … )` —— 表达式解析器不认这个形状,于是匹配器答"expected `expr` +fragment"。而**组合**几乎就是宏存在的理由。现在它按语言自己的规则识别 +(`name!` 紧跟 `(`/`[`/`{`,与 force unwrap 的区分规则同一条),整个平衡的 +定界组当作一个 fragment 捕获,substitute 之后由后续轮次展开 —— 和其它宏输出 +一样。 + +宏**可以定义宏**。展开是"先收集定义、再展开调用",而展开*产生*的 +`macro_rules!` 不在收集那一趟读到的输入里 —— 所以这一步会重复多轮,直到某一轮 +不再产出新定义(上限 8 层,防不终止)。不做嵌套定义的程序在第一轮就停,一分 +钱不花。 + fragment 之后允许跟随的 token 受 follow-set 约束 (`core/src/macro_system/follow.rs`),非法组合在宏**定义**时报错,不会 等到调用点。 @@ -54,6 +66,26 @@ fragment 之后允许跟随的 token 受 follow-set 约束 - 模板中元变量的重复深度必须与 matcher 一致 (`core/src/macro_system/validation.rs` 在定义时校验)。 +### 模板串里的宏 + +模板串的 `${…}` 洞里可以写宏调用,也可以写元变量,两者还能嵌套: + +```lk +macro_rules! twice { ($e:expr) => { ($e) + ($e) }; } +macro_rules! show { + ($label:expr, $e:expr) => { "${$label} = ${twice!($e)}" }; +} +show!("total", 21) // "total = 42" +``` + +只有 `${…}` 内部被改写。字面量部分的 `$e` 就是 `$` 和 `e` 两个字符 —— +和宏外面的 `"$e"` 一样;洞里引用未定义的元变量是错误,不会静默留下。 + +这曾经是个洞:模板串对 token 级展开器是**一个 token**,内部文本要到解析期 +才被重新切分。于是 `"${twice!(3)}"` 报"no macro named `twice` is defined" +(而它就定义在上面),`"${$e}"` 报 `Unexpected token: Dollar`。切分器现在只有 +一份(`token::split_template_string`),解析器和展开器共用。 + ### 卫生(hygiene) 宏体内引入的绑定不会捕获/污染调用点同名变量(`core/src/macro_system/ @@ -61,6 +93,58 @@ hygiene.rs`);展开产物中的控制流、参数名、语义名各有针对性 (见 `hygiene_tests/`)。`$crate` 锚在定义时解析为定义方的绝对包名 (`runtime_anchor.rs`),跨包展开不会错绑。 +反过来的方向**没有**保护,和 Rust 的 `macro_rules!` 一样:宏体里一个自由标识符 +在**调用点**解析。 + +```lk +macro_rules! dbl { ($e:expr) => { { let tmp = 100; ($e) + ($e) } }; } +macro_rules! addx { ($e:expr) => { ($e) + x }; } + +let tmp = 1; println(dbl!(tmp)); // 2 —— 实参里的 tmp 是调用方的,没被宏体遮住 +let x = 10; println(addx!(5)); // 15 —— 宏体里的自由 x 就是调用方的 x +``` + +两条合起来就是这个系统的全部保证:**宏放进来的名字不会撞出去,宏用到的名字在你这边 +解析**。所以一个引用自由名字的宏是在对调用点提要求,跨文件导入时那个要求也跟着走 —— +要么用元变量把名字接进来,要么让它是一个函数/常量(那些按定义处解析)。 + +## 内部规则与 `@` + +声明宏没有累加器,只有模式匹配。要把一串宽度加成偏移,做法是一条**调用自己**的规则, +把running 的总和放在自己的参数表里带着走 —— 而那条规则不能被调用方够到,否则 +`layout! { A: 8 }` 会匹配上它。 + +标记它的是 `@`,和 Rust 一样,理由也一样:它在 token 流里合法,在任何一个人会手写的 +位置上都不合法。这门语言此前没给 `@` 任何含义,这正是它合适的原因。 + +```lk +export macro_rules! layout { + // 一段的结尾:累加出来的偏移*就是*大小,所以它不可能和上面的字段不一致 + (@from $prev:expr, => $size:ident) => { + const $size = $prev; + }; + // 一个字段从这一段走到的地方开始,然后这一段前进它的宽度 + (@from $prev:expr, $name:ident : $width:expr, $($rest:tt)*) => { + const $name = $prev; + layout!(@from ($prev) + ($width), $($rest)*); + }; + // 调用方写的形式 + ($($body:tt)*) => { + layout!(@from 0, $($body)*); + }; +} + +layout! { + ETH_DEST: 6, + ETH_SOURCE: 6, + ETH_TYPE: 2, + => ETH_HEADER_SIZE // 14,不是 3 个机器字 +} +``` + +宏外面的 `@` 仍然是错误 —— 一个语法错误而不是词法错误,同一个答案配一条更好的消息。 +完整例子见 `examples/syntax/macro_internal_rules.lk`。 + ## 宏导入与导出 宏是编译期实体,用普通 `use` 语法导入,但在宏展开阶段消费: @@ -72,12 +156,29 @@ use { pkg_macro } from some_package; // 包导入(Lk.toml 依赖) use * as m from macros; // 命名空间导入:m::vec![1, 2] ``` -- 定义处需 `export macro_rules! name { ... }` 才可被导入; +- 定义处需 `export macro_rules! name { ... }` 才可被导入;`export` **只**用在这里 + (另一个含义是属性 `#[export]`,给原生链接命名符号)。顶层的 `fn` / `struct` / + `const` / `type` **不需要**导出,直接 `use { name } from module;` 就能拿到 —— + 写成 `export fn` 会被点名拒绝,消息里说明这三种含义; `pub use { name } from "path";`(可 `as` 改名)做再导出。 - **内建 `macros` 模块**(`core/src/macro_system/imports.rs` `BUILTIN_MACRO_SOURCE`)提供 8 个宏:`vec!`、`assert!`、`assert_eq!`、 `assert_ne!`、`matches!`、`panic!`、`todo!`、`unreachable!`。 +## REPL 里的宏(2026-08-21) + +宏在**解析期**展开,而 REPL 把每次输入当作独立源文本解析。因此定义和导入 +都只在当前这一次输入内有效——`macro_rules! m { … }` 被静默接受,下一行的 +`m!()` 报 "no macro named `m` is defined";`use { vec } from macros;` 单独 +成行时,内建模块在 REPL 中完全不可用。同一次输入内(写在一行)则正常。 +`fn`、`struct`、`impl`、`let` 都是跨输入保留的,只有宏不是。 + +现在 `ParseOptions::carried_macro_definitions` 把上一次展开收集到的定义带入 +下一次解析,REPL 在每次输入**执行成功后**记录(失败则不记录,与其他会话状态 +的"要么整体生效、要么完全不生效"一致)。重复定义同名宏时,本次输入的定义 +胜出——否则会撞上 "already defined in this macro scope",而这个名字正是上一 +行刚被告知不存在的那个。 + ## 属性与条件编译 - `#[cfg(true)]` / `#[cfg(false)]` / `#[cfg(feature = "...")]` 在宏展开阶段 @@ -132,3 +233,34 @@ lk macro expand FILE.lk --feature X # 开启 cfg feature(可重复) `#[derive(Debug)]` 整对象插值、`#[cfg]` 函数选择)。 - provider 协议/信任模型细节:[docs/packages.md](packages.md); 错误文本与展开语义边界:[docs/semantics.md](semantics.md)。 + +## 展开是一个表达式,不是一串 token(2026-08-21) + +声明宏的输出按 token 拼接进调用点,所以它的各部分会和**周围**结合,而不是彼此 +结合: + +```lk +macro_rules! twice { ($e:expr) => { ($e) + ($e) }; } +let n = 3; +twice!(n) // 6 —— 没有东西可结合,对了 +twice!(n) * 2 // 修复前 9,即 `n + n * 2` +2 * twice!(n) // 修复前 9 +"v=" + twice!(n) // 修复前 "v=33" +``` + +修复:落在**表达式位置**的展开加一层括号。两个条件缺一不可——调用点要的是表达式 +(语句位置只有 `;`、`{`、`}` 和流的开头这四种,其余都是表达式位置),并且展开本身 +是**一个**表达式(顶层出现 `;` 就说明不是,`swap_two!` 展开成三条语句,必须还是 +三条)。 + +## `$e:expr` 停在嵌套的宏调用上(2026-08-21) + +`twice!(n)` 能作为 `$e:expr` 捕获,`twice!(n) * 2` 不能——报 +"matched a prefix but left unexpected `*`"。原因是捕获遇到宏调用时把**整个调用** +当成片段直接返回,不再往下看。 + +表达式解析器不认识 `name!(…)`(这个形式只在宏展开前存在),所以现在把每个宏调用 +折叠成一个标识符,让**真正的**解析器去决定表达式在哪里结束,再按折叠前的长度换算 +回去。捕获到的仍然是原始 token,下一轮照常展开。 + +两条都钉在 `examples/syntax/macros.lk` 里。 diff --git a/docs/module-cycles.md b/docs/module-cycles.md new file mode 100644 index 00000000..f117e569 --- /dev/null +++ b/docs/module-cycles.md @@ -0,0 +1,68 @@ +# `core` 里的依赖环 + +`core` 是一个 crate,装着整个前端加 VM(~57k 行,`vm/` 占 53%)。**拆不开的 +原因是依赖环,不是体积** —— 每一个环都是下层反过来伸手够上层。 + +先修环,再谈拆 crate:先拆只会把环搬到 crate 层,而 Cargo 在那一层直接拒绝。 + +## 还剩的环 + +| 环 | 窄边 | 是什么 | +| --- | --- | --- | +| `val` ↔ `vm` | `val/runtime_model.rs` | `CallableValue` 内嵌 `vm::NativeFunction` 与 `Arc` | +| `rt` ↔ `vm` | `rt/runtime.rs` | `RuntimePayload` 里三处 `vm::copy_runtime_value` | + +**这两个是同一个问题。** `RuntimePayload` 伸进 VM 的唯一理由是 +`copy_runtime_value` —— 在两个 `HeapStore` 之间做深拷贝,一个**值**操作;它之所以 +住在 `vm/exec/runtime_callable.rs`,只因为拷贝 `CallableValue::Runtime` 需要 +`Arc` 和 `Arc>`。修好 `val` ↔ `vm`, +`rt` ↔ `vm` 自己就掉下来了;单独去修 `rt`,那个函数没有地方可放。 + +值类型本身**不是**问题:`RuntimeVal` 是 16 字节 `Copy` 枚举,标量加 `HeapRef`, +不提任何 VM 类型。环完全走 `CallableValue` 这个堆值。要断它,得在 `val` 一侧 +定义一个"VM 能调用的东西"的 trait —— 而这个 trait 要带的不止 `call`:回收器要 +走一个 runtime callable 的捕获和跨模块状态,`copy_runtime_value` 还要问它属于 +哪个模块(决定 `Reject` 还是 `SameModule`)。那是把值/VM 边界重新设计一遍, +不是搬一下代码。 + +### 实测(2026-08-06):不走类型擦除 + +`val/` 提到 `vm` 类型的地方总共 **5 处**:三处在同一个函数里(`heap.rs` 的 GC +边遍历),另两处是测试辅助。值层真正需要一个 runtime callable 提供的东西也就 +这么多 —— 遍历 `Closure.captures`(它自己的数据),以及在标记循环之后调 +`RuntimeCallable::collect_garbage()`(那是**另一个**堆,所以才推迟到循环外)。 +trait 会很小。 + +代价落在哪里才是关键。`callable_target` 前面有 `PerfCallTargetKind` 内联缓存, +存在的目的就是跳过那个 enum match;把 enum 的载荷换成 `Arc`,等于在那 +里加一次虚表跳转加一次 downcast。用 `lk coverage --runtime` 量到的、真正走这条 +路的调用占比: + +| 程序 | 调用总数 | 经过 `CallableValue` | +| --- | --- | --- | +| `bench/workloads_business_algorithms.lk` | 228 186 | 62 | +| `examples/stdlib/stream_demo.lk` | 34 | 34 | + +第二行是决定性的:一个写成裸全局的 stdlib 调用**就是**这条路,所以"冷"是算术 +基准的性质,不是这门语言的性质。为了满足一条分层规矩把有类型的分派擦成 +downcast,并不更优雅,而它换来的 crate 拆分目前没有人在兑现。**保持现状。** +将来真要拆,要照着上面那张表设计,而不是照着那 5 个引用点。 + +## 已经修掉的(5 → 2) + +- `token_lexeme` 移进 `token`:它是 `Token` 的属性,住在 `macro_system` 里害得 + `stmt` 仅仅为了打印一个 token 就依赖它。 +- `Program::execute*` 变成 `vm::ProgramExec` 扩展 trait:挂在 AST 上的固有方法 + 纯为调用点方便,却逼出 `stmt` → `vm`。 +- `ModuleResolver` + `execute_imports` 移进 `vm::resolver`:加载一个模块意味着 + 解析、执行、绑定导出 —— 那是执行,不是语法。 +- `macro_system` ↔ `package`(2026-07-30):宏导入解析改成从 + `MacroExpandOptions` 取一个 `PackageMacroModuleResolver` 函数指针,不再直接调 + `PackageGraph::discover`;`syntax::ParseOptions::default()` 装入 + `package::macro_module_root`。边现在是 `syntax → package → macro_system`,单向。 + + 依赖注入的典型失败是"忘了装默认值,于是功能静默消失"。这里由 + `package_named_macro_import_expands_and_is_compile_time_only` 兜底:把默认值 + 去掉,它就红。 + +`stmt` 现在只依赖 `compat` 和 `token`。 diff --git a/docs/packages.md b/docs/packages.md index a68206b7..95abecea 100644 --- a/docs/packages.md +++ b/docs/packages.md @@ -2,6 +2,99 @@ LK packages use `Lk.toml` and `Lk.lock`, modelled after Cargo manifests. + +## `lk pkg add` 按形状认来源,认不出就当场拒绝 + +```sh +lk pkg add dep owner/repo # GitHub +lk pkg add dep https://gitlab.com/a/b.git # 任意 git 主机 +lk pkg add dep ../dep # 本地包 +``` + +`` 此前被原样写成 GitHub 仓库名,于是 `lk pkg add dep ../dep` 写出 +`dep = "../dep"`,失败在很久以后才由 git 报出来: +`repository 'https://github.com/../dep.git/' not found`。清单从一开始就有 `path` +和 `git` 两种写法,只是 `add` 拼不出来。 + +判据:含 `://` 或 `git@` 开头 → git URL;`./`、`../`、`/`、`~` 开头 → 本地路径; +恰好一个 `/` 且两边非空且无空白 → GitHub `owner/repo`;其余**当场拒绝**并列出这 +三种写法。`--branch/--tag/--rev` 用在本地路径上也拒绝 —— 本地包没有 revision 可 +钉,清单里留着它只会让人以为钉住了。 + +## 未解析的依赖要说清是哪一种 + +`lk pkg check` / `lk pkg tree` 此前对所有未解析依赖都印 +"``"。对 `path` 依赖那是**做不到的建议** —— 目录就在 +那儿。现在分三种: + +- `not fetched; run lk pkg fetch` —— git/GitHub 依赖还没取下来。 +- `the path points at a directory that does not exist` —— `path` 指向的目录不存在, + fetch 造不出来。 +- `found, but the package has no library entry; add src/mod.lk (or src/.lk)` + —— 目录在,缺的是**库入口**。注意 `lk pkg init` 生成的是 `src/main.lk`,那是 + *应用*入口;一个要被别人依赖的包需要 `src/mod.lk` 或 `src/.lk`。 + +`lk pkg add` 写出的清单也不再带 `workspace = false` —— `Lk.toml` 是给人读和改的 +文件,每条依赖上挂一个什么都没说的字段是噪声。 + +## `lk compile` 在有依赖的包里会失败,而且是在编译期说清楚 + +`lk compile` 先试原生降低;降不下来就回落到 **Tier 0 打包**(把程序源码和 VM 一 +起塞进一个可执行文件)。而 Tier 0 只塞**一个文件**:被 `use` 进来的模块源码从来 +没被打进去。于是一个有依赖的包"编译成功",跑起来是 + + lk: execution failed + +现在两件事都修了: + +- **打包前就拒绝**:程序里只要有 `use "路径"` 或非 stdlib 的 `use 名字`, + `lk compile` 当场报错,说明 Tier 0 只带一个文件、建议 `lk 文件` 直接跑或把程序 + 写成一个文件。stdlib 的 `use math;` 不受影响 —— 打进去的 VM 自带整个标准库。 +- **打包出来的二进制会说原因**:`lk_vm_eval` 出错时只返回 NULL,消息被丢掉,所以 + wrapper 只能印 "execution failed"。C ABI 新增 `lk_vm_last_error(vm)`(借用 VM + 里的字符串,不用 free),wrapper 改印真实原因,例如 `lk: Module 'dep' not found`。 + +顺带一条语言事实:**LK 没有 `pub`**,模块里定义的东西默认全部导出。写 +`pub fn f()` 是语法错误。 + +## 在成员目录里,`lk pkg check` 说的是**这个成员**的话 + +```sh +cd my-ws/crates/b && lk pkg check # 说 b 的依赖齐不齐,不是工作区的 +``` + +`PackageGraph::discover` 沿祖先找清单时**优先取带 `[workspace]` 的那个**,于是在 +成员目录里 `check` / `tree` 描述的是工作区,成员自己的 `[dependencies]` 一条都不 +读。一个依赖了工作区之外的包的成员,把那个包删掉之后仍然得到 "package check ok", +而程序跑起来是 `Module 'outside' not found` —— check 存在的全部意义就是在跑之前 +回答这个问题。 + +现在:图以**最近的清单**为主体,外层 workspace 作为**上下文**另外记着 —— 它仍然 +提供兄弟成员模块和 `workspace = true` 的继承表。在工作区根目录跑,行为和以前逐字 +一样。 + +**`lk pkg check` 现在会失败(退出码非 0)** —— 有未解析依赖时。此前它印 +"package check ok (1 dependencies unresolved)" 还退出 0:一行里说了两件相反的 +事,而 CI 里的 `lk pkg check` 会在一个跑不起来的包上通过。 + +## 构建产物放在包根,不放进 `src/` + +```sh +cd my-pkg +lk compile # -> my-pkg/my-pkg +lk compile bytecode # -> my-pkg/my-pkg.lkm +``` + +`lk compile` 不带 FILE 时会把入口解析成 `<包>/src/main.lk`,而输出路径此前是"入 +口去掉扩展名" —— 于是一个几十 MB 的可执行文件(以及 `.lkm`)被丢进**源码目录**, +就躺在它编译自的那个文件旁边,下一次 `git add .` 顺手就提交了。 + +现在:**由清单解析出入口的构建**(即包构建),产物放在包根,名字取包目录名 —— +和 `go build` 把二进制放进模块目录而不是 `src` 下面是同一个规矩。**用户点名了文 +件**的构建保持原样(`lk compile foo.lk` → `foo`),点名一个文件本来就意味着"就放 +它旁边";`./main.lk` 这种散文件同理。`--output` 永远优先。 + + ## Package Manifest ```toml @@ -146,3 +239,35 @@ return util.answer(); - `lk pkg update [name]` re-resolves one or all dependencies. - `lk pkg check` validates package graph and macro provider distribution metadata. - `lk pkg tree` prints resolved package modules. + +## `[package]` 的三个字段是被校验的,缓存目录不会被 `..` 走出去(2026-08-06 裁决) + +`lk pkg check` 之前对 `[package]` 一句话都不说: + +| 写法 | 改前 | 改后 | +| --- | --- | --- | +| `edition = "1999"` / `"banana"` | package check ok | 拒绝 | +| `version = "not-a-version"` | package check ok | 拒绝 | +| `name = "../evil"` / `""` / `"9pk"` | package check ok | 拒绝 | + +`edition` 由 `lk pkg init` 写出来,而**没有任何代码读它**;`version` 同样没有 +读者。名字则是 `use ;` 要拼出来的东西,所以它必须是个标识符(字母、 +数字、`_`、`-`,不以数字开头)。校验放在 `pkg check` 而不是加载时:这条命令的 +职责就是回答"这个包是否规整",而一个没人读的装饰字段写错了,不该拦住一个不读 +它的程序运行。 + +版本按 `major.minor.patch` 判,允许 `-pre` 和 `+build` 尾巴;不引 semver 依赖, +因为这里要分辨的只是"写了个版本"还是"写了句话"。 + +### 缓存目录 + +`~/.lk/git/` 之下按 source URL 的形状分层,而那个字符串来自 `Lk.toml`(更糟的 +是也可能来自 `Lk.lock`)。逐段 `push` 且不过滤 `..`,于是 + + git = "https://example.com/../../../../../../tmp/x" + +让 git 报 `Cloning into '/home/…/.lk/git/example.com/../../../../../../tmp/x'` +—— 已经在缓存根之外。远端只要可克隆(本地路径或 `file://`)就落地。 + +`..` **拒绝**而不是丢弃:丢弃会让两个不同的 source 塌到同一个缓存目录上。空段 +和 `.` 照旧丢弃 —— 那两个本来就是同一个路径。 diff --git a/docs/semantics.md b/docs/semantics.md index a057f57e..4d792aea 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -8,17 +8,76 @@ 除特别注明外,每条的期望输出都用当前 VM 实测值锁定,并被差分语料 (手写 69 例 + `examples/` 语料 + 生成式 fuzz)持续验证与 native 一致。 +## 两个分支的值是并集,不是一次 unify(2026-07-31 记) + +`if`/`else` 和 `try`/`catch` 的两臂类型不同时,值的类型是 **`A | B`**,和"一个 +函数里两条 `return` 类型不同就是并集"同一条规则。 + +在这之前两臂是 unify 的,而这条规则和它自己都不一致: +`if c { xs } else { "x" }` 过得了检查(约束记下了,那条路上没人去解), +`try { xs.take(1) } catch e { "${e}" }` 直接被拒 —— 而后者正是 `catch` 最常见 +的写法,因为被捕获的值渲染成文本。同一条规则两种结果,取决于走的是哪条路。 + +两条边界: + +- 有一臂类型里还有**类型变量**时仍然 unify —— 那是推断没跑完,不是"这个值有 + 两种类型";lambda 形参就是靠它学到自己装什么。 +- `Any` 吸收一切:`Any | String` 比真相更窄。`xs[i]!` 展开成的空值检查,raise + 那一半是 `Any`,没有这条的话混合列表里每个 unwrap 都会变成 `Any | Elem`, + 然后算术就报错了。 + +带注解的位置照样拒绝,而且现在能把两半都说出来 +(`expected Int, but expression has type List | String`)。 + ## 数值 | 程序 | 期望 stdout | 期望退出 | 说明 | |------|-------------|----------|------| | `return 7 % 3;` | `1` | 成功 | `%` 是 Int→Int(截断取余,同 Rust `%`) | -| `return 20 / 4;` | `5` | 成功 | `/` 对 Int/Int **返回 Float**(类型层面);Float 值为整数时显示省略小数部分 | +| `return 20 / 4;` | `5` | 成功 | `/` 对 Int/Int **返回 Float**;Float 值为整数时显示省略小数部分 | +| `return 7 / 2;` | `3.5` | 成功 | 同上 —— 值也是 Float,不只是类型 | +| `return 1 / 0;` | `inf` | 成功 | `/` 是浮点除法,除零按 IEEE 给 inf/NaN;`%` 对 Int 除零仍 raise | | `return 1.0 / 7.0;` | `0.14285714285714285` | 成功 | Float 显示 = Rust `f64` 的 `Display`(VM-exact,native 侧经 `lkrt_f64_to_str` 逐字节对齐) | | `return 5 + 7.5;` | `12.5` | 成功 | Int/Float 混合算术提升为 Float | +| `let a = 9223372036854775807; return a + 1;` | `-9223372036854775808` | 成功 | Int 溢出**回绕**,不 raise;两端一致(2026-07-29 核对) | +| `math.abs(-9223372036854775808)` | `-9223372036854775808` | 成功 | 同一条回绕规则 —— 没有正的 `Int::MIN`。此前 `i64::abs` panic,进程 abort | + +### 排序里的 NaN:有序,不是"和一切相等"(2026-07-30 裁决) + +| 程序 | 期望 stdout | 说明 | +|------|-------------|------| +| `let z = 0.0; let n = z / z; return [n, 1.0].sort();` | `[1,NaN]` | NaN 大于每一个数,一个排好的列表读起来是升序的数后面跟着 NaN | +| `let z = 0.0; let n = z / z; return [n, n].sort();` | `[NaN,NaN]` | NaN 之间相等 | +| `return [0.0, -0.0].sort();` | `[0,-0]` | `-0.0` 与 `0.0` **仍然相等**(所以稳定排序保留输入序)—— `==` 说它们相等,`sort` 不该另立一条规矩 | + +之前两个执行器的浮点比较器都是 `partial_cmp(..).unwrap_or(Equal)`,也就是 +"NaN 和一切相等"。那**不是全序**(NaN == 1.0 且 NaN == 2.0,而 1.0 < 2.0, +不传递),而 Rust 的 `sort_by` 会检测到并 panic: + + user-provided comparison function does not correctly implement a total order + +于是含 NaN 的 `xs.sort()` 会让解释器 Rust panic(`try` 抓不到),native 侧 +abort。**打不打得中取决于数据**:601 个元素的列表过去了,60 个的没过 —— 这是最 +糟的那种可达。混合列表同样中招,因为 `compare_runtime_values` 的三个涉及 Float +的分支用的是同一个比较器。 + +现在两端共用一条**全序**规则(`val::compare_floats`,lkrt 侧 `compare_floats` +镜像):NaN 之间相等、每个 NaN 大于每个数、`-0.0 == 0.0`。没有用 +`f64::total_cmp`,因为它会把 `-0.0` 和 `0.0` 分开,那样 `sort` 就和 `==` 对同两个 +值有两种说法。 + +装箱载体(混合列表)的 `sort` **不做原生降低**:它的序跨类型,要镜像的是两张 +kind rank 表 + 深度受限的递归列表比较 + slice 视图 —— 那种规模的镜像该配自己的 +一致性测试(见 `lkrt/src/vm_mirror.rs` 为 map/set 做的那样),不是抄一份。 注:`/` 产 Float 是整数中点必须写成 `math.floor((lo + hi) / 2)` 的原因 -(VM 侧 lower 为 `MidInt`)。 +(VM 侧 lower 为 `MidInt`)。更一般地,`math.floor(a / b)` **就是**整数除法 +——语言里没有别的写法——所以它 lower 为单条 `FloorDivInt`(向下取整, +`math.floor(-7 / 2)` 是 `-4`;非 Int 操作数按 f64 除后取整)。 + +这条规则曾经只有类型检查器和常量折叠认,两个执行器都做整数除法, +于是同一个表达式字面量给 `3.5`、变量给 `3`;折叠还会看值定类型 +(`20 / 4` 折成 Int,`7 / 2` 折成 Float)。四条路径现已一致。 ## 位运算与移位 @@ -41,17 +100,48 @@ ## 响亮失败(loud failure) -失败路径的契约是**响亮失败 + stdout 为空**;具体退出机制不作为契约: -VM 以 `exit 1` + stderr 错误信息结束,native 以 guard `abort()`(SIGABRT, -壳层显示 134)结束。差分测试只比较 `success()` 与 stdout,不比较退出码数值 -与 stderr 文本。 +失败路径的契约是**响亮失败 + stdout 为空**,**两个后端退出码都是 1** +(2026-07-30 收紧)。差分测试仍只比较 `success()` 与 stdout,不比较 stderr +文本 —— 但退出码不再"不作为契约"。 + +此前 native 用 `abort()` 结束(SIGABRT,壳层显示 134,还可能落 core dump), +理由是"退出机制不是契约"。可这让同一个程序在 `lk prog.lk` 下 `$? = 1`、编译 +之后 `$? = 134`,壳层多打一行 `Aborted` —— 调用方拿脚本判退出码时两者不通用。 +未捕获的 raise 是**程序**失败,不是运行时故障,所以走 `exit(1)`;`panic()` 同理 +(仍然不可捕获,只是退出码对齐)。真正的运行时/链接故障(ABI 版本不匹配) +仍然 abort。 | 程序 | 期望 | 说明 | |------|------|------| -| `let x = 2; let y = 0; return x / y;` | 失败,stdout 空 | 整数除零。native 侧禁止直接依赖 LLVM `sdiv` UB,必须走 `lkrt_i64_div_checked` guard | -| `x % 0` | 失败,stdout 空 | 整数模零,同上 | -| `1.0 / 0.0` | 失败,stdout 空 | 浮点除零是响亮失败,**不是** IEEE `inf`(native guard 与 VM 对齐) | -| `let m = {"a": 1}; return m["z"] + 1;` | 失败,stdout 空 | 缺失值(nil)参与算术 = halt。VM 报 `Add expected numbers…got Nil`,native abort | +| `x % 0` | 失败,stdout 空 | 整数模零。native 侧禁止直接依赖 LLVM `srem` 的 UB,必须走 `lkrt` 的 guard | +| `1 << 64` | 失败,stdout 空 | 移位量越界(`0..63`),两端文本逐字一致 | +| `let m = {"a": 1}; return m["z"] + 1;` | 失败,stdout 空 | 缺失值(nil)参与算术 = halt。VM 报 `Add expected numbers…got Nil` | + +**这张表里曾经有两条退休的裁决,2026-07-30 删掉**:"整数除零 → 失败" 和 +"浮点除零是响亮失败,**不是** IEEE `inf`"。`/` 早就是**浮点除法**了(见上面的数值 +表),所以 `2 / 0` 和 `1.0 / 0.0` 都是 `inf`,`0.0 / 0.0` 是 `NaN`,两端逐字一致 +(2026-07-30 复核)。留在原地的退休裁决不是无害的注释 —— `lkrt` 的通道容量就是照着 +一条退休的裁决写的(见 channel 容量那节),两端因此给了不同答案。 + +**宿主错误是语言错误。** `fs.read_dir("/nope")` 这类 IO 失败在 VM 里是可以 +`try`/`catch` 住的 raise;native 侧此前经 `aborting()` 直接终止进程,同一个程序 +解释执行能恢复、编译之后必死。现在它们统一 raise(`lkrt::abi::raising`), +没人接就按上面的规则 exit 1。 + +## 可空不是数值属性(2026-07-30 裁决) + +`Int?` **不能**赋给 `Int`,和 `String?` 不能赋给 `String` 一样。 + +此前只有非数值类型有这条规则。`is_assignable_to` 里数值提升那条分支 +(`Int → Float`)问的是 `numeric_class`,而 `numeric_class` 会**穿过** +`Optional` —— 它必须穿过,因为它回答的是"这个值参与算术产出什么"。于是 +`Int?` 和 `Int` 被判成同一类,`let n: Int = xs.index_of(x);` 一路放行, +nil 流到下一个读 `n` 的地方才炸 —— 报的是错的行,而那个标注正是为了拦住 +它才写的。 + +现在可空性在数值提升**之前**判定:源可空、目标不可空 = 拒绝。`!`、`??`、 +`let n: Int?` 和不写标注仍然都成立 —— 说明"我处理过 nil 了"的四种写法 +一个没少。 ## nil 与缺失值 @@ -69,22 +159,222 @@ VM 以 `exit 1` + stderr 错误信息结束,native 以 guard `abort()`(SIGABRT, | `let xs = [10, 20, 30]; return xs[-1];` | `30` | 负索引从尾部计数 | | `let xs = ["a"]; return xs[5];` | (空) | 字符串列表越界同样返回 nil | +## 负数位置只有一个意思(2026-07-30 裁决) + +`-1` 是最后一个,`-2` 是倒数第二个 —— `[i]`、`get(i)`、`slice(start, end)` +在 List / Slice / String / Bytes 上一律如此,超界仍然钳到 `0..=len`。 + +此前 `slice` 有四份实现三种答案:List 和 Bytes 报错,String 和 Slice 悄悄把 +负数当 0(于是切出一个没人要的窗口),而 **native 的字符串 slice 早就从尾部 +数** —— `"abcde".slice(1, -1)` 解释执行给 `""`,编译之后给 `"bcd"`。同一个 +程序两个答案,差分语料里没有负数用例才一直没抓到。 + +裁决取"从尾部数",理由是语言在隔壁一个操作符上已经这么说了:`xs[-1]` 是 +最后一个元素。四份实现现在共用 `slice_position`(VM)与 `resolve_position` +(lkrt),差分语料补了负数窗口。 + +**`bytes` 模块是第五处,2026-07-30 才接上。** 上面说"四份实现现在共用 +`slice_position`",而 `bytes` 模块的 `get`/`slice` 仍然 raise —— +它在 stdlib crate 里,够不到 core 那个 `pub(super)` 的辅助函数,于是自己写了 +`usize_arg`。可观测的是**同一个操作两个答案**:`b.slice(1, -1)`(方法拼写,走 VM 的 +方法分发)给 `Bytes([98,99,100])`,而 `bytes.slice(b, 1, -1)`(模块拼写,走 stdlib +自己的代码)报 "expects a non-negative integer"。 + +判据因此提成了 `core::val::position` 的公开 API(`read_position` / +`element_position` / `write_position`),VM 侧现在真的只有一份;`element_position` 与 +`read_position` 的区别正是"元素**没有**可编的答案,窗口有" —— `xs[9]` 是 nil 而不是 +最后一个元素。native 侧保持自己的镜像(lkrt 不能依赖前端),这是既有的模式。 + +**写也一样。** `xs[-1] = 9`、`xs.set(-1, 9)`、`remove_at(-1)`、`insert(-1, v)` +都从尾部数;此前读能负、写报 "list index must be non-negative" —— 同一个下标 +表达式,一个方向能用。解析之后仍然越界的写是**响亮失败**(读越界是 nil,写越界 +不是一个程序能表达的意思);native 的 `lkrt_lklist_dyn_set` 此前会**把列表撑大** +去容纳越界下标,`xs[9] = 1` 在三元素列表上解释执行报错、编译之后追加六个 nil。 + +## 越界读在**两端**都是 nil —— 上一条只补了一端(2026-08-01 补) + +上面那条裁决把规则写成了 `element_position`:元素读越界给 `None`,不钳位。**这个 +函数至今零个调用点。** 规则写下来了,调用点没照办,于是 List 读的负端漏了: + +| | 大端越界 | 负端越界 | +| --- | --- | --- | +| `xs[10]` / `xs.get(10)`(List) | nil | **抛** `list index must be non-negative` | +| `s[-10]`(String) | nil | nil | +| `b[-10]`(Bytes) | nil | nil | + +而那句消息本身是假的:**负索引是支持的**,`xs[-1]` 就是最后一个元素 —— +`list_dispatch` 自己的测试里,上一行断言 `set(-9)` 报"必须非负",下一行断言 +`set(-1, 7)` 写成功。 + +更糟的是它盖住了一条真分歧:`xs[-10]` 解释执行**抛**、编译执行给 **nil**。两个后端 +不一致,而差分语料里两边"错得一样"(都报那句假消息),所以一直是绿的。 + +现在:`xs[i]` 越界一律 nil,两端、两个后端一致;消息统一成 `list index N out of +bounds`。 + +**写的消息里那个 N 现在是写下的那个,两端两个后端一致(2026-08-05 补)。** 此前 +VM 报的是解析后的下标 —— `xs.set(-9, v)` 在三元素列表上说 `-6`,一个程序从没写过 +的数字。原因是负数在构造 key 时就解析掉了,而错误在几步之后的 store 才抛,那时手 +里只剩解析值。改成**在解析点抛**:`negative_list_index_from_end` 同时握着原始下标 +和长度,所以它是唯一能说真话的位置。lkrt 因此也不必再镜像一个更差的消息。 + +## 顶层 `let` 里的容器,`Bytes` / `Set` 不算容器(2026-08-01 裁决) + +AOT 降低里有一张表 `container_ty`,同时决定两件事:哪些全局**保住自己的类型**,以 +及哪些在槽位并到 `Dyn` 时**被拒绝**(回落而不是错编译)。它上面的注释把道理讲得很 +清楚 —— 容器是句柄,装进形状不同的槽会造出第二个容器。 + +表里少了 `Bytes`、`Set`、`MapStrDyn`、`SliceI64`。少一项的后果是**两件事一起失 +效**:既没保住类型,也没被拒绝。于是 + +```lk +let b = "abc".bytes(); +fn f(n: Int) -> Int { return b[n] ?? -1; } +``` + +解释执行印 `98`,编译执行 `Error: runtime type error` —— **任何下标**都是,包括常量 +下标。同一个值做参数或局部变量没事,`List` 和 `String` 全局也没事,所以没有任何 +example、差分用例或 fuzz 种子碰到过它。AOT 覆盖门禁也看不见:它降低得很成功,只是 +降低成了错的代码。 + +现在这张表写成**穷尽 `match`,没有 `_` 臂** —— 新增一个 `Ty` 必须在这里被归类,不 +能默认落进"不是容器"。`every_handle_type_counts_as_a_container_global` 钉住分类本 +身,而不是钉一个程序:属性是"每个句柄类型都在表里",一个程序一次只能显示其中一 +个。 + +## 顶层 `let` 只有一份存储(2026-07-30 裁决) + +函数能看见的顶层 `let` 是**一个**变量,不是两个。 + +此前是两个:顶层把它缓存在寄存器里,函数读写的是全局槽,两者只在初始化那一刻 +一致。`let n = 0; fn bump() { n = n + 1; } bump();` 之后函数看到 1、顶层看到 0; +顶层写 `n = 5` 函数也看不见。两个后端做的是同一件事,所以差分测试永远抓不到 —— +这是语言 bug,不是分歧。 + +`const` 仍然保留寄存器缓存:没有东西能写它,副本不会走味。而且那不只是优化 —— +机器整数的**位宽**记在寄存器上,只走全局槽的 `const PAGE_NX: u64 = 0x8000…` +会打印成负的 `i64`。 + ## 语法边界(影响差分语料生成器) -- `while` 条件**必须**带括号;`if` 条件可不带,但 `if (expr) op rhs` 形式会把 - 首个括号组解析为整个条件——生成器 / 工具生成的 `if` 条件应整体加一层括号。 -- 语句以 `;` 结尾。 -- `try`/`catch`、`select`、`go`、后缀 `!` 均为 **parse 时糖**(分别降到隐藏 - native `try$call`、`select$block`、`spawn(闭包)`、nil 检查 Conditional), - 不存在专用 AST 节点;`select`/并发语义见 `docs/concurrency.md`。 +- `if` / `while` / `for` 的条件(被迭代对象)都**不需要**括号,写了也行 —— + 括号只是一个表达式,解析后即剥掉。三者一律扫到**顶层** `{` 为止,所以条件里 + 要写结构体字面量或 map 字面量得自己加一层括号。 +- 语句以 `;` 结尾,**但以 `}` 收尾的表达式语句不需要** —— + `match x { … }`、`unsafe { … }`、`if c { … }` 作语句时都不用分号,写了也行。 + 作为操作数时不适用:`return match x { … } == nil;` 比较的是 match 的值。 +- `select`、`go`、后缀 `!` 均为 **parse 时糖**(分别降到隐藏 native + `select$block`、`spawn(闭包)`、nil 检查 Conditional),不存在专用 AST + 节点;`select`/并发语义见 `docs/concurrency.md`。`try`/`catch` 曾经也是 + (降到 `try$call`),现在是真节点 `Expr::Try`,编译成 `TryBegin`/`TryEnd`。 - **后缀 `!`(force unwrap)**:`expr!` 在 nil 时 raise "unwrap of nil value" (可 catch),否则原值。两条边界:`!` 紧跟 `(`/`[`/`{` 是**宏调用**语法 (`name!(...)`),解包后调用/索引需加括号 `(x!)(...)`;lexer 贪婪 `!=`→Ne, `x!==1` 是 parse 错误,写 `x! == 1`。 +- **`?.` 可以调方法**:`a?.m(args)` 在 parse 时脱糖成 + `{ let t = a; t == nil ? nil : t.m(args) }` —— 接收者只求值一次, + 为 nil 时**调用根本不发生**,结果类型是 `T?`。此前只有字段访问 + (struct / map)走得通,`s?.len()` 会把 `OptionalAccess` 当成索引, + 运行时报 "String index must be Int"。 +- **可能落空的分支,类型是 `T?`**:`match` 无匹配时给 nil、`if` 没有 + `else` 时给 nil —— 这是运行时规则,类型必须跟着说。所以 + `let r: String = match x { 1 => "one" };` 是类型错误(它是 `String?`), + `let r = if c { "a" };` 也是 `String?` 而不是"String 与 Nil 冲突"。 + 判定"不会落空":match 有无守卫的 catch-all(`_` 或绑定名),或者被匹配 + 值是 Bool 且两个字面量都在;`if` 有 `else`。判定是**保守的**: + 判错方向的代价是多写一个 `?`,反方向的代价是 String 变量里装着 nil。 +- **catch-all 后面的臂永不运行,所以拒绝**(2026-07-30 裁决): + `match n { _ => "any", 1 => "one" }` 以前静默接受 —— 你写了一个你认为会发生的 + 情况,而它不会,并且没有任何东西说过一句话。核心检查器没有 warning 通道,而 + LK 对同类错误的做法是响亮拒绝(步长 0 的范围、占用声明名字的 `let`)。 + "catch-all" 的判据与上面那条落空检测**共用同一个谓词** —— 一个模式一旦算 + "匹配一切",就不能同时"对类型是全的、对可达性不是"。带守卫的 catch-all 是有 + 条件的,不遮蔽任何东西;or-pattern 里有一个全的分支就算全。 + 这条也不是假想:`examples/syntax/unsupported.lk` 里 `match 99 { n => n, _ => 0 }` + 的 `_` 就是死的。 +- **catch-all 臂不带运行时判断**(2026-08-05 裁决):`_` 和绑定名匹配一切, + 为它发一条恒真的 `Test` 既是空耗,也造出一条不存在的"判断失败"边。两个后端 + 都读这条边:全臂都 `return` 的函数在原生侧看起来仍能落到末尾,于是拒绝降低。 + 绑定臂上它还是错的 —— `lower_pattern_match` 与 `if let` 共用,那里绑定的含义 + 是"值非 nil",而 match 臂没有这个意思,`match nil { x => 1 }` 因此答 nil, + `match nil { _ => 1 }` 答 1,而检查器把两者都当作全覆盖。现在两者都答 1。 + 编译器认的 catch-all 比检查器**窄**:只有 `Wildcard` 和 `Variable`, + or-pattern 保留判断(它不能绑定,省下的只有判断本身,而它的各分支条件可以 + 求值任意表达式)。窄的方向是安全的 —— 多留一条检查器已证明不可达的落空边。 +- **match 臂里的 `return` 从函数返回,每条臂各算各的**:编译器那个"后面是死 + 代码"的标志曾在**臂与臂之间**被读,第一条臂的 `return` 因此跳过了后面每条臂 + 的臂体降低,`g(1)` 落出 match、落到声明 `-> Int` 的函数末尾、答 nil。 + `every_match_arm_return_returns` 是它的门禁,其中决定性的一组是 match 之后 + 再写 `return 99;`:控制流当时根本没在臂里停下。 +- **条件表达式的每条分支各算各的返回**(2026-08-05 裁决):`lower_conditional` + 过去完全不碰"接下来是死代码"的标志,分支块里的 `return` 因此泄漏到整个条件 + 表达式之外 —— `let a = if n > 0 { return 1; } else { 2 }; return a + 10;` 里 + 后一条 `return` 被当成死代码丢掉,函数落到末尾答 nil。`if` 作语句、 + `try`/`catch`、`match` 都是每条分支各存各的,条件表达式是漏的那处。 +- **`if` 是表达式**:`if c { a } else { b }` 取所在分支块的最后一个表达式为值; + 没有 `else`、或分支块以语句结尾,值为 nil。`else if` 链按嵌套展开。它与 + `match`、三元 `? :` 降到同一个节点(`Expr::Conditional`),所以三者不会走散。 + 两条边界:(1) 条件与 `match` 的被匹配值一样,在第一个**顶层** `{` 处截止, + 条件里要写结构体字面量得加括号;(2) 分支里的 `return`/`break`/`continue` + 仍是控制流,不是值 —— 因此语句位置的 `if` 依旧按语句解析,只有**块尾**的 + `if` 才作为块的值。 +- **条件是 truthiness,不是 `Bool`**:`if`/`while`/`? :` 的条件接受任何值, + 只有 nil 和 false 为假。以前 `? :` 单独要求 `Bool`,与 `if` 不一致 + (`fn g(x) { return x ? "y" : "n"; }` 报错而 `if x` 不报),现已统一。 +- **一元 `-`**:`-expr` 是真取负,不是 `0 - expr` 的糖——浮点有两个零, + `-(0.0)` 是 `-0.0` 而 `0.0 - 0.0` 是 `+0.0`。字面量的负号仍由 lexer 折叠 + (这是 `-9223372036854775808` 唯一的写法,它的绝对值放不进 i64),两条路径 + 产出同一棵 AST。操作数须是 Int / Float / 有符号机器整数;无符号取负报错。 - **v2 错误模型**:错误一律 **raise**(Swift 式),try/catch 是唯一捕获面, 无用户级 `pcall`、无 `[ok, value]` 状态对。`error(v)` 抛一等错误值; 并发原语失败即抛(`recv`/`send` on closed),非错误的"暂无"用 nil 表达 (`chan.try_recv` 空、`task.try_await` 未完成),配合 `!` 断言。 +- **raise 跨边界要复制载荷**(2026-08-21):一等错误值可以是堆值(列表、 + 映射、结构体、长字符串),而堆值是**句柄**,只在自己那个堆里有意义。 + 返回路径一直在两个堆之间复制,raise 路径没有,于是: + - **跨模块**:`error([7, 8, 9])` 到达 catch 时是一个指向被调方堆的句柄, + VM 报 `heap object 88 out of bounds`——内部不变量直接打给用户,而原生 + 构建打印 `7`。即 VM 与 AOT 分歧,且错的是解释器。 + - **跨 task**:task 有自己的堆,结果以「值 + 所在堆」的形式回来,raise 则 + 只带句柄,而 task 的堆在错误传出时已经析构。`spawn` 里 `error([1,2,3])` + 捕获到的是那个索引上现在的对象(``),不报错。 + - **原生 channel**:值全为 Int 的映射走类型化载体而非装箱映射,复制只认 + 装箱的那种,`send(c, {"code": 7})` 报 "value cannot cross a channel"。 + + Int 和短字符串载荷一直是对的——它们内联存在值里,没有句柄,所以最直接的 + 探针会通过,这是三处都长期没被发现的原因。现在跨 task 的 raise 由 + `rt::RaisedPayload` 承载(在 task 那侧 detach、await 那侧 reattach), + 与结果的载体对称;完全无法复制的载荷(裸闭包)退化为已渲染的消息,而不是 + 继续传一个会出错的句柄。门禁:`examples/syntax/cross_module_raise.lk`、 + `cross_task_raise.lk`,两者同时进入 VM/native sweep、AOT 覆盖率和 + VM/bytecode 差分。 + +## 块就是作用域(2026-07-30 裁决) + +块里的 `let` **不会活过那个块**。`if`、`while`、`for`、裸块、`match` 分支体 +—— 每一种都是作用域,遮蔽外层同名变量之后,块外读到的还是外层那个值。 + +此前每一种都漏,而且是静默的: + +```lk +let x = 1; +if c { let x = 2; } +x // 曾经是 2 +``` + +三个独立的洞喂着它: + +1. **语句路径复用寄存器。** 遮蔽用的 `let` 直接写外层绑定的寄存器,块退出恢复 + 的是"名字 → 旧寄存器"的映射,而那个寄存器里的值已经被改掉了。现在遮蔽 + 外层作用域的绑定一律分配新寄存器(`local_declared_in_current_scope`)。 +2. **内联器根本不恢复绑定。** `Stmt::Block` 那一臂只调了 rebind 抑制。后果比 + 泄漏更糟 —— + `fn f(c) { let y = 1; if c { let y = 2; } let s = 45; return y; }` 内联后 + 答 **45**:`y` 还指着内层那个寄存器,而 `s` 正好被分到它。 +3. **块表达式没有作用域。** `match` 分支体就是块表达式。 + +寄存器不因此变多:遮蔽在真实代码里罕见,而基准反而快了一点点(geomean +1.001x → 0.996x)。 ## 闭包与捕获 @@ -96,82 +386,3306 @@ VM 以 `exit 1` + stderr 错误信息结束,native 以 guard `abort()`(SIGABRT, | `for i in 0..3 { let f=\|x\| x+i; println(f(10)); }` | `10` `11` `12` | **for 循环变量**捕获为每站点快照 cell(fused 循环 opcode 驱动原始寄存器,不可重绑);快照是 copy 而非 move(曾把计数器 move 成 Nil) | | 循环内 `g = \|x\| x+i` 逃逸循环后调用 | 共享 cell 终值 | native 侧跨迭代闭包 ref 逃逸响亮拒绝(ref 一致性在 loop header 处终止) | +## 导入的类型可以构造(2026-07-30 裁决) + +`module.Type { field: value, … }` 成立。字段、trait 方法、按声明序的 display +全都对。 + +难点在于:类型的身份带着定义它的模块(`vm::TypeScope`) —— 两个模块里同名的 +`Pt` **不是**同一个类型,`impl` 也按这个身份注册。而 `NewObject` 只带类型 +**名**,`declared_type` 用的是"当前执行模块"的 scope。在导入方直接构造出来的 +`Pt` 会是另一个类型,`p.norm()` 找不到方法(实测如此)。 + +裁决:**让定义方模块来建这个对象**。每个 `struct S { a, b }` 旁边自动多一个 + +```lk +fn S$new({a: A, b: B}) -> S { return S { a: a, b: b }; } +``` + +而 `m.S { a: 1, b: 2 }` 是 parse 时糖,降到 `m.S$new(a: 1, b: 2)` —— 一次普通 +的跨模块调用,在**那边**执行。于是 scope、声明的字段序、trait 分发全部自然 +正确:没有新 opcode,没有 artifact 版本变化,AOT 降低也不用学任何新东西。 + +参数是**具名**的,所以调用点不需要知道声明:传的就是字面量里那些 +`field: value`,漏一个或拼错是构造函数自己的 arity 报错。`$` 不可词法化, +所以这个名字撞不上任何程序能写出来的东西 —— `select$block` 用的是同一招。 + +**脱糖不能漏出来**:那两句报错走的是具名参数的措辞("Missing required named +argument: y"),而读者写的是字段。类型检查器现在认得 `Type$new` 这个 callee, +说的是 `Missing required field 'y' for struct 'Pt'` —— 和本地字面量逐字一样。 + +`use { Pt } from "types";` 绑定的正是那个生成的构造函数(2026-08-06 补):模块 +导出的是**值**,而 `Pt$new` 就是一个普通的顶层 `fn`,也就是一个值。导入解析 +先找同名导出,找不到再找 `Pt$new`,把它绑到 `Pt` 上。于是裸写法也成立: + +```lk +use { Pt } from "types"; +let a = Pt { x: 4 }; // 与 types.Pt { x: 4 } 是同一个身份 +let b = Pt(x: 4); // 同一条路,直接调构造函数 +``` + +`Pt { … }` 的降低看编译器已知的两件事:本地 `struct S` 一定带来本地 `S$new`, +所以"没有本地 `Pt$new`、却有一个叫 `Pt` 的全局"就恰好是导入这一种情形,降到 +对那个全局的具名调用;否则照旧发 `NewObject`。同名的本地声明优先,因为它带来 +了本地 `Pt$new`。 + +`trait` 没有构造函数可绑,所以 `use { Shape } from "types";` 仍然报错,报的是 +这条理由,不是"不是导出"。 + +只通过命名空间看见的类型(`use "types";` 而没有 item 导入)裸写字面量仍然被 +拒:那里 `Pt` 没有绑定任何东西,`NewObject` 会盖上**当前**模块的 scope,建出 +一个同名但没有方法的类型。报错点名两条出路:`types.Pt { … }`,或者按名导入。 + +类型检查器这一侧对应两个标记:`imported_structs`(这个名字是别的模块声明的) +与 `constructible_imports`(而且本文件按名导入了它)。字段仍然按**声明方**的 +schema 校验 —— 多字段、少字段、类型不符都在检查期报,措辞与本地字面量一致。 + +代价是每个声明 struct 的模块也声明了一个函数 —— 于是"把程序当**单个 +`Function`** 执行"这条路(`compile_source` + `execute`,测试用的便利路径)对 +这类程序不再可用。它本来连 `fn` 都装不下,所以 `compile_source` 改走模块路径, +反而更能干。基准 geomean 1.001x → 1.008x,无系统性回退。 + +**原生降低已补齐**(同日):`CallNamed` 整条 opcode 此前没有 AOT 降低,所以 +每一个具名调用都会把模块拖回 VM。现在它和位置调用一样 devirtualize,多一步是 +实参**顺序** —— 每个名字都是编译器发出的常量,所以排列是编译期事实 +(`FunctionData::param_names` 给槽序,`positional_param_count` 给分界)。 +构造函数的**返回值**也带上了类型出身,否则 +`types.Pt { … }.x` 之后的方法调用又会因为接收者无类型而掉出去。 + +顺带把 `emit_trait_call` 改名成 `emit_call_with_args`:它并不特属 trait,而是 +"实参已按帧序排好"的那个发射器 —— trait 分发把 `self` 放在最前,具名调用按名字 +排列,两者要的是同一件事。 + +仍不降低的是**跨模块 trait 方法**(`types.make(3,4).norm()`):AOT 的 trait +环境只扫主模块,导入模块里注册的 impl 不在其中。与构造方式无关 —— 用老的 +构造函数写法一样不降低。 + ## 模块与 IO | 程序 | 期望 stdout | 说明 | |------|-------------|------| | `datetime.now()` | — | 返回 Unix epoch **秒**(非微秒;datetime_demo 曾因此假设而自身断言失败) | -| `std.write(out, "a")` | `a`,返回 `1` | `write`/`writeln` 返回写入字节数(writeln 含换行 = len+1);`flush` 恒返回 `true` | +| `std.write(out, "a")` | `a`,返回 `1` | `write`/`writeln` 返回写入字节数(writeln 含换行 = len+1);`flush` 恒返回 `true`。`std` 是 `io` 的**子模块** —— 写 `use { std } from io;` 或 `io.std.write(…)`,裸 `std` 不是全局(2026-07-30 更正) | | `std.write` 与 `println` 交错 | 程序序 | **stdout 顺序契约**:native 侧 Rust 写者先 `fflush(NULL)` 再写、写后 flush 自身流,保证与 C `printf` 缓冲的输出保持程序序 | | `math.sqrt(-4.0)` | 响亮失败 | 负参是致命错误(双方 loud),不是 NaN | +## 被捕获的错误:消息是输出(2026-07-30 立) + +响亮失败的契约一直是"比成功 + stdout,不比失败的文本"。那对**未捕获**的失败是对的 +—— 那段文本是宿主的外壳。它对**捕获**的什么也没说,而在那里消息**就是 stdout**: + +```lk +let r = try { xs[9] = 1; "no" } catch e { "${e}" }; +println(r); +``` + +所以规矩是:**可捕获的 raise,两个后端的消息必须逐字一致;未捕获失败的文本不要求。** +`a_caught_errors_message_matches` 是它的门禁。 + +立这条时两边有七处不一样,包括 `assert` 差一个大写字母、所有动态类型错误在 native +侧都是一句 `runtime type error`(VM 会说出运算符和两个操作数的种类)、越界写说 +`runtime error`(VM 说 `list index 9 out of bounds`)。 + +措辞以 VM 为准。立这条时 VM 自己有两处毛病,随后一并修了(见下),两边一起动 —— +这正是"先立门禁再改"的好处:门禁保证它们不会各改各的。 + +### 消息里的名字:类型,不是表示;运算符,不是 opcode(2026-07-30 修) + +- **类型不是表示。** 消息格式化的是 `RuntimeVal::kind()`,它对堆句柄只会说 + `Object`。于是 `"ab" - 1` 说 `String`(≤7 字节,内联)而 + `"aaaaaaaaaa" - 1` 说 `Object` —— 同一个类型两个名字,分界线是它塞不塞得进七个 + 字节;list / map / Set 也全是 `Object`。`RuntimeValKind::Obj` 的注释**早就写着** + "拿得到堆的调用方应该用 `HeapValue::type_name`",只是没有一个调用点照做。 + 现在有 `Executor::value_type_name`。 +- **运算符不是 opcode。** `1 < "a"` 报 `CmpLtInt expected ...` —— 那是编译器挑的 + 融合形式,源码里没有任何东西叫 `CmpLtInt`,而且它可以在程序没变的情况下改变。 + `operator_symbol` 这个映射**本来就在**,注释里连理由都写好了(算术那批就是这么 + 修的),只是比较那批没跟上。现在跟上了。 + +两条都是同一个模式:规矩已经写下来了,调用点没遵守。 + ## 容器 display | 程序 | 期望 stdout | 说明 | |------|-------------|------| | `println([1,2,3])` | `[1,2,3]` | 逗号分隔无空格;float 元素用 Rust `to_string`(`2.0`→`2`) | | `println(["a","b c"])` | `["a","b c"]` | 字符串元素 **Rust `{:?}` 引号+转义**(`"`→`\"`、tab→`\t`) | -| `println("${xs}")`(xs 是 list) | 响亮失败 | **两条 display 路径**:print/println/panic/assert 消息走 stdlib `runtime_display`(容器可显示);`ToString`/模板插值/`+` 拼接走 exec `runtime_value_display_string`(标量 only,容器 loud error)。native 对后者拒绝编译 | -| `println(map)` | hash 迭代序 | map display 顺序 = 底层 hash map 迭代序,**跨运行稳定但不可移植**(依赖 hasher+增长历史)——native 侧不进子集,响亮拒绝 | - -## `unique()` 等值语义(2026-07-06 裁决) - -`list.unique()` 走 VM `core_methods` 的 `runtime_values_equal`:数值按 `to_bits` -(`1 == 1.0` 去重、`0.0 != -0.0` 保留)、≤7 字节字符串(`ShortStr`)按内容、 -**列表/map/长字符串按 heap 句柄**。句柄同一性是 VM 内部表示细节,长字符串 -(>7 字节)在 typed String 列表里每次读出重新 alloc(`[s, s].unique()` 保留两个), -在 Mixed 列表里直存句柄(`[1, s, s].unique()` 去重)。native 侧字符串常量 intern, -指针无法区分这两种,**裁决:native 对长字符串永不去重**——对齐字面量重复与 -typed 列表两种常见形状;Mixed 列表同变量长串重复是已知分歧,不进差分子集。 -列表元素的句柄同一性 native 以「NewList 窗口内同寄存器装箱一次」保持 -(`let l=[7]; [l,l].unique()` 去重,两个 `[1]` 字面量不去重)。 - -## `in` 操作符等值语义(2026-07-06 裁决) - -`needle in list` 走 VM `list_contains`,**与 `==`/unique 都不同**——第三套 eq: -typed 列表严格同型(`1.0 in [1, 2]`、`1 in [1.0]` 均 false,无数值 coercion; -String 列表按内容,长短一致);Mixed 列表是 `RuntimeVal` 的 derive `PartialEq` -(同变体严格、float 按值 `==`(`0.0==-0.0` true、NaN 永 false)、ShortStr 内容、 -heap 对象按句柄)。native:typed 列表跨型 needle 编译期折叠 false,Mixed -(`ListDyn`)走 lkrt `contains_eq`(同款 strict 语义);长字符串/嵌套列表的句柄 -同一性限制与 unique() 同款(intern/转换边界,已留档,不进差分子集)。 +| `println("${xs}")`(xs 是 list) | `[1,2,3]` | 模板插值**显示容器**(2026-07-30 更正:此前这条写的是"响亮失败,标量 only",而 VM 早已不是那样)| +| `println("a${xs}b")` / `"m=${m}"` / `"${[P{v:1}]}"` | `a[1,2,3]b` / `m={"k":1}` / `[P{v:1}]` | 多段模板、map、结构体列表同样 | +| `println(map)` | 插入序 | map 的迭代与 display 顺序 = **键第一次被写入的顺序**(2026-08-17 裁决,见下)| +| `println(Set([1,2,10]))` | `Set([1,2,10])` | Set display **按成员值排序**:nil → Bool → Int(按数值)→ String(按内容,不分长短)。2026-07-30 修:此前排的是**渲染后的文本**,于是 `Set([1,2,10,20,3])` 打出 `Set([1,10,2,20,3])`。判据在 `RuntimeMapKey::display_order`,native 逐条镜像 —— 这个序是**强加的**、比的是内容,所以两边不可能因为 hasher 漂移而分开 | +| `for x in Set([5,6])` | hash 迭代序 | **迭代序不是显示序**:显示强加了排序,迭代没有。native 侧也降低了 —— 但它需要镜像纪律(显示不需要),前提是 lkrt 只有一份 `RtKey`,见 `set_iteration_order_matches_the_vm` | -## 错误文本(2026-07-08 裁决) +## map 的顺序是插入顺序(2026-08-17 裁决) -`catch e` 绑定的消息 = **裸 cause 文本**,无包装:native(Rust stdlib)函数 -失败不再加 `"native `{name}` failed: "` 前缀(曾有,`map_native_error` 处 -移除),与 `error(v)` 一等值对称;调用点归因由 traceback 承担,不进消息。 +`Map` 的迭代序、`.keys()`/`.values()` 的顺序、以及 `println(m)` 的顺序,都是**键第一次 +被写入的顺序**。重复写同一个键更新它的值、**保留它原来的位置**;删除保留其余键的相对 +顺序。结构体实例同理 —— 它的字段序就是 `struct` 声明的顺序,而 `p.z = v` 追加的新字段 +排在最后。 -**跨后端错误文本不保证逐字一致**:VM 与 native 的错误生成机制不同 -(如 `recv(999)` VM 报 "recv first argument must be a Channel"(类型检查), -native 报 "Channel not found"(id 查找))。差分语料因此**不打印 catch 到 -的错误文本**,只断言 catch 行为(进入 handler、后续状态可用);若未来要 -开放文本比对,需先逐条对齐两侧消息(fuzz 差分红为发现机制)。 +在此之前是**哈希表的迭代序**,而那不是值的属性,是**这张表怎么被建出来的**属性: -## trait 方法分发与 auto-Display(2026-07-07 裁决,plan J) +```lk +let a = {"zebra": 1, "apple": 2, "mango": 3, "kiwi": 4}; +let b = {}; +b["zebra"] = 1; b["apple"] = 2; b["mango"] = 3; b["kiwi"] = 4; +// a == b,而两边 println 出来的字段顺序不同。 +``` -native 侧 struct 实例是普通 string-keyed map(**无 `"$type"` 隐藏键**—— -`len()`/迭代/display 与 map 完全一致);运行时类型身份存 arena 句柄侧表 -(lkrt `OBJ_TYPE_MARKS`,`NewObject` 时打标记)。两个已知边界: +两个 `==` 的值渲染不同,是这条裁决要消掉的东西。 -- **类型标记不跨 channel**:深拷贝(`OwnedVal`)重建 map 时不复制标记, - 收方对该 struct 实例的动态 trait 方法调用会 raise(VM 能成功)。语料无 - 此形状;如需支持,`OwnedVal` 捕获/重放需带上标记。 -- **auto-Display 只镜像 `show`**:VM `try_runtime_display_show` 硬编码查 - 方法名 `"show"`(与 trait 名无关;`#[derive(Debug)]` 展开出的 - `__LKShow::show` 也走它)。native 在 display 上下文(print/println 参数、 - 模板插值 `ToString`/`ConcatString`/`ConcatN`)对带 provenance 的 struct - 直调注册的 `show`。**无 `show` impl 的整对象 display 不进子集**(VM 内部 - 有 `` debug 形与 registry 缺失 bail 等多种路径,未统一前不复刻)。 +**它同时消掉了一个跨后端的巧合。** 原来两个后端能对上这个序,靠的是 lkrt 用 +`vm_mirror` **复刻解释器的哈希布局**:两边链同一个 `hashbrown`、同一个 rustc 推导出 +同样的 `derive(Hash)` 判别值、同一个固定种子。现在两边都往一个向量里追加,序是**结构性 +相同**,不是碰巧相同。`vm_mirror` 的一致性测试留着,但它守的不再是一个巧合。 -动态分发(boxed receiver,经混合列表/Dyn 参数流动)限 `argc == 0`(self 之外 -无参数)且零捕获 impl;静态 devirt(NewObject provenance 已知)支持任意参数。 -分发臂按注册序排列,标记无匹配 → raise(VM 的 unknown-method 同为错误)。 +**代价与偿还**(基准是 `bench/run_workload_bench.sh`,10% 门禁):有序 map 的载体比裸哈希表宽 +8 字节,`HeapValue` 从 64 涨到 72;而有序 map 让 `RuntimeObject::field_slots`(一张手工 +维护的平行字段序表)成为冗余,删掉它省回 24 字节。净结果 geomean 与基线持平 +(1.015x vs 1.016–1.023x,三轮)。 -## 维护约定 +### 一条过时的裁决(2026-07-30 更正) -- 新增可下降形状时,先在此登记预期语义(尤其失败路径与显示格式),再写差分用例。 -- 当 VM 与 native 出现分歧:先查本表;表内未覆盖的,裁决后**新增条目 + 差分用例**, - 不允许只改一侧实现使测试变绿。 -- 退出机制(exit 1 vs SIGABRT)如未来需要统一,属于语言决策,需同时改本表、 - 差分 harness 的宽容逻辑(`success()` 对比)与 CLI 文档。 +上面那条曾经写着:`ToString` / 模板插值 / `+` 拼接走"标量 only"的显示路径, +容器在那里是响亮失败。VM 后来改了 —— `"${xs}"` 就是 `[1,2,3]` —— 而**AOT 一侧 +一直照着退休了的规则**传 `containers: false`,于是任何模板里带 list / 结构体 +列表的程序都掉回 VM。答案一致,只是慢,所以差分门禁抓不到;是探针撞上的。 + +现在两边都显示容器,差分语料补了这条。map 两种键都进来了(见下), +Set 的显示、相等、迭代都进来了。迭代那条要的是哈希序的镜像(显示不要), +前提是 lkrt 只有一份 `RtKey` —— 它本来有两份。 + +### 又一条(2026-07-30 更正):字符串键 map 的显示 + +上面那条"map 不进原生子集"同样是退休的裁决。它先于 `lkrt/src/vm_mirror.rs`, +而那个模块存在的全部意义就是让两个后端共享 map 的迭代序, +`lit_protocol_matches_vm_iteration_order` 直接拿 `lk-core` 比对。`MapStrDyn` +其实早就放行了 —— 裁决对一个 map 类型解除、对其余的留着,于是 +`println({"a": 1})` 让程序丢掉降低,而 `println({"a": 1, "b": "x"})` 不会。 + +现在字符串键和整数键都显示。整数键这条修的时候顺手挖出一个**潜伏的错答案**: +VM 对非字符串键不做第二阶段(`typed_map_from_entries` 直接返回 `Mixed`), +lkrt 的 `lit_finish_i64_*` 却又 rehash 了一遍,两边顺序真的不一样 —— +只是当时没有哪条路看得见它。载体已改成按 `RtKey::Int` 哈希、按字面量序重放, +细节记在 `docs/aot/aot-gaps-and-lkrt.md` §17.2。 + +### map 迭代序:为什么没有跟着结构体一起改(2026-07-30 记) + +结构体字段序当初从 hasher 序改成声明序,理由是"换个 hasher 会静默重排每一个 +结构体"。**同一条理由对 map 成立**,而且插入序正是 Python / JavaScript 给的 +东西。这里没有跟着改,是权衡后的决定,不是漏掉: + +- `TypedMap` 的五个变体都是 `FastHashMap`,全仓 260 处 `TypedMap::` 匹配点; + `lkrt` 还有一份自己的 map,由 `lkrt/src/vm_mirror.rs` 逐条比对迭代序 —— + 两边得一起换。 +- 插入序要么是 `Vec` + 索引表(IndexMap 布局),要么额外一条顺序向量。 + 代价落在 **`delete`**:保序删除是 O(n),而交换删除会毁掉顺序。Python 用 + 墓碑加周期性压缩解决,那是另一套实现。 +- 基准里 map 是最热的容器(`two_sum_map`、`histogram_group_count`、 + `event_join_by_id`、`config_defaults_merge`),而性能门禁是硬的 10%。 + +结构体那次的代价是"记录一个字段顺序",这次的代价是换掉最热容器的表示。 +所以现状保留:hash 迭代序,**跨运行稳定、两个后端一致**(有 `vm_mirror` +一致性测试兜着),但不可移植。要改就当一个独立项目做,连着基准一起。 + +## `unique()` 等值语义(2026-07-29 修订) + +`list.unique()` 与 `==`、`in` **用同一条规则**:数值按值(`1 == 1.0` 去重、 +`0.0 == -0.0` 去重、NaN 永不去重所以一串 NaN 原样保留)、字符串按内容 +(不分长短)、列表/map 按结构。native 侧 `lkrt_lklist_dyn_unique` 直接调 +`dyn_eq_inner`,与 `==` 同一个函数。 + +typed Float 列表仍是**单次哈希查找**而非 O(n²) 扫描:键在插入前规范化 +(NaN 给一个递增序号,零统一成 `+0.0` 的位型),所以既跟得上 `==` 又没丢 +性能。 + +此前这里是**第三套 eq**:VM 按 `to_bits`(`0.0 != -0.0`、NaN 自等), +native 另有一份 `unique_eq`(数值 to_bits、>7 字节字符串永不相等、 +列表按句柄)。后者写的是**当时**的 VM;VM 的相等后来改成 heap-aware, +这份没跟上,于是 `[s, s].unique()`、`[[1], [1]].unique()` 两条后端答案 +不同 —— 而它们恰好被这份文档划在差分子集之外,所以没人发现。现已并入 +`cli/tests/aot_differential_test.rs` 的 `differential_equality_and_unique`。 + +## 结构体按字段比较(2026-07-30 裁决) + +`P { x: 1, y: 2 } == P { x: 1, y: 2 }` 是 **true**。同一个声明的类型 +(模块 + 名字,不是名字)加上每个字段相等,递归走同一份深度受限的 +`runtime_values_equal`。 + +此前结构体按**句柄**比较,于是它是这门语言里唯一不按内容比较的聚合: +`[1] == [1]`、`{"a":1} == {"a":1}`、`Set([1]) == Set([1])` 全是 true,只有 +结构体是 false。而且是静默的 —— `xs.contains(p)`、`index_of`、`unique` 全部 +继承了它,一个结构体列表根本搜不了。 + +类型身份用 `scope` + `name`,不用整个 `DeclaredType`:后者的 `fields` 在声明 +够不到时(跨模块、宿主构造的对象)是空的,连它一起比会让同一个类型跨边界 +不等于自己。 + +结构体**仍然不能**作 map 键或 set 成员 —— 那是另一条裁决(键只有 nil / Bool / +Int / String),相等不蕴含可哈希。 + +## `in` 操作符等值语义(2026-07-29 修订) + +`needle in list` 与 `==` **用同一条规则**:Int/Float 跨类型按数值比较 +(`1 in [1.0]`、`1.0 in [1, 2]` 均 true),String 按内容(长短一致), +其余按 `runtime_values_equal`。native 侧新增 `list_h.i64_contains_f64` / +`f64_contains_i64` 两个 helper 与之逐条对齐。 + +此前这里是**第三套 eq**:typed 列表要求 needle 与元素同变体,Mixed 列表却 +按值比较——于是 `a == b` 为 true 而 `a in [b]` 为 false,且答案取决于列表的 +内部表示(程序看不见的东西)。同期常量折叠对 `==` 走 `LiteralVal` 的 derive +`PartialEq`(结构相等),所以 `println(1 == 1.0)` 是 false 而变量版是 true, +还与折叠器自己的序比较矛盾(`1 <= 1.0 && 1 >= 1.0` 折成 true)。 + +`in` 的类型检查此前只认 List/Map/Set,漏了 `String`(含子串)和 `Tuple` +(异构列表**字面量**推出来的类型)——两者在索引、`len()`、方法分发处都是 +容器。于是 `"a" in "abc"` 作为字面量折叠可用,换成变量就是类型错误。 + +`==`、`in`、`unique()` 三者现已同规则,常量折叠亦然。 + +### 嵌套容器的句柄同一性限制已取消(2026-08-20) + +此处曾写着"长字符串/嵌套列表的句柄同一性限制与 unique() 同款(intern/转换 +边界,已留档,不进差分子集)"。那句话描述的是 native 侧的实现,不是裁决: +lkrt 的 `contains_eq` 按句柄比较堆值,而 VM 的 `list_contains` 已经改成调 +`runtime_values_equal`。两边不一致,而"不进差分子集"这句话正好挡住了发现它 +的唯一手段 —— 与上一节 `unique()` 的漂移同一个成因,同一份文档,相隔一节。 + +七条表达式因此编译后答错、解释执行答对: + +| 表达式 | VM | 此前 native | +| --- | --- | --- | +| `[1, 2] in [[1, 2], [3]]` | true | false | +| `{"k": 1} in [{"k": 1}]` | true | false | +| `1.0 in [1, "a"]` | true | false | +| `[[1], [2], [3]] - [[2]]` | `[[1],[3]]` | 原样 | +| `[{"k": 1}, {"j": 2}] - [{"k": 1}]` | `[{"j":2}]` | 原样 | +| `[[1], [2]].index_of([2])` | 1 | nil | +| `[1, "x", 2].index_of(2.0)` | 2 | nil | + +现在 `contains_eq` 已删除,native 侧 `in` / `-` / `index_of` / `count` 全部走 +`dyn_eq_inner`(即 `==` 那份),嵌套与混合的形状进了 +`differential_equality_and_unique`。**这类形状不再有豁免**:两个后端必须一致 +的规则,只要被划出差分子集,漂移就无人可见。 + +唯一仍然不是 `==` 的载体是字节串:VM 的 `in` 只接受 `RuntimeVal::Int`,所以 +`97.0 in "ab".bytes()` 是 false,而 `97.0 in [97]` 是 true。这条是裁决,不是 +实现细节,已在 lkrt 里显式写出并进差分语料。 + +## 值遍历深度(2026-07-29 裁决) + +比较和渲染都按值的**形状**递归,深度就是数据的嵌套深度。一个循环就能造出 +超过 Rust 栈的链: + +```lk +let node: Any = [1]; +for i in 0..200000 { node = [node]; } +``` + +裁决:**脚本不能让进程 abort**。超过 `MAX_VALUE_DEPTH`(512)时,比较和 +渲染 raise 普通的可捕获错误(Python / Lua 同款)。512 对数据足够宽松 —— +JSON 嵌套是个位数,手写树是几十层。 + +GC **没有**这个上限,也不能有:回收不允许失败。`HeapStore::collect` 用显式 +工作表标记,深度不上 Rust 栈。 + +## 结构体 display 字段序(2026-07-29 裁决) + +`println(p)` 的字段按 **`struct` 声明的顺序**,与构造时写的顺序无关: + +```lk +struct Range { start: Int, end: Int } +println(Range { end: 9, start: 1 }) // Range{start:1,end:9} +``` + +字段存在 hash map 里,所以此前顺序是 hasher 的 —— `struct Range { start, end }` +先打 `end`,而且换个 hasher 会静默重排每一个结构体。声明顺序随类型走 +(`DeclaredType::fields`,编译期从 `Stmt::Struct` 收进 `TypeInfo.structs`, +`MODULE_ARTIFACT_VERSION` 15)。够不到声明时(别的模块的结构体、host 造的 +对象)按字段名排序 —— 任意但稳定,hash 序两样都不是。 + +字段值是容器里的数据,**加引号**,和列表元素、map 值一致: +`P { name: "a, b" }` 打印成 `P{name:"a, b"}`,此前是 `P{name:a, b}`(读起来 +像两个字段)。 + +## map 键与 set 成员(2026-07-29 裁决) + +**`Set` 是 map 的键集,回答同一个问题**:只有 nil / Bool / Int / String 能 +做键。Float 不行(`0.0` 与 `-0.0` 相等但哈希不同,NaN 不等于自己),容器 +也不行(可变的东西做键,改了它就找不回那条记录)。 + +此前这里是**两套转换**且分歧就在这一点上:`m[k] = v` 走执行器那份,拒绝 +列表;`s.add(...)` / `Set([...])` 走容器方法那份,接受为 `Obj(handle)`, +按**句柄**比较。于是 set 悄悄留下了它永远找不回的成员: + +```text +let s = Set([]); +s.add([1, 2]); s.has([1, 2]) → false +s.add([1, 2]); s.len() → 2 +println(s) → Set([,]) +``` + +现在只有一份(`RuntimeMapKey::from_value`),两条路都拒绝,错误文本相同。 + +统一之后 `RuntimeMapKey::Obj(HeapRef)` 就**没人造得出来**了,已删除 +(`MODULE_ARTIFACT_VERSION` 15 一并覆盖)。于是这条规则变成了结构性的:键 +一律自包含,不带堆句柄 —— set 因此**完全没有 GC 出边**,键跨堆搬运就是 +`clone()`,两侧原本各有一份追句柄的翻译函数也一并没了。 + +## 字符串序比较(2026-07-29 裁决) + +`"a" < "z"` 可用,按**字节字典序**,长短字符串一视同仁 —— 与 `list.sort()` +的排序、常量折叠的 `cmp_literal_ordering`、执行器 `number_compare` 的字符串 +分支都是同一条规则。混合类型仍然拒绝(`1 < "a"` 是类型错误)。 + +此前只有**类型检查器**不许:运行时一直支持,折叠器一直支持,于是问"哪个 +字符串在前"的唯一办法是排一个两元素列表。native 侧那条禁令的注释还写着 +"VM 只支持字符串的 ==/!=" —— 从来不是真的。`lkrt_str_cmp` 返回 -1/0/1,拿 +**同一个**运算符跟 0 比就同时实现了六种,所以放开禁令即可。 + +差分语料:`differential_equality_and_unique` 的 `str_lt_long` / +`str_ge_long` / `str_le_equal` / `str_gt_prefix`。 + +## 源缩短之后的窗口(2026-07-29 裁决) + +窗口(`xs.slice(a, b)`)不复制,所以源可以在它脚下变短。裁决:**窗口按源 +此刻还够得着的部分算长度**(`SliceValue::live_len`),越界读仍然给 nil —— +和语言其他地方一样,不报错。 + +此前每个读者各答各的。一个长度为 3 的窗口,源 `pop()` 掉最后一个之后: + +```text +s.len() → 3 s.to_list() → [1,2,nil] +println(s) → [1,2] s.last() → nil +s == [1,2] → false s.get(2) → nil +``` + +一个问题六个答案。 + +## `List.clear()`(2026-07-29 补齐) + +`clear()` 三个容器都有,原地清空、答容器本身(可链)。此前 map 和 set 有, +list 没有 —— 而 `docs/stdlib.md` 的方法表里写着它。签名表里还有一条测试 +断言 list **不**该有,类型检查器里又有个特例分支给 list 的 `clear` 返回 +`Nil`(而表里 map/set 是 `Self`)。三处各说各的。 + +三个 `clear` 都还不能原生降低,行为一致,不算新洞。 + +## 字符串的读取面按字符(2026-07-29 裁决) + +`len()` 数字符、`s[i]` 取字符,所以 `slice` / `take` / `skip` / `first` / +`last` / `index_of` / `s[-i]` 全按字符。native 侧 `str.slice_chars` 一直就是 +VM 的语义,补上降低即可。 + +修掉的两处两端不一致: + +- **负下标从字节长度往回数**(两端都是,lkrt 的注释还把它当"VM 的 quirk" + 照抄了)。`"中文abc"` 五个字符九个字节,于是 `[-1]` 问的是第 8 个字符 → + nil,`[-5]` 答 `"c"`。VM 里这条规则还抄了三份,其中两份靠 `usize` 下溢 + 碰巧对。现在收在 `index_string_at` 一处,按字符回绕。 +- **已删的 `substring` / `find` 方法**原生降到 `lkrt_str_substring` / + `lkrt_str_find`,两者按**字节**;miss 时 native 给 -1 而 VM 给 nil。多字节 + 文本上两端答案不同,而差分语料全是 ASCII,所以没人发现。方法删了,两个 + byte 版 helper 也删了。 + +`index_of` miss 给 **nil** 不给 -1:-1 是合法下标(最后一个字符), +`s[s.index_of(x)]` 会静悄悄答出最后一个字符而不是失败。 + +差分语料:`str_slice_multibyte`、`str_take_skip_multibyte`、 +`str_index_of_multibyte`、`str_index_of_miss`、 +`str_negative_index_multibyte`、`str_first_last_multibyte`。 + +`List` / `Slice` / `Bytes` 同此:miss 给 nil。差分语料 +`index_of_miss_is_nil`。 + +## `try` 是表达式(2026-07-29 裁决) + +`try { … } catch e { … }` 有值,和 `if`、`match` 一样:值是 body 末尾的表达式, +body raise 了就是 handler 末尾的表达式;以语句结尾的那一半没有值,给 nil —— +与 `if` 的分支同规则。类型是两半的并,一边 nil 一边不是就是 `T?` +(`unify_branch_values`,`if` 和它共用)。 + +语句位置**不变**:值被丢弃,`if`/`match` 在语句位置也是这样。而且语句位置 +根本不分配值寄存器 —— 这不只是省一条指令:在保护区域**内部**写的值要靠 +装箱进 cell 才能带出来,预留一个没人读的值会让 +`try { f(); } catch e { … }` 整个掉出原生路径。 + +AST 里只有一个节点(`Expr::Try`),`Stmt::Try` 删了。语句位置是 +`Stmt::Expr(Expr::Try)`,后续所有访问者本来就会递归进表达式,所以删掉语句 +节点比留着两个**改动更少**。它仍然是真节点而不是 parse 期糖:当年从 +`let [ok, e] = try$call(|| { body })` 改过来,就是因为那样每一层都看见的是 +闭包和解构 `let`,body 里赋值的带标注局部变量出来会变成一个新类型变量。 + +块尾的 `try` 交给表达式解析器(`try_parse_tail_expression_stmt`),这是 `if` +早就有的机制,现在两者共用 —— 所以 +`try { try { … } catch e { … } } catch e { … }` 里层也是值。 + +native 侧:cell 读回原本按**区域入口**的类型定型,而值寄存器进去是 nil +(`Ty::Nil` 没有 unboxer),于是整个表达式形式被拒。改成入口为 `Nil` 时按 +`Dyn` 读回 —— body 本来就是按自己的类型装箱写进 cell 的,`Dyn` 才是诚实的 +描述。顺带让 `let x = nil; try { x = 5; } catch e {}` 也能原生降低了,它此前 +是直接拒绝的。 + +差分语料:`try_expression_value`、`try_expression_nil_branch`。 + +**已知边界**:同一函数里多个 try 区域时,值寄存器在汇合处的 phi 还可能定不 +出类型(一边是 cell 带回的 `Dyn`,一边是 handler 写的具体类型),那种程序 +会退回混合/Tier-0。 + +## 字符串转义(2026-07-29 补 `\u`) + +认这些:`\n` `\r` `\t` `\\` `\'` `\"` `\$` `\0`,以及 +**`\u{XXXX}`** —— 一到六位十六进制,写一个按码点指定的字符。 +`\u{4e2d}` → `中`,`\u{1F600}` → 😀。代理区码点和超过 U+10FFFF 的报错, +不是静默产出一个不是字符的东西。 + +此前没有 `\u`,所以打不出来的字符(零宽连接符、不换行空格、星平面 emoji) +只能直接粘进源码 —— 而 `"\u{4e2d}"` 会原样打印它自己,因为: + +**未知转义保留反斜杠,不报错。** 这是有理由的,不是宽松:regex 模式在 LK 里 +就是普通字符串,`"\d+\s*"` 必须原样活到引擎手上。所以加 `\u` 是一个行为 +变更(此前 `"\u"` 是字面量),不是纯新增。 + +## `sort()` 的序(2026-07-29 补容器) + +数值按值(Int/Float 跨类型),字符串按内容(长短一致),**列表按字典序** +—— 逐元素比,前缀排在扩展它的东西前面,和 `==` 把它们当结构相等一致。 +窗口(slice)当列表比。其余堆类型之间按**种类**排(String < Bytes < +List/Slice < Map < Set < Object < Callable < Error < 其他):map 和 map 之间 +没有自然序,但分组排至少是确定的。 + +此前容器全是 `Obj`、同一个 rank,于是"相等" —— 排一个列表的列表**原样不动**: + +```text +[[1,"b"], [1,"a"], [0,"c"]].sort() → 不变 +``` + +和"长字符串排不动"是同一个洞的两半。 + +深度超过 `MAX_VALUE_DEPTH` 时按种类排而不是报错:`sort_by` 要的是 +`Ordering` 不是 `Result`,而且排到一半才 raise 会留下一个已经被重排过的 +列表。这是全语言唯一一处深度上限**给答案**而不是**报告**的地方。 + +## channel 容量(2026-07-29 裁决) + +`chan(0)` 是**无缓冲**,和每一个读者见过的 channel API 一致 —— 不是无界。 +`chan(n)` 是容量 n,`chan(负数)` 报错。 + +此前 `capacity <= 0` 映射到 `None`,也就是**无界队列**:一个要"最强背压"的 +程序拿到的是完全没有背压,队列一直涨到进程死掉。而 0 是唯一通向无界的写法, +所以它同时是最容易误写的那个数。 + +运行时的 mpsc 没有真正的会合(rendezvous)形式,所以 0 取它能给的最小上界 +(1)。和会合的差别是"在途一个值",而另一头的备选是无界 —— 这个取舍值。 +无界队列现在语言里够不到了,这是有意的:没有上界的队列是一个涨到进程死掉的 +队列。 + +`lkrt`(AOT 的运行时)直到 2026-07-30 还照着**退休前**的那条规矩写: +`capacity <= 0` 当无界、负数不报错。这不是"native 慢一点"那类看不见的差异, +是两端**给不同答案**:`chan.new(0)` 连发两次 `try_send`,VM 给 `true/false` +(队列上界 1),native 给 `true/true`;`chan.new(-1)` VM 报错,native 递回一个 +无界通道。裁决改在 VM 侧、运行时侧没跟上 —— 一条裁决落在两份实现上就是这个 +下场,和"退休的容器裁决"那条同一个病(见上文模板串一节)。 + +`capacity` 报的是**要的那个数**,不是队列的上界:`chan.new(0)` 的 +`chan.capacity` 是 `0`,而里面的 mpsc 上界是 1。两个数得分开记(VM 记在 +`ChannelValue::capacity`,lkrt 记在 `ChanInner::requested`)。 + +**`use chan;` 会遮蔽 `chan()` 全局**,因为模块名和构造函数同名。此前这是条 +死路:导入模块之后**没有任何办法**创建 channel。现在模块里有 +`chan.new(capacity[, type])`,和全局 `chan(…)` 共用一份实现 —— 导入之后用 +模块拼写,不导入就用全局。 + +模块**要自己完整**,不能只完整一半(2026-07-30)。`chan.new` 补上之后,模块里 +仍然只有 `try_send`/`try_recv`:阻塞的 `send`/`recv` 只作为**不带前缀的全局** +存在。也就是 `use chan;` 之后拿到的是个只能轮询的通道,要阻塞就得去写 +`send(c, v)` —— 一个和 `chan` 前缀无关的名字。现在 `chan.send`/`chan.recv` +和那两个全局共用一份实现(`blocking_send_value`/`blocking_recv_value`),和 +`new` 是同一个办法:一份实现,两个名字。 + +`chan` 同名的这件事也让 AOT 少降低了一半(2026-07-30):`chan` 既是内建构造 +函数又是模块,`builtin_for_name` 先命中构造函数,成员读取就找不到值了 —— +`chan.new(1)` 悄悄掉回 VM,`chan(1)` 正常降低,两边打印同一个答案。字节码分得 +清这两件事(构造函数是 `GetGlobal chan` + `Call`,模块多一步 `GetIndex`),所以 +判据放在 `GetIndex` 那侧:能走到那里就是模块拼写。 + +导入之后仍然写 `chan(1)` 的报错也说人话了(2026-07-30):**"this value is not +a function: it is a Map — an imported module is a map of its members, so call +one of them"**。此前是 `Call callee is not callable` —— 说的是操作数,读者 +没有任何可动作的信息。类型检查器抓得住普通 map(`{"a":1}(1)` 报 +"Cannot call non-function type"),但它不给导入的名字建模,所以这条路是运行时的。 + +同理 `task.await(h)` 第二次报 **"this task has already been awaited"**,不再是 +`Task not found`(那说的是任务表)。VM 与 lkrt 两侧用同一句话。 + +## 范围步长为 0(2026-07-29 裁决) + +步长 0 是错误,**三种拼写一句话**:`Range step cannot be zero`。 + +此前只有两种说这句话:范围值(`let r = 0..3..0`)和 `iter.range(0, 5, 0)`。 +第三种 —— `for i in 0..3..0` —— 静默跳过循环体然后往下走。原因是 `for` 的范围 +不走 `NewRange`,而是特化降低:动态步长路径先算 `step > 0`,0 让它为假,于是 +进降序分支比较 `0 > 3`,一次都不转。同一件荒谬事,写法不同待遇不同。 + +现在:字面量 0 **编译期**就拒(步长就摆在那儿,没有理由等到运行时);变量步长 +在循环入口断言一次(`step != 0`,不成立就 raise),不进循环体。循环体里每转一次 +的开销没有变化。 + +## `impl Type { … }`(2026-07-29 补) + +方法可以直接挂在类型上,不必先有 trait。此前 `impl Type { … }` 是语法错误 +("Expected 'for' in impl statement"),而语言也没有 UFCS(`fn f(s: S)` 不能 +写成 `s.f()`)—— 于是给结构体加一个方法的唯一办法是声明一个**什么也不说的 +trait** 再实现它: + +```lk +trait Methods { } +impl Methods for Point { fn norm2(self) -> Int { … } } +``` + +机制本来就齐:分发按**目标类型**索引,不按 trait,所以缺的只是这个拼写。 + +固有 impl 和 trait impl 可以并存于同一类型:trait 说这个类型**承诺**什么, +固有块放它自己的东西。trait impl 的一致性检查不变(缺方法、签名不符、arity +不符都照报);固有 impl 没有承诺,所以不检查。 + +**trait 方法可以带默认实现**(2026-07-30 补): + +```lk +trait Greet { + fn name(self) -> String; + fn hi(self) -> String { return "hi ${self.name()}"; } // 默认 +} +``` + +没写 `hi` 的实现者拿到这一份,写了的覆盖它。实现方式是**按实现类型逐份复制** +(`stmt::trait_defaults`,在宏之后、任何分发之前),因为分发本来就按目标类型 +索引,而默认体里的 `self` 就是那个实现类型 —— 复制既是最简单的降低也是正确的 +那个,类型检查器、VM 编译器、AOT 降低都不需要知道"默认"这回事。trait 写在 +impl **后面**也算数:先扫全程序收集,再填。 + +此前 trait 只能写签名,于是每个实现者都得把同一段方法抄一遍 —— 语言逼着用户 +干实现里一直在消灭的那件事(一个概念 N 份拷贝,靠记性同步)。而且那时报错是 +把 token 流倒出来:`Invalid type: String { Return "hi" Semicolon}} Struct P …`。 + +**trait impl 里不能出现 trait 没声明的方法**(同日补)。此前能 —— 而且不得 +不能:`impl Type { … }` 是语法错误,方法只能住在 trait impl 里,于是程序声明 +一个空 trait 把所有东西挂上去。现在类型能带自己的方法了,trait impl 里多出来 +的方法就是个有明确改法的错误,报错直接说改法。这条让 trait 的方法列表重新 +有意义:它列的就是全部。 + +`ImplDecl.trait_name` 因此变成 `Option`,`MODULE_ARTIFACT_VERSION` 16。 + +**这条规则此前只在运行期生效**(2026-08-18 补)。它写在 `TypeRegistry::validate_trait_impl` 里, +而那个函数是 **VM 注册 impl 时**调用的,所以 `lk check`——文档说它是"执行器跑的同一个检查"—— +会放过一个第一行就停的程序。缺方法那一半早就补到 `Stmt::Impl` 的类型检查里了,多方法这一半没有。 +现在两半在同一处。 + +发现方式:写了 32 个"应该被拒绝"的程序丢给 `lk check`,看哪些通过了。通过的两个里, +一个是顶层 `return`(合法,模块的返回值),另一个就是这条。 + +**用户方法名可以和内置方法同名**(同日补)。编译器把 `len`/`push`/`set`/ +`split`/`join` 降低成专用 opcode 时只看**方法名**,那里还没有接收者的类型 —— +对列表和字符串是对的,对同名的结构体方法是错的:`s.len()` 答 +"Len target object is not sized",另外四个带参数的更是在**编译期**就因为 arity +失败,方法根本写不出来。现在:凡是本程序里某个 `impl` 声明过的名字,都不再假定 +是内置的,那些调用走普通动态分发。代价只落在给方法起了内置同名的程序上,而且 +只落在那个名字上。 + +**一个 impl 块里不能重复定义同名方法**(同日补)。此前后者静默胜出,前者被 +编译出来却永远到不了。语言里没有别的地方允许一个声明被它的兄弟遮蔽。 + +**内置容器的 impl 目标不能写元素类型**(同日补)。运行时按**擦除元素类型** +之后分发(`heap_dispatch_type` 把每个列表都报成 `List` —— 一个 +`TypedList::Mixed` 也报不出别的),所以 `List` 和 `List` 到的是 +同一个分发口。写 `impl T for List` 直接报错并说改成 `List`。 + +而检查器此前按接收者的**静态**类型做键,于是 `impl T for List` 注册在 +`List` 下、对 `[1,2]` 的调用查 `List` —— 方法存在却找不到,还在运行 +时之前就被拒了。`String` 和 `Map` 能用只是因为它们不走这条路(`String` 无 +参;`Map` 有"entries 即 fields"的旁路)。两边现在用同一个键。 + +## `url` 的 component 一对要能往返(2026-07-30 裁决) + +```lk +url.encode_component("a b&c=d") // 以前:"a+b%26c%3Dd" +url.decode_component(那个) // 以前:"a+b&c=d" —— 不等于原串 +``` + +编码用的是 **form** 编码(空格变 `+`,`form_urlencoded::byte_serialize`),解码只 +撤 `%XX`。一对的两个方向得先**互相**同意,再谈和别的东西同意 —— 这和 datetime 的 +`format`/`parse` 不往返(见那条)是同一个形状。 + +裁决:**component 就按 component 编码**,空格是 `%20`,`+` 是字面的 `+` —— 也就是 +`encodeURIComponent` / `decodeURIComponent` 的规矩。未保留集取 +`A-Za-z0-9-_.!~*'()`。form 编码是 query body 要的东西,而 `query_stringify` / +`query_parse` 本来就是那一对(两端都走 `form_urlencoded`),不受影响。 + +编码器因此改成**手写**的,和本来就手写的解码器并排放着:一对的两个方向应该是**一份 +实现的两个方向**,而不是两个 crate 的两种约定。 + +`base64.encode` / `hex.encode` / `url.encode_component` / `url.decode_component` +同时有了原生实现(lkrt 用**与 stdlib 同一个 crate**,所以文本逐字节相同,和 +`datetime`/`json` 的理由一样)。`base64.decode` / `hex.decode` 给的是 `Bytes`,原生 +还没有那个承载类型,继续回落 —— 表里没有行的成员是普通回落,不是错答案。 + +## 子模块经父模块访问也要原生降低(2026-07-30) + +`encoding.json.parse(s)` 和 `use { json } from encoding; json.parse(s)` 是同一个 +成员的两种拼法。后者一直原生降低,前者**整个程序掉回 VM** —— 答案一样,慢三倍, +所以差分门禁看不见,是探针撞上的(和模板串里的容器、`chan.new` 同一类)。 + +缺的是两件事: + +1. 从父模块读一个**子模块**,给出的是父模块的一个"函数"(`ModuleFn`),而不是 + 另一个模块对象。链子因此停在第一个点上。`is_submodule` 谓词早就有了 —— 选择性 + 导入那条路一直在用它 —— 只是 `GetIndex` 那侧没用。 +2. `encoding.json.parse(s)` 编译成 **`CallMethodK`**,接收者是模块对象。那条路上 + 没有模块分支,于是去 `ssa.read` 一个只存在于降低期的 ref,报 + "register r7 is read before any definition"。 + +顺带补齐了 `MODULE_TABLE`:父模块(`encoding`/`net`/`io`)此前根本没有行,名字都绑 +不上;子模块补了 `base64`/`hex`/`url`/`udp`/`file`。名字**绑得上**和成员**降得下** +是两件事 —— 前者归这张表,后者归 `MODULE_ABI`;`encoding.base64.encode` 现在是后者 +缺(lkrt 里没有符号),报的也是那句话。 + +## 排序说的是排序的规矩(2026-07-30 裁决) + +`<` / `<=` / `>` / `>=` 排的是**数字和字符串**,两边要**同类**。规矩没变,报错以前 +说的是别的: + +- `1 < "a"` 报 "**the left operand** must be numeric types" —— 一句话怪错了两次: + 这里的左操作数**就是**数字,而换成字符串本来是合法的。现在报 + "an ordering compares two of a kind: a String orders against a String, a number + against a number"。 +- `[1,2] < [1,3]` 报 "must be numeric types(expected `Int | Float | Box`)" —— + 期望集合里**漏了 String**(字符串早就可排序了),而且答的是错的问题:列表的问题 + 是它根本**没有序**,不是它不是数字。现在报 "`<`, `<=`, `>` and `>=` order numbers + and strings; this type has no ordering"。 + +根因是排序复用了**算术**那条判据(`ensure_numeric_operand`)。算术里"必须是数值"是 +对的(字符串走的是拼接那条臂),排序里不对 —— 所以排序现在有自己的 +`ensure_orderable_operand`。一条规矩变了(字符串可排序),而复用它的第二个地方没跟上, +这是本会话反复出现的那个形状。 + +## 只有裸名字能起一个宏调用(2026-07-30 裁决) + +后缀 `!` 是解包,`name!(…)` / `name![…]` / `name!{…}` 是宏调用。判据以前**只看后面 +那个开括号**,于是: + +```lk +m["a"]![0] // 以前:"a macro invocation reached the parser" +xs[0]![0] // 同上 +m.field![0] // 同上 +``` + +宏名是**标识符**,`m["a"]` 不可能是宏名 —— 这些拼写里根本没有歧义,却要靠加括号 +(`(m["a"]!)[0]`)或者拆成两行绕过去。现在判据是"`!` 前面是不是一个裸 `Expr::Var`": +是,才可能是宏。 + +真正有歧义的只有裸名字一种(`f![0]`:是宏 `f!` 还是解包 `f` 再索引?),那一个归宏, +要解包就写 `(f!)[0]` —— 报错里也把这条说出来。 + +## 关键字可以当成员名(2026-07-30 补) + +关键字以前在**所有**位置都被保留,这比语法需要的多。一个**成员**总是经 `.` 到达, +或者声明在 `struct` / `impl` / `trait` 的体里,而这些位置**都不能起一条语句** —— +所以下面这些以前是语法错误,没有任何读者能据以行动的理由: + +```lk +struct Row { type: String, select: Int } +impl Row { fn match(self) -> Int { return self.select * 2; } } +trait Runner { fn go(self) -> Int; } +db.select() +parser.match(x) +``` + +放开的位置一共四处:`.` 之后的成员读取、结构体**字段声明**、结构体**字面量**的 +字段名、`impl`/`trait` 体里的方法名。值字面量(`true`/`false`/`nil`)故意不在里面 +—— 它们是值不是关键字,`p.nil` 读不出意思。 + +**顶层 `fn` 保留限制**:调用它是表达式位置上的一个裸名字,`select(1)` 和 `select { … }` +就得靠上下文区分了。报错也跟着说清:"`select` is a keyword, so it cannot name a +top-level function — a call to one is a bare name, where `select(…)` could not be +told from the `select` statement. It *can* name a method or a field"。 + +"这个 token 能不能当名字"只有一份判据(`token::keyword_as_name`),四个位置共用。 + +## 一个名字一个意思:方法只声明一次(2026-07-30 裁决) + +三种撞名以前都是**静默取最后一个**: + +```lk +impl Show for P { fn show(self) -> String { return "a"; } } +impl Show for P { fn show(self) -> String { return "b"; } } // 静默赢 + +impl P { fn get(self) -> Int { return 1; } } +impl P { fn get(self) -> Int { return 2; } } // 静默赢 +``` + +字段那一种更糟 —— 拿到哪个取决于**实参个数**: + +```lk +struct P { get: Int } +impl P { fn get(self) -> Int { return 9; } } +P { get: 1 }.get() // 以前:1(字段),方法永不可达 + +struct Q { f: (Int) -> Int } +impl Q { fn f(self) -> Int { return 9; } } +q.f(3) // 以前:走方法,报 "Method expects 0 arguments",字段闭包永不可达 +``` + +`p.get(…)` 说不出它指哪个,所以在**声明处**拒绝。两个同名顶层 `fn` 早就是报错的, +这是同一条规矩。两个**不同 trait** 各声明一个同名方法也拒绝:LK 没有 +`Trait::method(x)` 那种消歧写法,`p.run()` 会没有答案。 + +判据是**程序级的一遍**,不是有序遍历累积出来的 —— 理由和 `collect_function_names` +一样:问的是声明的**集合**,而检查器的注册表对重复注册的 `impl` 是**替换**的 +(REPL 的上下文跨次复用),所以它分不出"这里声明了两次"和"又见到一次"。 + +## trait 必需方法在 `lk check` 就该报(2026-07-30 补) + +`TypeRegistry::validate_trait_impl` 一直存在,但只在 **VM 注册 impl 时**跑 —— +也就是运行时。于是 `lk check`(预检命令)放过一个跑不起来的程序,一句话不说。 +现在检查器的 `Impl` 分支自己查:trait 声明的每个方法都得在。trait 默认实现在这之前 +已经由 `stmt::trait_defaults` 拷进去了,所以"在不在"就是全部问题。 + +## 顶层 `let` 不能占用声明已经绑走的名字(2026-07-30 裁决) + +```lk +fn pick() -> String { return "fn"; } +let pick = || { return "let"; }; // 以前:静默地是 let 那个 +``` + +两行**调换顺序也一样**是 `let` 赢。原因是 `fn` 和类型声明是被 **hoist** 的 —— +相互递归能写,说明一个 `fn` 在它那一行之前就可见了 —— 所以源码顺序对它们不适用, +"`let` 遮蔽了它"这句话没有连贯含义。两个同名 `fn` 早就是报错的 +("Compiler duplicate function"),`fn` + `let` 却静默。 + +现在拒绝,并说清为什么:"`pick` is already declared as a function in this module: +a function is visible before the line it is written on, so a `let` of the same +name cannot shadow it — rename one of them"。覆盖 `fn`、`struct`、`type` 别名。 + +**两个 `let` 仍然是正常遮蔽**(两者都是顺序敏感的),**可调用体里面的 `let` 也是** +—— 局部量在自己作用域内顺序敏感,而声明在作用域外面。 + +这条不是假想的:它正是 `examples/syntax/closure.lk` 里一个死掉的 `fn apply` 挨着 +一个活的 `let apply` 的来由 —— VM 跑得通(两条断言碰巧都被 lambda 那版满足), +native 拒绝,而没有任何东西说过一句话。 + +## 函数不能声明在另一个可调用体里面(2026-07-30 裁决) + +`fn outer() { fn helper(n) { … } return helper(1); }` 以前**语法上收下**,然后 +编译期报 "Compiler undefined function `helper`" —— 函数下标只从顶层语句收集 +(`collect_function_names`),嵌套的 `fn` 从没拿到过下标。语法接受、后端拒绝,而且 +是用后端的话说的,这是最糟的那种组合。 + +现在在类型检查里用语言的话拒绝,并且把两条替代路都说出来:挪到顶层,或者 +`let name = |…| …;` 用闭包(需要外层作用域时)。判据是"当前是否在某个可调用体 +里",直接读 return frame —— 每个函数/闭包体开一个,别的都不开,所以不需要第二 +份记账。`impl` 方法不受影响:它们是顶层的 `fn` 声明。 + +**支持它是一个特性,不是这条修复**:嵌套 `fn` 捕获不了外层(那是 Rust 的规矩), +所以做法是 hoist 加一个带作用域的名字 —— 见 todos。 + +顺带:递归因此**只有顶层 `fn` 写得出来** —— 和 Rust 一样(Rust 的闭包也不能递 +归)。`let fact = |n| … fact(n-1) …` 里 `fact` 在自己的初始化式里还不可见;手写 +Lua 那套 `let fact = nil; fact = |n| …;` 过不了类型检查(`fact` 是 Nil,"Cannot +call non-function type")。 + +这条规矩现在**自己说出来**:以前报 "Compiler undefined callable `fact`" —— +一句关于操作数的话,讲的是关于作用域的规矩,读者拿它没有任何可做的事。现在报 +"`fact` is not in scope inside its own initializer, so this closure cannot call +itself; write a recursive function as a top-level `fn fact(…)`"。判据是"被调的 +名字正是当前正在初始化的那个绑定",所以拼错的名字仍然读作拼错;外层同名绑定是 +另一个函数,调它不受影响。 + +## lambda 可以写自己的类型(2026-07-30 补) + +`|x: Int, y: Int| -> Int { … }`。此前两者都是**语法错误**,而 `Type::Function` +一直是有形参类型和返回类型两半的 —— 也就是 lambda 是语言里唯一一个类型写不出来 +的可调用体,它的形参类型只能从调用点**猜**。 + +- 形参类型和返回类型各自独立,都可省。 +- 谁说了算:**闭包自己写的 > 上下文期望的 > 新类型变量**。写下来的是作者的 + 声明,上下文只是关于它的一个推断。 +- 声明的返回类型是**用来检查**的,不只是记下来:体里每个 `return` 都要能赋给 + 它,不然报 "Return type mismatch in closure"。 +- 形参里写不了 union,因为那个位置的 `|` 是参数列表的收尾;括号也不行(带括号 + 的类型不在语法里),所以走 `type` 别名 —— 那是同一个类型的第二个拼法。 + +**每个"声明了函数类型"的位置都收得下 lambda**(2026-07-30):一条规矩落在多个 +地方,漏掉的那些就静默拒绝为它写的 lambda。清出来的有七处 —— `let`、结构体字段、 +`fn` 形参(报 "got `('T1) -> Int`")、命名实参、声明的返回类型(报 "Return type +mismatch")、`List<(Int) -> Int>` 的元素、`Map` 的值。现在是**一个** +`check_expr_against` 回答所有这些位置。 + +它会往聚合字面量里**分发**期望(`[|x| …]` 对 `List<(Int) -> Int>`),但只在那个 +位置真的坐着一个 lambda 时才走这条路 —— 否则普通通路的推断原样保留(混类型列表 +字面量是 `Tuple`,那条规矩不是这个 helper 该推翻的)。`Optional` 收下它的载荷, +括号不是类型层面的构造。 + +**期望流进去只是一半,答案还得回查**:无条件返回声明的类型是一句断言而不是描述, +它一度让 `let fs: List<(Int) -> String> = [|x| { return x + 1; }];` 通过。 + +"哪串 token 是一个类型"这件事以前只在语句 parser 里写了一份,lambda 需要同一 +件事而又到不了那个 parser。现在抽在 `core/src/type_syntax.rs`,两边共用;位置 +之间唯一的差别是**类型在哪结束**,由 `StopAt` 说:语句位置 `|` 是 union、`=` +收尾;lambda 形参位置顶层 `|` 和 `,` 收尾;lambda 返回位置顶层 `{` 收尾。 + +它放在 `type_syntax` 而不是 `token` 里,因为它要提 `Type`,而 `token` 不能伸进 +`val` —— 那条边会把 `token` 也拖进 `val` ↔ `vm` 的环(见 `docs/module-cycles.md`)。 + +**`Expr` 的大小是有门禁的**:`Expr` 是递归解析的,把 `Type` 按值塞进 +`Expr::Closure` 会让每个解析栈帧变大,大到深嵌套时在 parser 的深度守卫**之前** +就爆栈 —— 那条守卫只有先跳才有用。所以返回类型装箱,并且加了 +`the_expression_node_stays_small_enough_to_recurse_over` 把这条说出来。 + +## 块是要过类型检查的(2026-07-30 裁决) + +`Expr::Block` 以前直接返回 `Any`,**不往里看** —— 理由是块大多来自 desugar, +在拼出来之前已经查过了。这个理由对 desugar 成立,但闭包体也是块,于是: + +```lk +let f = |x| { let s: String = 1; return x; }; // 以前:接受 +let s: String = 1; // 同一条语句在顶层:报错 +``` + +也就是**块体 lambda 里的所有语句从来没过类型检查**,一整类代码对检查器不可见。 +现在 `Expr::Block` 和别的表达式一样查自己的语句,值是尾表达式的类型 +(`check_statements_value`),并且**自带一层作用域** —— 块是作用域这条规矩在 +别处已经立过了(见"块不是作用域"那条)。 + +`unsafe { … }` 曾是唯一往里看的地方(`check_block_value`),因为最需要 scrutiny +的构造反而一点都拿不到。它现在只是那条通路的入口。 + +## 闭包的类型(2026-07-30 裁决) + +**块体闭包的 `return` 就是闭包的返回类型。** 收集 `return` 的那个 frame 以前 +被 pop 掉就丢了,于是这类闭包一律是 `… -> Any`,而 `Any` 满足任何注解: + +```lk +let f = |x| { return x + 1; }; +let s: String = f(1); // 以前:接受,运行时打印 2 +``` + +命名 `fn` 一直是把收集到的 return join 起来的 —— 这不是新规矩,是闭包补上了 +同一条。体自身的类型只在"它说了点什么"时才 join 进来:以 `return` 语句结尾的 +块没有尾表达式,类型是 `Any`,放进去会把 union 吞掉。 + +**函数类型注解会流进 lambda。** 孤立推断的 lambda 是 `('T0) -> Any`,和为它写 +的注解不 unify,所以 lambda 根本没法被注解 —— 而同一个绑定换成命名 `fn` 就通: + +```lk +let f: (Int) -> Int = |x| { return x + 1; }; // 以前:类型不匹配 +fn inc(x: Int) -> Int { return x + 1; } +let f: (Int) -> Int = inc; // 一直是通的 +``` + +现在 `let` 的函数类型注解把形参类型压进 `check_closure`(调用点早就这么做了, +`calls.rs`),和 machine-int 字面量那条一样是**窄的**双向检查,理由也一样: +另一条路是一个没人能用的特性。 + +函数类型的拼法是 `(Int) -> Int`,**不带 `fn`**;`fn(Int) -> Int` 不是合法类型 +(类型位置的 token 收集器根本不收 `fn`)。返回位置同理:`fn mk() -> () -> Int`。 + +## 错误文本(2026-07-08 裁决) + +`catch e` 绑定的消息 = **裸 cause 文本**,无包装:native(Rust stdlib)函数 +失败不再加 `"native `{name}` failed: "` 前缀(曾有,`map_native_error` 处 +移除),与 `error(v)` 一等值对称;调用点归因由 traceback 承担,不进消息。 + +**跨 task 边界也不包装**(2026-07-29 补):`task.await` / `task.join_all` 曾 +加 `"Failed to await task: "` 前缀,于是同一个失败在 task 里 raise 和在原地 +raise 读出来是两个字符串 —— 而程序是可能按消息分支的。 + +**算术失败的文本两端逐字一致**(2026-07-29 补):除零、取模零、移位越界是 +程序能 `catch` 并据以分支的东西,所以这几条手工对齐。此前 `a % b`(b=0) +VM 说 `ModInt divisor is zero`、native 说 `Division by zero` —— 两个不同的 +字符串,而且都说错了是哪个运算符。 + +**其余跨后端错误文本不保证逐字一致**:VM 与 native 的错误生成机制不同 +(如 `recv(999)` VM 报 "recv first argument must be a Channel"(类型检查), +native 报 "Channel not found"(id 查找))。差分语料因此**不打印 catch 到 +的错误文本**,只断言 catch 行为(进入 handler、后续状态可用);若未来要 +开放文本比对,需先逐条对齐两侧消息(fuzz 差分红为发现机制)。 + +## 错误文本说语言的话,不说实现的话(2026-07-29 裁决) + +用户看得见的错误里不出现 **opcode 名**、**内部表示名**、**desugar 出来的 +内部函数名**: + +| 写的是 | 曾经说 | 现在说 | +|---|---|---| +| `a % 0` | `ModInt divisor is zero` | `modulo by zero` | +| `a % "s"` | `ModInt expected Int or Float, got …` | `% expects Int or Float, got …` | +| `-s` | `Neg expected Int or Float, got ShortStr` | `unary '-' expects Int or Float, got String` | +| `n << 99` | `__lk_shl shift amount 99 is out of range 0..63` | `shift amount 99 is out of range 0..63` | +| `5[0]` | `GetIndex target expected Obj, got Int` | `Int is not indexable` | +| `5[0] = 1` | `SetIndex target expected Obj, got Int` | `Int cannot be indexed for assignment` | + +`ShortStr` 尤其要紧:那是"短到能内联的字符串"这个**表示**,语言里没有这个 +类型。它是 `RuntimeValKind` 的变体名,而约四十条消息写的是 +`bail!("… got {:?}", v.kind())` —— 所以 `RuntimeValKind` 的 `Debug` 现在是 +手写的,打语言的类型名;要看表示用 `repr_name()`。 + +opcode 名同理:它说的是编译器挑了哪个**融合**形式,源码里没有 `ModInt`, +而且这个选择会随优化变化。`operator_symbol` 把算术 opcode 映回源码运算符; +映不回去的说明是编译器/执行器不匹配,那时 opcode 名才是有用的。 + +内部不变量被破坏的消息**保留** opcode 名(`GetList target object changed +while reading list`):那是 VM 的 bug,不是程序的。 + +## trait 方法分发与 auto-Display(2026-07-07 裁决,plan J) + +native 侧 struct 实例是普通 string-keyed map(**无 `"$type"` 隐藏键**—— +`len()`/迭代/display 与 map 完全一致);运行时类型身份存 arena 句柄侧表 +(lkrt `OBJ_TYPE_MARKS`,`NewObject` 时打标记)。两个已知边界: + +- **类型标记不跨 channel**:深拷贝(`OwnedVal`)重建 map 时不复制标记, + 收方对该 struct 实例的动态 trait 方法调用会 raise(VM 能成功)。语料无 + 此形状;如需支持,`OwnedVal` 捕获/重放需带上标记。 +- **auto-Display 只有 `show` 这一个钩子**,面向用户的文档曾说有三个。 + `LEARN.md` 写着"实现 `show`、`display` 或 `to_string`,`println` 就会用它", + 而查找是硬编码的 `"show"` —— 写 `display` 的人看到的是默认渲染,没有报错。 + 取一个名字而不是三个:一个钩子的第二种拼写正是这个代码库一直在删的东西, + 而 `#[derive(Show)]` 生成的也是 `show`。 + `auto_display_uses_show_and_only_show` 钉住这条,文档与查找不能再各说各的。 +- **auto-Display 只镜像 `show`**:VM `try_runtime_display_show` 硬编码查 + 方法名 `"show"`(与 trait 名无关;`#[derive(Debug)]` 展开出的 + `__LKShow::show` 也走它)。native 在 display 上下文(print/println 参数、 + 模板插值 `ToString`/`ConcatString`/`ConcatN`)对带 provenance 的 struct + 直调注册的 `show`。**无 `show` impl 的整对象 display 不进子集**(VM 内部 + 有 `` debug 形与 registry 缺失 bail 等多种路径,未统一前不复刻)。 + +动态分发(boxed receiver,经混合列表/Dyn 参数流动)限 `argc == 0`(self 之外 +无参数)且零捕获 impl;静态 devirt(NewObject provenance 已知)支持任意参数。 +分发臂按注册序排列,标记无匹配 → raise(VM 的 unknown-method 同为错误)。 + +## 容器方法的拼写:contains / has / delete(2026-07-31 记) + +同一个问题在不同容器上叫什么,是查表查出来的,不是猜的: + +| 容器 | 在不在里面 | 按键/值删 | +| --- | --- | --- | +| list | `contains(v)`、`v in xs` | `remove_at(i)`(按下标) | +| set | `contains(v)`、`v in st` | `delete(v)` | +| string | `contains(s)`、`s in text` | — | +| map | `has(k)`、`k in m` | `delete(k)` | + +规则是:**能不含歧义的地方一律 `contains`,map 用 `has`** —— 因为对 map 而言 +"contains 什么,键还是值?"是个真问题(Java 就得分成 `containsKey` / +`containsValue`)。`in` 在四种容器上都可用,是那个统一的写法。 + +这条记下来,是因为 AOT 的 Set 降低臂曾经同时接受 `has` 和 `remove`,而类型检查器 +两个都拒 —— 于是那两个名字永远到不了降低,读代码的人却会以为 `st.has(x)` 能用。 +删掉它们时顺手把规则写在这里,免得下一个人朝相反方向"修"。 + +## `m.x` 在 map 上是取键,而方法优先(2026-07-31 记) + +一个 map 的成员访问有两个意思,而它们都成立: + +```lk +let m = {"a": 1}; +m.a // 取键 "a" —— m["a"] 的写法糖 +m.len() // map 的方法:条目数 +m.f(1) // 键 "f" 存的是函数时,调用它 +``` + +**同名时方法赢。** `{"len": 5}.len()` 是 `1`(条目数),不是"调用 5";那个键仍 +然读得到,写 `m["len"]` 或 `m.len`(不带括号)。 + +**这条 2026-07-31 当天就被发现只在 `len` 上成立(同日修)**:写下它时只探了 +`len`,而 `len` 恰好有自己的 opcode,根本没走到分派器。分派器里键查找排在内建 +方法分派**前面**,于是 `{"keys": 5, "z": 1}.keys()` 答 `5`、 +`{"is_empty": 5}.is_empty()` 答 `5` —— 拿到方法还是键,取决于编译器有没有给那 +个方法单独发指令。现在内建方法先分派,没有同名内建时才查键(键里存的是可调用 +值就调它,`m.f(1)` 这条形状不变)。 +`a_map_method_is_not_shadowed_by_a_key_of_the_same_name` 钉住。 + +教训归档:**"读了一遍分派顺序"不等于探过**;一条裁决要按它覆盖的每一类名字各 +探一个,否则写下来的是实现在某一个样本上的行为。 + +为什么是方法赢:另一种选择(键存在就用键)会让内建方法在某些 map 上凭空消失, +而消失得没有任何提示;方法赢至少是**同一个名字在所有 map 上是同一件事**,并且 +被遮住的键有一个不含歧义的写法(`m["len"]`)可用。 + +和结构体那条(见"一个名字一个意思")的区别:结构体的字段名是**声明**出来的, +所以同名可以在声明处直接拒;map 的键是运行时数据,没有声明处可拒,只能定一条 +优先级。 + +## 常量条件不会藏起没走的那一臂(2026-07-31 裁决) + +```lk +let x = if false { undefined_fn() } else { 1 }; // 报 undefined_fn +let y = false && undefined_fn(); // 一样报 +let z = -true; // 一样报:Bool 不能取负 +``` + +以上三条此前**全部静默通过 `lk check`**,`-true` 还会打印 `false`。 + +原因是常量折叠 `Expr::fold_constants` 跑在 **parser 里**,早于名字解析和类型检 +查;而它做的不只是"算",还会整棵丢掉一个子树 —— 条件恒真/恒假时选一臂、`&&` +/`||` 短路、`??` 左边是常量。被丢掉的那棵子树后面没有任何人看过。`-true` 是同 +一个错误的另一半:折叠不看 op,把任何一元运算作用在 Bool 字面量上都当成 `!`。 + +**规则:解析期折叠可以"算",不可以"删"。** 一次二选一的折叠只有在**被丢弃的那 +一侧本身已经是字面量**时才允许(字面量没有什么可检查的)。因此: + +- `false && true` 仍折成 `false`,`nil ?? "a"` 仍折成 `"a"`,`if false { 1 } else { 2 }` 仍折成 `2`; +- `false && f()`、`if false { f() } else { 1 }`、`1 ?? f()` 不折,`f()` 照常过检查; +- `-3` 折,`-true` 不折 —— 交给类型检查器报"取负的操作数必须是数值"。 + +**短路仍然是运行期语义**:`false && (1 % z == 1)`(z 为 0)不会求值右边、不会 +raise,这是执行器做的事,和折不折无关。(探针用 `%` 而不是 `/`:`/` 是浮点除 +法,`1 / 0` 是 `inf`,根本不 raise,那样的探针求不求值都一样过。)恒定条件的分支消除属于优化,归类型检查之后的 VM +编译器和 AOT 后端,那里两边都看得见常量。 + +顺带修掉的:恒定条件的 `if` 表达式此前只报活下来那一臂的类型,于是顶层 +`let x: Int = if false { 9.5 } else { "x" };` 说 `String`,函数体里同一行说 +`Float | String`。现在两处都说并集。 + +## 下游关掉管道 = 程序停下,不是 panic(2026-07-31 裁决) + +```sh +lk gen.lk | head -1 +``` + +解释器此前打的是: + +``` +thread 'main' panicked at library/std/src/io/stdio.rs:1166:9: +failed printing to stdout: Broken pipe (os error 32) +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +退出码 101。而**原生编译出来的二进制一直是对的** —— 它的 `main` 是 C `main`, +Rust 的启动代码没跑过,于是它按 Unix 惯例被 SIGPIPE 杀掉(shell 报 141),一声 +不吭。两个后端不一致,且对的是 native 那边。 + +原因:Rust 在 `main` 之前把 `SIGPIPE` 设成 `SIG_IGN`,写关闭的管道于是返回 +`EPIPE`,`println!` 再把它 unwrap 成 panic。这既违反"错误文本说语言的话,不说 +实现的话",管道又恰恰是 shell 对一个会打印的程序最日常的用法。 + +**裁决:`lk` 在 `main` 开头把 `SIGPIPE` 恢复成 `SIG_DFL`**,和 `head`、`grep` +以及原生二进制一样。行为:下游关掉读端后程序立即停止,不打印任何东西,退出状 +态是"被信号 13 终止"。非 Unix 平台不适用。 + +`cli/tests/broken_pipe_test.rs` 钉住这条,并且正反两向验过。 + +## `typeof` 说结构体的名字,不说 `Object`(2026-07-31 裁决) + +```lk +struct S { a: Int } +typeof(S { a: 1 }) // "S" +typeof({"a": 1}) // "Map" +``` + +此前 **两个执行器给的都是错答案,而且互不相同**:解释器答 `Object`(堆表示的 +名字,不是语言里的类型),原生答 `Map`(结构体和普通 map 共用 `MapStrDyn` 载 +体,静态表就照载体答)。`typeof` 对最想问的那类值恰好没用。 + +这是同一条规矩第三次在下一层被发现有洞: +`RuntimeValKind::scalar_type_name` 的注释写着"手里有堆的调用者请改用 +`HeapValue::type_name`",而 `HeapValue::type_name` 自己对每个结构体实例答 +`Object`;lkrt 里那份"镜像"(`kind_name`)也一样。三处都改了 —— +`HeapValue::type_name` 答声明名,变体自己的拼写挪到 `representation_name`; +lkrt 读运行时的 type 标记。 + +原生侧的静态表因此**去掉了 `MapStrDyn`**:它同时是结构体载体,静态答不出来。 +降低时若能指名结构体(`ssa.struct_types`)就发字面量,否则装箱调 +`dyn.type_name` 让运行时读标记 —— 猜 `Map` 有一半时候是错的。 + +顺带,所有拿 `HeapValue::type_name` 拼错误消息的地方(三十余处)也跟着说对了: +`p.len()` 现在报 "`len()` has no answer for S",不再是 `Object`。 + +## `-> T` 是对**每一条路径**的承诺(2026-07-31 裁决) + +```lk +fn g(c: Bool) -> Int { if c { return 1; } } // 现在:lk check 报错 +``` + +此前这段过检查,`g(false)` 答 `nil`,失败在调用点才现形: +`Add expected numbers or strings, got Nil and Int` —— 报的是运算符,离那个承诺 +了 `Int` 的函数三个栈帧。 + +**裁决:声明了返回类型且该类型不接受 nil 的函数,必须每条路径都离开。** 判据只 +认**可证明的离开**:`return`、两臂都离开的 `if/else`、带 catch-all 且每臂都离开 +的 `match`、`error(...)` / `panic(...)`、没有 `break` 的 `while true`。不认识的 +构造一律算"可能落到末尾" —— 那只会要求多写一个 `return`,不会漏放。 + +不受此约束的三类,因为它们本来就接受落空值:`-> Nil`、`-> Any`、`-> T?`。没写 +注解的函数返回类型是**推断**出来的,没有承诺可违反。 + +实现在 `core/src/stmt/stmt_impl/flow.rs`,`a_declared_return_type_is_a_promise_about_every_path` +钉住(含全部"确实每条路都离开"的写法)。整个 examples / bench / 两个裸机 corpus +零误报。 + +## `u64` 的上半区,十进制和十六进制都写得出来(2026-07-31 补) + +```lk +let a: u64 = 0xFFFFFFFFFFFFFFFF; // 一直可以 +let b: u64 = 18446744073709551615; // 现在也可以;此前是 `Invalid int` +``` + +语言有 `u64` 类型,却只有十六进制能写出它 `i64::MAX` 以上的值 —— 同一个数,一 +种拼写收下、另一种报语法错。词法器的 radix 路径把字面量按 **u64 位模式**读、超 +出 i64 就发 `Token::UInt`;十进制路径只有 `i64::from_str`。补上后者。 + +边界没有变:`-18446744073709551615` 仍然两条 parse 都不过(负号是 `num` 的一部 +分),`let y: u8 = -1` 仍然被拒;超过 `u64::MAX` 的报"integer literal out of +range(Int 是 i64,u64 是最宽的)",而不是原来的 "Invalid int" —— 那个数不是写 +错了,是超范围了。 + +`Token::UInt` 现在带着**写下时的进制**,所以 `lk macro expand` 把它按原样打回 +去,不会把十进制的数重拼成十六进制、也不会把二进制掩码打成十六进制。**`Token::Int` +仍然不带进制**(它是最常见的 token,加字段要动 80 处),所以 `0x3F20_0000` 这 +种能装进 i64 的掩码经过 `macro expand` 仍会变成十进制 —— 已知的保真缺口,记在这 +里而不是假装没有。 + +## 容器当实参传进去,被调方的写就是调用方的写(2026-07-31 裁决,部分已修) + +```lk +fn mk() -> List { return [1]; } +fn add(xs: List, n: Int) -> Int { xs.push(n); return xs.len(); } +let xs = mk(); +add(xs, 2); +println("${xs}"); // [1,2] —— 两个执行器现在一致 +``` + +原生此前答 `[1]`:**错答案,不是回落**,程序照跑还打出一个看着合理的长度。 + +根因是**类型化列表是唯一一个装箱时会重建容器的载体**(`list_h.i64_to_dyn` 逐元 +素装箱成新表)。集合、bytes、窗口、dyn 列表和五种类型化 map 都是原地打标签装箱 +—— `DYN_RAW` 的注释早写下"装箱不得重新表示一个容器",类型化 map 正因违反它被修 +过。类型化列表是这条规矩最后没补的一格。 + +**已修的触发路径(2026-07-31)**:形参之所以被拓宽成 `Dyn`,是因为 fixpoint 的 +**第一遍**在被调方返回类型还停在 `I64` 默认值时就记下了实参观测,而形参格是**单 +调 join** 的:第一遍的 `I64` 和第二遍真实的 `list` 一 join 就变 `Dyn`,此后 +每个调用点都装箱 —— 由一个从来不成立的事实推出来的。`ret_known` 早就为 HOF 重路 +由记下了同一个坑,形参格没跟上。第一遍的观测现在整体不记(它的产物本来就丢弃)。 + +**第二条未修的触发路径,而且更糟:把类型化列表*存进另一个容器*。** + +```lk +let inner = [1]; +let outer = [inner]; +inner.push(2); +outer[0].len() // 解释执行 2,编译执行 1 +``` + +两个方向都断 —— 改 `outer[0]` 也照不到 `inner` —— 所以是 `[inner]` **构造那一刻** +就拷贝了。放进 map 一样(`{"k": inner}`)。而把 **map** 放进 list 没事:map 只有 +一种表示,装箱是打标签。 + +和下面那条不同的是观测面:那条是**回落**(慢但对),这条**编译成功且静默答错**。 +61 程序扫描和 fuzz 都没这个形状,所以一直没人看见。 + +**三条临时判据都验过了,都不成立**(记下来,免得下次再走一遍): + +1. **一刀切:元素是类型化 list 就拒绝装箱。** AOT 覆盖从 60 掉到 58 —— + `examples/stdlib/{iter_pipeline,list_iter_sugar}.lk` 造的是 + `[[0,"a"],[1,"b"]]` 这种字面量嵌套(反汇编看到的是 `LoadHeapConst` 经 `Move` + 链喂进 `NewList`),元素是新建的、没有第二个引用,重建不可观测,拒绝它们纯亏。 +2. **寄存器活跃性:装箱点之后这个寄存器还被读吗?** 不健全 —— 一个也能从全局到达 + 的值,它的寄存器是死的:`let g = [1]; fn f() { let outer = [g]; g.push(2); }` + 里 `g.push` 会重新 `GetGlobal` 到另一个寄存器。 +3. **具名槽边界:`reg < 具名局部数` 才算可命名。** 没有这条边界 —— + `Ssa::slot_count` 就是 `reg_count + cell_capacity + capture_count`,全部寄存器 + 一视同仁。 + +剩下的健全判据是**新鲜性**:这个值是本函数里由 `NewList`/`NewMap`/`LoadHeapConst` +造出来的,**并且**之后没有再被读。两半都要 —— 前一半排掉全局(2 的漏洞),后一半 +排掉 `let inner = [1]; let outer = [inner]; inner.push(2);`(inner 是新鲜的,但还被 +读)。 + +**而根治比"五条被推翻的缓解"听起来的要有谱:类型化 map 已经这么修过。** +`lkrt/src/lkdyn.rs` 有一段 `DYN_TMAP_BASE..DYN_TMAP_END` 的标签区间, +`lkrt_dyn_from_typed_map(handle, kind)` 把类型化 map **按标签**装箱,五个 +`lkmap::KIND_*` 各占一格 —— 装的是句柄,不重建。类型化 list 要的是同一个形状的 +`DYN_TLIST_*`(i64/f64/str 三格),而不是一条没人走过的新路。 + +**未修的触发路径**:同一个模块里有**两种列表载体**各自流进会改写它的函数时,形参 +格 join 成 `Dyn`,装箱又回到重建那条路。最小复现: + +```lk +fn mk() -> List { return [1]; } +fn add(xs: List, n: Int) -> Int { xs.push(n); return xs.len(); } +fn adds(xs: List, s: String) -> Int { xs.push(s); return xs.len(); } +let xs = mk(); add(xs, 2); // 单独看:两端一致 +let ss: List = []; +println(try { "${adds(ss, "a")}" } catch e { "c" }); +println("${xs} ${ss}"); +``` + +**这个复现已经过期(2026-08-06 复核)**:它当时答 `[1,2] []`(原生看不见 +被调方的写),现在**回落** —— 形参格不再 join 成 `Dyn`,而是冲突并拒绝降低 +(`in \`adds\`: an operand at pc 0 is a str where a i64 is required`)。 +显式写 `fn add(xs: List, …)` 那条也关上了:#184 让可变容器不变, +`List` 不再能传进 `List`。 + +重建那条 ABI 调用**仍在发**(语料里 8 个文件命中 `list_h.*_to_dyn`),所以 +表示层的问题照旧;换掉的只是它露头的形状。现在露头的是**拓宽**:两种载体 +流进同一个 `Any` 形参再 push,VM 就地拓宽答对、原生 raise。最小复现和四条 +被实测否掉的便宜修法见 `docs/aot/aot-gaps-and-lkrt.md` §23。 + +**这条已经证明不能在格上绕过。** 试过把"元素类型只是猜的空 `[]`"在进 try 区域时 +物化成 Dyn 列表:`ss` 修好了,`xs` 反而坏了 —— 新的 `ListDyn` 观测和原有的 +`ListI64` 在同一个被调方上 join 成 `Dyn`,于是**两个**调用点都开始装箱。换一个程 +序坏而已。 + +所以唯一的修法是把重建去掉:给 ListI64/F64/Str 各一个 tag(接在 `DYN_SLICE = 15` +之后),**原地打标签装箱**,照 `DYN_TMAP_BASE` 那次做。已知难点:VM 的 +`TypedList` 在插入不合型元素时会拓宽成 `Mixed`,而原生的 `Vec` 不能就地变身 +—— 这条语义必须逐字镜像,不能近似。 + +## 常量折叠只是**捷径**,不是第二门语言(2026-07-31 裁决) + +折叠跑在类型检查器**之前**。所以折叠器答得跟执行器不一样的每一条,都是任何诊断 +都够不到的一条 —— 检查器根本没见过那棵子树。两种走偏方式,都修了: + +**一、它实现了语言里没有的运算。** 类型检查器专门删掉了字符串重复,报错还点名了 +真正存在的写法: + +``` +Type Error: `*` does not repeat a string — write `text.repeat(count)` +``` + +而折叠器仍然实现着它。于是一个程序碰到哪条规则,取决于计数是不是字面量: + +```lk +let a = "ha" * 3; // 折出 "hahaha" +let n = 3; +let b = "ha" * n; // Type Error: `*` does not repeat a string +``` + +三份文档(`examples/syntax/unsupported.lk`、中英两份 LEARN)因此一直宣称这个特性 +可用 —— 它们能通过,靠的正是这条本该不存在的折叠。**检查器的规则就是语言的规则**, +折叠里的那条是被删特性的残留。 + +**二、它的整数运算用裸算符。** 两个执行器的 Int 运算都是回绕(`i64::MAX + 1` 给 +`i64::MIN`,`i64::MIN % -1` 给 `0`),而 Rust 的 `a + b` 在 debug 构建里 **panic**、 +在 release 里回绕。于是源码里写一个 `9223372036854775807 + 1`,解析器要么崩: + +``` +thread 'main' panicked at core/src/expr/expr_impl.rs:775: attempt to add with overflow +``` + +要么碰巧折对 —— 取决于 `lk` 自己是用哪个 profile 编的。现在一律 `wrapping_*`,把 +规则写出来。 + +**三、`a ?? b` 折成 `a`,把 `b` 从检查器眼前删了。** `??` 要求两侧能 unify,所以丢 +掉一侧就是丢掉那条类型错误: + +```lk +let a = 7 ?? "ab"; // 折成 7 +let b = maybe_int() ?? "ab"; // Cannot unify Int with String +``` + +把折叠器和运行时按「14 个运算符 × 5 种字面量类型」做全矩阵差分,**14 处不一致全部 +是这一条**。现在只折 `nil ?? e`(它丢掉的只有字面量 `nil`);`7 ?? 0` 少折一次的代 +价,是运行时多走一个分支 —— 而这种写法没人写。 + +三条是同一件事:**折叠器在替类型系统做决定,而它没有类型系统。** + +`constant_folding_answers_what_the_executors_answer` 钉住三条。 + +## `lk check` 答的必须是执行器答的那个问题(2026-08-01 裁决) + +已经有一条裁决说 `lk check` 不能**放过**跑不起来的程序(trait 必需方法那条)。反 +向同样成立,而这一边一直是错的:`lk check` 用 `TypeChecker::new_strict()`,两个执 +行器都用 `TypeChecker::new()`。于是 + +```lk +fn process_list(xs) { + return xs.filter(|x| x > 3).map(|x| x * x).reduce(0, |a, b| a + b); +} +``` + +- `lk examples/syntax/closure.lk` —— 跑通 +- `lk compile examples/syntax/closure.lk` —— 编出原生可执行文件 +- `lk check examples/syntax/closure.lk` —— **Type Error: infers implicit Any** + +语言自带的 4 个 example 被"跑之前先检查"这条命令拒了,而它们是能跑的。未标注的 +形参**是**这门语言收下的写法,所以严格性是一条 rigor 政策,不是"这程序能不能跑" +的答案 —— 政策不能当默认答案。 + +现在:`lk check` 默认与执行器逐字相同;`lk check --strict` 保留那条 lint。 + +顺带记一个探这条时的岔路:`VmContext::with_type_checker(Some(TypeChecker::new_strict()))` +在 CLI、REPL、wasm 三处都写着,读起来像"运行也是严格的" —— **不是**。 +`Program::execute_with_ctx_from` 给程序的类型检查另建了一个 `TypeChecker::new()`, +上下文里那个只用来登记模块的 trait/impl 表。这三处的 `new_strict()` 就严格性而言 +是句空话。(先别删:那个字段本身有人写、有人读,只是没人读它的严格位 —— +`get_type_checker_mut` 至今零调用点,值得单独查。) + +## REPL 回显什么,不能由"这串输入碰巧是不是合法语句"决定(2026-08-01 裁决) + +REPL 的契约是"敲一个东西,看见它的值"。而它此前是**先试语句解析、失败了才回退到 +把输入包成 `return (…)`**。绝大多数表达式需要分号才算语句,所以它们落到回退路径、 +回显了;而所有**自成语句**的东西,值算出来就丢: + +``` +> [1, 2, 3] [1,2,3] +> x + 1 2 +> if true { 1 } else { 2 } (什么也没有) +> S { x: 8 } (什么也没有) +> match n { 1 => "one", _ => "" } (什么也没有) +``` + +回显的那一半是**语法上碰巧**的,不是规矩。 + +现在:**先试表达式**。整串输入是一个表达式就当表达式求值并回显,否则当程序跑。包 +装是 `return (…)`,所以带分号的、`let`、声明、多条语句都不会被当成表达式 —— 这也 +正是 `x + 1;` 用尾分号压掉自己回显的机制。 + +`cli/tests/repl_echo_test.rs` 把三组都钉住:此前静默的、此前正常的、以及 +`println("x")` 只印一次(不能又印又回显 nil)。 + +**"这行输完了吗"也不能数字符。** 同一个文件里,`should_continue_multiline` 逐字符 +数 `(`/`{`/`[`,分不清括号和字符串/注释**里**的括号: + +``` +> let s = "("; +> s +Error: Syntax error: Unexpected tokens at end (found Let) at 2:1-2 +``` + +会话在等一个从没缺过的 `)`,把下一行吞进同一段输入,然后怪那一行。`// (` 结尾同 +理。改成问**词法器** —— 定义什么是字符串、什么是注释的正是它。词法不过的输入(比 +如没闭合的引号)不算"继续等",那是解析器该报的话:等下去会让任何一个手误挂死会话。 + +顺带删掉同文件里的 `normalize_binary_signs`(约 70 行):它在解析前把 `a+1` 重写成 +`a+ 1`,给一个**并不存在**的词法行为打补丁。带/不带做了 15 条输入的逐条对照 +(`a-1`、`a--1`、`-a`、`[1,2][0]-1`、`"a-1 ${a-1}"` 等),输出逐字相同。它只有钉 +住"它做了什么"的测试,没有一句说明"为什么需要它"。 + +## `in` 里的堆值一律按句柄比 —— 每一种载体,不是当时有标签的那两种(2026-08-01 裁决) + +```lk +let b = "ab".bytes(); +let xs = [b]; +b in xs // 解释执行 true,编译执行 false +``` + +`Set`、`Bytes`、窗口、类型化 map 四种载体都是这样:**永远不在任何列表里**。原因是 +`contains_eq` 的末尾是 `_ => false`,而它写下时的标签空间只有 `DYN_LIST` 和 +`DYN_MAP`。后来 `DYN_SET`、`DYN_BYTES`、`DYN_SLICE`、`DYN_TMAP_BASE..` 依次加进 +来,这个 match 没人回来看 —— 新标签**静默地**掉进了"跟谁都不相等"那一格。 + +这四种都是**原地装箱**(标签是唯一变的东西),所以它们的 payload 就是 VM 拿来比的 +那个句柄,上面那条 `payload ==` 本来就是对它们的正确答案。补上即可,不需要新规矩。 + +`DYN_RAW` 不在内:它停放的是一个**不是值**的句柄,把它当值读是设计上的响亮失败。 + +`==` 那条没有同样的洞(六种载体实测全同)。剩下的 `l in [l]`(List 放进 List)仍然 +两边不一致,那是类型化列表装箱重建那条,记在上面。 + +`every_heap_carrier_is_found_by_handle` 钉住四种载体各自找得到自己、且不同句柄仍然 +找不到。 + +**同一形状在这个运行时里是第三次了**,所以按判据把所有对 tag 的 catch-all 扫了一 +遍。`raise` 结尾的那些是响亮失败(可接受);**返回值**的那几个里又有一处: +`json.stringify` 的 `_ => Err("value has no JSON form")` 吞了 `DYN_SLICE`。窗口在 +VM 里就是个列表,`json.stringify([xs.slice(0,2)])` 那边给 `[[1,2]]`,这边报"没有 +JSON 形式"。`DYN_SLICE` 同样是后加进标签空间的。 + +三次的名单,留给下一个往标签空间里加东西的人:降低侧的 `container_ty`、`in` 的 +`contains_eq`、JSON 的 `to_serde`。加一个 `DYN_*` 就要走一遍这三处 —— 它们都不是 +穷尽匹配,编译器不会提醒。 + +## opcode 判别式必须连续 —— 一个洞值 9%(2026-08-05 裁决) + +删掉 `LoadNative`(一个任何生产路径都没发射过的 opcode)之后,工作负载几何均值从 +0.99 掉到 1.08。三次量在删除侧:1.075 / 1.086 / 1.089;HEAD 侧:0.991 / 0.986。 +不是噪声。 + +二分到最小改动 —— **只**删 `Opcode` 变体和它的分发臂,`Module.natives`、公开 API、 +artifact 检查全部留着 —— 仍然是 1.077 / 1.087。再把 72 号后面的 opcode 依次前移填 +洞,回到 0.994 / 0.987。 + +所以代价是**洞**,不是少一条臂:dispatch 那个 `match` 只在判别式稠密时降低成跳转 +表,一个缺口就够它退化。 + +这一点此前没有任何东西在守 —— 下一次删 opcode 会照付 9%,而且没有测试会红、审阅 +的人也看不出为什么。`opcodes_are_contiguous` 现在钉住它,而且钉的是**三个**从字节 +解码的枚举(`Opcode` / `InstrFormat` / `CastTarget`)—— 只守被量到的那一个,守的是 +这次事故而不是那条性质。 + +**断言的形式改过一次,因为第一版守错了东西。** 三个解码函数都是手写的字面量 +`match`,和判别式无关 —— 只断言"解得出的字节是连续的",在 `Sj = 40` 配 +`4 => Some(Self::Sj)` 时照样通过:解码连续、枚举稀疏、跳转表没了。现在断言的是 +**往返**:每个解得出的字节 `v`,`decode(v) as u8` 必须等于 `v`。四种破坏方式都反向 +验过(三个枚举各自"只改判别式",以及"只改解码臂"),全部变红。 + +重排编号会改变 artifact 编码,所以连带 bump `MODULE_ARTIFACT_VERSION`(16 -> 17)。 + +## stdlib 的第一个参数是主体 —— regex 是最后一个例外(2026-08-05 裁决) + +`string` 的第一个参数叫 `text`,`bytes` 叫 `value`,`encoding` 叫 `source`,`hash` +叫 `data`,`path` 叫 `path`。`regex` 六个成员全部是 `(pattern, text)`。 + +这不是风格问题,因为两个模块里有同一个操作: + +``` +string.split(text, separator) +regex.split(pattern, text) // 改前 +``` + +同一个操作,参数颠倒,两个参数都是 `String`。写反了类型检查看不出来,运行也不报 +错 —— `regex.is_match("a1b2", "[0-9]")` 答 `false`,`regex.replace("a1b2", +"[0-9]", "#")` 原样返回 `"[0-9]"`。第一次探这个模块的十二个用例全部像是模块坏了, +实际上是调用顺序反了。 + +`replace` 上此前有一条注释承认了这一点,并用 `named(text, replacement)` 让调用方 +给参数贴标签绕过去 —— 在错的顺序上打补丁,而不是改顺序。 + +现在六个成员都是主体在前,其余参数一律 named-eligible,与 +`string.replace(text, pattern, with, all)` / `named(pattern, with, all)` 同形。 +`lkrt` 侧的六个 extern 函数、AOT 降低表的 `named` 拷贝和 `leading` 起点同步改; +`lowering_named_parameter_lists_match_the_stdlib_declaration` 守着后两者与声明一致。 + +**探过一条更强的规则,不成立:**"同类型的参数必须可命名"。全 catalog 有 32 个成员 +不满足它(`string.contains(text, needle)`、`path.with_extension(path, ext)` …), +所以它是新造的判据,不是仓库现有的约定 —— 对这些成员,"主体在前"本身就定了序。 + +**成立的是更窄的一条:参数是对等项、没有主体来定序时,调用方必须能贴标签。** +`fs.copy(a, b)` 哪个是源、`math.atan2(y, x)` 哪个是 y、`random.int(min, max)` +哪个是上界,约定回答不了,交换后两边都跑得动且答案不同。按这条筛出十个成员, +第二个参数改为 named-eligible:`bytes.concat`、`fs.copy`、`fs.rename`、 +`iter.chain`、`iter.zip`、`math.atan2`、`math.pow`、`random.int`、`stream.chain`、 +`time.since`。`math.hypot` / `min` / `max` 也是对等项,但它们对称,交换无影响, +不在此列。 + +这次同时补上了守卫的另一半。`lowering_named_parameter_lists_match_the_stdlib_declaration` +只走降低表里**已经有名字**的行,所以"声明加了 `named(...)`、表里没加"它看不见 —— +而那种情况是静默的:`CallNamed` 找不到名字,命名拼写停止降低,整程序回落到 VM, +答案照样对。`every_declared_named_list_reaches_the_lowering_table` 补的就是这个方向, +反向验过:十个成员加完声明、表还没改时,它报出正好那五个有降低行的成员。 + +## 装箱的值必须**每一种读法**都认得两种表示(2026-08-05 裁决) + +`#118` 把类型化 map 改成原地打标签,理由写在 `DYN_RAW` 上:装箱不能重新表示 +容器。做对了,但只教会了三个消费点 —— `len`、显示、相等。 + +其余读法仍然先解箱。`dyn.as_map` 的答案是一个 `str_dyn` 句柄,而六种载体里只有 +一种是 `str_dyn`,所以下面这些在 `LK_AOT_NO_FALLBACK=1` 下**编译成完整原生**, +运行时 raise `runtime type error`,而 VM 全都答得出: + +| 形状 | 改前 | 改后 | +| --- | --- | --- | +| `c[0]["a"]` | raise | 答 | +| `c[0][3]`(整数键 map) | raise | 答 | +| `c[0].keys()` / `.values()` | raise | 答 | +| `c[0].has(k)` | raise | 答 | +| `c[0].delete(k)` | 编译失败 | 答,且原地删 | +| `for k in c[0]` | raise | 答 | +| `for x in [Set/Bytes/Str][0]` | raise | 答 | +| `"a" in c[0]` | 编译失败 | 答 | + +**分发按操作,不按解箱。** 让 `dyn.as_map` 在遇到类型化标签时物化一份 +`str_dyn` 返回,能答出上表里的四条读,然后**静默丢掉 `delete` 的写** —— 用错 +答案换编译通过。所以每个操作各有一个 Dyn 层入口(`dyn.map_keys` / +`map_values` / `map_has` / `map_delete` / `map_pairs`),在运行时按标签分派到 +载体自己的访问器,顺序也就是载体自己的顺序。 + +`for-in` 同理:它发的是 `dyn.as_list`,一个**列表**守卫,所以装箱的 map、Set、 +Bytes、字符串在循环里全部 raise。VM 的 `to_iter` 不是"解出一个列表",是"这个值 +按什么迭代",`dyn.to_iter` 现在照着写。 + +`in` 是第三处同形的:降低侧**根本没有 `Dyn` 干草堆这一臂**,所以整程序回落。 +`dyn.contains` 按标签分派 —— map 测键(存了 nil 的键也算,所以不能用 get 再判 +标签),其余载体测元素。 + +顺着这条查出 `in` 还漏了两种载体:`Bytes` 和窗口。两者都能索引、都有 `len`、 +都能 `for`,`Bytes` 连 `contains` 方法都有 —— `in` 是唯一不把它们当容器的地方, +而且检查器、VM、降低**三处都缺**(所以不是放宽检查就完事)。`Bytes` 里放不下的 +针值(`300`、`-1`、非整数)答 `false` 不报错,与"Int 列表里找字符串"同规矩。 + +连带删掉 `MethodRow::unbox_map` 整列:没有哪个名字再需要"先解箱成 map"了。 + +整数键那条单列一句,因为它不只是缺一个分支:`{3: 4}[3]` 是 **4**,而没有第 3 个 +元素 —— 整数键落在 map 上是键不是位置。常量整数键直接降低到 `dyn.index`,不经过 +`dyn.get`,所以规则写在实际到达的那一层。 + +## 类型化 list 装箱也是原地打标签(2026-08-05 裁决) + +`#118` 对类型化 map 立的规矩,同样适用于 list,而这里丢的不只是顺序 —— 是两个 +方向的别名: + +``` +let xs = [1]; +let c = [xs]; +xs.push(2); println(c[0].len()); // VM 2,native 1 +c[0].push(9); println(xs); // VM [1,2,9],native [1,2] +``` + +两条都**完整编译成原生**(`LK_AOT_NO_FALLBACK=1` 通过)然后答错。原因是装箱走 +`list_h.*_to_dyn`:逐元素重建成 `Vec`,那是另一个列表。 + +`DYN_TLIST_BASE..DYN_TLIST_END` 三个标签,一个载体一个,`dyn.from_typed_list` +原地打标签。消费点全部改为认两种表示:类型名、转整数的报错、`+` 拼接、与窗口的 +比较、跨表示的列表相等、显示、`len`(直接数载体,不装箱)、索引、`in` 的按句柄 +比较、`flatten`、`to_iter`、JSON 序列化、通道深拷贝。 + +**`dyn.as_list` 是只读的,这条得写下来。** `DYN_LIST` 交回自己的句柄,写得进去; +类型化载体必须物化一份元素,写不进去。两者不能都从这一个口出去。到得了这个守卫 +的名字 —— `map` / `filter` / `reduce` / `take` / `skip` / `concat` / `unique` / +`sort` / `reverse` —— 全部构造新列表,不动接收者(这门语言里 `sort` 和 `reverse` +返回新列表,不是原地排)。唯一的写是 `push`,它走 `dyn.list_push`,直接到载体。 + +规则靠 `no_unbox_list_name_mutates_its_receiver` 钉住:给一个会写的名字加上 +`unbox_list`,写就会在这个守卫里被静默丢掉。 + +`sort()` 在装箱接收者上仍然回落,与本次改动无关,单列(见任务表)。 + +### 附带裁到的一条:检查器提升过的 push,VM 得把提升物化 + +补完标签之后探到的,而且**不在装箱路径上** —— 未装箱的同一形状早就答错: + +``` +let xs = [1.5, 2.5]; +xs.push(9); +println(typeof(xs[2])); // 改前 VM: Int,native: Float +``` + +完整原生编译,两边都不报错。检查器按数值提升放行了这次 push(`Int` 可以给 +`Float`),也就是承诺了元素类型;VM 转头把 `TypedList::Float` 拓宽成 `Mixed` 并把 +`9` 原样存成 `Int`。原生存 `9.0`,与承诺一致。 + +**问题在 VM 侧**:它推翻了自己刚做出的接受。`Float` 载体收到 `Int` 现在存 +`value as f64`。反向不对称,保持原样:`Float` 进 `Int` 列表是收窄,检查器会拒, +所以只能从被擦除的类型到达,那里拓宽就是动态语义。 + +`xs.push("a")` 这类**真正**的拓宽(类型被 `List` / `Any` 擦掉)当时两边还不同:VM +拓宽成 Mixed,原生 raise。原生的 `Vec` 没法就地变成 `Vec` —— 别的别名 +按静态类型直接读这块内存。未装箱路径在那里回落,装箱路径 raise;两者都不是错答案。 +**已裁决并实现,见下文"载体在构造点决定"**。 + +**2026-08-05 更正**:上一段"别的别名按静态类型读这块内存"这条判据,在容器改成不变 +之后不再成立 —— 不变意味着同一个值在每个名字上的元素类型相同,拓宽因此对所有名字 +同时可见。剩下的问题不是别名,而是**载体在构造时就定了**:`let xs: List = [1,2];` +的载体按内容推成 Int,后面 `xs.push("a")` 时 VM 就地拓宽,而原生没有这一步。可判定的 +形状是"程序里有一次放入更宽元素",那时就该从构造起用 Dyn 载体。实测(2026-08-05) +这条形状在原生侧是干净回落,不是错答案: + + let xs: List = [1, 2]; + xs.push("a"); + → MIR lowering: an operand at pc 3 is a str where a i64 is required + +## 结构体字面量命名的是一个**已声明**的类型(2026-08-06 裁决) + + let p = Nope { a: 1 }; + 改前 → Nope{a:1} 改后 → 类型错误 + +检查器那句注释就是当时的全部规矩:"If struct is known, enforce field presence and +types; **otherwise, accept as named type**"。于是拼错一个类型名不报错,答一个值。 + +这条同时是另一个形状的一半:从别的模块导入的类型,写成裸 `P { x: 4 }` 时构造出来的是 +**残缺的** P —— + +| 构造写法 | typeof | impl 方法 | +| --- | --- | --- | +| 裸 `P { x: 4 }`(只有 `use "geo"`) | `P` | **没有** | +| 裸 `P { x: 4 }`(有 `use { P } from "geo"`) | `P` | 有 | +| `geo.P { x: 4 }` | `P` | 有 | +| 模块导出的构造函数 | `P` | 有 | + +第二行是 2026-08-06 补上的;第一行现在是**拒绝**,不再是那个残缺的值。 +`use { P as Q } from "geo"` 也算第二行:别名换的是这个文件里的写法,不是类型 —— +`Q { x: 4 }` 按 `P` 的 schema 校验,报错说 `struct 'P'`,`typeof` 答 `P`。 + +根因是 `NewObject` 盖的类型出身是**当前执行模块**的 `TypeScope`,而方法表按定义方的 +scope 建 —— 两个身份,名字碰巧一样。用户在调用点看到 "P has no method 'norm'",离构造 +处很远。 + +两半现在都拒了:名字在本模块没有声明是一种错,名字**只**因为别的模块声明而已知是另一种, +消息各说各的原因。注册表因此记来源(`register_struct` 是本模块声明的, +`register_imported_struct` 是 `seed_imports` 带进来的);本地声明会把标记清掉,所以 +本模块声明一个同名类型照旧盖过导入的那个。 + +`m.P { … }` 与模块导出的构造函数两条路不受影响 —— 它们本来就带着定义方的身份。 + +**另一半已经补上(2026-08-06)**:`use { P } from "geo"` 现在绑定的是定义方生成的 +`P$new`,所以裸 `P { x: 4 }` 与 `P(x: 4)` 都成立,与 `geo.P { x: 4 }` 同一个身份。 +改写放在编译器侧:本地 `struct S` 一定带来本地 `S$new`,所以"没有本地 `P$new`、 +却有一个叫 `P` 的全局"恰好是导入这一种情形,降到对那个全局的具名调用,别的情形 +照旧发 `NewObject` —— 本地构造这条热路径一条指令没动,不必押在内联器上。上面那张 +表里"裸 `P { x: 4 }`"因此有了第四种情形:按名导入时有方法,只通过命名空间看见时 +仍然是拒绝(不是残缺的值)。细节见 §导入的类型可以构造。 + +## 时长不能是负数,四个入口一条规矩(2026-08-05 裁决) + + use time; + println("before"); + time.sleep(-1); // 打印 before 之后再不返回 + +`Duration::from_millis(duration_ms as u64)`,`-1 as u64` 是 u64::MAX 毫秒 ≈ 5.8 亿年。 + +同一个操作四个答案: + +| 写法 | `-1` | `-0.5` | +| --- | --- | --- | +| `time.sleep` | **挂起** | 立即返回 | +| `task.sleep` | 报错 | 立即返回 | +| `time.timeout` / `time.after` | 定时器永不触发,静默 | 同上 | + +`-0.5` 那一列是关键:`task.sleep` **有**一个 `< 0` 守卫,但它跑在 `as i64` 之后 —— +截断已经把值变成 `0` 了。所以判负必须在截断**之前**。 + +现在:`lk_stdlib_common::duration_millis` 一处实现,四个入口共用,消息一句 +(`{name}() expects a non-negative duration in milliseconds, got {ms}`),lkrt 侧 +`lkrt_time_sleep_ms` 的措辞对齐 —— 被捕获的错误消息就是程序的输出,两端必须逐字一样。 + +**时刻不是时长。** `time.since(start, end)` 取的是钟面上的两个点,差值才有方向,所以 +它留用 `numeric_millis`(任意符号),与 `duration_millis` 是两个概念。 + +门禁:`a_duration_cannot_be_negative`。 + +## 模块拼写需要 import,两端都要(2026-08-05 裁决) + + let c = chan.new(1); // 没写 use chan; + vm → Error: index target object is not indexable: "Function" + 原生 → 7 + +`chan` 是唯一一个既是模块名、又是裸全局(通道构造函数)的名字。`math` / `string` / +`time` 这些不是全局,检查期就报 "undefined name",两端一致;`chan` 会走到运行时。 + +两端跑的是**同一份字节码**,加不加 `use chan;` 一个字都不差(`GetGlobal chan` / +`LoadString "new"` / `GetIndex`)。差别在运行时:import 把全局 `chan` 从构造函数换成模块 +对象,VM 的 `GetIndex` 这才成立。 + +原生侧原来无条件把这个形状解析成模块成员,注释里写着"字节码能区分二者"—— 那句对 VM +的行为判错了。现在它读 `sig.imports`:**import 才是模块拼写的许可**。 + +根因是 `ImportEnv::build` 里 `ImportStmt::Module { .. } => {}` 一个空分支 —— `use math;` +这种最常见的写法在原生侧被整个丢掉,所以降低器分不清"导入的模块"和"碰巧同名的全局"。 + +不动的一格:`chan(1)`(裸构造函数)无 import 可用,`use chan;` 之后 `chan(1)` 报 +"`chan` names the imported module here, and a module is not a function" —— 两端都对。 + +门禁:`differential_concurrency_edges` 的 `the module spelling after its import`。 + +## `format` 与 `println` 是同一个操作(2026-08-05 记录) + +占位符是 `{}`,按顺序取实参;**多出来的占位符原样留下,多出来的实参空格分隔追加在 +后面**: + +``` +"{}-{}".format("a", 1) → a-1 +"{}-{}".format("a") → a-{} +"{}".format("a", "b") → a b +"plain".format("a") → plain a +println("{} x", 1) → 1 x +println("a", 1, true) → a 1 true +``` + +这条规矩两边一致,而且"多余实参追加"正是 `println(a, b, c)` 这个主拼写赖以工作的 +东西 —— 它不是漏检。 + +代价是写惯 `%s`(C)或 `{0}`(Python)的人拿到的是一串拼接结果而不是报错: +`"%s-%d".format("a", 1)` 答 `%s-%d a 1`。**这不是缺陷**,是同一条规矩作用在一个没有 +占位符的模板上。编号占位 `{0}` / 具名占位 `{name}` 不支持,是特性缺口,不是错答案。 + +不改成"个数不匹配就报错"的原因:那会同时否掉 `println("a", 1)`(没有占位符、两个 +实参)和 `println("{} {}", 1)`(占位符多于实参),而这两条都是被明确写下来的行为; +核心检查器又没有 warning 通道(见"catch-all 后面的臂"那条),只能响亮拒绝或者不说话。 + +## 每一种赋值都按目标声明的类型校验(2026-08-05 裁决) + +除了普通 `name = value`,**没有任何赋值目标被检查过**: + +``` +let l: List = [1]; +l[0] = "a"; +let n: Int = l[0]; +println(n + 1); // 打印 a1 +``` + +同一形状在四个目标上都成立,而方法拼写是拒的:`l.set(0, "a")` 报 +"Argument 2 has the wrong type (expected Int)"。`l.set(i, v)` 和 `l[i] = v` 是同一个 +操作的两种拼写,只有一种被检查;结构体字段那格最重 —— 字段是这门语言里声明得最多的 +东西,而 `s.x = "a"` 之后 `let n: Int = s.x` 照样过。 + +根在解析期的降解:三种写法各自变成一个不同的隐藏调用,谁也没有校验值。 + +| 写法 | 降解成 | +| --- | --- | +| `l[0] = v`(下标是整数字面量) | `list.set(l, 0, v)` —— 字节码编译器认的类型化列表快路 | +| `l[i] = v` / `m[k] = v` | `__lk_set_index(c, k, v)` | +| `s.f = v` / `m.f = v` | `__lk_set_field(c, "f", v)` | + +现在:三处都调同一个 `check_container_store` —— 三种降解,一条规则。它按容器类型取出 +声明的元素/值类型(结构体则取字段声明的类型)与被存的值比;map 还比键的声明类型 +(`Map` 上的 `m[7] = 2` 此前收下,map 里就出现了 Int 键)。 + +载体是四个,不是三个:异质字面量推成 `Tuple`,它的每个位置类型不同,所以按位置校验 +—— 下标是整数字面量就比那一位;下标不是字面量时位置未知,值必须适配**每一位**(读 +`l[0]` 是按第 0 位定的类型,动态写落在那里不能把它推翻)。`let l = [1, "a"]; +l[0] = 2.5;` 此前收下,`let n: Int = l[0]` 随后拿到 2.5。 + +两条边界照旧:元素类型还是类型变量时**教**它而不是拒它(`let l = []; l.push(1); +l[0] = "a";` 仍然可以,与实参那条同规矩);`Any` 两侧都放行,那是这门语言的动态逃生口。 + +门禁:`a_store_is_checked_against_what_the_container_declares`,十种拒 + 六种收。 + +这条修完,"元素类型什么时候是承诺"这张表就一致了: + +| 写法 | 后续放入不同类型 | +| --- | --- | +| `let l = []` / `let m = {}` | 收 —— 元素类型还是变量,存进去是教它 | +| `let l = [1]` / `let m = {"k": 1}` | 拒 —— 字面量说了它装什么 | +| `let l = [1, "a"]` | 按位置拒 —— `Tuple`,每位各有类型 | +| `let l: List = …` | 拒 | +| `let l: List = …` | 收 | + +此前只有 `let m = {"k": 1}` 那格"收",而它收的原因不是拓宽,是压根没检查。 + +## 可变容器不再是协变的(2026-08-05 记录、裁决、实现) + +`values/src/types.rs` 的 `is_assignable_to` 里写着 `// Generic containers with +covariant element types`,是有意为之。实测它可以推翻类型系统自己的保证: + +``` +fn add_any(xs: List, v: Any) { xs.push(v); } +let a: List = [1, 2]; +add_any(a, "s"); +let b: Int = a[2]; // 过检查 +println(b); // 打印 s +``` + +`lk check` 全过。四种拼写都通:形参写 `List` 或 `List` 都接受 `List`; +`Map` 传给 `Map` 后写入,`let b: Int = m["k"]` 同样过检查并拿到字符串。 + +**洞在每一个拓宽位置,不止调用参数**(2026-08-05 实测,五种写法都答 `s`): + +``` +let b: List = a; // 裸别名 +let b: List = a; // Any 别名 +let bx = Box { xs: a }; // struct 字段,字段声明 List +let holder: List = [a]; // 容器元素 +fn widen(xs: List) -> List { return xs; } // 返回类型拓宽 +``` + +**它也是"拓宽"那条的根**:VM 之所以要把 `TypedList` 拓宽成 `Mixed`,正是因为检查器 +放进来了它本不该放的元素。 + +### 为什么还没改 + +四条路都量过或试过: + +| 路 | 代价 | +| --- | --- | +| 元素类型完全不变(Rust 的选择) | 这门语言**没有泛型函数**(`fn first(...)` 语法错误),所以写不出"对任意元素类型的列表"的签名。stdlib 里 15 个 `params(values: List)` 会全部拒绝类型化列表。 | +| 裸 `List`/`Map`/`Set` 改成只读视图,带参数的不变 | LK 代码里裸拼写用了 **0 处**,爆炸半径只在 stdlib 签名。但要求 `List` 与 `List` 是两个类型,`Type::List(Box)` 装不下,得加变体并改所有匹配点。 | +| ~~形参可变性推断驱动的按参数型变~~ | **不成立**:它只封住调用参数一处,上面另外四种拼写照旧。 | +| 运行时存储检查(Java 数组的做法) | 需要列表记住**声明的**元素类型;今天的载体是从内容推出来的,`let xs: List = [1,2]` 的载体是 Int,照这个检查会误拒。 | + +没有一条是小改动,而选错会把不健全换成另一种不健全。 + +### 裁决:容器不变,加一个"元素类型不详"的元素类型 + +先把第二条路的代价量出来,而不是估。把 `is_assignable_to` 的三条容器规则临时改成 +逐元素相等,跑 `lk-core` 的 1221 个测试和 64 个示例: + +| 量 | 结果 | +| --- | --- | +| lk-core 测试 | 1215 过 / 6 失败 | +| 示例语料 | 56 过 / 8 失败 | +| 失败的类别 | 两类,没有第三类 | + +两类是: + +1. **stdlib / builtin 签名里的裸 `List`**(报 "expected List, got List")。 + 8 个失败示例里没有一处是用户写的 `List` 注解 —— 全部来自签名。 +2. **空字面量的推断**:`let xs: List = [];` 里 `[]` 是 `List<'T0>`。这是合一, + 不是型变,逐元素相等把它一起挡了。 + +于是规则是: + +- 容器**不变**(`List` 不可赋给 `List` / `List` / `List`)。 +- 元素里出现类型变量时照旧合一 —— 那是推断,不是型变。 +- 新增元素类型 `_`(`Type::Unknown`),在类型参数位置书写:`List<_>`、`Map`。 + 它的含义是"元素类型不详":**没有任何类型可以赋给它**,所以以它为参数的容器可读 + 不可写;而任何 `List` 都可赋给 `List<_>`。只读性由类型本身推出,不是额外规矩。 + `_` 在模式里已经就是"不具名的任意",不引入新概念。 +- 裸 `List` 仍然是 `List`(可写、不变),所以 `returns = List` 一处不动。 +- 签名侧改的是**参数**:stdlib 24 个容器参数 + `builtin_method_sig.rs` 5 个,全部 + 改成 `List<_>` 之类。这 29 个都只读 —— 唯一可能就地改的 `random.shuffle` 实测是 + 拷出来再分配新列表。门禁:stdlib 的容器参数不许是可写容器。 + +`_` 只可赋给 `Any`,所以 `let n: Int = xs[0]`(`xs: List<_>`)是类型错误,要经过显式 +的 `Any` 一跳 —— 与这门语言已有的动态逃生口一致。 + +### 字面量是新值,所以按协变判 + +不变性会把 `let xs: List = [1, 2];` 也挡掉,而那里没有不健全:一个容器**字面量** +没有第二个名字,所以没有东西会因为它被拓宽而看错元素类型。规则因此是 +`Type::container_literal_fits`,只在实参表达式确实是容器字面量时走协变,与整数字面量 +那条(`let x: u8 = 5` 而不是 `5 as u8`)同形同因,用在同样三个位置:带注解的 `let`、 +位置实参、命名实参。经过变量就没有这条路 —— `let a: List = [1,2]; let b: List = a;` +仍然拒绝。 + +`Any` 作为**来源**元素类型照旧流动(`List` 可赋给 `List`),那是这门语言的 +动态逃生口;被封的是 `Any` 作为**目标**元素类型,也就是拓宽的那个方向。 + +门禁:`a_container_cannot_be_widened_at_its_element_type` 逐个探七种拓宽写法,并正向 +验字面量、`List<_>` 只读视图两侧。 + +**别名还是拷贝,这个问题必须先答。** 如果拓宽产生的是拷贝,这条就自动消失(写进宽 +别名不影响窄的那个)。但 LK 的容器是引用值 —— 被调方 `xs.push(x)` 对调用方可见正是 +#170 认定"装箱重建是缺陷"的依据 —— 所以拓宽是别名,只能从类型上封。 + +**别名还是拷贝,这个问题必须先答。** 如果拓宽产生的是拷贝,这条就自动消失(写进宽 +别名不影响窄的那个)。但 LK 的容器是引用值 —— 被调方 `xs.push(x)` 对调用方可见正是 +#170 认定"装箱重建是缺陷"的依据 —— 所以拓宽是别名,只能从类型上封。 + +## 纯序列操作在每个序列载体上都可用(2026-08-05 裁决) + +四个序列载体 —— `List` / `Str` / `Bytes` / `Slice`(窗口)。从 +`builtin_method_sig.rs` 的声明表算差集,`Bytes` 和窗口已经有 `len`、`is_empty`、 +`first`、`last`、`get`、`contains`、`index_of`、`take`、`skip`、`slice`、`min`、 +`max`、`sum`、`map`、`filter`、`reduce` —— 列表读取面的每一个,唯独少两个: + +| | List | Str | Bytes | Slice | +| --- | --- | --- | --- | --- | +| `reverse` | 有 | 有 | **无** | **无** | +| `count` | **无** | 有 | **无** | **无** | + +`count` 那一行的后果具体是:`"aa".count("a")` 答 2,而 `[1, 1].count(1)` 报 +"List has no method 'count'"。 + +**规则:结果能用同一载体表示时答同载体,否则答 `List`。** `b.reverse()` 是 +`Bytes`;`w.reverse()` 是 `List`,因为反转后的那段不是源列表的一个区间 —— 与 +`w.map(..)` 已经在做的事同规矩,也与 `w.take(1)` 答窗口不矛盾(子区间还是区间)。 +`bytes_dispatch` 的文档注释本来就写着判据("含义不依赖元素类型的操作"),这两个 +正属于这一类,是漏了不是排除。 + +`index_of` 与 `count` 现在从**同一个扫描函数**出(`typed_list_scan`),因为规则 +才是内容:`Int` 元素等于 `Float` 针值(`1.0 == 1`),`Float` 列表按值比所以 +`0.0` 找得到 `-0.0`,`Mixed` 交给 `runtime_values_equal`。分开写就是同一个操作的 +两种拼写将来会各自漂移。 + +`Bytes` 里放不下的针值(`300`、`-1`)`count` 答 0,与 `contains` 给它的答案一致。 + +`sort` / `unique` 按同一规则:`Bytes` 上答 `Bytes`(字节是有序标量,每个元素仍是 +字节),窗口上答 `List`(两种答案都不是源的一个区间)。窗口的这两个路由到 +`typed_list_sorted` / `typed_list_unique`,不重写一遍 —— "排序的序"和"后来的重复 +被丢掉、顺序保留"是规则,规则抄第二份就是将来漂移。 + +`enumerate` / `zip` / `chain` / `chunk` 的答案是**元素的列表**,与载体无关,所以 +它们是 `List` 的:两个载体各加**一条**委派臂,把元素物化一次再走 `List` 的实现。 +六个方法各写两份就是把 `enumerate` 的配对、`chunk` 的分组各抄两遍。降低侧同形 —— +接收者是 `Bytes` 或窗口且方法在这一组时,先物化成 `ListI64` 再让已有的 List 臂跑。 + +`flatten` **不**在这一组:`Bytes` 和 `i64` 窗口装的是标量,展平是空操作,检查器 +拒得对。 + +`join` 是第三种情况:字节码编译器按名字把它匹配成融合的 `ListJoin`,所以没有叫 +`join` 的方法调用能到降低的方法分发。VM 侧和降低侧的载体臂都得加在那个 opcode 上, +而且 VM 侧把五个渲染分支抽成了 `join_typed_list` —— 列表、窗口、`Bytes` 三个接收者 +共用它。 + +`concat` 收尾,而且要分载体:窗口上它是 `chain` 的另一个名字,答 `List`;`Bytes` +上两个字节串接起来还是字节串,保形,有自己的臂。把它一并委派会让一个语言已经 +能用 `Bytes` 表示的形状答成 `List` —— 判据因此写成"按载体",不是一张名字表。 + +四个序列载体的矩阵到此对齐:`Str` 不在这批里是因为它的"元素"是字符,`sum` / +`min` / `max` / `chain` 在字符上没有意义,拼接用 `+`,要列表用 `chars()`。 + +## 一个 `Set` 得能做集合的事(2026-08-05 裁决) + +`Set` 的全部方法是 7 个:`add` / `clear` / `contains` / `delete` / `is_empty` / +`len` / `values`。`a.union(b)` 报 "Set has no method 'union'",`intersection` / +`difference` / `is_subset` 同。只能加、删、判成员、取列表的 `Set` 是去重的袋子。 +这门语言把它做进了内建类型、给了构造、显示、迭代和 `in`,唯独没有让它做集合运算。 + +补齐(Rust 的命名):`union` / `intersection` / `difference` / +`symmetric_difference` 答 `Set`,`is_subset` / `is_superset` / `is_disjoint` +答 `Bool`。 + +**插入序是契约。** 集合的迭代序就是它的哈希序(见 `DYN_SET` 与镜像纪律),所以 +成员相同的两个集合仍可能迭代出不同的顺序 —— 换句话说,"用另一种方式构造同一个 +答案"会通过任何成员测试,然后打印得不一样。所以每个运算都按**一个写定的顺序**填 +结果:接收者自己的顺序在先,实参的在后。原生侧逐字复现同一序列,差分用例把它钉住。 + +`is_disjoint` 不是 `intersection().is_empty()` 的展开:它在第一个共同成员处停下, +一次分配都没有。 + +**同时记一条不改的:** 成员判定在 `List` / `Str` / `Bytes` / `Slice` / `Set` 上 +叫 `contains`,在 `Map` 上叫 `has`。这不是"一个操作两个名字" —— `Map` 的 +`contains` 语义上真有歧义(键还是值),Rust 用 `contains_key` 正是为避开它;序列 +和集合的 `contains` 无歧义。分野保留。 + +## `-` 从容器里移除 —— 教程写着,VM 实现着,检查器不收(2026-08-05 裁决) + +`LEARN.md` 的表达式表里有一行: + +``` +[1, 2, 3] - [2] // [1, 3] +``` + +那一行**从来没被任何东西验过**。逐条跑那张表时它报 "the left operand must be +numeric types" —— 而擦掉类型就答 `[1,3]`,map 也一样(`m - n` 去掉 `n` 的每个键)。 +与 `Map + Map` 完全同形,而修那条时我只补了 `+`,没看旁边的 `-`。 + +`lkrt_dyn_sub` **也从来没实现过移除**,它的报错却写着 "expected numbers or +list/map lhs" —— 借了 VM 的判据来描述自己没有的能力。这条路径此前不可达,所以 +没人发现。 + +三处补齐:检查器的 `Sub` 分出 list / map 两臂(结果保持左侧的元素类型 —— 移除只 +拿走不加入,不会变宽)、lkrt 实现移除、降低侧接上。答案保持**左侧自己的顺序**, +理由同上。 + +## `Map + Map` 合并 —— 两个执行器一直都实现了,只有检查器不收(2026-08-05 裁决) + +``` +let a = {"a": 1}; +let b = {"b": 2}; +println(a + b); // 改前:Type Error: the left operand must be numeric types +``` + +而把类型擦掉就跑得动: + +``` +let a: Any = {"a": 1}; +let b: Any = {"b": 2}; +println(a + b); // {"a":1,"b":2} +``` + +VM 的 `Add` 有 map 分支(`merge_typed_maps`),`lkrt_dyn_add` 也有,注释里写着 +"两个 map 合并,右侧胜"。**只有检查器不收**,而且只在它知道类型的时候不收。 +这正是 `lk check 答的必须是执行器答的那个问题` 的反面 —— 检查器多出一条两个执行器 +都没有的规则,和它漏掉一条一样是缺陷。 + +结果类型按 `check_list_addition` 已有的判据:哪一侧被另一侧包含就取哪一侧,都不 +包含取 `Any`;键和值各判一次。判据抽成 `wider_of`,两处共用。 + +**没有一并放开的:** `Set + Set` 和 `Bytes + Bytes` 在运行时确实报错 +(`Add expected numbers or strings, got Set and Set`),所以检查器拒它们是对的。 +集合的并集现在是 `a.union(b)`,字节串拼接是 `b.concat(c)`。 + +### 原生这一侧:先修填充序列,再接上降低 + +合并建的是一张**新表**,而新表的迭代序由它被填充的顺序决定 —— 所以"同样的成员" +不等于同样的答案。VM 的序列是:左侧的条目按左侧自己的顺序、跳过右侧也有的键,然后 +右侧的条目按右侧自己的顺序(`merge_typed_maps` + `typed_map_without_merge_keys`)。 + +lkrt 那份把**两个无序视图合成第三个**,也就是三种不同的顺序;它还把键一律 +`key_str().to_string()`,整数键合并会变成字符串键。两条都不可达(降低侧没有 map +臂),所以一直没人发现,而那个函数的注释还写着"这是一张新表,没有在声称保留它从未 +有过的顺序"—— 顺序确实是新的,但它是一个中间哈希表的顺序,不是任何一侧的。 + +现在:`map_entries_ordered` 按载体自己的顺序读,`str_dyn_from_ordered` 按给定序列 +填,非字符串键**报错**而不是转成字符串。降低侧只接字符串键的 map(装箱的 map 载体 +就是字符串键的),整数键合并继续回落 —— 与其答 `{"3": 1}` 而 VM 答 `{3: 1}`。 + +## 报错里的载体表是内容,不是装饰(2026-08-05 裁决) + +`[1][5].len()` 答 "`len()` works on a String, List, Map or Set" —— 而同一个函数 +的堆分支收 `Bytes` 和窗口,两者都是 `len` 好好答得出的。`for x in 1 {}` 答 +"For loop iterable must be List, String, Map, or Set",而检查器那个 `match` 实际 +放行九种(还有 `Any`、类型变量、`Slice`、`Bytes`、`Tuple`)。 + +这类消息**就是规则本身**:读的人拿它当"这个操作能用在什么上"的说明,而它说错的 +方向恰好是让一个能跑的程序看起来不可能。载体是后来加的,消息停在了加之前。 + +四处一起改正:`len` / `Slice target` / `SliceFrom target` / `ToIter target`,加上 +检查器那条 for 循环的。 + +**守卫**:`a_carrier_list_in_an_error_message_matches_what_the_operation_accepts` +逐个载体跑一遍,再拿被拒绝的那个接收者取出消息,要求**恰好**互相对应 —— 收下的 +载体必须在消息里,消息里的必须收得下。两个方向都反向验过(把 `Bytes` 从消息里 +去掉、把 `Bytes` 从 `match` 臂里去掉,各自变红)。 + +**这条守卫上一轮漏掉了 `slice`,而我恰好在那里把消息改错了。** 当时的判断是"任何 +会被拒绝的接收者都先被方法分发答掉,所以消息不可达",而那只对 `c.slice(a, b)` 这个 +**方法**拼写成立;`c[a..b]` 这个**范围索引**拼写走的是另一条路,到得了 opcode。我按 +"应该支持"把消息扩成 `string/list/bytes/slice`,而那两个臂当时并不存在 —— 正是这条 +裁决要修的毛病,方向相反。 + +现在两件事一起做对: + +- `c[a..b]` 在 `Bytes` 上答 `Bytes`、在窗口上答子窗口,与 `c.slice(a, b)` 一致。 + 之前检查器答 "Bytes index must be integer",而同一个操作的方法拼写好好的。 +- 守卫加上 `c[0..1]` 这一行,消息与臂重新绑住,反向验过(去掉 `Bytes` 臂变红)。 + +降低侧顺带补齐:范围索引原先只降低 `Str` 和 `List`,`List` / +`List` / 混合列表 / `Bytes` 全部回落 —— 符号早就都在,那张表只列了两行。窗口 +仍回落(没有取子窗口的符号),是回落不是错答案。 + +## 维护约定 + +- 新增可下降形状时,先在此登记预期语义(尤其失败路径与显示格式),再写差分用例。 +- 当 VM 与 native 出现分歧:先查本表;表内未覆盖的,裁决后**新增条目 + 差分用例**, + 不允许只改一侧实现使测试变绿。 +- 退出机制(exit 1 vs SIGABRT)如未来需要统一,属于语言决策,需同时改本表、 + 差分 harness 的宽容逻辑(`success()` 对比)与 CLI 文档。 + +## 标量没有内建方法,而声明的可见性不看位置(2026-08-06 裁决) + + let v = 1; + println(v.nope()); + 改前 → lk check 过,运行时报 "Int has no method 'nope'" + 改后 → 类型错误(带位置) + +Int / Float / Bool / Nil 的内建方法集是**空的** —— `abs` / `sqrt` / `round` / +`len` / `to_string`,逐个探过,全是 "no method"。所以一个标量接收者上的名字只 +可能由用户的 `impl Int { … }` 或 `impl Trait for Float` 解析,而那条路在检查器 +里先试。走到"没有签名"这一步就是真的没有。 + +之前放过它的原因:`BuiltinReceiverKind` 只有 List / Bytes / Slice / Map / Set / +Str 六个,`receiver_kind` 对标量答 None,那条"已知接收者上没有这个方法就是错" +的规矩够不到;后面的容器兜底对标量也一律不答,最后落到 `Any`。同一个错误因此 +在 `String` 上是检查期错误、在 `Int` 上要等到运行时。 + +**Map 仍然豁免**,而且是对的:map 的条目就是它的字段,`m.score(1)` 可以是一次 +普通的属性调用,而 map 的类型里没有它有哪些键这件事。 + +### `impl` 也被提升了 + +查这条时发现的另一半:`fn` 和 `struct` 都被预扫提升(声明写在使用之后照样能用), +唯独 `impl` 是按语句顺序读的 —— 一个方法是靠"被类型检查"才为检查器所知的。于是 + +```lk +struct P { x: Int } +let p = P { x: 1 }; +println(p.m()); // 改前:P has no method 'm' +impl P { fn m(self) -> Int { return self.x; } } +``` + +把 `impl` 往上挪两行就过。而**导入**的 `impl` 早就有预扫(`typ::imports` 的 +`seed_impl_methods`),所以一个 `use` 之外的 impl 能用、三行之下的不能用 —— +这个不对称也正是它一直没被发现的原因。 + +现在三种声明同一条规矩:签名从**声明**读(未标注的参数是 `Any`),预扫只让方法 +更早**可见**,不会收紧任何东西;有序遍历走到定义处时再用推断出的签名覆盖。元数 +之类的校验因此从上方也照样生效。 + +## 顶层调用不能读到还没初始化的顶层绑定(2026-08-06 裁决) + + fn f() -> Int { return LATER; } + println(f()); + const LATER = 7; + 改前 → nil 改后 → 类型错误 + +`typeof(f())` 答 `Nil`,而 `f` 声明的返回类型是 `Int`。碰到 nil 之后的操作报的 +是它自己的事("Add expected numbers, got Nil and Int"、"`len()` works on a +String, List, …"),从不指向顺序。对照:Python 抛 `NameError`,JavaScript 从 +TDZ 抛 `ReferenceError`,Lua 给 nil。这门语言有检查器,给 nil 是三者里最差的。 + +三种情形现在齐了: + +| 写法 | 判定 | +| --- | --- | +| `const B = A + 4;` 在 `const A = 1;` 之上 | 早就拒(直接读) | +| 函数体读后面声明的绑定,但不在上面调用 | **允许** —— 函数体在整个顶层之后才跑,裸机程序到处这么写 | +| 顶层语句**调用**一个(传递地)读到未初始化绑定的函数 | 现在拒 | + +分析在 `core/src/stmt/init_order.rs`,**刻意单向**:每个近似都只会漏报,不会 +误报。遮蔽是整体相减(函数里任何位置绑定过这个名字,就不算读全局的那个); +间接调用(通过函数值)看不见;闭包和嵌套 `fn` 的体不算"此刻执行";环上的 +回边不贡献。唯一会误报的是"读在一条永不执行的分支上",而把声明上移一行永远 +可行也永远正确。 + +两个遍历都对 AST 枚举**穷尽匹配**、没有兜底臂:新增一个 `Stmt` 或 `Expr` 变体 +会在这里编译失败,而不是静默掉出分析。传递闭包用显式工作表而不是递归,理由与 +`HeapStore::collect` 相同 —— 深度是程序的调用深度,生成出来的文件可以要多深有 +多深,放在 Rust 栈上就变成一次没有行号可指的进程 abort。 + +实测:4000 个函数 `lk check` 0.26s,3000 层调用链 0.12s,5 万层不崩。 + +## 嵌套深度只有一个预算,两个解析器共用(2026-08-06 裁决) + + fn main() -> Int { if true { if true { … 400 层 … } } } + 改前 → fatal runtime error: stack overflow(2MiB 线程上 abort) + 改后 → Syntax error: Expression nesting too deep + +`if c { … }` 在 LK 里既是语句也是表达式,解析时在两个解析器之间来回穿:语句 +解析器遇到 `if` 交给表达式解析器,表达式解析器解析块体时又建一个语句解析器。 +两侧各有一个深度计数器,而**每次穿越都新建一个子解析器并从零开始计**,于是两 +个计数器谁也不累积 —— 400 层嵌套时实测语句深度最大值是 1。 + +裁决:一个预算,两侧共用。`ast::parser::MAX_PARSE_DEPTH`(std 64,no_std 16) +是唯一的常量,两个解析器都往同一个预算里计数,且每一次穿越都把当前深度传给子 +解析器。表达式解析器内部原本就有这条规矩(`sub_parser` 的注释写着"嵌套解析 +即使拿到自己的 `Parser` 也仍然是嵌套"),只是没有跨到语句边界上。 + +两条拒绝消息都可达,对应两种形状: + +| 形状 | 每层开销 | 报出的消息 | +| --- | --- | --- | +| `if` / `while` / `for` / `try` / `match` | 略多于 2 帧 | `Expression nesting too deep` | +| 裸块 `{ … }`、嵌套 `fn` | 1 帧 | `nesting too deep (more than 64 levels)` | + +实测边界:`if` 嵌套 30 层收、31 层拒;裸块 63 层收、64 层拒。本仓库 `.lk` 语料 +里最深的花括号嵌套(算上 `fn`/`impl`/`struct`)是 6 层。 + +### 附带裁到的一条:投机解析不能吞掉预算错误 + +`try_parse_tail_expression_stmt` 先拿表达式解析器试一次,失败就当"不是这个形 +状",回落到语句路径重新解析同一批 token。这条重试是承重的 —— +`try { … } catch e { }` 的空 catch 体表达式解析器拒、语句解析器收。 + +但语句路径内部会在下一层再做同样的"先表达式后语句",所以只要表达式那次失败, +每层的工作量就翻倍:`T(k) = 2·T(k+1)`。此前表达式那次总是成功,指数一直没露 +面;深度预算开始生效后它立刻露了 —— 32 层嵌套 `if` 跑五分钟不结束,而同一个 +文件在表达式那次成功时是 0.1s。任何深层语法错误都能触发,与深度守卫无关。 + +裁决:语法错误是形状信息,可以吞;预算耗尽不是 —— 每个候选都会栽在它上面。 +`ast::parser::NestingTooDeep` 是一个可 downcast 的标记类型,投机解析只放行前 +者,后者直接向上传。 + +## 拓宽一个列表的载体,只有构造点能做(2026-08-06 裁决) + + fn widen(xs: Any) -> Int { xs.push("z"); return xs.len(); } + let a = [1, 2]; let b = [1.5, 2.5]; + println(widen(a)); println(widen(b)); println(a); println(b); + 改前 → 原生**完整编译**,运行期 `Error: runtime type error`;VM 答对 + 改后 → 两端都是 3 / 3 / [1,2,"z"] / [1.5,2.5,"z"] + +VM 把 `TypedList::Int` 就地拓宽成 `Mixed`。原生做不到:被调方拿到的是原地打标签的 +`DYN_TLIST_*`,标签背后是调用方的 `Vec`,而调用方的其它别名按静态类型直接读那块 +内存。改标签只改了这一份 `LkDyn` 拷贝,改不了别人的。 + +裁决:载体在**构造点**决定。一个会被拓宽的列表,从字面量起就用 Dyn 载体 —— 这与 +`#183`(同函数内的 push 推翻载体)和 `#196`(map 字面量)是同一条规矩,只是跨了函数 +边界,因此共用同一条 `LiteralElemTypeContradicted` 重试通路。 + +需要判定"这个形参会被拓宽",有两种发现方式,少一种就漏一半: + +| 形状 | 形参类型 | 谁发现 | +| --- | --- | --- | +| 两个调用点载体不一致 | 并成 `Ty::Dyn` | **调用方**:被调方走 `dyn.list_push`,拓宽与否要到运行期才知道,所以调用方一律悲观 | +| 单个调用点 | 保持 `ListI64` 等 | **被调方**:类型化的 push 臂当场发现元素放不进载体,报 `ParamCarrierContradicted` | + +第二条需要一趟额外的往返,原因是 fixpoint 的结构:形参观察在第二趟开头被清一次 +(那是为了不让第一趟的临时类型污染单调的 join),而被调方按函数下标先于调用方降低, +所以在整个 fixpoint 里它看到的都是未观察时的 `I64` 默认值,**只有最终趟**才第一次看见 +真实载体。最终趟的可重试失败原本只有 `DynLoopPhi` 一种被收回去重跑,现在这条也收。 + +同一条规矩对 map 也成立,两种形状都覆盖。装箱接收者上的下标写入走 `dyn.index_set` —— +键也装箱传过去,因为"哪种键形状能用"是载体的规矩,由被调方判定:整数键在 map 上是**键** +不是位置(与 `lkrt_dyn_index` 读的那条裁决同一条),在列表上是位置,负数从末尾数,越界是 +VM 的那条 halt(`list index N out of bounds`)。写入落到标签背后的载体本身,所以装箱和原件 +仍是同一个容器 —— 与 `dyn.list_push` 同一条纪律,而 `dyn.as_list` / `dyn.as_map` 是只读的, +从它们写会写进物化出来的拷贝。 + +载体放不下的值在这里 raise,不拓宽:那块内存是构造方的,它的别名按静态类型读同一块。 + +代价是拿类型化换正确性:流进这类形参的容器失去类型化载体。实测 AOT 覆盖仍是 60/60, +VM/原生扫描 identical=61,perf 1.034x(噪声带内)—— 这条形状不在任何基准的热路径上。 + +## `defer` 在 return 上跑,在 raise 上不跑(2026-07-30 裁决,2026-08-06 记录) + + fn a() -> Int { defer { println("a"); } error("boom"); return 0; } + fn b() -> Int { defer { println("b"); } return a(); } + println(try { b() } catch e { -1 }); + → -1(两端一致,"a" 与 "b" 都不打印) + +`defer` 是一次 AST 改写,不是运行期机制:它把释放语句复制到每个 `return` 之前, +在解析器之后、类型检查器与两个编译器之前完成,下游看不到 `Stmt::Defer`。 +raise 在原生侧是 `longjmp` 离开,不是 `return`,这次改写看不见它。 + +这条不是遗漏。关掉它的做法(把函数体包进 `try`,在 `catch` 里跑释放再重抛)**建过 +两次,两次都被实测退回**:第一次让函数体里每个被赋值的寄存器都变成 try 区域的输出格, +`examples/syntax/defer.lk` 当场失去原生降低;第二次的阻碍是返回值的接线(整个函数的 +`return` 都在被包的体内时,它自己没有 `Exit::Ret`,返回类型丢失)。理由与测量在 +`core/src/stmt/defer.rs` 的模块注释和 `docs/aot/aot-gaps-and-lkrt.md` §17。 + +在缺口关掉之前:**必须活过 raise 的资源用 `try`/`catch`,不用 `defer`。** + +另一条同源的限制:`defer` 只能出现在函数体的顶层,不能在 `if`、循环或嵌套块里。 +顶层的文本顺序就是执行顺序,所以"写在这个 `return` 上面的每个 `defer`"恰好就是 +"已经跑过的每个 `defer`";分支里的 `defer` 需要运行期跟踪,那正是这里不做的机制。 + +## 擦掉类型的容器仍然是容器(2026-08-06 裁决) + + fn has(h: Any, n: Any) -> Bool { return n in h; } + fn app(xs: Any) -> Int { println(xs + [7]); return 0; } + 改前 → Type Error: 'in' operator requires container type (expected List, got Any) + Type Error: List concatenation requires both operands to be lists + 改后 → 两端都答,两端都完整原生 + +`Any` 上的索引读、索引写、`len()`、`for-in`、方法分发、`push`、相等、`<`、`slice`、 +`sort`、`join`、`delete` 一直都收;拒的是四个容器二元运算符:`in`、列表 `+`、列表 `-`、 +map `+`。`Any` 的含义是"运行时才检查",而两个执行器在运行时都 +一直有答案 —— 拒绝发生在检查器,不在语言里。 + +这条与 `in` 那条臂此前几次放宽是同一条队列:`Tuple` 与 `String` 曾经"别处都是容器、 +只有这里不是",`Bytes` 与 `Slice` 是随后两个(见 #185)。`Any` 是最后一个。 + +### 附带查到的一条:`"b" in "abc"` 根本没有原生降低 + +同一次探测发现的,与上面那条无关:字符串作为 `in` 右侧的形状,检查器收、VM 答,而 +**任何拼写都不原生降低**,整程序回落到 VM(约 3 倍慢,没有任何提示)。而它的方法拼写 +`s.contains("b")` 一直降得下去 —— 又是"同一个操作两种拼写只降一种"。现在两种拼写共用 +`str.contains`;`dyn.contains` 也补上了缺的 `DYN_STR` 载体(它此前覆盖 map / list / set / +slice / bytes,独缺字符串)。 + +## 列表操作数吸收另一个操作数(2026-08-20 裁决) + +`x + [列表]` 把 `x` 放到列表前面,`[列表] + x` 放到后面。两个执行器一直都这么 +答,`lkrt_dyn_add` 的注释把它写成规则:"列表操作数胜过字符串操作数,所以 +`"p=" + [1, 2]` 是列表 `["p=", 1, 2]`,不是文本 `p=[1,2]`"。 + +| 表达式 | 结果 | +| --- | --- | +| `"p=" + [1, 2]` | `["p=",1,2]` | +| `[1, 2] + "x"` | `[1,2,"x"]` | +| `1 + [2, 3]` | `[1,2,3]` | +| `nil + [1]` | `[nil,1]` | +| `[1] + {"k": 2}` | `[1,{"k":2}]` | +| `{"k": 2} + [1]` | `[{"k":2},1]` | + +没有排除任何操作数类型,因为没有一种会 raise —— set、字节串、map、nil 都进列表。 +这与 `Set + Set` / `Bytes + Bytes` 不同,那两个运行时确实报错,检查器拒它们是对的。 + +**此前只有检查器不收**,而且只在它看得见类型的时候不收:`Any` 操作数时同一个 +表达式跑得通,类型已知时是 type error。与 `Any + Any` map 合并同一条缺陷, +同一条判据 —— 检查器多出一条两个执行器都没有的规则,和它漏掉一条一样是缺陷。 + +### 更糟的一半:异构字面量被判成 String + +`[1, "a"]` 推出来是 `Tuple`。`Tuple` 在别处都是列表(索引、`len()`、遍历、`in`), +唯独没有进 `+` 的列表分支,于是 `"" + [1, "a"]` 落到字符串拼接那条路,**被判成 +`String`**,而两个执行器答的是列表 `["",1,"a"]`。 + +判错类型比拒绝更糟:它会传播。`let v: String = "" + [1, "a"];` 过了 `lk check`, +运行时 `v` 是列表。 + +### 拼接的元素类型取更宽的那侧 + +`wider_of` 的文档一直写着它就是"`check_list_addition` 用的那条规则",而 +`check_list_addition` 展开写的是相反的取舍:它取**可赋值给对方**的那一侧,也就是 +更窄的那侧。`Int <: Float`,于是 + +```lk +let v: List = [1] + [1.5]; // 收 +let n: Int = v[1]; // 收 +typeof(v[1]) // Float +``` + +同一对类型,调 `wider_of` 的 map 合并答的是 `Map`。一条规则两个 +答案,文档还指着错的那个。 + +`wider_of` 自己底下还有一个洞:`is_assignable(Any, Int)` 是 true —— `Any` 可以 +**传**到要 `Int` 的地方 —— 所以拿它问包含关系,答案是"`Int` 更宽"。不是。 +`Map` 合并后被判成 `Map`,里面装着字符串。现在 `Any` +在问之前就先答掉。 + +原生侧随之补上:一侧是列表另一侧不是,两边装箱走 `dyn.add` 再拆回列表句柄, +和 map 合并同一形状 —— 否则检查器刚放行的程序会整个回落到 VM。 + +### `+` 拼接是第四种打印方式,而它是唯一还在失败的那种(2026-08-20 补完) + +上面那条 2026-07-30 的更正写着 `ToString` / 模板插值 / `+` 拼接的"标量 only"规则 +已经退休,容器该显示。插值改了,`+` 没有: + +| 写法 | 改前 | 改后 | +| --- | --- | --- | +| `println(xs)` | `[1,2]` | 不变 | +| `println("{}", xs)` | `[1,2]` | 不变 | +| `println("${xs}")` | `[1,2]` | 不变 | +| `println("" + s)`(Set) | 运行时报错 | `Set([1])` | +| `println("" + b)`(Bytes) | 运行时报错 | `Bytes([97,98])` | +| `println("" + w)`(窗口) | 运行时报错 | `[1]` | +| `println("" + p)`(结构体) | 运行时报错 | `P{x:1}` | +| `println("v=" + m)`(map) | **检查器**拒绝 | `v={"k":1}` | + +列表不在表里,因为它不是同一个问题:列表操作数**胜过**字符串操作数,答案是列表 +(见上一节)。map + map 仍然是合并。变的只是"一边是字符串、另一边是没有 `+` 含义 +的容器"这一种组合。 + +`1 + {"k": 1}` 仍然报错,两边都是 —— 既不是合并也不是拼接。 + +三处此前互不一致:VM 的 `runtime_value_display_string` 对堆对象直接 bail,而插值走 +`runtime_display_value`;检查器只拒 map,放行 Set / Bytes / 窗口 / 结构体(它们照样 +运行时报错);lkrt 的 `lkrt_dyn_add` 第 4 步**一直是对的**,所以类型被擦掉的程序答得 +出来,同一个程序类型已知反而不原生降低。现在三处同一条规则。 + +顺带修好的一条:`runtime_value_display_string` 的另一个调用点是 "X is not a function" +的错误消息,容器 callee 在那里把诊断换成了一条更差的错误。 + +## 问是全函数,建键不是(2026-08-20 裁决) + +`v in c`、`m.has(k)`、`s.contains(v)` 是**谓词**,一律有答案:不能当键的值不是成员, +答 `false`,不报错。而**构造键**的操作仍然报错:`m.set(1.5, x)`、`m.delete(1.5)`、 +`s.add([1])`、`m[1.5]`、`m - 1.5`。 + +改前有两处不一致: + +| 表达式 | 改前 | 改后 | +| --- | --- | --- | +| `1.5 in {"k": 1}` | false | 不变 | +| `1.5 in {1: 2}` | **报错** | false | +| `[1] in {"k": 1}` | false | 不变 | +| `[1] in {1: 2}` | **报错** | false | +| `1.5 in Set([1])` | **报错** | false | +| `m.has(1.5)` | **报错** | false | +| `s.contains(1.5)` | **报错** | false | +| `m - 1.5`、`m - [1]` | **报错** | 原样返回 | +| `m.delete(1.5)` | **报错** | nil | + +第一处:同一个问题,答案由 map 的**内部载体**决定 —— 字符串键的 map 走 +`string_map_contains_key` 答 false,整数键的 map 是 `Mixed`,走 +`runtime_map_key_from_value` 报错。载体是程序看不见的东西,这与列表 `in` 那条 +"答案取决于列表的内部表示"是同一个成因。 + +第二处:运算符和方法两种拼写答得不一样。`k in m` 答 false,`m.has(k)` 报错。 + +**删除也是"问"**:`m - k` 和 `m.delete(k)` 是查一个键再丢掉,不是构造键,所以 +不能当键的值删掉的是"没有",不是报错。`-` 的分派按**左**操作数,与解释器一致 —— +读任一侧会把 `{"a":1} - [1]`(map 减一个恰好是列表的键)送去列表规则,然后被告知 +左操作数不是列表。 + +**为什么选"答 false"而不是"一律报错"**:`in` 在列表上已经是全函数 —— +`"s" in [1, 2]` 是 false,不是类型错误。map / set 是同一个谓词换个容器。而且 +"一律报错"会破坏现在能跑的程序(`1.5 in {"k": 1}` 现在是 false)。 + +native 侧同步:`lkrt_lkset_has` 与 `lkrt_dyn_contains` 的 map 分支改用 +`key_from_dyn_opt`;`m.has(k)` 在 `k` 类型未知时走 `dyn.contains`(即 `in` 的 +运行时分发),两种拼写共用一份实现。 + +### 谓词的方法拼写也收任何值(2026-08-20 补完) + +上一节把 `in` 改成全函数,但方法拼写还按容器自己的元素/键类型声明参数,于是同一个 +问题两种拼写两条规则: + +| 运算符拼写 | 方法拼写 | 改前 | +| --- | --- | --- | +| `1 in ["a","b"]` 收,答 false | `["a","b"].contains(1)` | 检查器拒 | +| `1 in "abc"` 收,答 false | `"abc".contains(1)` | 检查器拒 + **运行时报错** | +| `"a" in "ab".bytes()` 收 | `"ab".bytes().contains("a")` | 检查器拒 + **运行时报错** | +| `"k" in {1:2}` 收 | `{1:2}.has("k")` | 检查器拒 | +| `{1:2} - "k"` 收 | `{1:2}.delete("k")` | 检查器拒 | + +`contains` / `index_of` / `count` / `has` / `delete` 的参数类型改为 `Any`, +`string.*` 与 `bytes.*` 的运行时守卫改为答"不在"而不是报错。**插入不变**: +`push` / `add` / `insert` / `set` 仍然要求元素/键类型 —— 收了就等于让 +`List` 这个类型说谎。 + +原生侧:类型settle得了的直接折成常量(`"abc".contains(1)` 恒为 false), +settle 不了的走装箱后按值比较的 helper。顺带用上了 `list_h.i64_contains_f64` —— +这个 ABI 入口一直在表里没人调用,因为 `[1,2].contains(1.5)` 这个形状被检查器 +挡在前面,谁也到不了。 + +### 反向的一条:归约要求元素类型 + +`["a"].sum()` 和 `[1.5].to_bytes()` 过检查、运行时必报错。这与 `Set + Set` 同款 +—— 运行时对该类型的每个值都报错,检查器先说是对的。现在拒,且逐元素判: +`[1, "a"].sum()` 里的 `String` 就足以拒(`Tuple` 塌成 `List` 会把这条信息丢掉)。 + +`min` / `max` 不在其列:字符串有序。 + +### 索引:读是"问",写是"建"(2026-08-20 补完) + +同一条线延伸到 `c[i]`: + +| 表达式 | 改前 | 改后 | +| --- | --- | --- | +| `{"k":1}[0]` | 检查器"Cannot unify String with Int" | nil | +| `{1:2}["k"]` | 同上 | nil | +| `{"k":1}[0] = 9` | 拒 | **不变,仍然拒** | +| `[1,"a"][0..2]` | "Tuple index must be integer" | `[1,"a"]` | + +读一个别的类型的键是**没查到**,不是错误 —— 解释器答 nil,与"键根本不存在"同一个 +答案。检查器原来加的是一条 unify 约束,于是报的是它自己机器的话。约束还在,只在 +两边都是具体且互不相容时跳过 —— 那正是"只可能查不到"的情形。 + +写仍然拒:`m[0] = 9` 会往 map 里放一个它自己的类型说不存在的键。 + +`Tuple` 切片是漏了一条 guard:每个容器分支都有 `matches!(&field, Expr::Range{..})`, +唯独 `Tuple` 没有,于是异构字面量成了唯一不能切片的列表。 + +原生侧两处跟上:键类型 settle 得了的直接折成 nil(`{"k":1}[0]`);settle 不了的 +(键是 `Dyn`)把 map 装箱走 `dyn.get` 按 tag 分发 —— 原来是把键拆箱成 map 的键类型, +对 Int 键直接报错,而 VM 答 nil。**这条是本次改动引入的分歧,同一轮内修掉。** + +`c[[1]]` 这种手写列表下标仍然拒:范围 `0..2` 就是列表 `[0,1]`,所以列表下标是切片 +路径的实现细节漏出来,不是特性。 + +## 条件里的赋值是语法错,不是被丢掉的 token(2026-08-06 裁决) + + let a = 1; + if a = 2 { println(1); } + 改前 → 通过检查,运行打印 1;编译出来的是 `if a { println(1); }`,常量 `2` 不在字节码里 + 改后 → Syntax error: `=` assigns, and an assignment in LK is a statement rather than + an expression — a comparison is `==` + +赋值在 LK 里是语句,不是表达式(`let b = (a = 1);` 一直是语法错)。但**头部表达式**的 +解析有两条路,只有一条查剩余: + +| 路径 | 何时走 | 剩余 token | +| --- | --- | --- | +| 语句侧(`if`/`while`/`for` 的条件) | 一般情况 | `Parser::parse` 自带检查,报错 | +| 表达式侧(`parse_header_expr_before_brace`) | 尾位置的 `if` / `match` | 调的是 `parse_expr`,**不查** | + +于是同一行代码,在文件最后一条时被接受并静默改写语义,往后挪一条就是语法错。这是 +`=` 误写成 `==` 这个最常见的手滑,变成了错答案。 + +两处现在共用一条规矩:`{` 之前的全部 token 就是那个表达式,没消费完就报错。消息也 +统一,并给 `=` 单独一句 —— 与 `export fn`(#201)同样的理由:人人会犯的错拼要被点名, +而不是只说"有个多余的 token"。 + +## 一个绑定器不能把同一个名字绑两次(2026-08-06 裁决) + + fn f(a: Int, a: Int) -> Int { return a; } + println(f(1, 2)); 改前 → 2(第一个实参没人读得到) + match t { [a, a] => { println(a); } … } 改前 → 匹配任意两个元素,绑后一个 + struct P { x: Int, x: Int } 改前 → 接受;字面量填一次就满足"两个" + 改后 → 七个位置全部报错并指名那个名字 + +同一个绑定器里绑两次,第一次绑定永远读不到 —— 第二次在任何代码运行前就把它盖住了。 +`[a, a]` 尤其危险:它**看起来**像"两个元素相等",而那是非线性模式的语言(Prolog、Erlang) +的读法;这里它匹配任意两个元素并绑定后一个。 + +七个位置一条规矩:`fn` 形参、`impl` 方法形参、lambda 形参、`struct` 字段、`let` 解构、 +`for` 循环模式、`match` / `if let` / `while let` 的模式。 + +反向半边同样是规矩的一部分,因为这条是**按绑定器**判的,不是按作用域:后一条语句里重新 +绑定同名是正常的(`let a = 1; let a = 2;`),`Or` 模式的每个分支绑同名是**必须的** +(`1 | 2` 里的绑定要在两臂都存在才可用),所以 `Or` 的各分支各自独立查。 + +### 附带修到的一条:`if let` / `while let` 的模式检查结果被 `.ok()` 丢掉 + +那两处的注释写着"拒绝匹配类型产生不了的模式",而 `.ok()` 恰好把那个拒绝扔了。类型不匹配 +在 `if let` 上确实不该静态拒绝 —— 测试它正是这个构造的用途;但重名不是那类,没有任何值 +能让它成立。所以查重拆成独立的方法,不经过那条被吞的返回值。 + +## `impl` 方法的签名必须顶得住 trait 的声明,而这句话在 `lk check` 里说(2026-08-06 裁决) + + trait T { fn m(self, a: Int) -> Int; } + struct S { x: Int } + impl T for S { fn m(self) -> Int { return 1; } } + 改前 → lk check 通过,一运行就 "Method 'm' arity mismatch for trait 'T'" + 改后 → lk check 就报同一句 + +判据本身一直存在(`TypeRegistry::validate_trait_impl`),只是在 **VM 注册 impl 时**才跑。 +#99 把"方法有没有实现"那一半搬进了检查器,并在注释里写下"present 就是全部问题" —— +那句不准确:元数、形参类型、返回类型同样由那份判据管,同样被 `lk check` 放过。 + +现在是一条规则两个调用者:`trait_method_conformance` 被注册路径和 `impl` 语句的检查 +共同调用。形参逆变、返回协变,是"一个签名要顶替另一个"的通常规矩。 + +## trait 是一个类型(2026-08-18) + +trait 系统有三部分:`trait` 声明方法集、`impl` 为某类型提供实现、调用时按目标类型分发。 +前两部分一直在,第三部分——**把 trait 的名字写在类型的位置**——一直报错: + +```lk +trait Show { fn show(self) -> String; } +struct P { x: Int } +impl Show for P { fn show(self) -> String { return "P"; } } + +fn render(v: Show) -> String { return v.show(); } +render(P { x: 4 }) // 此前:Argument 1 has the wrong type (expected Show, got P) +``` + +于是唯一能写"任何有 show 的东西"的办法是把参数留成无类型的,也就是什么都不检查。 + +修好后四个位置都成立:参数类型、返回类型、绑定的类型、容器的元素类型 +(`examples/syntax/trait_as_type.lk` 逐条钉住)。查出来的原因有四层,每一层都是独立的: + +| 层 | 现象 | +| --- | --- | +| `TypeRegistry.implementations` 在类型检查期是空的 | impl 只在**模块加载**时注册(`VmContext::register_module_types`),那是所有类型检查之后。`predeclare_type_declarations` 现在把 impl 和 struct/trait 一起提前登记 | +| `Type::is_assignable_to` 没有 registry | 它是 `values` 里两个类型之间的纯函数。加了 `TraitOracle` 参数,由类型检查器实现;`NoTraits` 是其他调用方的答案 | +| 返回类型走的是**合一**,不是可赋值性 | 同一个问题的两个答案,只有一个有这条规则。`unify` 补上对应分支 | +| 异构字面量推成 `Tuple` | `container_literal_fits` 只有 List/Set/Map 三个分支,`[p, q]` 写给 `List` 从来没走到 List 那支 | + +顺带删掉了一个重复结构:`TypeInferenceEngine` 自己持有一份 `TypeRegistry` **克隆**, +是构造检查器时拷的,之后所有声明它都看不见。它只用来发 `T{n}` 编号,现在改成一个 `u32` +计数器,registry 由 `solve_constraints` / `unify` 按引用传入。 + +**跨模块也成立**(同日补)。trait、struct 和 impl 的**方法签名**本来就都过了模块边界, +唯独"这个类型实现了这个 trait"这条关系没过——`seed_declared_types` 只登记 struct / trait / 别名。 +于是被导入文件里的 `fn describe(v: Area)` 对它自己声明了 impl 的那个类型报 +"expected Area, got Sq":这个特性在一个文件里成立,出了文件就不成立。 +`cli/tests/compile_cli_test.rs::test_trait_as_a_type_accepts_an_imported_implementor` 钉住了它。 + +还有一条:trait 类型的接收者只能调用 trait 声明过的方法。此前 `v.nosuch()` 直接到运行期, +而两个后端"发现"的方式不同,报的话也不同——解释器说 `P has no method 'nosuch'`, +编译版说的是 map 属性那句。 + +## 容器模式里的子模式(2026-08-18) + +`match` 的列表/映射模式此前**只检查形状**——列表的长度、映射的键——里面写别的一律拒绝: +`Compiler does not support nested refutable pattern yet`。也就是说 + +```lk +match p { + [0, 0] => "origin", + [0, y] => "on-y", + [x, y] => "point", +} +``` + +这种写法不能编译,而模式匹配大半是这么用的。现在子模式和顶层模式是同一种东西, +字面量、区间、嵌套容器、`|`、`if` 守卫都可以出现在里面。 + +三处配套: + +| 位置 | 内容 | +| --- | --- | +| `pattern_control::lower_container_pattern` | 形状测试 + 逐个子模式。取元素放在形状守卫**内部**——对非列表取下标会抛错 | +| `lower_subpattern` | 容器里的裸名字是**绑定**,不是"非 nil"测试。`lower_pattern_match` 是和 `if let` 共用的,那里裸名字意味着非 nil | +| `type_checker/patterns` | 元组按位置给类型。此前所有位置共用一个元素类型,`match ["a", 2] { ["b", n] => … }` 报的是检查过程自己造出来的 `Cannot unify Int with String` | + +模式树用到的寄存器在**第一个形状测试之前**统一分配并置 nil。VM 里没写过的寄存器就是 nil, +原生降级要建 SSA,一条走不到的路径上的读也是读。`..rest` 例外,留在守卫外面:它产出的是 +列表或映射句柄,一边 nil 一边句柄没有统一类型。 + +顺带修了两个原生降级的**静默错答**(不是回退,是编译通过并答错): + +- `IsList` / `IsMap` 只比较一个 tag(`DYN_LIST` / `DYN_MAP`)。列表有五种表示、映射有六种, + 解释器还把 `String` 算作列表(`let [a, b] = "ab"` 成立)。落在类型化载体里的列表因此答 `false`, + 跳过了本该走的分支。现在走 `dyn.is_list` / `dyn.is_map`,与 lkrt 其余部分用同一个判定。 +- `lkrt_dyn_index` 对装箱的 `String` 抛 `runtime type error`,而未装箱的 `s[0]` 一直是可以的。 + +**`match` 的分支是提问,不是要求**(同日续)。无类型参数被多个模式匹配时,scrutinee 的类型 +还是开放的,而列表/映射模式会给它加一条 `= List` 的约束——于是同一个 `match` 里三个分支 +互相报冲突,冲突是检查过程自己造的。类型开放(`Variable` / `Union`)时不再加这条约束: +其它分支存在,正是因为答案可以是"不匹配"。 + +`..rest` 的绑定位置由此定下来:它在形状守卫**内部**,槽位事先用**同类的空容器**(而不是 `nil`) +初始化。放在守卫外面,一个 Map 走到 `[x, ..rest]` 分支会对它跑 `SliceFrom` 并抛 +"not sliceable"——从一个根本不匹配的分支里抛出来;用 `nil` 初始化,则一边 nil 一边句柄, +原生降级没有统一类型可给。 + +## 位运算补上 `^`(2026-08-18) + +`&` `|` `<<` `>>` `~` 都在,只有异或没有拼法——而 `__lk_bit_xor` 这个名字已经出现在 +类型检查器的 arity 表和 VM 编译器的 builtin 列表里,是一个**没有东西能产生**的名字。 +现在:词法器认 `^`,解析器在 `|` 和 `&` 之间加一层(C 和 Rust 的优先级),VM 装了 +`core_bit_xor_builtin`,AOT 走 `IntBinOp::Xor`(`~x` 本来就是拿它和 `-1` 做的)。 + +顺带补了 tree-sitter 文法:它此前**一个位运算都没有**。加进去暴露一处真实歧义—— +`p if x | y` 里的 `|` 是守卫的按位或还是 or-pattern 的分隔符。真实解析器里守卫先赢 +(`parse_guard_pattern` 把守卫当作一整个表达式解析,`|` 在 `parse_or_pattern` 看到之前 +就被吃掉了),文法里按同样的方式声明了这个冲突。 + +位运算的复合赋值也补齐了(`&=` `|=` `^=` `<<=` `>>=`)。它们**不**新增 `BinOp` 变体—— +`a & b` 本来就是 `__lk_bit_and` 调用,给它第二种拼法就意味着第二套降级、第二条类型规则, +以及两边分歧的可能。`a &= b` 直接脱糖成 `a = a & b` 已经产生的那个调用, +名字、下标、字段三种目标都走同一条路。 + +`<<=` / `>>=` 是**三个相邻的 token**:词法器从不发射移位,`<<` 是两个 `<`,所以 `<<=` 是 +`<` 接 `<=`。靠 span 相邻区分它和 `a < (b <= c)`,与 `Parser::peek_shift` 判断移位本身的方式相同。 + +## 声明的字段类型是要执行的(2026-08-18) + +此前 `struct P { v: Int }` 的 `v` 可以装字符串,解释器不拦: + +```lk +fn poison(p) { p["v"] = "s"; } +let a = P { v: 1 }; +poison(a); +println(a.v); // "s" +``` + +类型检查器只能检查它**看得到**的写入;通过无类型绑定的写入它看不到。于是声明只是注释—— +而 AOT 想按声明类型给字段读定型时,这一点立刻变成静默错答(见 `docs/aot/aot-gaps-and-lkrt.md` §41)。 + +现在四种写法都按声明检查,VM 与原生同一条规则、同一句话: + +| 写法 | 位置 | +| --- | --- | +| `p["v"] = x` / `p.v = x` | `exec::container` 的对象写入 / `map_h.str_dyn_set` | +| `P { v: x }` | `read_object_fields` / NewObject 降级(**先打标记再写字段**) | +| `P { ..m }` | `core_make_struct_builtin` / `map_h.obj_mark`(标记时校验已有字段) | + +`field \`v\` of P is declared Int, and a String cannot be stored in it` + +**检查在能静态判定时不进入运行期。** 降级这一侧知道被存值的类型和字段的声明码,所以 +`P { x: 1 }` 这种一个字面 `Int` 存进 `Int` 字段的写法**一条指令都不发**;判不了的时候 +声明码是编译期常量,运行期只是一次 tag 比较(`obj_ty.check`),不查表。只有当接收者的结构体类型 +连降级也不知道时才走标记查表那条(`obj_ty.check_marked`)。 + +第一版把标记提到字段写入之前、让 `map_h.str_dyn_set` 自己查表,结果是 20 万次构造从 0.72s 变成 +0.88s——**比调试版解释器还慢**。现在构造 0.72s、字段自增循环 0.04s。 + +两道静态过滤把剩下的运行期检查也基本清空了:接收者是这个函数看着 map **字面量**产生的, +那它不是结构体实例(结构体来自 `NewObject`,那条已经带类型名);以及**没有任何声明的结构体 +有这个名字的带类型字段**时,不论这个 map 是什么,这个键都不可能违反声明。于是不声明结构体的程序 +一条检查都不发,`m["k"] = v` 这样的循环也不发。 + +**只检查标量**(`Int` `Float` `Bool` `String` 及其可空形式)。容器的元素类型不是单个值携带的东西—— +`List` 和 `List` 在运行期是同一个 `HeapValue::List`——所以容器字段、`Any`、联合、 +命名类型都不检查。剩下的正好是"写错了会静默损坏"的那一组。 + +`Int` 满足 `Float` 字段,并且**仍然是 Int**:这门语言在类型边界上从不做隐式转换, +`fn f(x: Float)` 收到 `1` 时 `typeof(x)` 也是 `Int`。字段这里保持一致。 + +**容器的元素类型不按这条办,而且是有意的。** `fn add(xs) { xs.push(B { … }); }` 确实能把一个 `B` +推进 `List`,但那不是同一类问题: + +- `struct` 是显式的、按类型声明一次的,而且**跟着值走**(`DeclaredType` 挂在实例上)。列表没有 + 对应的东西——运行期的列表只有表示(`TypedList::I64` 之类),没有声明。 +- 列表的元素类型多半是**推断**出来的,不是写出来的。`let xs = []; xs.push(1); xs.push("a")` + 是这门语言正常的写法,异构列表是一等公民;把推断出的 `List` 变成运行期约束会直接废掉它。 +- 想只约束**写出来的**那一种(`let xs: List`),就得让值本身记住"我的元素类型是被声明的", + 那是给每个列表加一个类型字段外加每次 push 一次检查——为一个语言本来就允许的形状付全程代价。 + +所以这条到此为止。AOT 也因此**不能**按元素类型给读定型(`docs/aot/aot-gaps-and-lkrt.md` §41 +撤回的那一半),这是设计上的结论,不是待办。 + +## 拿"该被拒绝的程序"当 oracle(2026-08-18) + +> 这份表留了下来:`cli/tests/check_oracle_test.rs`。35 条"必须拒绝"、15 条"必须接受", +> 后者里包括**有意接受**的那几条,并写明理由——否则下一个读到的人会把它们当 bug"修掉"。 + +前几轮都是 VM ↔ 原生的差分。换个问法:**哪些明显有错的程序 `lk check` 放过了?** +写了 62 个用例,两个方向都有——32 个"必须报错"、15 个"必须通过",外加边界形状。 + +放过的里面,三条是真缺口,已修: + +| 形状 | 此前 | 现在 | +| --- | --- | --- | +| `impl T for P { fn b… }`,`b` 不是 `T` 声明的 | 只在 VM 注册 impl 时报,`lk check` 放过 | 检查期报 | +| `P { x: 1, x: 2 }` | 静默取后一个 | 报错。理由与"参数名重复""模式里绑定名重复"完全一样:第一个值没有任何东西能读到 | +| `use math as m; m.nope(1)` | 放过,运行期 "nil is not a function" | 报 `` `math` has no member `nope` ``——别名记了名字没记模块,于是成员检查查的是一个叫 `m` 的模块 | + +放过但**有意为之**的:`if 5` / `while "a"`(这门语言有真值性)、`{"a":1,"a":2}` +(后写的覆盖先写的,与 `IndexMap::insert` 一致)、`"a".repeat(-1)` 和 `P { ..5 }` +(运行期错误,消息清楚)、顶层 `return`(模块的返回值)。 + +第四条 `P { ..5 }` 也修了:spread 脱糖成 `__lk_merge_fields(base, overlay)`,而没有人看过 base, +所以运行期抛的是 `__lk_merge_fields base must be Object, Map, or Nil, got Int`—— +一句话里说的是脱糖结果,不是读者写的东西。现在检查期就说"`..base` 要的是结构体、映射或 nil"。 + +**下标的可空性:量过之后决定不改。** `m["z"]` 和 `xs[9]` 运行期给 `nil`,而类型检查器把它们 +定为 `V` 而不是 `V?`,所以 `let v: Int = m["z"]` 通过、`v` 是 `nil`。按 `?` 模型这该是 `V?`。 + +不改的理由是数出来的:examples 和 bench 里共 113 处下标读,只有 3 处所在行有 `??` 或 `!`。 +改成 `V?` 意味着其余 110 处、加上标准库和所有用户代码都要改写,而 `assert(listed[0] == 7)` +这种比较也要跟着变。收益是"`let v: Int = m["z"]` 会报错"——而那个 `nil` 本来也会在下一步绊倒程序。 +这门语言在这件事上的立场是一致的(`if 5` 有真值性、下标越界给 nil),不是漏了一条规则。 + +## 列表模式说的是**几个**(2026-08-20) + +`match xs { [] => …, [a] => …, [a, b] => … }` 对任何长度的列表都答第一条 —— +`[]` 匹配一切,后面每条都是死的;`[a]` 也匹配两个元素的列表。 + +`lower_list_pattern_condition` 发的是 `CmpGeInt`,长度**下限**,而且六个调用点 +全都把 `rest` 丢掉了。于是"有没有 `..rest`"这件事对形状测试没有影响。 + +改成:**没有 `..rest` 就是相等,有才是下限**。这正是 `..rest` 存在的意义, +而且语料里每一处需要前缀的地方都已经显式写了它 +(`let [first, second, ..rest]`、`let [_, second_item, .._]`、`while let [top, ..rest]`), +文档里也没有任何地方说过裸模式是"至少"。 + +影响到的不只是 `match`:`if let` / `while let` / `for` 的元组与数组模式、 +以及 `let` 解构都走同一条。`let [a, b] = [1, 2, 3]` 现在报 +`Pattern does not match value`,`if let [a, b] = [1,2,3]` 现在不匹配 —— 都是对的。 + +两个后端一致(这条 bug 在 VM 的编译器里,所以此前两边一起错)。 +全量测试、61 程序 sweep、覆盖率门禁、性能门禁(geomean 1.038x)全绿; +`examples/syntax/match.lk` 加了两组断言,其中一组把空模式写在长模式**之后**, +这样即使分支顺序救不了它也能被发现。 + +## 未标注参数的加宽,不该被一个自由变量挡住(2026-08-20) + +`fn f(v) { … }` 同时被 `f([])` 和 `f(5)` 调用,报 +`Cannot unify Int with List<'T2>`。而 `f([1])` 加 `f(5)` 是接受的 —— 同一个程序, +只是列表里多了一个元素。`Int + String`、`Int + List`、`Map + Int`、`Int + Float` +也全都接受。唯一被拒的是**空**列表(和空 map)。 + +`widen_rebound_variable` 的作用是"一个变量先被绑成一个类型,现在又要求是另一个, +那就绑成两者的并集"。它两侧都写着"含自由变量就不加宽",理由是 +"推导还没决定完的事,不该由加宽来决定"。 + +对**裸变量**来说这是对的:它还没有形状。但 `List<'T2>` —— 空字面量给出的那个 —— +形状已经定了:它是列表,而列表无论 `'T2` 最后是什么都不可能和 `Int` 统一。 +所以拒绝加宽并没有推迟一个决定,它报告了一个冲突。判据改成 +**只有裸变量才拒绝**,构造出来的类型照常加宽,里面的变量随并集留下,以后照常代换。 + +顺带:哪一侧被挡住取决于求解器先弹哪条约束(`constraints.pop()` 是 LIFO), +所以两侧的判据都要改。 + +真正的类型错误照旧报错(`fn f(v: Int)` 传 `"s"` 仍然是 +`Argument 1 has the wrong type`)。 + +## 一个浮点数怎么变成字符串,和它从哪来无关(2026-08-20) + +用**期望值**(而不是拿两个后端互比)逐条探语言语义时出来的两条。上一条列表模式的教训是: +编译器里的 bug 两个后端一起错,差分门禁按定义看不见。 + +### 常量折叠用的是另一个格式化器 + +`"" + 3.0` 折叠成 `"3.0"`,而 `let x = 3.0; "" + x` 是 `"3"`。 +折叠那一侧用 `ryu`(最短往返格式化器),其余一切用 Rust 的 `Display` —— +后者才是本文档定下的规则,lkrt 那边也是逐字节对着它做的。 + +差别不只是那个小数点:`"" + 1.0e300` 折叠出来是 `1e300`,运行时是三百位数字。 +**同一个表达式两个答案,取决于操作数碰巧是不是字面量。**折叠改用 `to_string()`。 + +### 浮点操作码没有动态回退,而它的整数孪生兄弟有 + +`"" + (1.0 + 2.0)` 在**运行时**报 `register 3 expected Int or Float: got String`, +而 `lk check` 一声不吭就放过去了。`"" + (1 + 2)` 是好的。 + +编译器按**一个**操作数的类型挑 `AddFloat`,不看另一个;括号里那半被折叠成浮点常量之后, +外层就成了"字符串加浮点"。`AddInt` 遇到非整数对会转去 `dynamic_add`, +`AddFloat` 直接报错 —— 这个不对称就是 bug。 + +改的是**回退**而不是选择:选择是一个关于类型的猜测,而这里是知道答案的地方; +守卫本来就在(它就是报错的那个),所以冷分支不比原来的错误多花什么。 +`AddFloat` / `SubFloat` / `MulFloat` / `DivFloat` / `ModFloat` 五个现在都和整数孪生兄弟一样转去动态形式。 + +原生降级那边同样处理:`Str` 操作数走 `dyn.*`,和它本来就有的 `Dyn` 分支同一条路。 + +语料在 `examples/syntax/numeric_auto_promotion.lk`:同一个值一次来自字面量一次来自变量, +断言两者相等 —— 这一条不需要知道正确答案是什么就能发现分歧。 + +## `!` 的操作数从来没被检查过(2026-08-20) + +`lk check` 的立场是"执行器跑的就是这一份检查"。拿一批**运行时会报类型错**的程序 +逐条对着它跑(检查通过 + 运行失败 = 漏检),15 条里出来一条: + +```lk +println(!5); // lk check 通过,运行时 Not expected Bool or Nil, got Int +println(5 && true); // 两边都在检查时就拒绝 +``` + +`check_unary_op` 的 `Not` 分支只在操作数是类型变量时加一条约束,然后无条件答 `Bool` —— +任何东西都放过。它的兄弟 `Neg` 一直走 `classify_numeric_operand`。 + +改成:**只有确定不可能是 `Bool` 或 `Nil` 的才拒绝**。类型变量、`Any`、`Optional`、 +以及成员里有 `Bool` 或 `Nil` 的联合都照旧接受 —— 在一个大多数值不带标注的语言里, +"证明不了对就拒绝"会把日常写法拒掉。措辞用执行器那句(`Not expected Bool or Nil`), +两边说同一句话。 + +`cli/tests/check_oracle_test.rs` 两个方向都记了:四条必须拒(`!5`、`!"a"`、 +`!f()`、`5 && true`),三条必须收(未标注参数、`Int?`、容器读)。 + +同一批探测里其余 14 条都是对的(下标类型、调用非函数、缺方法、字段访问、 +负号作用于字符串、迭代整数、返回类型、参数个数),记下来免得重探。 + +## 闭包里的 `defer` 根本没被改写(2026-08-20) + +```lk +fn f() -> Int { let t=[]; defer t.push(1); return t.len(); } // 0 +let g = || { let t=[]; defer t.push(1); return t.len(); }; // 1 +``` + +同样三行,函数里答 0,闭包里答 1。字节码上一目了然:函数那份是 +`Len`(算返回值)→ `Move`(存起来)→ `ListPush`(defer)→ `Return`; +闭包那份是 `ListPush` → `Len` → `Return` —— defer 先跑了。 + +`defer` 是一次**改写**:把延迟语句复制到每个 `return` 之前,并把返回值先存进临时变量。 +改写的入口 `descend` 处理 `Stmt::Function`,而 lambda 是一个**表达式**, +所以闭包体既没有被改写,也没有走到那个"`defer` 不能写在分支/循环/嵌套块里"的拒绝 —— +`Stmt::Defer` 直接当普通语句编译,原地就跑了。 + +补了一个 `descend_expr`,走到闭包体和 `try` 区域。**这个 match 没有 `_` 分支**: +这次出问题的正是一个没人列出来的形状,而 `_` 就是下一个漏网之鱼的入口。 +顺带,写在闭包里分支中的 `defer` 现在也会被正确拒绝(此前一声不吭地原地执行)。 + +`examples/syntax/defer.lk` 里加了一对断言:同样的三行,一次写成闭包一次写成函数, +断言两者相等 —— 和上一条浮点渲染一样,不需要知道正确答案就能发现分歧。 + +## 解析出来的文档保持文档序(2026-08-20) + +map 的迭代与 display 顺序是"键第一次被写入的顺序"——这是契约。对一个**解析出来的** +文档,那就是它在文档里出现的顺序。而 JSON 和 TOML 都是按字母排的: + +```lk +use encoding; +encoding.json.parse("{\"b\":1,\"a\":2}").keys() // 之前 [a, b],现在 [b, a] +encoding.toml.parse("b = 1\na = 2\n").keys() // 同上 +``` + +两处都不是决定,只是各自中间类型的默认:`serde_json::Value::Object` 与 +`toml::Value` 的表都是 `BTreeMap`。YAML 的 `Mapping` 本来就是有序的。 + +- JSON:不再经过 `serde_json::Value`。`serde_json` 的 `preserve_order` 特性会**显式打开 + `std`**,而这个 crate 必须能不带 std 构建(裸机保留 JSON),所以改成直接反序列化到一个 + 对象存 `Vec<(String, _)>` 的中间类型 —— serde 的 `MapAccess` 本来就按文档序交付。 +- TOML:用 crate 自己的 `preserve_order`。这里不花钱,TOML 解码本来就只在 std 下。 + +**写出去的方向不动**,而且那是有意的(`core/src/val/ser.rs` 的模块注释): +`stringify` 排序,所以同一份数据写两次字节相同、diff 得动,JSON 本身也不规定键序。 +读与写不冲突:写强加一个顺序让字节稳定,读报告它拿到的顺序。 + +lkrt 那边跟着改了同一份(它的注释本来就写着"和 VM 逐字节一致"), +第一版只改了 VM,`vm_native_sweep` 当场报 `yaml_toml.lk` 两个后端 stdout 不一致 —— +这正是那条门禁存在的理由。 + + +## 容器字面量在四个位置有三个答案(2026-08-21 裁决) + +容器在元素类型上是**不变**的:一次加宽就是一个别名,宽的那个名字能写进窄的 +那个名字不允许的元素(`a_container_cannot_be_widened_at_its_element_type` 举了 +完整的例子)。字面量是例外:它是新建的,没有第二个名字,所以按元素逐个协变检查。 + +例外这条以前写了两份——`let` 语句一份、调用实参一份——剩下两个位置一份都没有: + +| 写法 | 修复前 | +| --- | --- | +| `let f: List = [1];` | 接受 | +| `take([1])`,`fn take(v: List)` | 接受 | +| `S { f: [1] }`,`f: List` | 拒绝 | +| `fn f() -> List { return [1]; }` | 拒绝 | + +同一个写下来的值,四个位置三个答案。规则收成 `TypeChecker::value_fits` 一处, +四个位置都用它;两份副本删掉。`return` 那处在记录返回类型时直接采用声明类型 +(`record_return` 的调用点同时看得见表达式和声明),所以 `pop_return_frame` 仍然 +是一串普通类型。 + +不变性没有松:变量在四个位置仍然全部拒绝,元素类型不合的字面量 +(`S { f: [1.5] }`,`f: List`)也仍然拒绝。 + +机器整数的字面量规则(`let x: u8 = 5` 不需要写 `5 as u8`)是同一条裂缝的另一半, +缺的是同样那两个位置,一并并进 `value_fits`。越界仍然是拒绝——有范围正是定宽的 +意义。三处的越界文案不同(`let` 那处会说"literal 300 is out of range for u8", +另两处说类型不匹配),都准确,没有统一。 + +把剩下的位置也走了一遍,又缺两个:**重新赋值**(`xs = [1]`)和**字段写** +(`s.f = [1]`)。写进去和第一次绑定是同一件事,两处都改用 `value_fits`。 + +规则本身还漏了一层:它比较的是**类型**,只往下看一级就停,所以 +`let xs: List> = [[1]];` 和 `let xs: List = [5];` 被拒——这两句 +对**变量**来说都成立,对字面量都不成立。改成由**表达式**驱动、递归下降: +元素类型取自已经推好的结果(同构字面量是 `List`,异构的是 `Tuple`),所以没有 +任何东西被重新检查,只有豁免往下走。`Type::container_literal_fits{,_with}` 因此 +没有调用者了,删掉。 + +## 一段文本只能有一种键表示(2026-08-21) + +字符串键短的时候内联存(`ShortStr`),长的时候放在 `Arc` 后面(`String`)。 +`RuntimeMapKey` 派生 `Eq`/`Hash`,所以同一段文本的这两种**是两个不同的键**。 +选哪种必须只由文本决定——而"把有类型的字符串 map 提升成通用载体"这条路上, +每个键都被写成了 `String`,短的也是: + +```lk +fn put(m, k, v) { m[k] = v; } +let m = {"a": 1}; +put(m, 3, 9); // 提升成通用 map +println(m); // {"a":1,3:9} +println(m.len()); // 2 +println(m["a"]); // 1 +println("a" in m); // 修复前:false +println(m.has("a")); // 修复前:false +println(m.delete("a"));// 修复前:nil,而且没删掉 +``` + +`[]` 和 `.get()` 之所以还对,是因为 `get_str` 会先试 `ShortStr` 再试 `String` +——它把这个错盖住了,只盖住了自己那一条路。`in` / `has` / `delete` 由文本构造键, +拿到的是另一种,于是找不到。 + +规则收成 `RuntimeMapKey::from_text` / `from_shared` 两个构造器,工作区里每一处 +**由文本构造键**的地方都改用它们(提升、`entries()`、跨堆相等、模块导出表、 +反序列化、两个 mirror 辅助函数)。`get_str` 的双重探测删掉——文本决定表示, +没有第二种可试。 + +顺带修掉一个 stdlib 的错答:`net.udp` 的 `recv` 结果 map 用 `String("data")` +和 `String("addr")` 建键,两个都短。`r["data"]` 靠双重探测侥幸能读, +`"data" in r` 一直是 `false`。 + +## `a[i]` 和 `a.i` 在编译器眼里是同一个东西(2026-08-21) + +两者解析成同一种节点——一次带键的访问——区别全在键上:成员名**永远是字符串 +字面量**(`a.f` → `Access(a, Literal("f"))`),下标是任意表达式 +(`a[i]` → `Access(a, Var("i"))`)。 + +编译器取成员名的那个函数把裸 `Var` 也当成成员名: + +```lk +let fs = [|| 7, || 8]; +let i = 1; +println(fs[0]()); // 7 —— 0 不是标识符,所以走对了 +println(fs[i]()); // 修复前:Error: List has no method 'i' +``` + +`lk check` 一直是对的(它按下标处理),错的只有编译器。这条规则在编译器里有**三份 +副本**,两份是取成员名的(`call.rs`、`loop_consts.rs`),第三份 +(`for_value_usage.rs`)干脆把两种形状都认——那个 `||` 正是这条规则从没被写清楚 +的痕迹。三份收成 `support::access_member_name` 一处,只认字面量。 + +工作区里除了下标解析,没有任何地方构造"成员是 `Var`"的访问节点,所以那条分支 +除了制造这个错以外没有用途。 + +`examples/syntax/index_by_variable.lk` 钉住:变量下标调用、map 键变量、链式、 +下标变量恰好和方法同名(`xs[len]` 对 `xs.len()`)、下标由调用算出。 + +## 浮点窄化到定宽整数,饱和的是**那个宽度**的范围(2026-08-21) + +`/` 是浮点除法,所以除零是 `inf`;而定宽整数装不下 `inf`,于是这条路会走到 +"把浮点转成定宽整数"。两端此前都是**先饱和到 `i64`、再按位掩掉**,所以超范围的值 +回来时是一个没有意义的位型: + +| 表达式 | 修复前 | 现在 | +| --- | --- | --- | +| `1 / 0`(`i32`) | `-1` | `2147483647` | +| `-1 / 0`(`i32`) | `0` | `-2147483648` | +| `1 / 0`(`u8`) | `255` | `255` | +| `0 / 0`(`u8`) | `0` | `0` | + +四条里有两条本来就是对的——纯属巧合:`u8` 的掩码正好留下 `i64::MAX` 的低字节, +就是 255。 + +规则:**整数**之间的 `as` 仍然回绕(`300 as u8` 是 44,这就是 `as` 的意思), +**浮点**到定宽整数**饱和到目标自己的范围**,`NaN` 是 0。和 Rust 的 `as` 一致。 + +一份实现两端共用:`lkrt_f64_to_machine_int`,VM 侧 `cast_to_machine_int` 镜像它。 +一次强制转换不值得为两份可能悄悄走偏的规则付代价。 + +顺带记一条**没有改**的:`u8 / u8` 是整数除法(`7/2` 得 3),`Int / Int` 是浮点除法 +(得 3.5)。`/` 因此在两种操作数上是两个意思。这是定宽整数照硬件建模的结果,两端 +一致,没有动。 + +## 声明的宽度要活过每一个边界,有四个没活过(2026-08-21) + +`250 + 10` 在 `u8` 上是 4。算术那条路问的是**寄存器**的宽度,而从调用或容器里出来 +的寄存器没有宽度——同一个值先绑到局部变量再用就是对的: + +| 写法 | 修复前 | +| --- | --- | +| `bytes[0] + 10`,`bytes: List` | 260 | +| `counts["k"] + 10`,`counts: Map` | 260 | +| `ret() + 10`,`fn ret() -> u8` | 260 | +| `s.f + 10`,`f: u8` | 4(本来就对) | + +移位是第四个:`<<` / `>>` 和 `~` 一样会脱出宽度(两个都在范围内的操作数做 +`&` / `|` / `^` 不会),但只有 `~` 被教过要回绕。`1 << 9` 在 `u8` 上答 512, +`1 << 31` 在 `i32` 上答 2147483648,而那一位是符号位。 + +三处都补上了:调用的声明返回宽度记到结果寄存器上;局部容器的声明**元素**宽度 +记一张表(`local_element_widths`,和 `local_struct_types` 是一对);移位加进 +"结果要回绕"的那一小组。 + +把"值可以从哪里出来"的十种位置逐条量了一遍,发现补一处漏一处不是错觉——**十种里 +七种是错的**:参数容器、参数 map、全局容器、结构体的容器字段、返回容器的调用、 +被捕获的容器、`for` 的循环变量、解构绑定。只有标量走的那三种是对的。 + +所以没有继续加表,改成一张表两种答案: + +```rust +enum RegisterWidth { Scalar(IntKind), Elements(IntKind) } +``` + +宽度是**静态**事实——`RuntimeVal::Int` 不带宽度,给它带上就是在这门语言最热的路径 +上加一次 tag 检查——所以它从声明处一路传到算术处,靠的是寄存器。一个寄存器要么装 +着那个数,要么装着它出来的那个容器,而这两件事的**产出点是同一批**:参数、`let` +注解、全局、捕获、声明的返回类型、结构体字段。分成两张表就会出现"记了标量那半、 +忘了元素那半",七个洞就是这么来的;合成一张,产出点必须说清楚它记的是哪种。 + +读出来那一侧对应加两条:索引/字段读把容器的 `Elements` 变成结果的 `Scalar` +(一处,覆盖所有容器),`ToIter` 把 `Elements` 原样传给快照(`for` 遍历参数容器 +时编译器证不出它已经是列表,会先 `ToIter`)。 + +十种位置现在全部一致,`a_declared_width_survives_a_call_a_container_and_a_shift` +钉住。 + +**仍然没有修的那一半**:宽度这件事类型检查器全都算过了(它会说 +`bytes[0] + 10` 是 `u8`),编译器还是在重新推一遍。现在只剩一处推导而不是七处, +但根子还在——`Expr` 没有 span 也没有 id,检查器算出的类型没有地方挂,要么给 AST +加标识,要么让检查器改写 AST。两条都是重新设计。 + +## `use` 的四种写法,只有两种在检查成员(2026-08-21) + +模块不存在的成员应当在 `lk check` 时报错,而不是运行到一半说 "nil is not a +function"——那句话既没提模块也没提成员。这条规则本来就在,但只对 `use module;` 和 +`use module as alias;` 生效: + +| 写法 | 修复前 | +| --- | --- | +| `use math; math.nope()` | 检查时报错 | +| `use math as m; m.nope()` | 检查时报错 | +| `use { json } from encoding; json.nope()` | **通过检查,运行时 nil** | +| `use * as m from math; m.nope()` | **通过检查,运行时 nil** | + +第三种正是示例里用的写法(`encoding.json` 只能这样引进来),所以 +`json.encode(v)` 一路通过检查,跑起来才发现这个模块的方法叫 `stringify`。 + +原因:`use { a } from m;` 被当成"绑定成员,不绑定模块"——而**成员本身可以是模块**。 +`encoding.json` 就是,它被绑到裸名 `json` 上,而 `json` 自己不是任何已声明模块, +成员检查因此整条跳过。`use * as ns from m` 是同一件事换个写法,也漏了。 + +两处都改成注册别名(机制早就有:`add_imported_stdlib_module` + `resolve_stdlib_alias`), +报错时说的是**真实路径**:`` `encoding.json` has no member `nope` ``。 +`every_import_spelling_checks_its_members` 把五种写法的拒绝和三种写法的接受都钉住。 + +## 编辑器说程序坏了,而编译器说没有(2026-08-21) + +`lk check` 默认不报隐式 `Any`——那条检查要 `--strict` 才开,带着它的程序编译、 +运行都正常。而 LSP 无条件用 `TypeChecker::new_strict()`,并把它说的每一句都渲染成 +`DiagnosticSeverity::ERROR`。结果:**本仓库自己的三个示例** +(`general/recursive.lk`、`syntax/closure.lk`、`general/word_count.lk`)在任何 +LSP 客户端里都标红,而 `lk check` 接受它们。 + +编辑器和编译器对"什么是错误"的判断必须一致,否则红波浪线不再是信号。 + +修法不是把这条检查关掉——它在编辑器里是有用的建议——而是让**产出方**说清楚这是 +哪一类发现:`TypeError` 加一个 `lint: bool`,只有 `implicit_any_type_err` 置真。 +LSP 据此给 `WARNING` 和单独的 code `lk_type_lint`,真正的类型错误仍然是 `ERROR`。 + +标记放在产出方而不是消费方,是因为另一条路只有"按消息文本匹配"。 + +## 报错里的 token 说的是变体名,不是你打的字(2026-08-21) + +`format!("found {:?}", token)`——语句解析器每一条语法错误的结尾都是这么来的, +于是读者看到的是 Rust 枚举的变体名: + +| 你写的 | 修复前 | 现在 | +| --- | --- | --- | +| `let a = 1` 少分号 | `found Let` | ``found `let` `` | +| `for p in {"a":1} {}` | `found LBrace` | ``found `{` `` | +| `f: fn(Int) -> Int` | `found Fn` | ``found `fn` `` | +| `x!== 1` | `found Ne` | ``found `!=` `` | + +`token_lexeme` 一直存在,表达式解析器的一部分消息也在用它。五个位置改用它。 + +**同一趟里还查出一处真的漂移。** `stmt_parser` 有一份 `type_syntax::spelling` 的 +副本(`tokens_to_type_string` + `token_to_string`),文件上的 TODO 预言过"两份会 +分家"。逐 token 比对两个渲染器:字面量(字符串、整数、浮点、布尔、标识符)一致, +而**每一个关键字和多数运算符都不一致**——副本的 token 表里根本没有关键字,落到 +`format!("{:?}")`。所以经过那三个位置的类型拼写会印成 `Fn(Int) -> Int`、`Nil`。 + +副本删掉,`tokens_to_type_string` 转发给共享的那个;TODO 缩小成"给三个位置各加一个 +`StopAt` 变体,这个转发方法也能去掉"。 + +查法值得记:**先按 token 逐个比对两个实现的输出**,而不是读代码找差异。两个循环 +结构完全相同,差别只在它们各自调用的渲染函数里,读代码很容易看漏。 + +## `math.random()` 每个进程给同一串数(2026-08-21) + +``` +$ lk r.lk $ lk r.lk +0.9941414... 0.9941414... +0.0504114... 0.0504114... +``` + +生成器的重播种代码一直在,而且一直**没跑过**:状态从一个非零常量 `INITIAL` 开始, +而 xorshift 从非零状态永远产生不出 0,所以 `if seed == 0 { reseed() }` 这一支不可达。 +周一打出的三个数就是周二打出的三个数。叫 `random` 的函数不能这样。 + +改法就是那条 TODO 自己写下的那个:**主机上状态从 0 开始**,于是第一次调用去问时钟。 +裸机保持 `INITIAL`——它没有时钟,固定序列是那里的诚实答案,而不是假装。 + +零作为"尚未播种"的标记是安全的:xorshift 从非零状态产生不出零,所以这个值不会 +在流跑起来之后再出现。 + +**没有做的一半**:现在没有任何办法要一段*可重现*的随机流(`math` 里没有 `seed` +成员)。此前是所有人都只能拿到固定流,现在是所有人都只能拿到随机流;要两者可选 +需要新增一个 stdlib 成员,那是加特性不是修缺陷,单独定。 + +## REPL 里一个未标注参数被第一次调用钉死(2026-08-21) + +``` +> fn f(x) { return x; } +> f(1) +1 +> f("a") +Error: Cannot unify Int with String +``` + +同样三行写在文件里没有问题。原因:检查器在**一个程序检查完**之后,把解出的 +替换应用到它记录的所有声明上(`apply_substitutions_to_environment`,注释就写着 +"runs once, after the whole program")。REPL 每次输入都是一个独立程序,于是第二 +次输入解出的 `x = Int` 被写回 `f` 的签名,第三次输入就撞上了。 + +`FunctionSig::annotated` 已经写明了正确的原则:未标注参数的类型是**从函数体推 +导出来的**,不是源码作出的声明,所以调用点不拿它作要求。REPL 把「一次输入的推 +导」变成了「约束后续所有输入的声明」。 + +修法在 REPL 一侧:一个函数的签名由**声明它的那次输入**定下来。检查完一次输入 +后,本次输入没有重新声明的函数,签名和记录类型恢复成检查前的样子;本次输入声 +明的函数不动——它的定义和用法本来就是一起检查的,和文件里一样。这修好了 +`match.lk` 整个在 REPL 中的行为(它靠一个未标注参数匹配六种形状)。 + +**残留(未修)**:函数体本身把参数收窄时,那个收窄会随签名一起留下。 + +``` +> fn g(x) { return x * 2; } +> g("a") +Error: Cannot unify Int with String // 文件里这行是通过的 +``` + +文件更宽松是**有意**的(见 `FunctionSig::annotated` 的注释:`fn scale(x) { +return x * 2.5; }` 可能把 `x` 定成 `Int`,而据此拒绝 `scale(4.0)` 等于拿源码没 +说过的话去拒绝)。REPL 这里更严。原因未查明:调用点对未标注参数照样加约束,而 +文件里同样的两条约束(`T = Int` 来自函数体、`T = String` 来自调用)并没有报错, +求解器这处的不对称还没查清。 + +## 命名参数的默认值在**声明它的**作用域里读(2026-08-21) + +默认值是在**调用点**降级的,因为它可以引用这次调用前面的参数—— +`fn f(x: Int, {y: Int = x + 1})` 要用 `x`,而只有调用方手里有。为此调用点会 +临时把被调方的参数名绑好,但其余的名字此前会落到**调用方**当时的作用域里, +于是调用方碰巧有同名绑定就会被捕获: + +```lk +const LIMIT: Int = 7; +fn f({n: Int = LIMIT}) -> Int { return n; } +fn g() -> Int { let LIMIT = 99; return f(); } // 答 99 +``` + +顶层调 `f()` 得 7,从 `g` 里调得 99。单模块内、不涉及导入和并发的静默错答, +`lk check` 也不报。默认值属于**声明**,它能看见的只有被调方自己的参数(按声明 +顺序绑好的那些)和模块作用域。现在降级默认值时会把调用方的名字整体让开 +(`Compiler::take_name_environment`),模块级的表保持可见——那正是它该看见的。 + +门禁:`examples/syntax/named_default_scope.lk`,包含调用方同时遮蔽参数名 `x` +和模块级 `LIMIT` 的情形、同一作用域内的两次调用、以及嵌套调用。 + +**同一处仍未修**:跨模块调用根本用不上默认值。 + +```lk +// conf.lk +fn configure({host: String, timeout_ms: Int? = 1000}) -> String { … } +// 调用方 +use { configure } from "conf"; +configure(host: "a") // Error: missing required named argument `timeout_ms` +``` + +编译器的签名表(`collect_function_signatures`)只收当前程序的 AST,导入的函数 +没有签名,于是走通用的具名调用路径;而运行期那条路径 +(`write_named_args_to_frame*`)只有「哪些具名参数被传了」这一个信息,根本没有 +默认值的概念——`Function` 元数据里也不存默认值。 + +**几条路都试过了,只有一条成立(2026-08-21 实测):** + +- *把默认值当常量存进 artifact*:不行。语料里 `fn f(x: Int, {y: Int = x + 1})` + 说明默认值可以引用前面的**参数**,不是常量。 +- *把被调方的默认值表达式传给调用方去降级*:不行。那就是上面刚修掉的 + bug 换一层——表达式会在**导入方**的作用域里解析,被调方模块里的 `const` + 在那边可能根本不存在,或者是另一个同名的东西。 +- *用哨兵值标记「没传」,让被调方序言替换*:不行。显式传 `nil` 和不传是**两 + 回事**: + + ```lk + fn box(w: Int, {h: Int? = 100}) -> String { return "${w} ${h}"; } + box(1) // 1 100 + box(1, h: nil) // 1 nil + ``` + + nil 不能当哨兵,而语言里也没有别的「不可能出现的值」。 +- *在填参处直接调被调方的默认值函数*:不行。那时被调方的 + `RuntimeModuleState` 已经被取走(`borrowed_for_call`),再进去就是重入。 + +**在没修好之前,至少不要在运行期说假话(2026-08-22)。** 原先跨模块省略一个有默认值 +的具名参数,是在**运行期**报 `missing required named argument` —— 而那个参数明明 +不是必需的,消息本身是错的。现在这条在 **check 期**就拒绝,并说出真正的原因和出路 +(显式传这个参数)。签名多了一个 `SigOrigin`,只为这一条规则服务:`Imported` 的签名 +才受限。REPL 里跨输入调用也标成 `Imported` —— 每次输入本来就是一个模块,限制一模一样。 + +剩下的一条是**被调方序言 + 一个「哪些具名参数传了」的位掩码**:调用方多传一个 +隐藏参数,被调方在序言里对每个有默认值的具名参数判断该位,没传就地求值它自己 +的默认值。默认值因此在**声明它的模块**里求值(顺带把上面那条作用域修复变成结 +构上必然的),跨模块自然可用,而 nil 与「没传」也天然分得开。代价是**改调用约 +定**:编译器、`Function` 元数据、`ModuleArtifact` 版本、AOT 降级和所有调用路径 +都要跟。尚未动手。 + +## REPL 每次输入是一个模块,由此来的两条差异(2026-08-21) + +REPL 把每次输入编译成**自己的模块**并执行。这带来两条与文件不同的行为。 + +**一、结构体字段顺序(已修)。** 字段的声明顺序跟着类型走,而两条构造路径 +(`exec::container::declared_type` 和 `__lk_make_struct`)都从**正在执行的模块** +里读它。声明在一行、构造在下一行时,后者的模块里没有这个声明,于是退回字段 +映射自己的迭代顺序: + +``` +> struct Reading { zebra: Int, apple: Int, mango: Int, kiwi: Int, pear: Int, fig: Int } +> Reading { zebra: 1, apple: 2, mango: 3, kiwi: 4, pear: 5, fig: 6 } +Reading{apple:2,fig:6,kiwi:4,mango:3,pear:5,zebra:1} +``` + +写在同一行、写在文件里、或跨真正的 `use` 导入,同样的值都按声明顺序打印。现在 +会话把已声明的 struct 带进后续每次输入的模块(本次输入自己重新声明的优先)。 + +**二、trait 的默认方法体(已修)。** 默认方法体是在**解析期**、针对一个程序的 +语句列表,复制进那些没写它的 `impl` 里的。`trait` 在一次输入、`impl` 在下一次 +输入时,后者根本没见过默认体,检查器报 "Method 'tripled' required by trait +'Scaled' not implemented for type 'Rect'" —— 而这个方法本来就不必写。现在会话 +把已声明 trait 的默认体带进后续输入(`impl` 自己写了的方法仍然优先)。 + +注意这只解开了「找不到默认体」这一层;默认体如果做**动态派发的调用** +(`self.base()`),跨输入调用仍会撞上模块边界的派发限制(见 +`docs/vm-cross-module-dispatch.md`),那属于下面第三条。 + +**三、导入只带来了值,没带来类型(已修)。** 其它每个入口都会用 +`typ::seed_imported_signatures` 把导入声明的签名和类型喂给检查器(文件由 CLI 喂、 +编译由原生编译器喂、被当作导入加载的模块由 `execute_with_ctx_from` 喂),会话这条 +路没有。于是 `use { Pt } from "lib";` 只绑定了构造器,类型仍然未知,`Pt { x: 1 }` +被拒绝——而拒绝的消息恰好建议"按名导入它",也就是用户刚写过的那一行。 +`use * as m from "lib"; m.Pt { … }` 一直是好的,因为命名空间那条路不查这张表。 +现在会话按工作目录喂,和 `lk FILE` 一致。 + +**四、跨输入的函数改不动传进去的值(未修)。** + +``` +> fn push_it(xs) { xs.push(9); } +> let xs = [1]; +> push_it(xs); +> xs +[1] // 文件里是 [1,9] +``` + +列表、映射、结构体都一样,而且不报错。原因是**模块之间是 isolate 语义**:跨 +模块传值是结构化深拷贝,不是共享句柄。这条规则本身是对的,但 REPL 的一次会话 +在概念上是**一个程序**,不是一串模块,规则用错了地方。 + +要修得让会话共用一个堆:每次输入的模块对着同一个堆执行,句柄才跨输入有效, +`seed_module_globals` 也就不必把每个 runtime global 复制进新堆 +(`execute_compiled_module_with_ctx_full` 里现在是 `HeapStore::new()`)。 + +**真正的拦路石不是复制,是重入。** 直接共享一个 `RuntimeModuleState` 行不通: +调用一个跨模块函数会把状态从它的 mutex 里**整个取走**并置 +`borrowed_for_call`(`take_runtime_callable_state`),所以第 3 次输入去调第 1 次 +输入定义的函数时,会撞上正在执行的同一个状态,得到 +`ReentrantModule::AlreadyExecuting`。要共享,得先把**堆**从 +`RuntimeModuleState` 里拆出去(堆是会话级的,globals 槽和 `borrowed_for_call` +仍然按模块各一份)——这正是 CLAUDE.md 里 `val ↔ vm` 那条边界的问题。设计时要 +针对的约束是这一条。 + + +## 流(stream)不能跨模块边界,除非它不带堆值(2026-08-21) + +一个 stream 是**进程级注册表里的一个 id + 若干句柄**:`StreamValue.roots` 是堆 +引用,注册表为那个 id 保存的流水线里,`map`/`filter` 的回调也是堆引用。两者都属 +于**建它的那个堆**,而堆间复制两个都不改写——于是对面拿到一个 id,它的回调指向 +一个它读不了的堆。 + +结果取决于收集器什么时候跑:回调句柄越界时程序直接死在 +`heap object 102 out of bounds`;若中间发生过一次收集、那个槽被别的对象占了, +过滤器就被**静默跳过**——该给 `[16,25,36]` 的地方回了 `[1,2,3,4,5,6]`。REPL 里 +每次输入自己是一个模块,所以这条在 REPL 中是常态。 + +现在**拒绝**这种跨越,并且只拒绝真正不安全的那些:`roots` 里含有堆句柄 +(`RuntimeVal::Obj`)才拒绝。`stream.range(0, 5)` 和纯标量列表建的流没有堆 +roots,只是一个 id,照常跨。两条复制路径都要改—— +`runtime_callable::copy_runtime_value_with`(任务/跨模块传值)和 +`exec::imports`(导入,也是 REPL 走的那条)。 + +**没有修的是什么**:让它真正能跨,得把注册表里那条流水线**改写**到目标模块去 +(回调提升成携带模块的 callable,roots 复制进目标堆)。注册表在 stdlib 里,而 +`core` 不能反向依赖 stdlib,所以这需要一个由 stream 模块向 core 注册的 +"把这个流复制到另一个堆"的钩子。是个设计,不是一行。 + + +## `let` 的模式是**要求**,不是提问(2026-08-22) + +`match` 的一条 arm 在**提问**,所以检查器不拿它去约束被匹配的值——这是对的,否则 +多条 arm 会互相报冲突。但 `let` 的模式是**要求**:程序在断言这个形状。这两者此前 +用的是同一套(其实是:`let` 的模式根本没做类型检查),于是下面这些一路跑到运行期 +才 `Pattern does not match value`,而 `lk check` 一声不吭——它被文档描述为"执行器 +跑的同一套检查"。 + +现在在 check 期拒绝的,只有两类**从类型就能判定不可能**的: + +- **字面量模式**:`let 1 = 2;` 和 `let 1 = 1;` 都拒绝。`let` 是用来绑定名字的, + 而字面量一个都不绑——它只有"匹配"和"失败"两种结局。要断言就写 + `assert(x == 1)`。 +- **对标量解构**:`let [a] = 5;`、`let {x: v} = 5;`。`Int`/`Float`/`Bool`/`Nil` + 和定宽整数没有部件可绑。`String` **不在**此列:用列表模式解构字符串是按字符拆, + 检查器本来就这么建模。 + +**故意没有扩大**:形状只有运行期才知道的解构仍然照常,失败时在运行期抛——那是 +语言的设计。`let [a, b] = f();`(f 返回 `List`)、`let [1, b] = xs;`(嵌套 +字面量,是对某一位的断言)都还是合法的。两个方向都进了 +`cli/tests/check_oracle_test.rs` 的表:拒绝的四条,和它们旁边那三条必须继续接受 +的邻居——更宽的规则会把它们一起带走。 diff --git a/docs/stdlib.md b/docs/stdlib.md index 9ea250eb..a4903546 100644 --- a/docs/stdlib.md +++ b/docs/stdlib.md @@ -14,13 +14,153 @@ exports. - `os` is intentionally narrow: platform and clock helpers only. - `env`, `path`, and `process` split out environment lookup, path manipulation, and process execution/state. -- `encoding` is a parent namespace for data formats and byte/text encodings: - `json`, `yaml`, `toml`, `base64`, `hex`, and `url`. +- `encoding` is a parent namespace for data formats and byte/text encodings. + Every codec under it is a **pair**: `json`/`yaml`/`toml` have `parse` and + `stringify`, `base64`/`hex` have `encode` and `decode`, `url` has + `encode_component` and `decode_component`. A parser without its serializer is + half an operation — `stringify` was missing, so a script could read a config + and change it but not write it back. - Concurrency is Go-shaped (see `docs/concurrency.md`): the `go` statement / - `spawn` global start goroutines, `chan` owns channel operations, and - `task` owns task management (`await`, `try_await`, `join_all`, `sleep`). + `spawn` global start goroutines, `chan` owns channel operations — the whole + surface, blocking (`send`/`recv`) as well as polling (`try_send`/`try_recv`), + because `use chan;` shadows the `chan` global and a module that is only half + there sends the reader back to unqualified globals — and + `task` owns task management (`await`, `try_await`, `join_all` — which takes + either the tasks or one list of them — and `sleep`). Failures raise (v2 error model) — there are no `[ok, value]` pairs. +## Method Naming + +One operation, one name, across every container. The rules, and the reason each +exists — they are what a new container type should be checked against: + +| 操作 | 名字 | 谁有 | +|---|---|---| +| 成员 | `contains(value)` | List / Slice / Bytes / Str / Set | +| 键成员 | `has(key)` | Map | +| 位置 | `index_of(needle)`,找不到给 nil | List / Slice / Bytes / Str | +| 读一个 | `get(index)`,越界给 nil | List / Slice / Bytes / Str / Map | +| 窗口 | `slice(start[, end])`,**起止**不是起点+长度 | List / Slice / Bytes / Str | +| 前/后 n 个 | `take(n)` / `skip(n)` | List / Slice / Bytes / Str | +| 两端 | `first()` / `last()` | List / Slice / Bytes / Str | +| 删一个 | `delete(key)` | Map / Set | +| 清空 | `clear()`,原地,答容器本身 | List / Map / Set | + +**列表的可变方法一律原地改**,答复只有两种:改完的**列表本身**(所以 +`xs.push(1).push(2)` 能链),或者**被取出来的那个元素**。 + +| 方法 | 答复 | +|---|---| +| `push(v)` / `insert(i, v)` | 列表本身 | +| `pop()` / `remove_at(i)` | 被取出的元素(空列表 `pop()` 给 nil) | +| `set(i, v)` / `map.set(k, v)` / `clear()` | 容器本身 | +| `last()` / `first()` / `get(i)` | 只读,不改列表 | + +`Set` 的 `add` / `delete` 是**有理由的例外**:集合没有"另一个值"可以 +交回(你交进去的就是那个值),能说的只有"是不是新的 / 在不在",所以 +它们答 Bool。 + +曾经这里是三套约定:`push`/`set` 原地改,`insert` 复制一份返回新列表, +`remove_at` 复制一份返回 `[新列表, 旧值]` 二元组(全语言唯一这个形状, +而那个"新列表"没人持有),`pop` 则是 `last` 的逐字重复、根本不弹出。 +于是"加一个元素会不会改变这个列表"有两个相反的答案。 + +`has` 不是 `contains` 的同义词:对 map 来说 "contains" 说不清问的是键 +还是值,所以键成员单独一个名字。这是有理由的区分,不是历史遗留。 + +`slice` 取**起止**是硬规则。曾经 `String` 只有 `substring(start, length)`, +于是 `xs.slice(1, 3)` 和 `s.substring(1, 3)` 从同样的数字里切出不同的 +窗口 —— 同形的调用,不同的语义,是陷阱不是特性。`substring` 与 `find` 两 +种拼写(方法与模块函数)都已删除,统一为 `slice` 与 `index_of`;模块的 +`string.index_of` 多一个可选的起始位置,那是方法形式没地方放的东西。 + +`string` 模块的每个成员都是**方法的拼写**,而不是第二份实现:模块函数体 +就一句 `forward("name", …)`,把第一个实参当 receiver 交给 +`core_methods` 的同名臂。这条规则从 2026-07-31 起是完整的 —— 在那之前 +`capitalize`/`title`/`count`/`strip`/`strip_prefix`/`strip_suffix`/ +`pad_left`/`pad_right`/`format` 九个只有模块拼写,`get`/`first`/`last`/ +`take`/`skip`/`bytes` 六个只有方法拼写,两边各写各的地方就是漂移的来源 +(`count("")` 一边按字节数一边按字符数,差了两倍)。 + +`format` 是这批里最后一个两种拼写都还落回 VM 的成员(它是变参,实参类型还 +各不相同)。它现在按**编译期展开**降低,走的正是 `println("a {} b", x)` 早就 +在走的那条路 —— `format_parts` 一份代码同时服务两者,所以"多余的 `{}` 保持 +字面、多余的实参空格分隔追加"这套规矩不会在两种写法之间漂。代价是模板必须是 +常量:模板由运行时算出来的 `t.format(x)` 照旧回落,和 `println(t, x)` 一样。 + +`string.char_at` 因此改叫 `string.get`:元素访问在每个序列载体上都拼作 +`get`(`xs.get(i)`、`bytes.get(b, i)`、`s.get(i)`),第三个名字也意味着 +第三套规矩 —— `char_at` 拒绝负数,而 `s[-1]`、`s.get(-1)` 和原生的 +`str.char_at` 符号都从末尾往回数。`byte_at` 保留原名,因为它答的是**字节**, +不是元素。 + +`bytes` 模块同样是纯转发(2026-07-31 补齐):`len`/`is_empty`/`get`/`slice`/ +`to_list` 曾经模块和方法各一份实现,而 `slice` 已经漂了 —— `bytes.slice(b, 2, 1)` +报错,`b.slice(2, 1)` 答 `Bytes([])`。现在只有方法侧那一份,答案是截断的那个 +(和 `"abcde".slice(-1, -3)`、`xs.slice(-1, -3)` 一致)。`to_string_utf8`/ +`to_string_lossy`/`concat` 补上了方法拼写,`contains`/`index_of`/`first`/`last`/ +`sum`/`min`/`max`/`take`/`skip` 补上了模块拼写;带回调的 `map`/`filter`/`reduce` +不进 `bytes` 模块,它们的模块拼写在 `iter` 里(`iter.map(b, f)` 本来就能用)。 + +两个构造函数是"方法名和成员名不一样"的仅有情况,写下来免得被当成疏漏: +`bytes.from_string(s)` **就是** `s.bytes()`,`bytes.from_list(xs)` 就是新加的 +`xs.to_bytes()` —— receiver 是 String / List,方法自然长在那边。 + +`string.to_int` / `string.to_float` 是这条规则的例外,且是有意的:它们收 +`String | Number | Bool`,第一个参数不是 String,所以它们是**转换函数**而 +不是字符串方法,没有 receiver-first 的方法拼写。 + +`Set.has` 也已删:它是 `contains` 的纯别名。`Map.has` 留着 —— 见上面 +那条,它问的是键,不是同义词。`bytes.eq(a, b)` 同样已删:它逐字节就是 +`a == b`,而运算符不需要一个模块函数替身。 + +方法的**元数以 `core/src/typ/builtin_method_sig.rs` 的声明为准**: +分发前按它校验,所以实现里再写一份 arity 守卫是够不到的。三次漂移 +(`bytes.slice`、`map.get`、`str.slice`)都是因为声明和实现各写各的。 + +## 文本 → 数字 + +`string.to_int(value[, base])` 与 `string.to_float(value)` 是把 **String** 读成 +数字的地方,也顺带做数字之间的转换。 + +在这之前语言里**没有**这条路:两个函数都只收 `Number | Bool`,给个 `"42"` +直接类型报错;`"42".to_int()` 不存在;全局也没有 `int()`/`float()`。也就是说 +读一行配置、切一段 CSV、取一个命令行参数,到"变成数字"这步全是死路 —— 而 +唯一看起来像答案的名字明确拒绝字符串。 + +两种失败,分得很清楚: + +- **文本不是数字 → `nil`。**「这行是不是数字」问的是输入,不是程序错误, + 所以用值回答,配 `??` 或 `!` 用,和 `index_of` 一个形状。 +- **Float 没有对应的 Int → raise。** NaN、无穷、超出 `i64` 范围都是程序错误。 + Rust 的 `as` 会给 `0` 或 `i64::MAX` —— 一个装成正确答案的错误答案。 + +首尾空白会被 trim:从文件读的一行带着换行,`"42\n"` 和 `"42"` 在任何读者 +眼里是同一个答案。`base` 取 2–36,符号写在前面(`to_int("-ff", 16)`)。 +`to_float` 认 `"nan"` / `"inf"` / `"-inf"`,那是 Float 有而 Int 没有的值。 + +## `datetime.format` / `datetime.parse` 是一对 + +`parse` 接受 **`format` 能写出来的一切**:完整日期时间、只有日期、只有时间。 +少的那一半按 `format` 丢掉时的默认补 —— 只有日期 = 当天 UTC 零点,只有时间 = +epoch 那天的那个时刻。 + +此前 `parse` 只试 `NaiveDateTime`(必须同时有日期和时间),于是这一对**不能 +往返**:`format(t, "%Y-%m-%d")` 给出 `1970-01-02`,拿同一个 format 串 parse 回去 +报 "input is not enough for unique date and time" —— 一句在讲 chrono 自己解析器 +的内部要求,而程序从没提过 chrono。format 串是调用方对**两侧文本**的描述,两个 +方向必须对它的含义达成一致。 + +不匹配时的报错现在说 `` `zz` does not match the format `%Y-%m-%d` ``。 + +## 数值哈希是 i64 位模式 + +`hash.crc32` / `hash.fnv64` 返回 `Int`,而 `Int` 是 i64:`fnv64("")` 的 FNV +offset basis 是 `0xcbf29ce484222325`,超过 `i64::MAX`,所以看到的是负数。这与 +语言"Int 溢出回绕"的规则一致(`docs/semantics.md`),不是缺陷 —— 但拿它做桶 +下标要先取绝对值或按位掩码。要文本形式的哈希用 `sha256` / `sha1`,它们返回 +十六进制串。 + ## Common Modules - `hash`: `sha256`, `sha1`, `crc32`, `fnv64`. @@ -105,6 +245,44 @@ fn clamp(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result cargo test -p lk-cli --test aot_fuzz_differential_test`. + +## 计时断言:单样本是硬币,松预算是摆设 + +两种坏法都在这个仓库里出现过,而且互为对方的"修法": + +- **单样本 + 紧预算 = flaky。** `compiling_many_functions_stays_linear` 断言 + `large < small * 3`,在 `cargo test --workspace --all-features`(几十个测试线程 + 抢核)里失败过一次,单跑连过 5 次。一条会因为**别的原因**变红的门禁比没有门禁 + 更糟 —— 它教会所有人重跑,而重跑的习惯一旦养成,真回归也会被重跑掉。 +- **单样本 + 松预算 = 摆设。** `lsp/tests/perf_latency_test.rs` 的 6 条延迟断言, + 余量是 67x 到 **2381x**(`semantic_tokens(example workspace main)` 实测 21µs, + 预算 50ms)。比被测量高三个数量级的预算不可能失败,所以它什么也没说 —— LSP 慢 + 10 倍(交互工具"跟手"和"不跟手"的分界)六条全过。 + +两种都源于同一个选择:**用一次墙钟采样做判据**。噪声让你不敢收紧预算,松预算又 +让断言失去意义。 + +规矩:**取 N 次里的最小值,再把预算收到观测值的约 10 倍。** 最小值是这里正确的估 +计量 —— 调度噪声、缺页、降频只会让某次更慢,所以最小的样本最接近被测的工作量。噪 +声去掉之后,预算才敢收到"能抓住 10 倍回归、抓不到机器间差异"的位置。 + +**第三条(2026-08-05 补):取最小值治不了持续竞争,所以墙钟断言不能待在正确性套 +里。** "取五次最小值"消的是**单次**采样被打断;整套并行跑时五次一起慢,最小值同样 +被抬高。`test_analyze_complex_program_latency` 隔离下是 1.16ms(预算 10ms,余量 +8.6 倍),在一次 `cargo test --workspace` 里报了 11.99ms —— 慢约 10 倍,正好把 10 倍 +的预算吃穿。 + +试过用"机器有多快"的标定循环去缩放预算,**否掉了**:标定循环自己在同一台机器同一次 +运行里的离散度就有 3.6 倍,10ms 的预算会被撑成 184ms,那就回到了"不可能失败"的摆设。 + +现在:`lsp/tests/perf_latency_test.rs` 的六条全部 `#[ignore]`,由 `check.yml` 里 +`LSP latency budgets` 这一步单独、单线程跑。墙钟断言是性能门禁,这个仓库的性能门禁 +本来就单独跑(`bench/run_workload_bench.sh`)。 + +**第四条(2026-08-06 补):比值型断言比预算型更脆,取最小值不够。** +`compiling_many_functions_stays_linear` 不设预算,它比的是"输入翻倍、耗时不能翻 +三倍",本来正是为了躲开墙钟。但它是**两个**测量的比值,两边各取五次最小值仍然会 +被竞争打穿 —— 一边碰上快样本、另一边没碰上,比值就炸,而两边的最小值各自都是干净 +的。它在 2026-08-05 已经因此红过一次并加了 min-of-5;2026-08-06 在 +`cargo test --workspace --all-features` 与一个 `cargo clippy` 抢核时又红了(那次 +0.68s,单独跑 0.17s),之后单独连过八次,其中四次还并行着三个构建。 + +所以这条也 `#[ignore]`,由 `check.yml` 的 `Compiler scaling budget` 单独单线程跑。 +判据:**只要断言里出现墙钟,不管是预算还是比值,就不进正确性套。** + +配套两条: + +- 把**观测值和日期**写在预算旁边(`// Observed 3.3ms (debug, 2026-08-01).`), + 预算漂成摆设时看得见。 +- 新加或收紧一条计时断言之后**反向验一次**:把预算调到实测值以下,确认它真的会 + 红。一条从没红过的断言和一条不可能红的断言,从外面看是一样的。 + +## What the fuzzer still cannot reach + +Its vocabulary is fixed, so it finds regressions in shapes it already knows, not +new categories. Known gap: every list it generates is a `List`, so it +cannot produce two different list carriers both flowing into functions that +mutate them — which is the shape of the open typed-list boxing divergence +recorded in `docs/semantics.md`. Adding a second carrier is worth doing *after* +that is fixed; until then it would only make the gate red. + +**The carrier is what keeps being wrong.** The generator learned to build a +shared top-level container after a `List` global miscompiled — and then +reproduced *that* carrier only. A `Bytes` global went on miscompiling +(`b[n]` inside a function printed `98` interpreted and `runtime type error` +compiled, for any index) until 2026-08-01, because the vocabulary knew `List` +and `Map` and nothing else. Shared `Bytes` and `Set` globals are generated now, +read from inside the helpers with a *runtime* index — the constant-index case +lowered correctly even while that one did not. + +Widening the vocabulary is only worth anything if the new shapes can fail: +re-introducing the bug turned three seeds red, and restoring the fix turned them +green again. A vocabulary addition that has never failed is in the same position +as the timing budgets above — do the same negative verification. diff --git a/docs/vm-cross-module-dispatch.md b/docs/vm-cross-module-dispatch.md index ff5cc23f..3d06ed5d 100644 --- a/docs/vm-cross-module-dispatch.md +++ b/docs/vm-cross-module-dispatch.md @@ -11,7 +11,42 @@ Each entry records **where its body lives** (`MethodImpl`): | variant | body | called against | | --- | --- | --- | | `Local { module, function }` | function index in `module` | the executing state — directly when `module` *is* the executing module, otherwise as described below | -| `Imported(RuntimeCallable)` | function index in the callable's own module | that module's own `Arc>`, with arguments and the result marshalled across heaps | +| `Imported(RuntimeCallable)` | function index in the callable's own module | that module's own `Arc>`, with arguments and the result marshalled across heaps — unless that module is already executing, see below | + +### `Imported` when the module is already on the stack + +Borrowing a module's state means *moving* it out of its mutex until the call +returns, so nothing can enter that module again in the meantime. Two ordinary +programs do exactly that: + +```lk +// shape.lk — a method calling another method on self +impl Sq { fn area(self) -> Int { return self.side * self.side; } + fn twice(self) -> Int { return self.area() * 2; } } +``` + +```lk +// a.lk — out to another module and back +impl A { fn base(self) -> Int { return self.v; } + fn viab(self) -> Int { return helper(self); } } // helper() calls x.base() +``` + +Both failed with `module expected 83 globals, got 0`: the re-entering call got +the `Default::default()` placeholder left in the mutex, which is indistinguishable +from a real state that happens to be empty. The placeholder now carries +`borrowed_for_call`, so "in use" is something it says about itself rather than +something a later length check infers. + +Knowing that, the two cases take the two paths that already existed: + +- **The module is the one executing** (`twice` → `self.area()`) — no borrowing is + needed at all; the live state *is* that module's. Runs like a local closure. +- **The module is elsewhere on the stack** (`viab` → `helper` → `base`) — run it + the way any foreign body is run, below: current heap, globals seeded to the + declaring module's shape. That needs the module, not the module's state. + +A body that *writes* a module global still cannot take the second path, and is +refused by name rather than writing into a table that is about to be discarded. ## The hard case: an `impl` reached from another module's frame @@ -80,3 +115,35 @@ Two things this deliberately does not do: correctness. Narrowing it wants the call-site target facts the analysis already computes (`PerfCallTargetKind`): a call proven to reach a *native* cannot execute a `SetGlobal` at all. + +## A function value crossing a module boundary (2026-07-31) + +`apply(double, 5)`, where `apply` came from another file, is the same question +asked about an ordinary function rather than an impl method — and it used to be +refused outright, because a bare closure is a `function_index` into *its own* +module's table. + +It is now promoted at the crossing: the value becomes a `RuntimeCallable` +carrying its defining module, so the index still means what it meant. The +executor is the only place that knows which module the arguments come from, so +it is what supplies it (`ClosureCopy::Promote`); a crossing that cannot name a +source module — a channel payload, a stdlib HOF re-entering the VM — still +refuses, and says that is why. + +What the promoted callable does **not** get is that module's live state: the +caller's state belongs to a frame further down the Rust stack and cannot be +taken while it is running. It gets a fresh, empty one instead — arguments copied +in, result copied out, which is what every `RuntimeCallable` call already does. + +That leaves the globals, and the same three answers as above, from the same +walk (`analysis::function_global_use`, one implementation for both callers): + +- reads a global → refused, naming the global (a fresh state has nil there, and + nil is a wrong answer, not a slow one); +- writes a global → refused (the write would land in a table nobody reads); +- makes a call the walk cannot follow → refused **as its own case**, not as a + write. `println` is the everyday one. Nothing is known to be wrong there, only + unproven, and reporting a write would be a guess stated as a fact. + +The captures come along, copied into the callable's own heap, so a capturing +`|x| x + n` crosses too. diff --git a/ecosystem/tree-sitter-lk/grammar.js b/ecosystem/tree-sitter-lk/grammar.js index 460c5b0b..b15f4b83 100644 --- a/ecosystem/tree-sitter-lk/grammar.js +++ b/ecosystem/tree-sitter-lk/grammar.js @@ -33,6 +33,10 @@ module.exports = grammar({ [$.parenthesized_expression, $._argument_list], [$.or_pattern], [$.or_pattern, $.guarded_pattern], + // `p if x | y`: the `|` is the guard's bitwise-or, not the or-pattern's + // separator — `parse_guard_pattern` parses the guard as a whole + // expression, so it consumes the `|` before `parse_or_pattern` sees it. + [$.binary_expression, $.guarded_pattern], [$.index_access, $.list_expression], [$.index_access, $.match_arm], [$.if_statement], @@ -55,7 +59,7 @@ module.exports = grammar({ ], precedences: $ => [ - ['binary_or', 'binary_and', 'binary_comparison', 'binary_range', 'binary_add', 'binary_mul', 'binary_unary', 'binary_nullish', 'binary_ternary'], + ['binary_or', 'binary_and', 'binary_bit_or', 'binary_bit_xor', 'binary_bit_and', 'binary_comparison', 'binary_shift', 'binary_range', 'binary_add', 'binary_mul', 'binary_unary', 'binary_nullish', 'binary_ternary'], ], word: $ => $._word_identifier, @@ -260,7 +264,7 @@ module.exports = grammar({ // ── Unary ───────────────────────────────────────────────────────── unary_expression: $ => prec.left('binary_unary', seq( - field('operator', '!'), + field('operator', choice('!', '~')), field('operand', $._expression), )), @@ -280,6 +284,15 @@ module.exports = grammar({ prec.left('binary_comparison', seq(field('left', $._expression), field('operator', choice('==', '!=', '<', '>', '<=', '>=')), field('right', $._expression))), prec.left('binary_and', seq(field('left', $._expression), '&&', field('right', $._expression))), prec.left('binary_or', seq(field('left', $._expression), '||', field('right', $._expression))), + // Bitwise. Below comparison and above the logical operators, which is + // where the parser puts them (`parse_bit_or` → `parse_bit_xor` → + // `parse_bit_and` → `parse_cmp`). Shifts are two adjacent comparison + // tokens in the lexer, so `<<`/`>>` are written out here rather than + // being single tokens. + prec.left('binary_bit_or', seq(field('left', $._expression), field('operator', '|'), field('right', $._expression))), + prec.left('binary_bit_xor', seq(field('left', $._expression), field('operator', '^'), field('right', $._expression))), + prec.left('binary_bit_and', seq(field('left', $._expression), field('operator', '&'), field('right', $._expression))), + prec.left('binary_shift', seq(field('left', $._expression), field('operator', choice('<<', '>>')), field('right', $._expression))), ), // ── Nullish coalescing ──────────────────────────────────────────── @@ -445,7 +458,20 @@ module.exports = grammar({ type: $ => $._type, - primitive_type: $ => choice('Int', 'Float', 'String', 'Bool', 'Nil', 'Any'), + // The machine ints belong here because they take no parameters, the same + // way `Int` does. `Set`/`Tuple`/`Task`/`Channel`/`Box` do take one + // (`Set`), so they want a rule of their own next to `list_type` rather + // than a bare keyword here; until then they fall through to + // `type_identifier`, which still highlights as a type. + primitive_type: $ => choice( + 'Int', 'Float', 'String', 'Bool', 'Nil', 'Any', + // `Number` is `Int | Float`; `i64`/`f64` are second spellings of + // `Int`/`Float` — see `TYPE_SPELLINGS` in lk-values. One type, two + // names: the widthed one for code that is about widths, the plain one + // for code that is not. + 'Number', 'f64', + 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'isize', 'usize', + ), list_type: $ => seq('List', '<', $._type, '>'), diff --git a/ecosystem/tree-sitter-lk/src/grammar.json b/ecosystem/tree-sitter-lk/src/grammar.json index 8b154b85..56d4e3ef 100644 --- a/ecosystem/tree-sitter-lk/src/grammar.json +++ b/ecosystem/tree-sitter-lk/src/grammar.json @@ -893,8 +893,17 @@ "type": "FIELD", "name": "operator", "content": { - "type": "STRING", - "value": "!" + "type": "CHOICE", + "members": [ + { + "type": "STRING", + "value": "!" + }, + { + "type": "STRING", + "value": "~" + } + ] } }, { @@ -1135,6 +1144,147 @@ } ] } + }, + { + "type": "PREC_LEFT", + "value": "binary_bit_or", + "content": { + "type": "SEQ", + "members": [ + { + "type": "FIELD", + "name": "left", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + }, + { + "type": "FIELD", + "name": "operator", + "content": { + "type": "STRING", + "value": "|" + } + }, + { + "type": "FIELD", + "name": "right", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + } + ] + } + }, + { + "type": "PREC_LEFT", + "value": "binary_bit_xor", + "content": { + "type": "SEQ", + "members": [ + { + "type": "FIELD", + "name": "left", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + }, + { + "type": "FIELD", + "name": "operator", + "content": { + "type": "STRING", + "value": "^" + } + }, + { + "type": "FIELD", + "name": "right", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + } + ] + } + }, + { + "type": "PREC_LEFT", + "value": "binary_bit_and", + "content": { + "type": "SEQ", + "members": [ + { + "type": "FIELD", + "name": "left", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + }, + { + "type": "FIELD", + "name": "operator", + "content": { + "type": "STRING", + "value": "&" + } + }, + { + "type": "FIELD", + "name": "right", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + } + ] + } + }, + { + "type": "PREC_LEFT", + "value": "binary_shift", + "content": { + "type": "SEQ", + "members": [ + { + "type": "FIELD", + "name": "left", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + }, + { + "type": "FIELD", + "name": "operator", + "content": { + "type": "CHOICE", + "members": [ + { + "type": "STRING", + "value": "<<" + }, + { + "type": "STRING", + "value": ">>" + } + ] + } + }, + { + "type": "FIELD", + "name": "right", + "content": { + "type": "SYMBOL", + "name": "_expression" + } + } + ] + } } ] }, @@ -2157,6 +2307,54 @@ { "type": "STRING", "value": "Any" + }, + { + "type": "STRING", + "value": "Number" + }, + { + "type": "STRING", + "value": "f64" + }, + { + "type": "STRING", + "value": "i8" + }, + { + "type": "STRING", + "value": "i16" + }, + { + "type": "STRING", + "value": "i32" + }, + { + "type": "STRING", + "value": "i64" + }, + { + "type": "STRING", + "value": "u8" + }, + { + "type": "STRING", + "value": "u16" + }, + { + "type": "STRING", + "value": "u32" + }, + { + "type": "STRING", + "value": "u64" + }, + { + "type": "STRING", + "value": "isize" + }, + { + "type": "STRING", + "value": "usize" } ] }, @@ -4312,6 +4510,10 @@ "or_pattern", "guarded_pattern" ], + [ + "binary_expression", + "guarded_pattern" + ], [ "index_access", "list_expression" @@ -4395,10 +4597,26 @@ "type": "STRING", "value": "binary_and" }, + { + "type": "STRING", + "value": "binary_bit_or" + }, + { + "type": "STRING", + "value": "binary_bit_xor" + }, + { + "type": "STRING", + "value": "binary_bit_and" + }, { "type": "STRING", "value": "binary_comparison" }, + { + "type": "STRING", + "value": "binary_shift" + }, { "type": "STRING", "value": "binary_range" diff --git a/ecosystem/tree-sitter-lk/src/node-types.json b/ecosystem/tree-sitter-lk/src/node-types.json index 3a6a067a..785a0a43 100644 --- a/ecosystem/tree-sitter-lk/src/node-types.json +++ b/ecosystem/tree-sitter-lk/src/node-types.json @@ -210,6 +210,10 @@ "type": "%", "named": false }, + { + "type": "&", + "named": false + }, { "type": "*", "named": false @@ -230,6 +234,10 @@ "type": "<", "named": false }, + { + "type": "<<", + "named": false + }, { "type": "<=", "named": false @@ -245,6 +253,18 @@ { "type": ">=", "named": false + }, + { + "type": ">>", + "named": false + }, + { + "type": "^", + "named": false + }, + { + "type": "|", + "named": false } ] }, @@ -4887,6 +4907,10 @@ { "type": "!", "named": false + }, + { + "type": "~", + "named": false } ] } @@ -5287,6 +5311,10 @@ "type": "<", "named": false }, + { + "type": "<<", + "named": false + }, { "type": "<=", "named": false @@ -5311,6 +5339,10 @@ "type": ">=", "named": false }, + { + "type": ">>", + "named": false + }, { "type": "?", "named": false @@ -5355,6 +5387,10 @@ "type": "Nil", "named": false }, + { + "type": "Number", + "named": false + }, { "type": "String", "named": false @@ -5367,6 +5403,10 @@ "type": "]", "named": false }, + { + "type": "^", + "named": false + }, { "type": "_", "named": false @@ -5416,6 +5456,10 @@ "type": "export", "named": false }, + { + "type": "f64", + "named": false + }, { "type": "false", "named": false @@ -5440,6 +5484,22 @@ "type": "go", "named": false }, + { + "type": "i16", + "named": false + }, + { + "type": "i32", + "named": false + }, + { + "type": "i64", + "named": false + }, + { + "type": "i8", + "named": false + }, { "type": "if", "named": false @@ -5456,6 +5516,10 @@ "type": "integer_literal", "named": true }, + { + "type": "isize", + "named": false + }, { "type": "let", "named": false @@ -5521,10 +5585,30 @@ "type": "type", "named": false }, + { + "type": "u16", + "named": false + }, + { + "type": "u32", + "named": false + }, + { + "type": "u64", + "named": false + }, + { + "type": "u8", + "named": false + }, { "type": "use", "named": false }, + { + "type": "usize", + "named": false + }, { "type": "while", "named": false @@ -5544,5 +5628,9 @@ { "type": "}", "named": false + }, + { + "type": "~", + "named": false } ] \ No newline at end of file diff --git a/ecosystem/tree-sitter-lk/src/parser.c b/ecosystem/tree-sitter-lk/src/parser.c index 04361fa5..45d6c663 100644 --- a/ecosystem/tree-sitter-lk/src/parser.c +++ b/ecosystem/tree-sitter-lk/src/parser.c @@ -15,11 +15,11 @@ #endif #define LANGUAGE_VERSION 15 -#define STATE_COUNT 1558 -#define LARGE_STATE_COUNT 102 -#define SYMBOL_COUNT 226 +#define STATE_COUNT 1654 +#define LARGE_STATE_COUNT 120 +#define SYMBOL_COUNT 242 #define ALIAS_COUNT 0 -#define TOKEN_COUNT 100 +#define TOKEN_COUNT 116 #define EXTERNAL_TOKEN_COUNT 0 #define FIELD_COUNT 21 #define MAX_ALIAS_SEQUENCE_LENGTH 9 @@ -56,203 +56,219 @@ enum ts_symbol_identifiers { anon_sym_QMARK_LBRACK = 26, anon_sym_LBRACE = 27, anon_sym_BANG = 28, - anon_sym_STAR = 29, - anon_sym_SLASH = 30, - anon_sym_PERCENT = 31, - anon_sym_PLUS = 32, - anon_sym_DASH = 33, - anon_sym_EQ_EQ = 34, - anon_sym_BANG_EQ = 35, - anon_sym_LT = 36, - anon_sym_GT = 37, - anon_sym_LT_EQ = 38, - anon_sym_GT_EQ = 39, - anon_sym_AMP_AMP = 40, - anon_sym_PIPE_PIPE = 41, - anon_sym_QMARK_QMARK = 42, - anon_sym_DOT_DOT = 43, - anon_sym_DOT_DOT_EQ = 44, - anon_sym_QMARK = 45, - anon_sym_PIPE = 46, - anon_sym_match = 47, - anon_sym_EQ_GT = 48, - anon_sym_SEMI = 49, - anon_sym__ = 50, - anon_sym_if = 51, - anon_sym_spawn = 52, - anon_sym_chan = 53, - anon_sym_send = 54, - anon_sym_recv = 55, - anon_sym_select = 56, - anon_sym_case = 57, - anon_sym_default = 58, - anon_sym_Int = 59, - anon_sym_Float = 60, - anon_sym_String = 61, - anon_sym_Bool = 62, - anon_sym_Nil = 63, - anon_sym_Any = 64, - anon_sym_List = 65, - anon_sym_Map = 66, - anon_sym_DASH_GT = 67, - anon_sym_POUND = 68, - anon_sym_use = 69, - anon_sym_as = 70, - anon_sym_from = 71, - anon_sym_export = 72, - anon_sym_macro_rules = 73, - anon_sym_DOLLAR = 74, - anon_sym_COLON_COLON = 75, - anon_sym_EQ = 76, - anon_sym_AMP = 77, - anon_sym_let = 78, - anon_sym_COLON_EQ = 79, - anon_sym_PLUS_EQ = 80, - anon_sym_DASH_EQ = 81, - anon_sym_STAR_EQ = 82, - anon_sym_SLASH_EQ = 83, - anon_sym_PERCENT_EQ = 84, - anon_sym_else = 85, - anon_sym_while = 86, - anon_sym_for = 87, - anon_sym_in = 88, - anon_sym_fn = 89, - anon_sym_struct = 90, - anon_sym_type = 91, - anon_sym_trait = 92, - anon_sym_impl = 93, - anon_sym_return = 94, - anon_sym_break = 95, - anon_sym_continue = 96, - anon_sym_go = 97, - anon_sym_try = 98, - anon_sym_catch = 99, - sym_program = 100, - sym_identifier = 101, - sym_type_identifier = 102, - sym_boolean_literal = 103, - sym_nil_literal = 104, - sym_string_literal = 105, - sym_double_string = 106, - sym_single_string = 107, - sym_string_interpolation = 108, - sym__full_expression = 109, - sym__expression = 110, - sym_primary_expression = 111, - sym_parenthesized_expression = 112, - sym_call_expression = 113, - sym__argument_list = 114, - sym_named_argument = 115, - sym_field_access = 116, - sym_optional_field_access = 117, - sym_index_access = 118, - sym_optional_index_access = 119, - sym_list_expression = 120, - sym_map_expression = 121, - sym_map_entry = 122, - sym_struct_literal = 123, - sym_struct_field_init = 124, - sym_unary_expression = 125, - sym_unwrap_expression = 126, - sym_binary_expression = 127, - sym_nullish_coalescing_expression = 128, - sym_range_expression = 129, - sym_ternary_expression = 130, - sym_closure = 131, - sym__parameter_list = 132, - sym_parameter = 133, - sym_match_expression = 134, - sym_match_arm = 135, - sym_pattern = 136, - sym_wildcard_pattern = 137, - sym_literal_pattern = 138, - sym_identifier_pattern = 139, - sym_list_pattern = 140, - sym_map_pattern = 141, - sym_map_pattern_entry = 142, - sym_or_pattern = 143, - sym_guarded_pattern = 144, - sym_range_pattern = 145, - sym_spawn_expression = 146, - sym_chan_expression = 147, - sym_send_expression = 148, - sym_recv_expression = 149, - sym_select_expression = 150, - sym_select_case = 151, - sym__type = 152, - sym_primitive_type = 153, - sym_list_type = 154, - sym_map_type = 155, - sym_function_type = 156, - sym_optional_type = 157, - sym_union_type = 158, - sym_named_type = 159, - sym__statement = 160, - sym_attribute = 161, - sym_attributed_item = 162, - sym_import_statement = 163, - sym_import_item = 164, - sym_macro_export = 165, - sym_macro_export_item = 166, - sym_macro_definition = 167, - sym_macro_invocation = 168, - sym_macro_group = 169, - sym__macro_token = 170, - sym_let_statement = 171, - sym_define_statement = 172, - sym_assignment_statement = 173, - sym_compound_assignment_statement = 174, - sym_if_statement = 175, - sym_while_statement = 176, - sym_for_statement = 177, - sym_for_pattern = 178, - sym_for_pattern_entry = 179, - sym_function_definition = 180, - sym_function_params = 181, - sym_named_params_block = 182, - sym_named_param = 183, - sym_struct_definition = 184, - sym_struct_field = 185, - sym_type_alias_definition = 186, - sym_trait_definition = 187, - sym_trait_method = 188, - sym_trait_method_params = 189, - sym_trait_method_param = 190, - sym_impl_definition = 191, - sym_return_statement = 192, - sym_break_statement = 193, - sym_continue_statement = 194, - sym_expression_statement = 195, - sym_go_statement = 196, - sym_try_statement = 197, - sym_block = 198, - aux_sym_program_repeat1 = 199, - aux_sym_double_string_repeat1 = 200, - aux_sym_single_string_repeat1 = 201, - aux_sym__argument_list_repeat1 = 202, - aux_sym__argument_list_repeat2 = 203, - aux_sym_map_expression_repeat1 = 204, - aux_sym_struct_literal_repeat1 = 205, - aux_sym__parameter_list_repeat1 = 206, - aux_sym_match_expression_repeat1 = 207, - aux_sym_list_pattern_repeat1 = 208, - aux_sym_map_pattern_repeat1 = 209, - aux_sym_or_pattern_repeat1 = 210, - aux_sym_select_expression_repeat1 = 211, - aux_sym_function_type_repeat1 = 212, - aux_sym_union_type_repeat1 = 213, - aux_sym_attribute_repeat1 = 214, - aux_sym_attributed_item_repeat1 = 215, - aux_sym_import_statement_repeat1 = 216, - aux_sym_macro_export_repeat1 = 217, - aux_sym_for_pattern_repeat1 = 218, - aux_sym_for_pattern_repeat2 = 219, - aux_sym_named_params_block_repeat1 = 220, - aux_sym_struct_definition_repeat1 = 221, - aux_sym_trait_definition_repeat1 = 222, - aux_sym_trait_method_params_repeat1 = 223, - aux_sym_impl_definition_repeat1 = 224, - aux_sym_block_repeat1 = 225, + anon_sym_TILDE = 29, + anon_sym_STAR = 30, + anon_sym_SLASH = 31, + anon_sym_PERCENT = 32, + anon_sym_PLUS = 33, + anon_sym_DASH = 34, + anon_sym_EQ_EQ = 35, + anon_sym_BANG_EQ = 36, + anon_sym_LT = 37, + anon_sym_GT = 38, + anon_sym_LT_EQ = 39, + anon_sym_GT_EQ = 40, + anon_sym_AMP_AMP = 41, + anon_sym_PIPE_PIPE = 42, + anon_sym_PIPE = 43, + anon_sym_CARET = 44, + anon_sym_AMP = 45, + anon_sym_LT_LT = 46, + anon_sym_GT_GT = 47, + anon_sym_QMARK_QMARK = 48, + anon_sym_DOT_DOT = 49, + anon_sym_DOT_DOT_EQ = 50, + anon_sym_QMARK = 51, + anon_sym_match = 52, + anon_sym_EQ_GT = 53, + anon_sym_SEMI = 54, + anon_sym__ = 55, + anon_sym_if = 56, + anon_sym_spawn = 57, + anon_sym_chan = 58, + anon_sym_send = 59, + anon_sym_recv = 60, + anon_sym_select = 61, + anon_sym_case = 62, + anon_sym_default = 63, + anon_sym_Int = 64, + anon_sym_Float = 65, + anon_sym_String = 66, + anon_sym_Bool = 67, + anon_sym_Nil = 68, + anon_sym_Any = 69, + anon_sym_Number = 70, + anon_sym_f64 = 71, + anon_sym_i8 = 72, + anon_sym_i16 = 73, + anon_sym_i32 = 74, + anon_sym_i64 = 75, + anon_sym_u8 = 76, + anon_sym_u16 = 77, + anon_sym_u32 = 78, + anon_sym_u64 = 79, + anon_sym_isize = 80, + anon_sym_usize = 81, + anon_sym_List = 82, + anon_sym_Map = 83, + anon_sym_DASH_GT = 84, + anon_sym_POUND = 85, + anon_sym_use = 86, + anon_sym_as = 87, + anon_sym_from = 88, + anon_sym_export = 89, + anon_sym_macro_rules = 90, + anon_sym_DOLLAR = 91, + anon_sym_COLON_COLON = 92, + anon_sym_EQ = 93, + anon_sym_let = 94, + anon_sym_COLON_EQ = 95, + anon_sym_PLUS_EQ = 96, + anon_sym_DASH_EQ = 97, + anon_sym_STAR_EQ = 98, + anon_sym_SLASH_EQ = 99, + anon_sym_PERCENT_EQ = 100, + anon_sym_else = 101, + anon_sym_while = 102, + anon_sym_for = 103, + anon_sym_in = 104, + anon_sym_fn = 105, + anon_sym_struct = 106, + anon_sym_type = 107, + anon_sym_trait = 108, + anon_sym_impl = 109, + anon_sym_return = 110, + anon_sym_break = 111, + anon_sym_continue = 112, + anon_sym_go = 113, + anon_sym_try = 114, + anon_sym_catch = 115, + sym_program = 116, + sym_identifier = 117, + sym_type_identifier = 118, + sym_boolean_literal = 119, + sym_nil_literal = 120, + sym_string_literal = 121, + sym_double_string = 122, + sym_single_string = 123, + sym_string_interpolation = 124, + sym__full_expression = 125, + sym__expression = 126, + sym_primary_expression = 127, + sym_parenthesized_expression = 128, + sym_call_expression = 129, + sym__argument_list = 130, + sym_named_argument = 131, + sym_field_access = 132, + sym_optional_field_access = 133, + sym_index_access = 134, + sym_optional_index_access = 135, + sym_list_expression = 136, + sym_map_expression = 137, + sym_map_entry = 138, + sym_struct_literal = 139, + sym_struct_field_init = 140, + sym_unary_expression = 141, + sym_unwrap_expression = 142, + sym_binary_expression = 143, + sym_nullish_coalescing_expression = 144, + sym_range_expression = 145, + sym_ternary_expression = 146, + sym_closure = 147, + sym__parameter_list = 148, + sym_parameter = 149, + sym_match_expression = 150, + sym_match_arm = 151, + sym_pattern = 152, + sym_wildcard_pattern = 153, + sym_literal_pattern = 154, + sym_identifier_pattern = 155, + sym_list_pattern = 156, + sym_map_pattern = 157, + sym_map_pattern_entry = 158, + sym_or_pattern = 159, + sym_guarded_pattern = 160, + sym_range_pattern = 161, + sym_spawn_expression = 162, + sym_chan_expression = 163, + sym_send_expression = 164, + sym_recv_expression = 165, + sym_select_expression = 166, + sym_select_case = 167, + sym__type = 168, + sym_primitive_type = 169, + sym_list_type = 170, + sym_map_type = 171, + sym_function_type = 172, + sym_optional_type = 173, + sym_union_type = 174, + sym_named_type = 175, + sym__statement = 176, + sym_attribute = 177, + sym_attributed_item = 178, + sym_import_statement = 179, + sym_import_item = 180, + sym_macro_export = 181, + sym_macro_export_item = 182, + sym_macro_definition = 183, + sym_macro_invocation = 184, + sym_macro_group = 185, + sym__macro_token = 186, + sym_let_statement = 187, + sym_define_statement = 188, + sym_assignment_statement = 189, + sym_compound_assignment_statement = 190, + sym_if_statement = 191, + sym_while_statement = 192, + sym_for_statement = 193, + sym_for_pattern = 194, + sym_for_pattern_entry = 195, + sym_function_definition = 196, + sym_function_params = 197, + sym_named_params_block = 198, + sym_named_param = 199, + sym_struct_definition = 200, + sym_struct_field = 201, + sym_type_alias_definition = 202, + sym_trait_definition = 203, + sym_trait_method = 204, + sym_trait_method_params = 205, + sym_trait_method_param = 206, + sym_impl_definition = 207, + sym_return_statement = 208, + sym_break_statement = 209, + sym_continue_statement = 210, + sym_expression_statement = 211, + sym_go_statement = 212, + sym_try_statement = 213, + sym_block = 214, + aux_sym_program_repeat1 = 215, + aux_sym_double_string_repeat1 = 216, + aux_sym_single_string_repeat1 = 217, + aux_sym__argument_list_repeat1 = 218, + aux_sym__argument_list_repeat2 = 219, + aux_sym_map_expression_repeat1 = 220, + aux_sym_struct_literal_repeat1 = 221, + aux_sym__parameter_list_repeat1 = 222, + aux_sym_match_expression_repeat1 = 223, + aux_sym_list_pattern_repeat1 = 224, + aux_sym_map_pattern_repeat1 = 225, + aux_sym_or_pattern_repeat1 = 226, + aux_sym_select_expression_repeat1 = 227, + aux_sym_function_type_repeat1 = 228, + aux_sym_union_type_repeat1 = 229, + aux_sym_attribute_repeat1 = 230, + aux_sym_attributed_item_repeat1 = 231, + aux_sym_import_statement_repeat1 = 232, + aux_sym_macro_export_repeat1 = 233, + aux_sym_for_pattern_repeat1 = 234, + aux_sym_for_pattern_repeat2 = 235, + aux_sym_named_params_block_repeat1 = 236, + aux_sym_struct_definition_repeat1 = 237, + aux_sym_trait_definition_repeat1 = 238, + aux_sym_trait_method_params_repeat1 = 239, + aux_sym_impl_definition_repeat1 = 240, + aux_sym_block_repeat1 = 241, }; static const char * const ts_symbol_names[] = { @@ -285,6 +301,7 @@ static const char * const ts_symbol_names[] = { [anon_sym_QMARK_LBRACK] = "\?[", [anon_sym_LBRACE] = "{", [anon_sym_BANG] = "!", + [anon_sym_TILDE] = "~", [anon_sym_STAR] = "*", [anon_sym_SLASH] = "/", [anon_sym_PERCENT] = "%", @@ -298,11 +315,15 @@ static const char * const ts_symbol_names[] = { [anon_sym_GT_EQ] = ">=", [anon_sym_AMP_AMP] = "&&", [anon_sym_PIPE_PIPE] = "||", + [anon_sym_PIPE] = "|", + [anon_sym_CARET] = "^", + [anon_sym_AMP] = "&", + [anon_sym_LT_LT] = "<<", + [anon_sym_GT_GT] = ">>", [anon_sym_QMARK_QMARK] = "\?\?", [anon_sym_DOT_DOT] = "..", [anon_sym_DOT_DOT_EQ] = "..=", [anon_sym_QMARK] = "\?", - [anon_sym_PIPE] = "|", [anon_sym_match] = "match", [anon_sym_EQ_GT] = "=>", [anon_sym_SEMI] = ";", @@ -321,6 +342,18 @@ static const char * const ts_symbol_names[] = { [anon_sym_Bool] = "Bool", [anon_sym_Nil] = "Nil", [anon_sym_Any] = "Any", + [anon_sym_Number] = "Number", + [anon_sym_f64] = "f64", + [anon_sym_i8] = "i8", + [anon_sym_i16] = "i16", + [anon_sym_i32] = "i32", + [anon_sym_i64] = "i64", + [anon_sym_u8] = "u8", + [anon_sym_u16] = "u16", + [anon_sym_u32] = "u32", + [anon_sym_u64] = "u64", + [anon_sym_isize] = "isize", + [anon_sym_usize] = "usize", [anon_sym_List] = "List", [anon_sym_Map] = "Map", [anon_sym_DASH_GT] = "->", @@ -333,7 +366,6 @@ static const char * const ts_symbol_names[] = { [anon_sym_DOLLAR] = "$", [anon_sym_COLON_COLON] = "::", [anon_sym_EQ] = "=", - [anon_sym_AMP] = "&", [anon_sym_let] = "let", [anon_sym_COLON_EQ] = ":=", [anon_sym_PLUS_EQ] = "+=", @@ -514,6 +546,7 @@ static const TSSymbol ts_symbol_map[] = { [anon_sym_QMARK_LBRACK] = anon_sym_QMARK_LBRACK, [anon_sym_LBRACE] = anon_sym_LBRACE, [anon_sym_BANG] = anon_sym_BANG, + [anon_sym_TILDE] = anon_sym_TILDE, [anon_sym_STAR] = anon_sym_STAR, [anon_sym_SLASH] = anon_sym_SLASH, [anon_sym_PERCENT] = anon_sym_PERCENT, @@ -527,11 +560,15 @@ static const TSSymbol ts_symbol_map[] = { [anon_sym_GT_EQ] = anon_sym_GT_EQ, [anon_sym_AMP_AMP] = anon_sym_AMP_AMP, [anon_sym_PIPE_PIPE] = anon_sym_PIPE_PIPE, + [anon_sym_PIPE] = anon_sym_PIPE, + [anon_sym_CARET] = anon_sym_CARET, + [anon_sym_AMP] = anon_sym_AMP, + [anon_sym_LT_LT] = anon_sym_LT_LT, + [anon_sym_GT_GT] = anon_sym_GT_GT, [anon_sym_QMARK_QMARK] = anon_sym_QMARK_QMARK, [anon_sym_DOT_DOT] = anon_sym_DOT_DOT, [anon_sym_DOT_DOT_EQ] = anon_sym_DOT_DOT_EQ, [anon_sym_QMARK] = anon_sym_QMARK, - [anon_sym_PIPE] = anon_sym_PIPE, [anon_sym_match] = anon_sym_match, [anon_sym_EQ_GT] = anon_sym_EQ_GT, [anon_sym_SEMI] = anon_sym_SEMI, @@ -550,6 +587,18 @@ static const TSSymbol ts_symbol_map[] = { [anon_sym_Bool] = anon_sym_Bool, [anon_sym_Nil] = anon_sym_Nil, [anon_sym_Any] = anon_sym_Any, + [anon_sym_Number] = anon_sym_Number, + [anon_sym_f64] = anon_sym_f64, + [anon_sym_i8] = anon_sym_i8, + [anon_sym_i16] = anon_sym_i16, + [anon_sym_i32] = anon_sym_i32, + [anon_sym_i64] = anon_sym_i64, + [anon_sym_u8] = anon_sym_u8, + [anon_sym_u16] = anon_sym_u16, + [anon_sym_u32] = anon_sym_u32, + [anon_sym_u64] = anon_sym_u64, + [anon_sym_isize] = anon_sym_isize, + [anon_sym_usize] = anon_sym_usize, [anon_sym_List] = anon_sym_List, [anon_sym_Map] = anon_sym_Map, [anon_sym_DASH_GT] = anon_sym_DASH_GT, @@ -562,7 +611,6 @@ static const TSSymbol ts_symbol_map[] = { [anon_sym_DOLLAR] = anon_sym_DOLLAR, [anon_sym_COLON_COLON] = anon_sym_COLON_COLON, [anon_sym_EQ] = anon_sym_EQ, - [anon_sym_AMP] = anon_sym_AMP, [anon_sym_let] = anon_sym_let, [anon_sym_COLON_EQ] = anon_sym_COLON_EQ, [anon_sym_PLUS_EQ] = anon_sym_PLUS_EQ, @@ -830,6 +878,10 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .visible = true, .named = false, }, + [anon_sym_TILDE] = { + .visible = true, + .named = false, + }, [anon_sym_STAR] = { .visible = true, .named = false, @@ -882,6 +934,26 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .visible = true, .named = false, }, + [anon_sym_PIPE] = { + .visible = true, + .named = false, + }, + [anon_sym_CARET] = { + .visible = true, + .named = false, + }, + [anon_sym_AMP] = { + .visible = true, + .named = false, + }, + [anon_sym_LT_LT] = { + .visible = true, + .named = false, + }, + [anon_sym_GT_GT] = { + .visible = true, + .named = false, + }, [anon_sym_QMARK_QMARK] = { .visible = true, .named = false, @@ -898,10 +970,6 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .visible = true, .named = false, }, - [anon_sym_PIPE] = { - .visible = true, - .named = false, - }, [anon_sym_match] = { .visible = true, .named = false, @@ -974,6 +1042,54 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .visible = true, .named = false, }, + [anon_sym_Number] = { + .visible = true, + .named = false, + }, + [anon_sym_f64] = { + .visible = true, + .named = false, + }, + [anon_sym_i8] = { + .visible = true, + .named = false, + }, + [anon_sym_i16] = { + .visible = true, + .named = false, + }, + [anon_sym_i32] = { + .visible = true, + .named = false, + }, + [anon_sym_i64] = { + .visible = true, + .named = false, + }, + [anon_sym_u8] = { + .visible = true, + .named = false, + }, + [anon_sym_u16] = { + .visible = true, + .named = false, + }, + [anon_sym_u32] = { + .visible = true, + .named = false, + }, + [anon_sym_u64] = { + .visible = true, + .named = false, + }, + [anon_sym_isize] = { + .visible = true, + .named = false, + }, + [anon_sym_usize] = { + .visible = true, + .named = false, + }, [anon_sym_List] = { .visible = true, .named = false, @@ -1022,10 +1138,6 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .visible = true, .named = false, }, - [anon_sym_AMP] = { - .visible = true, - .named = false, - }, [anon_sym_let] = { .visible = true, .named = false, @@ -1768,46 +1880,46 @@ static const TSStateId ts_primary_state_ids[STATE_COUNT] = { [0] = 0, [1] = 1, [2] = 2, - [3] = 3, - [4] = 3, + [3] = 2, + [4] = 4, [5] = 2, - [6] = 2, - [7] = 2, - [8] = 3, - [9] = 3, + [6] = 4, + [7] = 4, + [8] = 4, + [9] = 2, [10] = 10, [11] = 10, [12] = 10, [13] = 10, [14] = 14, - [15] = 15, + [15] = 14, [16] = 16, - [17] = 17, + [17] = 16, [18] = 18, - [19] = 15, - [20] = 14, - [21] = 15, - [22] = 14, + [19] = 14, + [20] = 16, + [21] = 21, + [22] = 22, [23] = 23, [24] = 24, [25] = 25, - [26] = 23, - [27] = 25, - [28] = 23, + [26] = 24, + [27] = 27, + [28] = 24, [29] = 29, - [30] = 29, - [31] = 31, - [32] = 31, - [33] = 25, + [30] = 23, + [31] = 29, + [32] = 24, + [33] = 27, [34] = 23, - [35] = 29, - [36] = 31, - [37] = 24, + [35] = 25, + [36] = 29, + [37] = 29, [38] = 25, - [39] = 24, - [40] = 31, - [41] = 29, - [42] = 24, + [39] = 27, + [40] = 23, + [41] = 25, + [42] = 27, [43] = 43, [44] = 43, [45] = 43, @@ -1886,1022 +1998,1022 @@ static const TSStateId ts_primary_state_ids[STATE_COUNT] = { [118] = 118, [119] = 119, [120] = 120, - [121] = 119, - [122] = 118, + [121] = 121, + [122] = 122, [123] = 123, - [124] = 124, - [125] = 124, - [126] = 118, - [127] = 119, - [128] = 120, - [129] = 123, - [130] = 118, - [131] = 123, - [132] = 124, - [133] = 119, - [134] = 124, - [135] = 120, - [136] = 120, - [137] = 123, - [138] = 138, - [139] = 139, - [140] = 138, - [141] = 138, + [124] = 122, + [125] = 125, + [126] = 126, + [127] = 127, + [128] = 123, + [129] = 127, + [130] = 125, + [131] = 125, + [132] = 126, + [133] = 127, + [134] = 123, + [135] = 122, + [136] = 125, + [137] = 126, + [138] = 127, + [139] = 123, + [140] = 126, + [141] = 122, [142] = 142, [143] = 143, - [144] = 142, - [145] = 138, - [146] = 142, + [144] = 143, + [145] = 145, + [146] = 145, [147] = 147, - [148] = 147, - [149] = 142, - [150] = 143, - [151] = 147, - [152] = 147, - [153] = 153, - [154] = 154, - [155] = 155, - [156] = 156, + [148] = 142, + [149] = 145, + [150] = 147, + [151] = 151, + [152] = 143, + [153] = 147, + [154] = 145, + [155] = 143, + [156] = 147, [157] = 157, [158] = 158, - [159] = 159, + [159] = 158, [160] = 160, - [161] = 161, + [161] = 158, [162] = 162, [163] = 163, - [164] = 160, - [165] = 162, + [164] = 164, + [165] = 158, [166] = 163, - [167] = 160, - [168] = 160, - [169] = 162, + [167] = 164, + [168] = 158, + [169] = 158, [170] = 163, - [171] = 160, - [172] = 172, - [173] = 162, - [174] = 174, - [175] = 163, + [171] = 164, + [172] = 158, + [173] = 163, + [174] = 164, + [175] = 175, [176] = 176, - [177] = 162, - [178] = 160, - [179] = 153, + [177] = 163, + [178] = 178, + [179] = 179, [180] = 180, [181] = 181, - [182] = 163, - [183] = 183, - [184] = 163, - [185] = 162, + [182] = 157, + [183] = 158, + [184] = 164, + [185] = 185, [186] = 186, - [187] = 163, + [187] = 187, [188] = 188, [189] = 189, - [190] = 160, + [190] = 158, [191] = 191, - [192] = 160, - [193] = 162, - [194] = 163, - [195] = 160, + [192] = 158, + [193] = 163, + [194] = 164, + [195] = 163, [196] = 196, - [197] = 162, - [198] = 162, + [197] = 163, + [198] = 163, [199] = 199, - [200] = 163, - [201] = 160, + [200] = 200, + [201] = 201, [202] = 202, - [203] = 162, - [204] = 163, - [205] = 205, - [206] = 163, - [207] = 160, - [208] = 160, - [209] = 162, - [210] = 160, + [203] = 164, + [204] = 164, + [205] = 158, + [206] = 206, + [207] = 207, + [208] = 208, + [209] = 209, + [210] = 158, [211] = 211, - [212] = 160, - [213] = 160, - [214] = 160, - [215] = 160, - [216] = 186, - [217] = 217, - [218] = 154, - [219] = 219, - [220] = 161, + [212] = 158, + [213] = 158, + [214] = 158, + [215] = 158, + [216] = 206, + [217] = 208, + [218] = 218, + [219] = 218, + [220] = 207, [221] = 221, - [222] = 222, - [223] = 223, - [224] = 172, - [225] = 174, - [226] = 180, - [227] = 202, - [228] = 205, - [229] = 163, - [230] = 188, - [231] = 196, - [232] = 199, + [222] = 202, + [223] = 176, + [224] = 224, + [225] = 225, + [226] = 226, + [227] = 227, + [228] = 228, + [229] = 181, + [230] = 179, + [231] = 191, + [232] = 164, [233] = 233, - [234] = 176, - [235] = 153, - [236] = 236, - [237] = 237, - [238] = 219, - [239] = 222, - [240] = 162, - [241] = 241, - [242] = 242, - [243] = 236, - [244] = 217, - [245] = 186, - [246] = 217, - [247] = 154, - [248] = 222, - [249] = 223, - [250] = 202, - [251] = 205, - [252] = 233, - [253] = 188, - [254] = 199, - [255] = 176, - [256] = 153, - [257] = 186, - [258] = 154, - [259] = 222, - [260] = 223, - [261] = 202, - [262] = 205, - [263] = 233, - [264] = 188, - [265] = 199, - [266] = 176, - [267] = 267, - [268] = 233, - [269] = 233, - [270] = 233, - [271] = 233, - [272] = 233, - [273] = 233, - [274] = 233, - [275] = 217, - [276] = 221, - [277] = 277, - [278] = 233, - [279] = 267, - [280] = 223, - [281] = 277, - [282] = 221, - [283] = 277, - [284] = 233, - [285] = 277, - [286] = 233, - [287] = 233, - [288] = 233, - [289] = 233, - [290] = 221, - [291] = 267, - [292] = 267, - [293] = 233, - [294] = 294, - [295] = 295, - [296] = 296, + [234] = 185, + [235] = 199, + [236] = 188, + [237] = 224, + [238] = 225, + [239] = 163, + [240] = 226, + [241] = 164, + [242] = 164, + [243] = 158, + [244] = 163, + [245] = 227, + [246] = 246, + [247] = 228, + [248] = 206, + [249] = 208, + [250] = 218, + [251] = 202, + [252] = 176, + [253] = 227, + [254] = 228, + [255] = 181, + [256] = 179, + [257] = 196, + [258] = 185, + [259] = 199, + [260] = 206, + [261] = 218, + [262] = 202, + [263] = 176, + [264] = 227, + [265] = 228, + [266] = 181, + [267] = 179, + [268] = 196, + [269] = 185, + [270] = 199, + [271] = 181, + [272] = 181, + [273] = 181, + [274] = 181, + [275] = 181, + [276] = 181, + [277] = 181, + [278] = 208, + [279] = 178, + [280] = 200, + [281] = 181, + [282] = 164, + [283] = 178, + [284] = 200, + [285] = 181, + [286] = 200, + [287] = 181, + [288] = 181, + [289] = 181, + [290] = 181, + [291] = 178, + [292] = 160, + [293] = 163, + [294] = 160, + [295] = 160, + [296] = 196, [297] = 297, [298] = 298, [299] = 299, - [300] = 300, + [300] = 298, [301] = 301, - [302] = 295, + [302] = 302, [303] = 303, [304] = 304, [305] = 305, - [306] = 303, + [306] = 306, [307] = 307, - [308] = 296, + [308] = 308, [309] = 309, - [310] = 299, - [311] = 300, - [312] = 301, - [313] = 295, - [314] = 303, - [315] = 304, - [316] = 294, - [317] = 296, - [318] = 318, - [319] = 299, - [320] = 300, - [321] = 301, - [322] = 295, - [323] = 303, - [324] = 304, - [325] = 325, - [326] = 294, - [327] = 327, - [328] = 304, - [329] = 296, - [330] = 296, - [331] = 294, - [332] = 299, - [333] = 300, - [334] = 301, - [335] = 295, - [336] = 303, - [337] = 304, - [338] = 294, - [339] = 307, - [340] = 309, - [341] = 318, - [342] = 327, - [343] = 297, - [344] = 298, - [345] = 305, - [346] = 299, - [347] = 300, - [348] = 304, - [349] = 349, - [350] = 299, - [351] = 300, - [352] = 304, + [310] = 310, + [311] = 311, + [312] = 298, + [313] = 301, + [314] = 302, + [315] = 301, + [316] = 302, + [317] = 303, + [318] = 304, + [319] = 305, + [320] = 306, + [321] = 307, + [322] = 308, + [323] = 309, + [324] = 310, + [325] = 303, + [326] = 311, + [327] = 304, + [328] = 305, + [329] = 306, + [330] = 307, + [331] = 308, + [332] = 298, + [333] = 309, + [334] = 310, + [335] = 301, + [336] = 302, + [337] = 303, + [338] = 304, + [339] = 305, + [340] = 306, + [341] = 308, + [342] = 309, + [343] = 310, + [344] = 311, + [345] = 311, + [346] = 298, + [347] = 301, + [348] = 302, + [349] = 303, + [350] = 304, + [351] = 305, + [352] = 306, [353] = 307, - [354] = 309, - [355] = 318, - [356] = 327, - [357] = 297, - [358] = 298, - [359] = 305, - [360] = 360, - [361] = 296, - [362] = 307, - [363] = 309, - [364] = 318, - [365] = 299, - [366] = 297, - [367] = 298, - [368] = 305, - [369] = 300, - [370] = 301, - [371] = 295, - [372] = 303, - [373] = 304, - [374] = 307, - [375] = 309, - [376] = 318, - [377] = 297, - [378] = 298, - [379] = 305, - [380] = 307, - [381] = 309, - [382] = 318, - [383] = 297, - [384] = 298, + [354] = 308, + [355] = 309, + [356] = 310, + [357] = 311, + [358] = 299, + [359] = 298, + [360] = 301, + [361] = 302, + [362] = 303, + [363] = 304, + [364] = 305, + [365] = 306, + [366] = 307, + [367] = 308, + [368] = 309, + [369] = 310, + [370] = 311, + [371] = 371, + [372] = 301, + [373] = 302, + [374] = 310, + [375] = 301, + [376] = 302, + [377] = 310, + [378] = 371, + [379] = 371, + [380] = 298, + [381] = 301, + [382] = 302, + [383] = 303, + [384] = 304, [385] = 305, - [386] = 294, + [386] = 306, [387] = 307, - [388] = 309, - [389] = 318, - [390] = 297, - [391] = 298, - [392] = 305, - [393] = 299, - [394] = 296, - [395] = 299, - [396] = 300, - [397] = 301, - [398] = 295, - [399] = 303, - [400] = 304, - [401] = 294, - [402] = 296, - [403] = 301, - [404] = 295, - [405] = 303, - [406] = 294, - [407] = 296, - [408] = 299, - [409] = 300, - [410] = 301, - [411] = 295, - [412] = 303, - [413] = 304, - [414] = 294, - [415] = 296, - [416] = 301, - [417] = 295, - [418] = 303, - [419] = 294, - [420] = 300, - [421] = 299, - [422] = 300, - [423] = 304, - [424] = 296, - [425] = 301, - [426] = 295, - [427] = 303, - [428] = 294, - [429] = 349, - [430] = 301, - [431] = 296, - [432] = 299, - [433] = 300, - [434] = 301, - [435] = 295, - [436] = 303, - [437] = 304, - [438] = 349, - [439] = 349, - [440] = 294, - [441] = 57, - [442] = 61, - [443] = 62, - [444] = 80, - [445] = 59, - [446] = 60, - [447] = 58, - [448] = 81, - [449] = 67, + [388] = 308, + [389] = 309, + [390] = 310, + [391] = 311, + [392] = 298, + [393] = 303, + [394] = 304, + [395] = 305, + [396] = 306, + [397] = 307, + [398] = 308, + [399] = 309, + [400] = 311, + [401] = 298, + [402] = 301, + [403] = 302, + [404] = 303, + [405] = 304, + [406] = 305, + [407] = 306, + [408] = 307, + [409] = 308, + [410] = 309, + [411] = 310, + [412] = 311, + [413] = 298, + [414] = 303, + [415] = 304, + [416] = 305, + [417] = 306, + [418] = 307, + [419] = 308, + [420] = 309, + [421] = 311, + [422] = 301, + [423] = 302, + [424] = 310, + [425] = 298, + [426] = 303, + [427] = 304, + [428] = 305, + [429] = 306, + [430] = 307, + [431] = 308, + [432] = 309, + [433] = 311, + [434] = 299, + [435] = 298, + [436] = 301, + [437] = 302, + [438] = 303, + [439] = 304, + [440] = 305, + [441] = 306, + [442] = 307, + [443] = 308, + [444] = 309, + [445] = 310, + [446] = 311, + [447] = 299, + [448] = 307, + [449] = 449, [450] = 450, - [451] = 451, + [451] = 449, [452] = 452, [453] = 453, [454] = 454, [455] = 455, - [456] = 456, - [457] = 457, - [458] = 458, - [459] = 459, + [456] = 450, + [457] = 449, + [458] = 452, + [459] = 453, [460] = 460, - [461] = 452, - [462] = 462, - [463] = 450, - [464] = 451, - [465] = 465, - [466] = 466, - [467] = 467, - [468] = 468, - [469] = 469, - [470] = 470, - [471] = 471, - [472] = 472, - [473] = 473, - [474] = 57, - [475] = 475, - [476] = 476, - [477] = 477, - [478] = 478, - [479] = 479, - [480] = 480, - [481] = 481, - [482] = 482, - [483] = 483, - [484] = 484, - [485] = 485, - [486] = 486, - [487] = 487, - [488] = 488, - [489] = 58, - [490] = 490, - [491] = 491, - [492] = 492, - [493] = 493, - [494] = 494, - [495] = 495, - [496] = 496, - [497] = 497, - [498] = 498, - [499] = 499, - [500] = 500, - [501] = 493, - [502] = 500, - [503] = 466, - [504] = 478, - [505] = 462, + [461] = 449, + [462] = 452, + [463] = 453, + [464] = 454, + [465] = 455, + [466] = 450, + [467] = 449, + [468] = 452, + [469] = 453, + [470] = 455, + [471] = 450, + [472] = 452, + [473] = 453, + [474] = 454, + [475] = 455, + [476] = 450, + [477] = 449, + [478] = 452, + [479] = 453, + [480] = 454, + [481] = 455, + [482] = 450, + [483] = 449, + [484] = 452, + [485] = 453, + [486] = 454, + [487] = 455, + [488] = 450, + [489] = 489, + [490] = 454, + [491] = 455, + [492] = 454, + [493] = 63, + [494] = 66, + [495] = 64, + [496] = 61, + [497] = 91, + [498] = 62, + [499] = 67, + [500] = 84, + [501] = 90, + [502] = 502, + [503] = 503, + [504] = 504, + [505] = 69, [506] = 506, [507] = 507, - [508] = 488, - [509] = 457, - [510] = 490, - [511] = 453, - [512] = 468, - [513] = 491, - [514] = 507, - [515] = 492, - [516] = 506, - [517] = 487, - [518] = 458, - [519] = 479, - [520] = 506, - [521] = 507, - [522] = 506, - [523] = 459, - [524] = 485, - [525] = 486, - [526] = 495, - [527] = 460, - [528] = 467, - [529] = 469, - [530] = 477, - [531] = 471, - [532] = 480, - [533] = 496, - [534] = 465, - [535] = 470, - [536] = 57, - [537] = 481, - [538] = 476, - [539] = 482, - [540] = 497, - [541] = 58, - [542] = 483, - [543] = 498, - [544] = 484, - [545] = 472, - [546] = 455, - [547] = 507, - [548] = 499, - [549] = 454, - [550] = 456, - [551] = 475, - [552] = 473, - [553] = 494, - [554] = 554, - [555] = 75, - [556] = 114, - [557] = 97, - [558] = 109, - [559] = 59, - [560] = 112, - [561] = 101, - [562] = 77, - [563] = 78, - [564] = 79, - [565] = 82, - [566] = 83, - [567] = 84, - [568] = 85, - [569] = 88, - [570] = 89, - [571] = 90, - [572] = 56, - [573] = 76, - [574] = 60, - [575] = 91, - [576] = 57, - [577] = 58, - [578] = 106, - [579] = 80, - [580] = 81, - [581] = 63, - [582] = 61, - [583] = 62, - [584] = 115, - [585] = 107, - [586] = 108, - [587] = 92, - [588] = 110, - [589] = 110, - [590] = 93, - [591] = 94, - [592] = 95, - [593] = 64, - [594] = 96, - [595] = 99, - [596] = 100, - [597] = 55, - [598] = 65, - [599] = 66, - [600] = 86, - [601] = 68, - [602] = 87, - [603] = 69, - [604] = 98, - [605] = 70, - [606] = 71, - [607] = 72, - [608] = 73, - [609] = 74, - [610] = 113, - [611] = 67, - [612] = 112, - [613] = 82, - [614] = 76, - [615] = 81, - [616] = 84, - [617] = 85, - [618] = 62, - [619] = 70, - [620] = 112, - [621] = 71, - [622] = 72, - [623] = 73, - [624] = 74, - [625] = 75, - [626] = 101, - [627] = 60, - [628] = 88, - [629] = 89, - [630] = 90, - [631] = 91, - [632] = 92, - [633] = 77, - [634] = 63, - [635] = 83, - [636] = 93, - [637] = 94, - [638] = 97, - [639] = 95, - [640] = 86, - [641] = 98, - [642] = 64, - [643] = 65, - [644] = 66, - [645] = 68, - [646] = 59, - [647] = 96, - [648] = 87, - [649] = 99, - [650] = 100, - [651] = 69, - [652] = 57, - [653] = 78, - [654] = 79, - [655] = 55, - [656] = 58, - [657] = 56, - [658] = 67, - [659] = 80, - [660] = 61, - [661] = 661, - [662] = 662, - [663] = 662, - [664] = 110, - [665] = 665, - [666] = 665, - [667] = 109, - [668] = 668, - [669] = 114, - [670] = 670, - [671] = 665, - [672] = 108, - [673] = 113, - [674] = 668, - [675] = 668, - [676] = 668, - [677] = 107, - [678] = 110, - [679] = 665, - [680] = 106, - [681] = 115, - [682] = 682, - [683] = 683, - [684] = 682, - [685] = 683, - [686] = 108, - [687] = 687, - [688] = 107, - [689] = 109, - [690] = 110, - [691] = 687, - [692] = 114, - [693] = 110, - [694] = 112, - [695] = 113, - [696] = 106, - [697] = 115, - [698] = 107, - [699] = 113, - [700] = 106, - [701] = 114, - [702] = 702, - [703] = 115, - [704] = 107, - [705] = 108, - [706] = 115, - [707] = 107, - [708] = 108, - [709] = 110, - [710] = 710, - [711] = 110, - [712] = 110, - [713] = 110, - [714] = 714, - [715] = 715, - [716] = 716, - [717] = 702, - [718] = 716, - [719] = 109, - [720] = 715, - [721] = 109, - [722] = 113, - [723] = 106, - [724] = 114, - [725] = 113, - [726] = 106, - [727] = 114, - [728] = 115, - [729] = 108, - [730] = 715, - [731] = 731, - [732] = 716, - [733] = 109, - [734] = 714, - [735] = 702, - [736] = 736, - [737] = 61, - [738] = 91, - [739] = 92, - [740] = 93, - [741] = 731, - [742] = 95, - [743] = 96, - [744] = 99, - [745] = 100, - [746] = 55, - [747] = 97, - [748] = 59, - [749] = 86, - [750] = 98, - [751] = 56, - [752] = 76, - [753] = 60, - [754] = 754, - [755] = 57, - [756] = 58, - [757] = 67, - [758] = 80, - [759] = 81, - [760] = 87, - [761] = 62, - [762] = 63, - [763] = 110, - [764] = 64, - [765] = 110, - [766] = 65, - [767] = 66, - [768] = 68, - [769] = 69, - [770] = 70, - [771] = 71, - [772] = 72, - [773] = 73, - [774] = 74, - [775] = 109, - [776] = 115, - [777] = 107, - [778] = 75, - [779] = 108, - [780] = 101, - [781] = 77, - [782] = 78, - [783] = 79, - [784] = 113, - [785] = 106, - [786] = 114, - [787] = 115, - [788] = 107, - [789] = 108, - [790] = 82, - [791] = 113, - [792] = 106, - [793] = 114, - [794] = 83, - [795] = 84, - [796] = 85, - [797] = 109, - [798] = 88, - [799] = 89, - [800] = 90, - [801] = 94, - [802] = 106, - [803] = 115, - [804] = 108, - [805] = 107, - [806] = 114, - [807] = 731, - [808] = 109, - [809] = 113, - [810] = 110, - [811] = 114, - [812] = 812, - [813] = 110, - [814] = 110, - [815] = 115, - [816] = 113, - [817] = 108, - [818] = 107, - [819] = 109, - [820] = 106, - [821] = 114, - [822] = 113, - [823] = 106, - [824] = 824, - [825] = 115, - [826] = 107, - [827] = 824, - [828] = 824, - [829] = 108, - [830] = 824, - [831] = 109, - [832] = 832, - [833] = 833, - [834] = 834, - [835] = 835, - [836] = 836, - [837] = 837, - [838] = 838, - [839] = 839, - [840] = 837, - [841] = 841, - [842] = 842, - [843] = 839, - [844] = 844, - [845] = 833, - [846] = 844, - [847] = 847, - [848] = 848, - [849] = 849, - [850] = 837, - [851] = 841, - [852] = 852, - [853] = 841, - [854] = 854, - [855] = 855, - [856] = 856, - [857] = 857, - [858] = 858, - [859] = 859, - [860] = 860, - [861] = 861, - [862] = 859, - [863] = 863, - [864] = 863, - [865] = 863, - [866] = 60, - [867] = 867, - [868] = 868, - [869] = 868, - [870] = 868, - [871] = 871, - [872] = 872, - [873] = 872, - [874] = 62, - [875] = 875, - [876] = 67, - [877] = 875, - [878] = 878, - [879] = 80, - [880] = 81, - [881] = 878, - [882] = 61, - [883] = 883, - [884] = 884, - [885] = 885, - [886] = 886, - [887] = 887, - [888] = 886, - [889] = 889, - [890] = 890, - [891] = 890, - [892] = 892, - [893] = 893, - [894] = 894, - [895] = 895, - [896] = 896, - [897] = 897, - [898] = 898, - [899] = 76, - [900] = 900, - [901] = 56, + [508] = 112, + [509] = 118, + [510] = 113, + [511] = 114, + [512] = 115, + [513] = 116, + [514] = 117, + [515] = 61, + [516] = 62, + [517] = 63, + [518] = 109, + [519] = 519, + [520] = 520, + [521] = 502, + [522] = 59, + [523] = 85, + [524] = 64, + [525] = 61, + [526] = 62, + [527] = 84, + [528] = 90, + [529] = 91, + [530] = 66, + [531] = 67, + [532] = 119, + [533] = 107, + [534] = 108, + [535] = 110, + [536] = 110, + [537] = 537, + [538] = 538, + [539] = 539, + [540] = 504, + [541] = 541, + [542] = 542, + [543] = 68, + [544] = 70, + [545] = 71, + [546] = 546, + [547] = 72, + [548] = 73, + [549] = 549, + [550] = 74, + [551] = 75, + [552] = 76, + [553] = 77, + [554] = 78, + [555] = 79, + [556] = 80, + [557] = 81, + [558] = 82, + [559] = 83, + [560] = 86, + [561] = 56, + [562] = 88, + [563] = 89, + [564] = 92, + [565] = 93, + [566] = 94, + [567] = 95, + [568] = 96, + [569] = 569, + [570] = 570, + [571] = 571, + [572] = 572, + [573] = 573, + [574] = 574, + [575] = 575, + [576] = 576, + [577] = 577, + [578] = 97, + [579] = 579, + [580] = 580, + [581] = 581, + [582] = 582, + [583] = 583, + [584] = 584, + [585] = 585, + [586] = 586, + [587] = 587, + [588] = 588, + [589] = 98, + [590] = 590, + [591] = 591, + [592] = 592, + [593] = 593, + [594] = 594, + [595] = 595, + [596] = 596, + [597] = 597, + [598] = 598, + [599] = 599, + [600] = 600, + [601] = 99, + [602] = 100, + [603] = 57, + [604] = 604, + [605] = 605, + [606] = 101, + [607] = 58, + [608] = 55, + [609] = 60, + [610] = 65, + [611] = 106, + [612] = 503, + [613] = 87, + [614] = 585, + [615] = 571, + [616] = 541, + [617] = 542, + [618] = 593, + [619] = 594, + [620] = 569, + [621] = 595, + [622] = 584, + [623] = 599, + [624] = 600, + [625] = 579, + [626] = 61, + [627] = 604, + [628] = 596, + [629] = 629, + [630] = 630, + [631] = 537, + [632] = 605, + [633] = 580, + [634] = 629, + [635] = 630, + [636] = 630, + [637] = 520, + [638] = 539, + [639] = 572, + [640] = 573, + [641] = 546, + [642] = 507, + [643] = 588, + [644] = 574, + [645] = 630, + [646] = 590, + [647] = 591, + [648] = 592, + [649] = 629, + [650] = 575, + [651] = 549, + [652] = 586, + [653] = 587, + [654] = 506, + [655] = 570, + [656] = 538, + [657] = 62, + [658] = 598, + [659] = 576, + [660] = 577, + [661] = 581, + [662] = 597, + [663] = 519, + [664] = 582, + [665] = 583, + [666] = 629, + [667] = 667, + [668] = 109, + [669] = 109, + [670] = 76, + [671] = 93, + [672] = 94, + [673] = 95, + [674] = 96, + [675] = 97, + [676] = 98, + [677] = 60, + [678] = 70, + [679] = 99, + [680] = 71, + [681] = 72, + [682] = 65, + [683] = 73, + [684] = 91, + [685] = 100, + [686] = 57, + [687] = 58, + [688] = 84, + [689] = 55, + [690] = 80, + [691] = 74, + [692] = 692, + [693] = 81, + [694] = 82, + [695] = 83, + [696] = 86, + [697] = 87, + [698] = 75, + [699] = 88, + [700] = 89, + [701] = 63, + [702] = 77, + [703] = 64, + [704] = 90, + [705] = 66, + [706] = 78, + [707] = 67, + [708] = 61, + [709] = 59, + [710] = 85, + [711] = 56, + [712] = 79, + [713] = 68, + [714] = 69, + [715] = 92, + [716] = 62, + [717] = 101, + [718] = 718, + [719] = 718, + [720] = 106, + [721] = 119, + [722] = 114, + [723] = 110, + [724] = 115, + [725] = 116, + [726] = 108, + [727] = 117, + [728] = 107, + [729] = 112, + [730] = 118, + [731] = 110, + [732] = 113, + [733] = 117, + [734] = 108, + [735] = 110, + [736] = 110, + [737] = 110, + [738] = 110, + [739] = 106, + [740] = 112, + [741] = 118, + [742] = 113, + [743] = 114, + [744] = 115, + [745] = 116, + [746] = 117, + [747] = 109, + [748] = 748, + [749] = 106, + [750] = 112, + [751] = 118, + [752] = 113, + [753] = 114, + [754] = 115, + [755] = 116, + [756] = 119, + [757] = 107, + [758] = 108, + [759] = 119, + [760] = 107, + [761] = 112, + [762] = 762, + [763] = 763, + [764] = 106, + [765] = 765, + [766] = 766, + [767] = 767, + [768] = 768, + [769] = 769, + [770] = 770, + [771] = 771, + [772] = 772, + [773] = 765, + [774] = 774, + [775] = 762, + [776] = 776, + [777] = 763, + [778] = 118, + [779] = 113, + [780] = 114, + [781] = 115, + [782] = 116, + [783] = 117, + [784] = 112, + [785] = 774, + [786] = 113, + [787] = 114, + [788] = 115, + [789] = 116, + [790] = 117, + [791] = 119, + [792] = 107, + [793] = 108, + [794] = 794, + [795] = 795, + [796] = 796, + [797] = 797, + [798] = 774, + [799] = 762, + [800] = 800, + [801] = 801, + [802] = 802, + [803] = 803, + [804] = 804, + [805] = 804, + [806] = 806, + [807] = 110, + [808] = 766, + [809] = 106, + [810] = 119, + [811] = 107, + [812] = 110, + [813] = 813, + [814] = 108, + [815] = 815, + [816] = 118, + [817] = 72, + [818] = 113, + [819] = 114, + [820] = 115, + [821] = 821, + [822] = 116, + [823] = 117, + [824] = 110, + [825] = 825, + [826] = 826, + [827] = 110, + [828] = 101, + [829] = 60, + [830] = 65, + [831] = 56, + [832] = 59, + [833] = 770, + [834] = 85, + [835] = 68, + [836] = 69, + [837] = 70, + [838] = 71, + [839] = 64, + [840] = 821, + [841] = 73, + [842] = 74, + [843] = 75, + [844] = 76, + [845] = 77, + [846] = 78, + [847] = 119, + [848] = 107, + [849] = 61, + [850] = 825, + [851] = 79, + [852] = 108, + [853] = 80, + [854] = 81, + [855] = 82, + [856] = 83, + [857] = 112, + [858] = 118, + [859] = 113, + [860] = 114, + [861] = 115, + [862] = 116, + [863] = 117, + [864] = 119, + [865] = 107, + [866] = 108, + [867] = 86, + [868] = 87, + [869] = 88, + [870] = 89, + [871] = 92, + [872] = 93, + [873] = 94, + [874] = 95, + [875] = 96, + [876] = 97, + [877] = 98, + [878] = 821, + [879] = 99, + [880] = 825, + [881] = 100, + [882] = 57, + [883] = 58, + [884] = 55, + [885] = 62, + [886] = 84, + [887] = 106, + [888] = 90, + [889] = 91, + [890] = 66, + [891] = 821, + [892] = 106, + [893] = 67, + [894] = 112, + [895] = 825, + [896] = 118, + [897] = 63, + [898] = 115, + [899] = 110, + [900] = 106, + [901] = 118, [902] = 902, [903] = 903, - [904] = 904, - [905] = 905, - [906] = 906, + [904] = 770, + [905] = 108, + [906] = 114, [907] = 907, - [908] = 908, - [909] = 909, - [910] = 910, - [911] = 911, - [912] = 911, - [913] = 913, - [914] = 914, - [915] = 915, - [916] = 916, - [917] = 917, - [918] = 918, - [919] = 919, - [920] = 914, - [921] = 921, - [922] = 922, - [923] = 923, - [924] = 924, - [925] = 913, - [926] = 926, - [927] = 927, - [928] = 928, + [908] = 112, + [909] = 113, + [910] = 110, + [911] = 119, + [912] = 116, + [913] = 903, + [914] = 112, + [915] = 118, + [916] = 113, + [917] = 114, + [918] = 115, + [919] = 116, + [920] = 117, + [921] = 117, + [922] = 902, + [923] = 107, + [924] = 119, + [925] = 107, + [926] = 108, + [927] = 106, + [928] = 110, [929] = 929, - [930] = 930, - [931] = 931, + [930] = 106, + [931] = 119, [932] = 932, - [933] = 933, - [934] = 934, - [935] = 935, - [936] = 936, - [937] = 937, - [938] = 938, - [939] = 939, - [940] = 940, - [941] = 941, - [942] = 942, - [943] = 943, - [944] = 938, - [945] = 938, - [946] = 938, + [933] = 929, + [934] = 112, + [935] = 932, + [936] = 107, + [937] = 118, + [938] = 113, + [939] = 114, + [940] = 115, + [941] = 932, + [942] = 116, + [943] = 117, + [944] = 932, + [945] = 108, + [946] = 946, [947] = 947, [948] = 948, - [949] = 56, - [950] = 950, + [949] = 949, + [950] = 949, [951] = 951, - [952] = 952, - [953] = 948, - [954] = 954, - [955] = 955, - [956] = 956, - [957] = 947, - [958] = 61, - [959] = 76, - [960] = 62, - [961] = 951, - [962] = 962, - [963] = 936, + [952] = 951, + [953] = 953, + [954] = 949, + [955] = 948, + [956] = 953, + [957] = 953, + [958] = 951, + [959] = 959, + [960] = 959, + [961] = 959, + [962] = 64, + [963] = 963, [964] = 964, - [965] = 952, - [966] = 952, - [967] = 948, - [968] = 951, - [969] = 955, - [970] = 951, - [971] = 952, - [972] = 955, - [973] = 67, - [974] = 80, + [965] = 964, + [966] = 964, + [967] = 967, + [968] = 967, + [969] = 969, + [970] = 970, + [971] = 66, + [972] = 972, + [973] = 972, + [974] = 67, [975] = 975, - [976] = 904, - [977] = 977, - [978] = 948, - [979] = 952, - [980] = 948, - [981] = 952, - [982] = 948, - [983] = 951, - [984] = 955, - [985] = 951, + [976] = 90, + [977] = 975, + [978] = 91, + [979] = 84, + [980] = 980, + [981] = 981, + [982] = 982, + [983] = 983, + [984] = 980, + [985] = 985, [986] = 986, - [987] = 955, - [988] = 81, - [989] = 952, - [990] = 952, - [991] = 948, - [992] = 951, + [987] = 987, + [988] = 986, + [989] = 989, + [990] = 990, + [991] = 991, + [992] = 992, [993] = 993, - [994] = 955, - [995] = 951, + [994] = 994, + [995] = 995, [996] = 996, - [997] = 955, - [998] = 948, - [999] = 955, - [1000] = 936, + [997] = 997, + [998] = 998, + [999] = 999, + [1000] = 1000, [1001] = 1001, - [1002] = 1001, - [1003] = 1003, - [1004] = 1001, + [1002] = 59, + [1003] = 85, + [1004] = 1004, [1005] = 1005, - [1006] = 1001, - [1007] = 1001, - [1008] = 61, + [1006] = 1006, + [1007] = 1007, + [1008] = 1008, [1009] = 1009, - [1010] = 62, + [1010] = 1010, [1011] = 1011, - [1012] = 56, - [1013] = 76, + [1012] = 1008, + [1013] = 1013, [1014] = 1014, [1015] = 1015, - [1016] = 1001, - [1017] = 1009, - [1018] = 1001, + [1016] = 1005, + [1017] = 1017, + [1018] = 1018, [1019] = 1019, - [1020] = 81, - [1021] = 1019, - [1022] = 1001, - [1023] = 1009, - [1024] = 1019, - [1025] = 941, - [1026] = 1001, - [1027] = 933, - [1028] = 1001, - [1029] = 1019, - [1030] = 943, - [1031] = 67, - [1032] = 1032, + [1020] = 1020, + [1021] = 1014, + [1022] = 1022, + [1023] = 1023, + [1024] = 1024, + [1025] = 1025, + [1026] = 1026, + [1027] = 1027, + [1028] = 1028, + [1029] = 1029, + [1030] = 1030, + [1031] = 1031, + [1032] = 1028, [1033] = 1033, - [1034] = 935, - [1035] = 80, - [1036] = 1036, + [1034] = 1034, + [1035] = 1035, + [1036] = 1028, [1037] = 1037, - [1038] = 1001, - [1039] = 1001, - [1040] = 904, - [1041] = 1041, + [1038] = 1038, + [1039] = 1039, + [1040] = 1040, + [1041] = 1028, [1042] = 1042, [1043] = 1043, [1044] = 1044, [1045] = 1045, - [1046] = 1046, + [1046] = 991, [1047] = 1047, [1048] = 1048, [1049] = 1049, - [1050] = 1050, - [1051] = 1051, - [1052] = 1052, + [1050] = 1043, + [1051] = 1044, + [1052] = 1047, [1053] = 1053, - [1054] = 1048, + [1054] = 1047, [1055] = 1055, - [1056] = 1056, - [1057] = 1057, - [1058] = 1058, + [1056] = 1049, + [1057] = 1043, + [1058] = 1044, [1059] = 1059, - [1060] = 1060, - [1061] = 1061, - [1062] = 1062, - [1063] = 1063, - [1064] = 1057, - [1065] = 1065, - [1066] = 1058, - [1067] = 935, - [1068] = 1068, - [1069] = 975, - [1070] = 1070, - [1071] = 1071, - [1072] = 1072, - [1073] = 1048, + [1060] = 1044, + [1061] = 1047, + [1062] = 1031, + [1063] = 1049, + [1064] = 1043, + [1065] = 1043, + [1066] = 84, + [1067] = 90, + [1068] = 91, + [1069] = 1069, + [1070] = 1049, + [1071] = 1043, + [1072] = 1044, + [1073] = 1047, [1074] = 1074, - [1075] = 1045, - [1076] = 1071, - [1077] = 1077, - [1078] = 1078, - [1079] = 1056, - [1080] = 1074, - [1081] = 1078, + [1075] = 59, + [1076] = 85, + [1077] = 1049, + [1078] = 1043, + [1079] = 1079, + [1080] = 1049, + [1081] = 1081, [1082] = 1082, - [1083] = 1072, - [1084] = 1084, - [1085] = 1085, - [1086] = 1044, - [1087] = 1087, - [1088] = 1088, - [1089] = 1042, - [1090] = 1090, - [1091] = 1056, - [1092] = 1092, - [1093] = 1093, - [1094] = 1082, - [1095] = 1056, - [1096] = 1096, - [1097] = 1057, - [1098] = 1058, - [1099] = 1099, - [1100] = 941, + [1083] = 1049, + [1084] = 1049, + [1085] = 1043, + [1086] = 66, + [1087] = 67, + [1088] = 1044, + [1089] = 1047, + [1090] = 1044, + [1091] = 1047, + [1092] = 1044, + [1093] = 1047, + [1094] = 1059, + [1095] = 1095, + [1096] = 90, + [1097] = 991, + [1098] = 1098, + [1099] = 1098, + [1100] = 1100, [1101] = 1101, - [1102] = 1102, - [1103] = 1090, - [1104] = 1057, - [1105] = 1068, + [1102] = 67, + [1103] = 85, + [1104] = 66, + [1105] = 1105, [1106] = 1106, - [1107] = 933, - [1108] = 1047, - [1109] = 1053, - [1110] = 1110, - [1111] = 1074, - [1112] = 1058, - [1113] = 1113, - [1114] = 1114, - [1115] = 1115, - [1116] = 1048, - [1117] = 1043, - [1118] = 1071, + [1107] = 1030, + [1108] = 1108, + [1109] = 1031, + [1110] = 1037, + [1111] = 59, + [1112] = 84, + [1113] = 1108, + [1114] = 1098, + [1115] = 1098, + [1116] = 1098, + [1117] = 1117, + [1118] = 1098, [1119] = 1119, - [1120] = 1087, - [1121] = 1078, - [1122] = 1122, - [1123] = 943, - [1124] = 1124, - [1125] = 1049, - [1126] = 1126, - [1127] = 1055, - [1128] = 1071, - [1129] = 1099, - [1130] = 1077, - [1131] = 1106, - [1132] = 1126, - [1133] = 1122, - [1134] = 1134, + [1120] = 1029, + [1121] = 1108, + [1122] = 1105, + [1123] = 1098, + [1124] = 1098, + [1125] = 1098, + [1126] = 1098, + [1127] = 1127, + [1128] = 1040, + [1129] = 1098, + [1130] = 91, + [1131] = 1105, + [1132] = 1098, + [1133] = 1133, + [1134] = 1108, [1135] = 1135, - [1136] = 1135, + [1136] = 1136, [1137] = 1137, [1138] = 1138, [1139] = 1139, @@ -2913,233 +3025,233 @@ static const TSStateId ts_primary_state_ids[STATE_COUNT] = { [1145] = 1145, [1146] = 1146, [1147] = 1147, - [1148] = 930, + [1148] = 1148, [1149] = 1149, - [1150] = 1150, + [1150] = 1040, [1151] = 1151, [1152] = 1152, [1153] = 1153, [1154] = 1154, - [1155] = 1155, - [1156] = 1156, - [1157] = 1137, - [1158] = 1156, - [1159] = 1159, - [1160] = 1137, + [1155] = 1138, + [1156] = 1053, + [1157] = 1157, + [1158] = 1158, + [1159] = 1145, + [1160] = 1146, [1161] = 1161, - [1162] = 1134, + [1162] = 1138, [1163] = 1163, - [1164] = 1164, + [1164] = 1037, [1165] = 1165, - [1166] = 1135, - [1167] = 1167, + [1166] = 1152, + [1167] = 1153, [1168] = 1168, - [1169] = 1169, - [1170] = 1139, - [1171] = 1171, + [1169] = 1030, + [1170] = 1029, + [1171] = 1137, [1172] = 1172, - [1173] = 1146, + [1173] = 1173, [1174] = 1174, - [1175] = 1134, - [1176] = 1163, + [1175] = 1175, + [1176] = 1176, [1177] = 1177, [1178] = 1178, - [1179] = 1179, + [1179] = 1153, [1180] = 1180, - [1181] = 1164, - [1182] = 1135, + [1181] = 1181, + [1182] = 1182, [1183] = 1183, - [1184] = 1184, + [1184] = 1145, [1185] = 1185, - [1186] = 1186, + [1186] = 1152, [1187] = 1187, [1188] = 1188, - [1189] = 1189, + [1189] = 1140, [1190] = 1190, - [1191] = 1191, - [1192] = 1139, - [1193] = 1178, - [1194] = 1194, + [1191] = 1145, + [1192] = 1146, + [1193] = 1193, + [1194] = 1146, [1195] = 1195, [1196] = 1196, - [1197] = 1197, - [1198] = 1198, - [1199] = 927, - [1200] = 1200, - [1201] = 1191, - [1202] = 1188, - [1203] = 1203, - [1204] = 1204, - [1205] = 928, + [1197] = 1148, + [1198] = 1178, + [1199] = 1199, + [1200] = 1151, + [1201] = 1161, + [1202] = 1163, + [1203] = 1154, + [1204] = 1178, + [1205] = 1178, [1206] = 1206, [1207] = 1207, - [1208] = 60, - [1209] = 1146, + [1208] = 1193, + [1209] = 1209, [1210] = 1210, - [1211] = 1147, - [1212] = 924, - [1213] = 929, - [1214] = 1164, - [1215] = 1144, - [1216] = 1154, - [1217] = 1155, - [1218] = 910, - [1219] = 1219, - [1220] = 1146, - [1221] = 1221, - [1222] = 462, - [1223] = 1223, - [1224] = 1224, - [1225] = 1139, - [1226] = 1147, - [1227] = 1227, - [1228] = 1228, - [1229] = 1228, - [1230] = 500, - [1231] = 919, - [1232] = 909, + [1211] = 1211, + [1212] = 1212, + [1213] = 1213, + [1214] = 1214, + [1215] = 1147, + [1216] = 1152, + [1217] = 1196, + [1218] = 1218, + [1219] = 1207, + [1220] = 1210, + [1221] = 1175, + [1222] = 1153, + [1223] = 1211, + [1224] = 1209, + [1225] = 1188, + [1226] = 1185, + [1227] = 1154, + [1228] = 1218, + [1229] = 1213, + [1230] = 1230, + [1231] = 1231, + [1232] = 1007, [1233] = 1233, - [1234] = 1234, - [1235] = 1207, - [1236] = 1236, - [1237] = 1187, - [1238] = 1238, + [1234] = 1017, + [1235] = 1019, + [1236] = 1053, + [1237] = 1010, + [1238] = 1013, [1239] = 1239, - [1240] = 1096, - [1241] = 915, - [1242] = 916, + [1240] = 1240, + [1241] = 1241, + [1242] = 1242, [1243] = 1243, - [1244] = 923, - [1245] = 1245, + [1244] = 1244, + [1245] = 1231, [1246] = 1246, [1247] = 1247, [1248] = 1248, - [1249] = 1246, - [1250] = 1138, - [1251] = 1164, + [1249] = 1249, + [1250] = 1250, + [1251] = 1249, [1252] = 1252, [1253] = 1253, [1254] = 1254, - [1255] = 921, + [1255] = 1255, [1256] = 1256, [1257] = 1257, [1258] = 1258, - [1259] = 1163, - [1260] = 1156, - [1261] = 1137, - [1262] = 922, - [1263] = 975, - [1264] = 918, - [1265] = 931, + [1259] = 1259, + [1260] = 1260, + [1261] = 1261, + [1262] = 1262, + [1263] = 1263, + [1264] = 1264, + [1265] = 1265, [1266] = 1266, [1267] = 1267, - [1268] = 1156, - [1269] = 1144, + [1268] = 1268, + [1269] = 1269, [1270] = 1270, - [1271] = 1203, - [1272] = 1168, - [1273] = 1134, - [1274] = 1163, - [1275] = 1258, - [1276] = 1276, - [1277] = 1149, - [1278] = 926, + [1271] = 1271, + [1272] = 1262, + [1273] = 1273, + [1274] = 1274, + [1275] = 1275, + [1276] = 1247, + [1277] = 1277, + [1278] = 1278, [1279] = 1279, [1280] = 1280, - [1281] = 1281, - [1282] = 1282, - [1283] = 1283, + [1281] = 1009, + [1282] = 1018, + [1283] = 1011, [1284] = 1284, - [1285] = 1285, + [1285] = 1267, [1286] = 1286, - [1287] = 1287, - [1288] = 1288, + [1287] = 1267, + [1288] = 1286, [1289] = 1289, [1290] = 1290, [1291] = 1291, - [1292] = 1292, + [1292] = 1265, [1293] = 1293, [1294] = 1294, - [1295] = 1295, + [1295] = 1265, [1296] = 1296, [1297] = 1297, [1298] = 1298, [1299] = 1299, [1300] = 1300, - [1301] = 1301, + [1301] = 1296, [1302] = 1302, - [1303] = 1289, - [1304] = 1304, - [1305] = 1279, - [1306] = 1306, - [1307] = 1307, - [1308] = 1293, - [1309] = 1285, + [1303] = 1303, + [1304] = 1289, + [1305] = 1294, + [1306] = 1286, + [1307] = 1265, + [1308] = 1308, + [1309] = 1309, [1310] = 1310, - [1311] = 490, - [1312] = 1312, + [1311] = 1296, + [1312] = 1142, [1313] = 1296, [1314] = 1314, - [1315] = 1315, + [1315] = 1289, [1316] = 1316, [1317] = 1317, [1318] = 1318, - [1319] = 1319, - [1320] = 1320, - [1321] = 1321, - [1322] = 1290, + [1319] = 546, + [1320] = 1294, + [1321] = 1289, + [1322] = 1231, [1323] = 1323, [1324] = 1324, - [1325] = 496, - [1326] = 1326, - [1327] = 1327, - [1328] = 1321, - [1329] = 1300, - [1330] = 1330, - [1331] = 1331, + [1325] = 1020, + [1326] = 1026, + [1327] = 1015, + [1328] = 1023, + [1329] = 1231, + [1330] = 1274, + [1331] = 1253, [1332] = 1332, - [1333] = 454, + [1333] = 1333, [1334] = 1334, - [1335] = 1335, + [1335] = 1294, [1336] = 1336, - [1337] = 482, + [1337] = 1337, [1338] = 1338, - [1339] = 1301, - [1340] = 1340, - [1341] = 1326, - [1342] = 1299, - [1343] = 1295, - [1344] = 1299, - [1345] = 1345, - [1346] = 1294, - [1347] = 1327, - [1348] = 1314, - [1349] = 1306, - [1350] = 1334, - [1351] = 1351, - [1352] = 1352, - [1353] = 1315, + [1339] = 1316, + [1340] = 1239, + [1341] = 1341, + [1342] = 1261, + [1343] = 549, + [1344] = 1267, + [1345] = 1247, + [1346] = 64, + [1347] = 1242, + [1348] = 1348, + [1349] = 1349, + [1350] = 1025, + [1351] = 1006, + [1352] = 1247, + [1353] = 1353, [1354] = 1354, - [1355] = 1297, - [1356] = 1314, - [1357] = 1307, - [1358] = 1331, - [1359] = 1299, - [1360] = 1360, + [1355] = 1355, + [1356] = 1348, + [1357] = 1252, + [1358] = 1358, + [1359] = 1233, + [1360] = 1334, [1361] = 1361, [1362] = 1362, - [1363] = 1362, - [1364] = 1364, - [1365] = 1361, - [1366] = 1366, - [1367] = 1362, + [1363] = 1024, + [1364] = 1027, + [1365] = 1244, + [1366] = 1286, + [1367] = 1337, [1368] = 1368, - [1369] = 1369, + [1369] = 1349, [1370] = 1370, - [1371] = 1371, + [1371] = 1252, [1372] = 1372, - [1373] = 1373, - [1374] = 1374, + [1373] = 1242, + [1374] = 1273, [1375] = 1375, [1376] = 1376, [1377] = 1377, @@ -3153,176 +3265,272 @@ static const TSStateId ts_primary_state_ids[STATE_COUNT] = { [1385] = 1385, [1386] = 1386, [1387] = 1387, - [1388] = 1362, - [1389] = 1374, - [1390] = 1378, + [1388] = 1388, + [1389] = 1389, + [1390] = 1390, [1391] = 1391, [1392] = 1392, [1393] = 1393, [1394] = 1394, [1395] = 1395, - [1396] = 1361, + [1396] = 1396, [1397] = 1397, - [1398] = 1364, + [1398] = 1398, [1399] = 1399, - [1400] = 1366, - [1401] = 1401, - [1402] = 1402, + [1400] = 1400, + [1401] = 1399, + [1402] = 1383, [1403] = 1403, - [1404] = 1362, - [1405] = 1369, + [1404] = 1384, + [1405] = 1405, [1406] = 1406, [1407] = 1407, - [1408] = 1370, - [1409] = 1409, + [1408] = 1399, + [1409] = 583, [1410] = 1410, [1411] = 1411, [1412] = 1412, - [1413] = 1413, - [1414] = 1407, - [1415] = 1372, - [1416] = 1416, - [1417] = 1362, - [1418] = 1360, - [1419] = 1362, - [1420] = 1380, - [1421] = 1403, + [1413] = 590, + [1414] = 1414, + [1415] = 597, + [1416] = 604, + [1417] = 1379, + [1418] = 1418, + [1419] = 1407, + [1420] = 1420, + [1421] = 1421, [1422] = 1422, - [1423] = 1423, - [1424] = 1375, - [1425] = 1413, + [1423] = 1398, + [1424] = 1424, + [1425] = 1425, [1426] = 1426, [1427] = 1427, - [1428] = 1406, - [1429] = 1373, - [1430] = 1386, + [1428] = 1428, + [1429] = 1421, + [1430] = 1425, [1431] = 1431, - [1432] = 1403, - [1433] = 1376, - [1434] = 1434, - [1435] = 1403, - [1436] = 1371, - [1437] = 1369, - [1438] = 1406, - [1439] = 1416, - [1440] = 1383, - [1441] = 1382, - [1442] = 1442, - [1443] = 1443, - [1444] = 1444, - [1445] = 1445, - [1446] = 1406, - [1447] = 1416, - [1448] = 1378, - [1449] = 1362, - [1450] = 1362, - [1451] = 1451, - [1452] = 1452, - [1453] = 1362, - [1454] = 1377, - [1455] = 1362, + [1432] = 1432, + [1433] = 1428, + [1434] = 1399, + [1435] = 1431, + [1436] = 1426, + [1437] = 1437, + [1438] = 1438, + [1439] = 1439, + [1440] = 1377, + [1441] = 1441, + [1442] = 1420, + [1443] = 1412, + [1444] = 1394, + [1445] = 1376, + [1446] = 1414, + [1447] = 1395, + [1448] = 1410, + [1449] = 1449, + [1450] = 1450, + [1451] = 1380, + [1452] = 1388, + [1453] = 1420, + [1454] = 1454, + [1455] = 1455, [1456] = 1456, - [1457] = 1362, + [1457] = 1457, [1458] = 1458, - [1459] = 1409, - [1460] = 1362, + [1459] = 1459, + [1460] = 1460, [1461] = 1461, - [1462] = 1362, - [1463] = 1374, + [1462] = 1462, + [1463] = 1463, [1464] = 1464, - [1465] = 1445, - [1466] = 1466, + [1465] = 1465, + [1466] = 1462, [1467] = 1467, - [1468] = 1394, - [1469] = 1378, - [1470] = 1369, - [1471] = 1406, - [1472] = 1422, + [1468] = 1460, + [1469] = 1469, + [1470] = 1470, + [1471] = 1471, + [1472] = 1472, [1473] = 1473, [1474] = 1474, - [1475] = 1423, - [1476] = 1392, - [1477] = 1393, - [1478] = 1399, - [1479] = 1458, - [1480] = 1467, - [1481] = 1407, - [1482] = 1379, + [1475] = 1475, + [1476] = 1457, + [1477] = 1459, + [1478] = 1478, + [1479] = 1479, + [1480] = 1480, + [1481] = 1464, + [1482] = 1482, [1483] = 1483, [1484] = 1484, [1485] = 1485, - [1486] = 1410, - [1487] = 1474, - [1488] = 1395, - [1489] = 1422, - [1490] = 1406, - [1491] = 1406, - [1492] = 1484, - [1493] = 1473, - [1494] = 1362, + [1486] = 1486, + [1487] = 1487, + [1488] = 1486, + [1489] = 1489, + [1490] = 1473, + [1491] = 1491, + [1492] = 1486, + [1493] = 1493, + [1494] = 1494, [1495] = 1495, - [1496] = 1466, - [1497] = 1497, + [1496] = 1496, + [1497] = 1486, [1498] = 1498, - [1499] = 1368, - [1500] = 1368, + [1499] = 1464, + [1500] = 1464, [1501] = 1501, - [1502] = 1502, - [1503] = 1502, - [1504] = 1391, - [1505] = 1505, - [1506] = 1423, - [1507] = 1406, - [1508] = 1394, - [1509] = 1484, - [1510] = 1484, - [1511] = 1384, - [1512] = 1464, - [1513] = 1445, - [1514] = 1466, + [1502] = 1458, + [1503] = 1503, + [1504] = 1504, + [1505] = 1498, + [1506] = 1506, + [1507] = 1474, + [1508] = 1508, + [1509] = 1456, + [1510] = 1510, + [1511] = 1478, + [1512] = 1512, + [1513] = 1513, + [1514] = 1514, [1515] = 1515, - [1516] = 1502, - [1517] = 1392, - [1518] = 1393, - [1519] = 1406, - [1520] = 1379, + [1516] = 1516, + [1517] = 1491, + [1518] = 1518, + [1519] = 1464, + [1520] = 1512, [1521] = 1521, - [1522] = 1464, - [1523] = 1445, - [1524] = 1466, - [1525] = 1411, - [1526] = 1392, - [1527] = 1406, - [1528] = 1379, - [1529] = 1423, - [1530] = 1368, - [1531] = 1406, - [1532] = 1416, - [1533] = 1362, - [1534] = 1406, - [1535] = 1464, - [1536] = 1407, - [1537] = 1374, - [1538] = 1538, - [1539] = 1412, - [1540] = 1391, - [1541] = 1361, - [1542] = 1542, - [1543] = 1543, - [1544] = 1502, - [1545] = 1394, - [1546] = 1385, - [1547] = 1538, - [1548] = 1485, - [1549] = 1387, - [1550] = 1362, - [1551] = 1426, - [1552] = 1391, - [1553] = 1553, - [1554] = 1553, - [1555] = 1426, - [1556] = 1426, - [1557] = 1515, + [1522] = 1522, + [1523] = 1483, + [1524] = 1524, + [1525] = 1485, + [1526] = 1486, + [1527] = 1524, + [1528] = 1457, + [1529] = 1529, + [1530] = 1486, + [1531] = 1464, + [1532] = 1529, + [1533] = 1533, + [1534] = 1534, + [1535] = 1496, + [1536] = 1536, + [1537] = 1464, + [1538] = 1486, + [1539] = 1539, + [1540] = 1464, + [1541] = 1464, + [1542] = 1529, + [1543] = 1464, + [1544] = 1544, + [1545] = 1464, + [1546] = 1546, + [1547] = 1547, + [1548] = 1548, + [1549] = 1460, + [1550] = 1464, + [1551] = 1469, + [1552] = 1552, + [1553] = 1465, + [1554] = 1522, + [1555] = 1486, + [1556] = 1471, + [1557] = 1552, + [1558] = 1460, + [1559] = 1464, + [1560] = 1560, + [1561] = 1539, + [1562] = 1510, + [1563] = 1478, + [1564] = 1510, + [1565] = 1487, + [1566] = 1480, + [1567] = 1493, + [1568] = 1464, + [1569] = 1494, + [1570] = 1503, + [1571] = 1483, + [1572] = 1572, + [1573] = 1560, + [1574] = 1574, + [1575] = 1504, + [1576] = 1576, + [1577] = 1577, + [1578] = 1485, + [1579] = 1548, + [1580] = 1456, + [1581] = 1464, + [1582] = 1486, + [1583] = 1583, + [1584] = 1584, + [1585] = 1494, + [1586] = 1486, + [1587] = 1475, + [1588] = 1510, + [1589] = 1589, + [1590] = 1483, + [1591] = 1459, + [1592] = 1459, + [1593] = 1464, + [1594] = 1473, + [1595] = 1457, + [1596] = 1467, + [1597] = 1547, + [1598] = 1598, + [1599] = 1518, + [1600] = 1478, + [1601] = 1583, + [1602] = 1602, + [1603] = 1552, + [1604] = 1465, + [1605] = 1522, + [1606] = 1485, + [1607] = 1536, + [1608] = 1589, + [1609] = 1487, + [1610] = 1598, + [1611] = 1503, + [1612] = 1513, + [1613] = 1552, + [1614] = 1465, + [1615] = 1522, + [1616] = 1486, + [1617] = 1589, + [1618] = 1494, + [1619] = 1503, + [1620] = 1529, + [1621] = 1621, + [1622] = 1475, + [1623] = 1602, + [1624] = 1624, + [1625] = 1625, + [1626] = 1626, + [1627] = 1627, + [1628] = 1506, + [1629] = 1629, + [1630] = 1463, + [1631] = 1496, + [1632] = 1576, + [1633] = 1461, + [1634] = 1634, + [1635] = 1496, + [1636] = 1636, + [1637] = 1479, + [1638] = 1638, + [1639] = 1475, + [1640] = 1458, + [1641] = 1473, + [1642] = 1464, + [1643] = 1486, + [1644] = 1624, + [1645] = 1577, + [1646] = 1470, + [1647] = 1625, + [1648] = 1521, + [1649] = 1456, + [1650] = 1533, + [1651] = 1625, + [1652] = 1625, + [1653] = 1589, }; static bool ts_lex(TSLexer *lexer, TSStateId state) { @@ -3330,1143 +3538,1163 @@ static bool ts_lex(TSLexer *lexer, TSStateId state) { eof = lexer->eof(lexer); switch (state) { case 0: - if (eof) ADVANCE(92); + if (eof) ADVANCE(90); ADVANCE_MAP( - '!', 377, - '"', 347, - '#', 450, - '$', 458, - '%', 383, - '&', 463, - '\'', 353, - '(', 363, - ')', 364, - '*', 379, - '+', 385, - ',', 365, - '-', 388, - '.', 370, - '/', 381, - ':', 368, - ';', 411, - '<', 393, - '=', 462, - '>', 395, - '?', 405, - 'A', 280, - 'B', 291, - 'F', 273, - 'I', 286, - 'L', 262, - 'M', 225, - 'N', 261, - 'S', 323, - '[', 372, - '\\', 84, - ']', 373, - '_', 412, - 'b', 302, - 'c', 234, - 'd', 241, - 'e', 278, - 'f', 230, - 'g', 289, - 'i', 255, - 'l', 252, - 'm', 226, - 'n', 264, - 'r', 223, - 's', 242, - 't', 299, - 'u', 310, - 'w', 260, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '#', 515, + '$', 523, + '%', 418, + '&', 440, + '\'', 387, + '(', 397, + ')', 398, + '*', 414, + '+', 420, + ',', 399, + '-', 423, + '.', 404, + '/', 416, + ':', 402, + ';', 452, + '<', 428, + '=', 527, + '>', 432, + '?', 448, + 'A', 312, + 'B', 323, + 'F', 304, + 'I', 318, + 'L', 291, + 'M', 253, + 'N', 293, + 'S', 355, + '[', 406, + '\\', 82, + ']', 407, + '^', 439, + '_', 453, + 'b', 335, + 'c', 257, + 'd', 269, + 'e', 309, + 'f', 249, + 'g', 321, + 'i', 242, + 'l', 282, + 'm', 254, + 'n', 295, + 'r', 241, + 's', 270, + 't', 331, + 'u', 243, + 'w', 290, + '{', 409, + '|', 438, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(0); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('C' <= lookahead && lookahead <= 'Z') || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 1: ADVANCE_MAP( - '!', 377, - '"', 347, - '#', 450, - '$', 458, - '%', 382, - '&', 463, - '\'', 353, - '(', 363, - ')', 364, - '*', 378, - '+', 384, - ',', 365, - '-', 389, - '.', 370, - '/', 380, - ':', 367, - ';', 411, - '<', 393, - '=', 462, - '>', 395, - '?', 404, - '[', 372, - ']', 373, - 'e', 220, - 'f', 104, - 'm', 111, - 'n', 147, - 'r', 101, - 't', 193, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '#', 515, + '$', 523, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + ')', 398, + '*', 413, + '+', 419, + ',', 399, + '-', 424, + '.', 404, + '/', 415, + ':', 401, + ';', 452, + '<', 429, + '=', 527, + '>', 431, + '?', 447, + '[', 406, + ']', 407, + 'e', 236, + 'f', 112, + 'm', 119, + 'n', 160, + 'r', 99, + 't', 209, + '{', 409, + '|', 438, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(1); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 2: ADVANCE_MAP( - '!', 377, - '"', 347, - '#', 450, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - ')', 364, - '*', 378, - '+', 384, - ',', 365, - '-', 386, - '.', 370, - '/', 380, - ':', 366, - ';', 411, - '<', 393, - '=', 36, - '>', 395, - '?', 405, - '[', 372, - ']', 373, - 'c', 39, - 'd', 46, - 'f', 40, - 'i', 53, - 'm', 41, - 'n', 56, + '!', 411, + '"', 381, + '#', 515, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + ')', 398, + '*', 413, + '+', 419, + ',', 399, + '-', 421, + '.', 404, + '/', 415, + ':', 400, + ';', 452, + '<', 428, + '=', 35, + '>', 432, + '?', 448, + '[', 406, + ']', 407, + '^', 439, + 'c', 38, + 'd', 45, + 'f', 39, + 'i', 52, + 'm', 40, + 'n', 55, 'r', 16, - 's', 77, - 't', 66, - '{', 375, - '|', 407, - '}', 361, + 's', 76, + 't', 65, + '{', 409, + '|', 438, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(2); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); END_STATE(); case 3: ADVANCE_MAP( - '!', 377, - '"', 347, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - ')', 364, - '*', 378, - '+', 384, - ',', 365, - '-', 386, - '.', 370, - '/', 380, - ':', 366, - ';', 411, - '<', 393, - '=', 36, - '>', 395, - '?', 405, - '[', 372, - ']', 373, - 'c', 145, - 'f', 104, - 'm', 113, - 'n', 147, - 'r', 100, - 's', 126, - 't', 193, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + ')', 398, + '*', 413, + '+', 419, + ',', 399, + '-', 421, + '.', 404, + '/', 415, + ':', 400, + ';', 452, + '<', 428, + '=', 35, + '>', 432, + '?', 448, + '[', 406, + ']', 407, + '^', 439, + 'c', 157, + 'f', 112, + 'm', 121, + 'n', 160, + 'r', 98, + 's', 135, + 't', 209, + '{', 409, + '|', 438, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(3); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 4: ADVANCE_MAP( - '!', 377, - '"', 347, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - '*', 378, - '+', 384, - ',', 365, - '-', 386, - '.', 370, - '/', 380, - ':', 366, - '<', 393, - '=', 461, - '>', 395, - '?', 405, - '[', 372, - ']', 373, - 'c', 145, - 'f', 104, - 'i', 140, - 'm', 113, - 'n', 147, - 'r', 100, - 's', 126, - 't', 193, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + '*', 413, + '+', 419, + ',', 399, + '-', 421, + '.', 404, + '/', 415, + ':', 400, + '<', 428, + '=', 526, + '>', 432, + '?', 448, + '[', 406, + ']', 407, + '^', 439, + 'c', 157, + 'f', 112, + 'i', 152, + 'm', 121, + 'n', 160, + 'r', 98, + 's', 135, + 't', 209, + '{', 409, + '|', 438, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(4); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 5: ADVANCE_MAP( - '!', 377, - '"', 347, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - '*', 378, - '+', 384, - ',', 365, - '-', 386, - '.', 370, - '/', 380, - ';', 411, - '<', 393, - '=', 35, - '>', 395, - '?', 405, - '[', 372, - ']', 373, - '_', 413, - 'f', 104, - 'n', 147, - 'r', 101, - 't', 193, - '{', 375, - '|', 82, - '}', 361, + '!', 411, + '"', 381, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + '*', 413, + '+', 419, + ',', 399, + '-', 421, + '.', 404, + '/', 415, + ';', 452, + '<', 428, + '=', 34, + '>', 432, + '?', 448, + '[', 406, + ']', 407, + '^', 439, + '_', 454, + 'f', 112, + 'n', 160, + 'r', 99, + 't', 209, + '{', 409, + '|', 438, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(5); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 6: ADVANCE_MAP( - '!', 377, - '"', 347, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - '*', 378, - '+', 384, - ',', 365, - '-', 386, - '.', 370, - '/', 380, - ';', 411, - '<', 393, - '=', 35, - '>', 395, - '?', 405, - '[', 372, - '_', 413, - 'c', 145, - 'f', 104, - 'm', 113, - 'n', 147, - 'r', 100, - 's', 126, - 't', 193, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + '*', 413, + '+', 419, + ',', 399, + '-', 421, + '.', 404, + '/', 415, + ';', 452, + '<', 428, + '=', 34, + '>', 432, + '?', 448, + '[', 406, + '^', 439, + '_', 454, + 'c', 157, + 'f', 112, + 'm', 121, + 'n', 160, + 'r', 98, + 's', 135, + 't', 209, + '{', 409, + '|', 438, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(6); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 7: ADVANCE_MAP( - '!', 377, - '"', 347, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - '*', 378, - '+', 384, - '-', 386, - '.', 370, - '/', 380, - ';', 411, - '<', 393, - '=', 35, - '>', 395, - '?', 405, - '[', 372, - 'c', 115, - 'd', 133, - 'f', 104, - 'm', 113, - 'n', 147, - 'r', 100, - 's', 126, - 't', 193, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + '*', 413, + '+', 419, + '-', 421, + '.', 404, + '/', 415, + ';', 452, + '<', 428, + '=', 34, + '>', 432, + '?', 448, + '[', 406, + '^', 439, + 'c', 123, + 'd', 142, + 'f', 112, + 'm', 121, + 'n', 160, + 'r', 98, + 's', 135, + 't', 209, + '{', 409, + '|', 438, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(7); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 8: ADVANCE_MAP( - '!', 377, - '"', 347, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - '*', 378, - '+', 384, - '-', 386, - '.', 370, - '/', 380, - '<', 393, - '=', 36, - '>', 395, - '?', 405, - '[', 372, - 'c', 145, - 'f', 104, - 'i', 140, - 'm', 113, - 'n', 147, - 'r', 100, - 's', 126, - 't', 193, - '{', 375, - '|', 407, + '!', 411, + '"', 381, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + '*', 413, + '+', 419, + '-', 421, + '.', 404, + '/', 415, + '<', 428, + '=', 35, + '>', 432, + '?', 448, + '[', 406, + '^', 439, + 'c', 157, + 'f', 112, + 'i', 152, + 'm', 121, + 'n', 160, + 'r', 98, + 's', 135, + 't', 209, + '{', 409, + '|', 438, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(8); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 9: ADVANCE_MAP( - '!', 377, - '%', 383, - '&', 19, - '(', 363, - ')', 364, - '*', 379, - '+', 385, - '-', 387, - '.', 370, - '/', 381, - ':', 369, - ';', 411, - '<', 393, - '=', 461, - '>', 395, - '?', 405, - 'A', 169, - 'B', 177, - 'F', 160, - 'I', 173, - 'L', 148, - 'M', 110, - 'N', 153, - 'S', 211, - '[', 372, - '{', 375, - '|', 82, + '!', 411, + '%', 418, + '&', 440, + '(', 397, + ')', 398, + '*', 414, + '+', 420, + '-', 422, + '.', 404, + '/', 416, + ':', 403, + ';', 452, + '<', 428, + '=', 526, + '>', 432, + '?', 448, + 'A', 184, + 'B', 192, + 'F', 174, + 'I', 188, + 'L', 161, + 'M', 118, + 'N', 166, + 'S', 227, + '[', 406, + '^', 439, + 'f', 107, + 'i', 100, + 'u', 101, + '{', 409, + '|', 438, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(9); if (('C' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 10: ADVANCE_MAP( - '!', 377, - '%', 382, - '&', 19, - '(', 363, - '*', 378, - '+', 384, - ',', 365, - '-', 386, - '.', 370, - '/', 380, - ':', 366, - ';', 411, - '<', 393, - '=', 461, - '>', 395, - '?', 405, - '[', 372, - ']', 373, - 'c', 39, - 'd', 46, - 'i', 52, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '%', 417, + '&', 440, + '(', 397, + '*', 413, + '+', 419, + ',', 399, + '-', 421, + '.', 404, + '/', 415, + ':', 400, + ';', 452, + '<', 428, + '=', 526, + '>', 432, + '?', 448, + '[', 406, + ']', 407, + '^', 439, + 'c', 38, + 'd', 45, + 'i', 51, + '{', 409, + '|', 438, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(10); END_STATE(); case 11: ADVANCE_MAP( - '!', 376, - '"', 347, - '\'', 353, - '(', 363, - ')', 364, - ',', 365, - '/', 25, - ':', 366, - ';', 411, - '<', 392, - '=', 460, - '>', 394, - '?', 404, - '[', 372, - ']', 373, - 'c', 145, - 'f', 104, - 'm', 113, - 'n', 147, - 'r', 100, - 's', 126, - 't', 193, - '{', 375, - '|', 406, - '}', 361, + '!', 410, + '"', 381, + '\'', 387, + '(', 397, + ')', 398, + ',', 399, + '/', 24, + ':', 400, + ';', 452, + '<', 427, + '=', 525, + '>', 430, + '?', 447, + '[', 406, + ']', 407, + 'c', 157, + 'f', 112, + 'm', 121, + 'n', 160, + 'r', 98, + 's', 135, + 't', 209, + '{', 409, + '|', 437, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(11); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 12: - if (lookahead == '"') ADVANCE(347); - if (lookahead == '$') ADVANCE(81); - if (lookahead == '/') ADVANCE(348); - if (lookahead == '\\') ADVANCE(84); + if (lookahead == '"') ADVANCE(381); + if (lookahead == '$') ADVANCE(80); + if (lookahead == '/') ADVANCE(382); + if (lookahead == '\\') ADVANCE(82); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') ADVANCE(351); - if (lookahead != 0) ADVANCE(352); + lookahead == ' ') ADVANCE(385); + if (lookahead != 0) ADVANCE(386); END_STATE(); case 13: ADVANCE_MAP( - '"', 347, - '\'', 353, - '*', 378, - '.', 31, - '/', 25, - 'r', 101, - '{', 375, - '}', 361, + '"', 381, + '\'', 387, + '*', 413, + '.', 30, + '/', 24, + 'r', 99, + '{', 409, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(13); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 14: ADVANCE_MAP( - '"', 347, - '\'', 353, - '.', 31, - '/', 25, - '[', 372, - ']', 373, - '_', 413, - 'f', 104, - 'n', 147, - 'r', 101, - 't', 193, - '{', 375, + '"', 381, + '\'', 387, + '.', 30, + '/', 24, + '[', 406, + ']', 407, + '_', 454, + 'f', 112, + 'n', 160, + 'r', 99, + 't', 209, + '{', 409, ); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(14); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 15: - if (lookahead == '"') ADVANCE(347); - if (lookahead == '\'') ADVANCE(353); - if (lookahead == '/') ADVANCE(25); - if (lookahead == 'f') ADVANCE(104); - if (lookahead == 'n') ADVANCE(147); - if (lookahead == 'r') ADVANCE(101); - if (lookahead == 't') ADVANCE(193); + if (lookahead == '"') ADVANCE(381); + if (lookahead == '\'') ADVANCE(387); + if (lookahead == '/') ADVANCE(24); + if (lookahead == 'f') ADVANCE(112); + if (lookahead == 'n') ADVANCE(160); + if (lookahead == 'r') ADVANCE(99); + if (lookahead == 't') ADVANCE(209); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(15); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 16: if (lookahead == '"') ADVANCE(17); if (lookahead == '#') ADVANCE(16); END_STATE(); case 17: - if (lookahead == '"') ADVANCE(359); - if (lookahead == '\\') ADVANCE(87); + if (lookahead == '"') ADVANCE(393); + if (lookahead == '\\') ADVANCE(85); if (lookahead != 0) ADVANCE(17); END_STATE(); case 18: - if (lookahead == '$') ADVANCE(81); - if (lookahead == '\'') ADVANCE(353); - if (lookahead == '/') ADVANCE(354); - if (lookahead == '\\') ADVANCE(84); + if (lookahead == '$') ADVANCE(80); + if (lookahead == '\'') ADVANCE(387); + if (lookahead == '/') ADVANCE(388); + if (lookahead == '\\') ADVANCE(82); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') ADVANCE(357); - if (lookahead != 0) ADVANCE(358); + lookahead == ' ') ADVANCE(391); + if (lookahead != 0) ADVANCE(392); END_STATE(); case 19: - if (lookahead == '&') ADVANCE(398); - END_STATE(); - case 20: ADVANCE_MAP( - '(', 363, - ')', 364, - ',', 365, - '.', 31, - '/', 25, - ':', 366, - ';', 411, - '=', 460, - '[', 372, - ']', 373, - 'i', 256, - '{', 375, - '|', 406, - '}', 361, + '(', 397, + ')', 398, + ',', 399, + '.', 30, + '/', 24, + ':', 400, + ';', 452, + '=', 525, + '[', 406, + ']', 407, + 'i', 286, + '{', 409, + '|', 437, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(20); + lookahead == ' ') SKIP(19); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 21: - if (lookahead == '(') ADVANCE(363); - if (lookahead == '/') ADVANCE(25); - if (lookahead == '[') ADVANCE(372); - if (lookahead == '_') ADVANCE(413); - if (lookahead == '{') ADVANCE(375); + case 20: + if (lookahead == '(') ADVANCE(397); + if (lookahead == '/') ADVANCE(24); + if (lookahead == '[') ADVANCE(406); + if (lookahead == '_') ADVANCE(454); + if (lookahead == '{') ADVANCE(409); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(21); + lookahead == ' ') SKIP(20); if (('A' <= lookahead && lookahead <= 'Z') || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 22: - if (lookahead == ')') ADVANCE(364); - if (lookahead == ',') ADVANCE(365); - if (lookahead == '.') ADVANCE(31); - if (lookahead == '/') ADVANCE(25); - if (lookahead == ';') ADVANCE(411); - if (lookahead == ']') ADVANCE(373); - if (lookahead == '}') ADVANCE(361); + case 21: + if (lookahead == ')') ADVANCE(398); + if (lookahead == ',') ADVANCE(399); + if (lookahead == '.') ADVANCE(30); + if (lookahead == '/') ADVANCE(24); + if (lookahead == ';') ADVANCE(452); + if (lookahead == ']') ADVANCE(407); + if (lookahead == '}') ADVANCE(395); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(22); + lookahead == ' ') SKIP(21); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 23: + case 22: ADVANCE_MAP( - ')', 364, - ',', 365, - '.', 30, - '/', 25, - ':', 366, - ';', 411, - '<', 392, - '=', 460, - '>', 394, - '?', 404, - ']', 373, - 'f', 62, - 'i', 52, - '{', 375, - '|', 406, - '}', 361, + ')', 398, + ',', 399, + '.', 29, + '/', 24, + ':', 400, + ';', 452, + '<', 427, + '=', 525, + '>', 430, + '?', 447, + ']', 407, + 'f', 61, + 'i', 51, + '{', 409, + '|', 437, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(23); + lookahead == ' ') SKIP(22); END_STATE(); - case 24: - if (lookahead == ')') ADVANCE(364); - if (lookahead == ',') ADVANCE(365); - if (lookahead == '/') ADVANCE(25); - if (lookahead == '{') ADVANCE(375); - if (lookahead == '|') ADVANCE(406); - if (lookahead == '}') ADVANCE(361); + case 23: + if (lookahead == ')') ADVANCE(398); + if (lookahead == ',') ADVANCE(399); + if (lookahead == '/') ADVANCE(24); + if (lookahead == '{') ADVANCE(409); + if (lookahead == '|') ADVANCE(437); + if (lookahead == '}') ADVANCE(395); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(24); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(335); + lookahead == ' ') SKIP(23); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(369); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 24: + if (lookahead == '*') ADVANCE(26); + if (lookahead == '/') ADVANCE(93); END_STATE(); case 25: - if (lookahead == '*') ADVANCE(27); - if (lookahead == '/') ADVANCE(95); + if (lookahead == '*') ADVANCE(25); + if (lookahead == '/') ADVANCE(94); + if (lookahead != 0) ADVANCE(26); END_STATE(); case 26: - if (lookahead == '*') ADVANCE(26); - if (lookahead == '/') ADVANCE(96); - if (lookahead != 0) ADVANCE(27); + if (lookahead == '*') ADVANCE(25); + if (lookahead != 0) ADVANCE(26); END_STATE(); case 27: - if (lookahead == '*') ADVANCE(26); - if (lookahead != 0) ADVANCE(27); - END_STATE(); - case 28: ADVANCE_MAP( - ',', 365, - '.', 31, - '/', 25, - ':', 366, - '=', 460, - ']', 373, - 'i', 52, - '|', 406, - '}', 361, + ',', 399, + '.', 30, + '/', 24, + ':', 400, + '=', 525, + ']', 407, + 'i', 51, + '|', 437, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(28); + lookahead == ' ') SKIP(27); END_STATE(); - case 29: + case 28: ADVANCE_MAP( - ',', 365, - '.', 30, - '/', 25, - ':', 366, - ';', 411, - '=', 460, - ']', 373, - 'i', 256, - '|', 406, - '}', 361, + ',', 399, + '.', 29, + '/', 24, + ':', 400, + ';', 452, + '=', 525, + ']', 407, + 'i', 286, + '|', 437, + '}', 395, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(29); + lookahead == ' ') SKIP(28); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 29: + if (lookahead == '.') ADVANCE(445); END_STATE(); case 30: - if (lookahead == '.') ADVANCE(402); + if (lookahead == '.') ADVANCE(444); END_STATE(); case 31: - if (lookahead == '.') ADVANCE(401); + if (lookahead == '.') ADVANCE(29); + if (lookahead == '/') ADVANCE(24); + if (lookahead == '=') ADVANCE(36); + if (lookahead == 'i') ADVANCE(51); + if (lookahead == '|') ADVANCE(437); + if (('\t' <= lookahead && lookahead <= '\r') || + lookahead == ' ') SKIP(31); END_STATE(); case 32: - if (lookahead == '.') ADVANCE(30); - if (lookahead == '/') ADVANCE(25); - if (lookahead == '=') ADVANCE(37); - if (lookahead == 'i') ADVANCE(52); - if (lookahead == '|') ADVANCE(406); + if (lookahead == '/') ADVANCE(24); + if (lookahead == 'f') ADVANCE(313); + if (lookahead == '}') ADVANCE(395); if (('\t' <= lookahead && lookahead <= '\r') || lookahead == ' ') SKIP(32); - END_STATE(); - case 33: - if (lookahead == '/') ADVANCE(25); - if (lookahead == 'f') ADVANCE(281); - if (lookahead == '}') ADVANCE(361); - if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(33); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 33: + if (lookahead == '=') ADVANCE(433); END_STATE(); case 34: - if (lookahead == '=') ADVANCE(396); + if (lookahead == '=') ADVANCE(425); END_STATE(); case 35: - if (lookahead == '=') ADVANCE(390); + if (lookahead == '=') ADVANCE(425); + if (lookahead == '>') ADVANCE(451); END_STATE(); case 36: - if (lookahead == '=') ADVANCE(390); - if (lookahead == '>') ADVANCE(410); + if (lookahead == '>') ADVANCE(451); END_STATE(); case 37: - if (lookahead == '>') ADVANCE(410); + if (lookahead == '_') ADVANCE(69); END_STATE(); case 38: - if (lookahead == '_') ADVANCE(70); + if (lookahead == 'a') ADVANCE(71); END_STATE(); case 39: - if (lookahead == 'a') ADVANCE(72); + if (lookahead == 'a') ADVANCE(60); + if (lookahead == 'n') ADVANCE(543); + if (lookahead == 'o') ADVANCE(66); END_STATE(); case 40: - if (lookahead == 'a') ADVANCE(61); - if (lookahead == 'n') ADVANCE(479); - if (lookahead == 'o') ADVANCE(67); + if (lookahead == 'a') ADVANCE(44); END_STATE(); case 41: - if (lookahead == 'a') ADVANCE(45); + if (lookahead == 'a') ADVANCE(54); + if (lookahead == 'u') ADVANCE(47); END_STATE(); case 42: - if (lookahead == 'a') ADVANCE(55); - if (lookahead == 'u') ADVANCE(48); + if (lookahead == 'a') ADVANCE(78); END_STATE(); case 43: - if (lookahead == 'a') ADVANCE(79); + if (lookahead == 'c') ADVANCE(74); END_STATE(); case 44: - if (lookahead == 'c') ADVANCE(75); + if (lookahead == 'c') ADVANCE(68); END_STATE(); case 45: - if (lookahead == 'c') ADVANCE(69); + if (lookahead == 'e') ADVANCE(53); END_STATE(); case 46: - if (lookahead == 'e') ADVANCE(54); + if (lookahead == 'e') ADVANCE(468); END_STATE(); case 47: - if (lookahead == 'e') ADVANCE(427); + if (lookahead == 'e') ADVANCE(372); END_STATE(); case 48: - if (lookahead == 'e') ADVANCE(338); + if (lookahead == 'e') ADVANCE(549); END_STATE(); case 49: - if (lookahead == 'e') ADVANCE(485); + if (lookahead == 'e') ADVANCE(375); END_STATE(); case 50: - if (lookahead == 'e') ADVANCE(341); + if (lookahead == 'e') ADVANCE(70); END_STATE(); case 51: - if (lookahead == 'e') ADVANCE(71); + if (lookahead == 'f') ADVANCE(455); END_STATE(); case 52: - if (lookahead == 'f') ADVANCE(414); + if (lookahead == 'f') ADVANCE(455); + if (lookahead == 'm') ADVANCE(63); END_STATE(); case 53: - if (lookahead == 'f') ADVANCE(414); - if (lookahead == 'm') ADVANCE(64); + if (lookahead == 'f') ADVANCE(42); END_STATE(); case 54: - if (lookahead == 'f') ADVANCE(43); + if (lookahead == 'i') ADVANCE(73); END_STATE(); case 55: - if (lookahead == 'i') ADVANCE(74); + if (lookahead == 'i') ADVANCE(56); END_STATE(); case 56: - if (lookahead == 'i') ADVANCE(57); + if (lookahead == 'l') ADVANCE(378); END_STATE(); case 57: - if (lookahead == 'l') ADVANCE(344); + if (lookahead == 'l') ADVANCE(555); END_STATE(); case 58: - if (lookahead == 'l') ADVANCE(491); + if (lookahead == 'l') ADVANCE(75); END_STATE(); case 59: - if (lookahead == 'l') ADVANCE(76); + if (lookahead == 'l') ADVANCE(50); END_STATE(); case 60: - if (lookahead == 'l') ADVANCE(51); + if (lookahead == 'l') ADVANCE(72); END_STATE(); case 61: - if (lookahead == 'l') ADVANCE(73); + if (lookahead == 'o') ADVANCE(66); END_STATE(); case 62: - if (lookahead == 'o') ADVANCE(67); + if (lookahead == 'o') ADVANCE(37); END_STATE(); case 63: - if (lookahead == 'o') ADVANCE(38); + if (lookahead == 'p') ADVANCE(57); END_STATE(); case 64: - if (lookahead == 'p') ADVANCE(58); + if (lookahead == 'p') ADVANCE(48); END_STATE(); case 65: - if (lookahead == 'p') ADVANCE(49); + if (lookahead == 'r') ADVANCE(41); + if (lookahead == 'y') ADVANCE(64); END_STATE(); case 66: - if (lookahead == 'r') ADVANCE(42); - if (lookahead == 'y') ADVANCE(65); + if (lookahead == 'r') ADVANCE(540); END_STATE(); case 67: - if (lookahead == 'r') ADVANCE(476); + if (lookahead == 'r') ADVANCE(77); END_STATE(); case 68: - if (lookahead == 'r') ADVANCE(78); + if (lookahead == 'r') ADVANCE(62); END_STATE(); case 69: - if (lookahead == 'r') ADVANCE(63); + if (lookahead == 'r') ADVANCE(79); END_STATE(); case 70: - if (lookahead == 'r') ADVANCE(80); + if (lookahead == 's') ADVANCE(520); END_STATE(); case 71: - if (lookahead == 's') ADVANCE(455); + if (lookahead == 's') ADVANCE(46); END_STATE(); case 72: - if (lookahead == 's') ADVANCE(47); + if (lookahead == 's') ADVANCE(49); END_STATE(); case 73: - if (lookahead == 's') ADVANCE(50); + if (lookahead == 't') ADVANCE(552); END_STATE(); case 74: - if (lookahead == 't') ADVANCE(488); + if (lookahead == 't') ADVANCE(546); END_STATE(); case 75: - if (lookahead == 't') ADVANCE(482); + if (lookahead == 't') ADVANCE(471); END_STATE(); case 76: - if (lookahead == 't') ADVANCE(430); + if (lookahead == 't') ADVANCE(67); END_STATE(); case 77: - if (lookahead == 't') ADVANCE(68); + if (lookahead == 'u') ADVANCE(43); END_STATE(); case 78: - if (lookahead == 'u') ADVANCE(44); + if (lookahead == 'u') ADVANCE(58); END_STATE(); case 79: if (lookahead == 'u') ADVANCE(59); END_STATE(); case 80: - if (lookahead == 'u') ADVANCE(60); + if (lookahead == '{') ADVANCE(394); END_STATE(); case 81: - if (lookahead == '{') ADVANCE(360); + if (lookahead == '+' || + lookahead == '-') ADVANCE(84); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(371); END_STATE(); case 82: - if (lookahead == '|') ADVANCE(399); + ADVANCE_MAP( + '"', 396, + '$', 396, + '\'', 396, + '0', 396, + '\\', 396, + 'n', 396, + 'r', 396, + 't', 396, + ); END_STATE(); case 83: - if (lookahead == '+' || - lookahead == '-') ADVANCE(86); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(337); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(370); END_STATE(); case 84: - ADVANCE_MAP( - '"', 362, - '$', 362, - '\'', 362, - '0', 362, - '\\', 362, - 'n', 362, - 'r', 362, - 't', 362, - ); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(371); END_STATE(); case 85: - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(336); - END_STATE(); - case 86: - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(337); - END_STATE(); - case 87: if (lookahead != 0 && lookahead != '\n') ADVANCE(17); END_STATE(); - case 88: - if (eof) ADVANCE(92); + case 86: + if (eof) ADVANCE(90); ADVANCE_MAP( - '!', 377, - '"', 347, - '#', 450, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - ')', 364, - '*', 378, - '+', 384, - ',', 365, - '-', 386, - '.', 370, - '/', 380, - ':', 366, - ';', 411, - '<', 393, - '=', 36, - '>', 395, - '?', 405, - '[', 372, - ']', 373, - 'b', 186, - 'c', 144, - 'e', 220, - 'f', 103, - 'g', 174, - 'i', 139, - 'l', 124, - 'm', 105, - 'n', 147, - 'r', 99, - 's', 125, - 't', 184, - 'u', 195, - 'w', 146, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '#', 515, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + ')', 398, + '*', 413, + '+', 419, + ',', 399, + '-', 421, + '.', 404, + '/', 415, + ':', 400, + ';', 452, + '<', 428, + '=', 35, + '>', 432, + '?', 448, + '[', 406, + ']', 407, + '^', 439, + 'b', 202, + 'c', 156, + 'e', 236, + 'f', 111, + 'g', 189, + 'i', 151, + 'l', 133, + 'm', 113, + 'n', 160, + 'r', 97, + 's', 134, + 't', 199, + 'u', 211, + 'w', 158, + '{', 409, + '|', 438, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(88); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + lookahead == ' ') SKIP(86); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 89: - if (eof) ADVANCE(92); + case 87: + if (eof) ADVANCE(90); ADVANCE_MAP( - '!', 377, - '"', 347, - '#', 450, - '%', 382, - '&', 19, - '\'', 353, - '(', 363, - '*', 378, - '+', 384, - '-', 386, - '.', 370, - '/', 380, - ';', 411, - '<', 393, - '=', 35, - '>', 395, - '?', 405, - '[', 372, - 'b', 186, - 'c', 144, - 'e', 165, - 'f', 103, - 'g', 174, - 'i', 139, - 'l', 124, - 'm', 105, - 'n', 147, - 'r', 99, - 's', 125, - 't', 184, - 'u', 195, - 'w', 146, - '{', 375, - '|', 407, - '}', 361, + '!', 411, + '"', 381, + '#', 515, + '%', 417, + '&', 440, + '\'', 387, + '(', 397, + '*', 413, + '+', 419, + '-', 421, + '.', 404, + '/', 415, + ';', 452, + '<', 428, + '=', 34, + '>', 432, + '?', 448, + '[', 406, + '^', 439, + 'b', 202, + 'c', 156, + 'e', 179, + 'f', 111, + 'g', 189, + 'i', 151, + 'l', 133, + 'm', 113, + 'n', 160, + 'r', 97, + 's', 134, + 't', 199, + 'u', 211, + 'w', 158, + '{', 409, + '|', 438, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(89); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + lookahead == ' ') SKIP(87); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 90: - if (eof) ADVANCE(92); + case 88: + if (eof) ADVANCE(90); ADVANCE_MAP( - '!', 376, - '"', 347, - '#', 450, - '\'', 353, - '(', 363, - ')', 364, - ',', 365, - '.', 31, - '/', 25, - ':', 366, - ';', 411, - '<', 34, - '=', 37, - '>', 394, - '?', 404, - '[', 372, - ']', 373, - 'b', 186, - 'c', 144, - 'e', 220, - 'f', 103, - 'g', 174, - 'i', 139, - 'l', 124, - 'm', 105, - 'n', 147, - 'r', 99, - 's', 125, - 't', 184, - 'u', 195, - 'w', 146, - '{', 375, - '|', 406, - '}', 361, + '!', 410, + '"', 381, + '#', 515, + '\'', 387, + '(', 397, + ')', 398, + ',', 399, + '.', 30, + '/', 24, + ':', 400, + ';', 452, + '<', 33, + '=', 36, + '>', 430, + '?', 447, + '[', 406, + ']', 407, + 'b', 202, + 'c', 156, + 'e', 236, + 'f', 111, + 'g', 189, + 'i', 151, + 'l', 133, + 'm', 113, + 'n', 160, + 'r', 97, + 's', 134, + 't', 199, + 'u', 211, + 'w', 158, + '{', 409, + '|', 437, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(90); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + lookahead == ' ') SKIP(88); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 91: - if (eof) ADVANCE(92); + case 89: + if (eof) ADVANCE(90); ADVANCE_MAP( - '!', 376, - '"', 347, - '#', 450, - '\'', 353, - '(', 363, - '/', 25, - ';', 411, - '[', 372, - 'b', 186, - 'c', 144, - 'e', 165, - 'f', 103, - 'g', 174, - 'i', 139, - 'l', 124, - 'm', 105, - 'n', 147, - 'r', 99, - 's', 125, - 't', 184, - 'u', 195, - 'w', 146, - '{', 375, - '|', 406, - '}', 361, + '!', 410, + '"', 381, + '#', 515, + '\'', 387, + '(', 397, + '/', 24, + ';', 452, + '[', 406, + 'b', 202, + 'c', 156, + 'e', 179, + 'f', 111, + 'g', 189, + 'i', 151, + 'l', 133, + 'm', 113, + 'n', 160, + 'r', 97, + 's', 134, + 't', 199, + 'u', 211, + 'w', 158, + '{', 409, + '|', 437, + '}', 395, + '~', 412, ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(91); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + lookahead == ' ') SKIP(89); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); if (('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 92: + case 90: ACCEPT_TOKEN(ts_builtin_sym_end); END_STATE(); - case 93: + case 91: ACCEPT_TOKEN(sym_line_comment); - if (lookahead == '\n') ADVANCE(358); + if (lookahead == '\n') ADVANCE(392); if (lookahead == '$' || lookahead == '\'' || - lookahead == '\\') ADVANCE(95); - if (lookahead != 0) ADVANCE(93); + lookahead == '\\') ADVANCE(93); + if (lookahead != 0) ADVANCE(91); END_STATE(); - case 94: + case 92: ACCEPT_TOKEN(sym_line_comment); - if (lookahead == '\n') ADVANCE(352); + if (lookahead == '\n') ADVANCE(386); if (lookahead == '"' || lookahead == '$' || - lookahead == '\\') ADVANCE(95); - if (lookahead != 0) ADVANCE(94); + lookahead == '\\') ADVANCE(93); + if (lookahead != 0) ADVANCE(92); END_STATE(); - case 95: + case 93: ACCEPT_TOKEN(sym_line_comment); if (lookahead != 0 && - lookahead != '\n') ADVANCE(95); + lookahead != '\n') ADVANCE(93); END_STATE(); - case 96: + case 94: ACCEPT_TOKEN(sym_block_comment); - if (lookahead == '*') ADVANCE(26); - if (lookahead != 0) ADVANCE(27); + if (lookahead == '*') ADVANCE(25); + if (lookahead != 0) ADVANCE(26); END_STATE(); - case 97: + case 95: ACCEPT_TOKEN(sym_block_comment); - if (lookahead == '*') ADVANCE(355); + if (lookahead == '*') ADVANCE(389); if (lookahead == '$' || lookahead == '\'' || - lookahead == '\\') ADVANCE(27); - if (lookahead != 0) ADVANCE(356); + lookahead == '\\') ADVANCE(26); + if (lookahead != 0) ADVANCE(390); END_STATE(); - case 98: + case 96: ACCEPT_TOKEN(sym_block_comment); - if (lookahead == '*') ADVANCE(349); + if (lookahead == '*') ADVANCE(383); if (lookahead == '"' || lookahead == '$' || - lookahead == '\\') ADVANCE(27); - if (lookahead != 0) ADVANCE(350); + lookahead == '\\') ADVANCE(26); + if (lookahead != 0) ADVANCE(384); END_STATE(); - case 99: + case 97: ACCEPT_TOKEN(aux_sym_identifier_token1); if (lookahead == '"') ADVANCE(17); if (lookahead == '#') ADVANCE(16); - if (lookahead == 'e') ADVANCE(116); + if (lookahead == 'e') ADVANCE(125); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 100: + case 98: ACCEPT_TOKEN(aux_sym_identifier_token1); if (lookahead == '"') ADVANCE(17); if (lookahead == '#') ADVANCE(16); - if (lookahead == 'e') ADVANCE(117); + if (lookahead == 'e') ADVANCE(126); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 101: + case 99: ACCEPT_TOKEN(aux_sym_identifier_token1); if (lookahead == '"') ADVANCE(17); if (lookahead == '#') ADVANCE(16); @@ -4474,2956 +4702,3487 @@ static bool ts_lex(TSLexer *lexer, TSStateId state) { ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 100: + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == '1') ADVANCE(108); + if (lookahead == '3') ADVANCE(102); + if (lookahead == '6') ADVANCE(105); + if (lookahead == '8') ADVANCE(491); + if (lookahead == 's') ADVANCE(159); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 101: + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == '1') ADVANCE(109); + if (lookahead == '3') ADVANCE(103); + if (lookahead == '6') ADVANCE(106); + if (lookahead == '8') ADVANCE(499); + if (lookahead == 's') ADVANCE(167); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 102: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == '_') ADVANCE(190); + if (lookahead == '2') ADVANCE(495); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 103: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(164); - if (lookahead == 'n') ADVANCE(481); - if (lookahead == 'o') ADVANCE(185); + if (lookahead == '2') ADVANCE(503); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 104: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(164); + if (lookahead == '4') ADVANCE(489); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 105: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(119); + if (lookahead == '4') ADVANCE(497); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 106: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(219); + if (lookahead == '4') ADVANCE(505); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 107: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(154); + if (lookahead == '6') ADVANCE(104); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 108: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(149); - if (lookahead == 'u') ADVANCE(128); - if (lookahead == 'y') ADVANCE(503); + if (lookahead == '6') ADVANCE(493); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 109: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(166); + if (lookahead == '6') ADVANCE(501); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 110: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(180); + if (lookahead == '_') ADVANCE(206); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || - lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 111: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(120); + if (lookahead == 'a') ADVANCE(178); + if (lookahead == 'n') ADVANCE(545); + if (lookahead == 'o') ADVANCE(200); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 112: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(213); + if (lookahead == 'a') ADVANCE(178); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 113: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(201); + if (lookahead == 'a') ADVANCE(128); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 114: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(209); + if (lookahead == 'a') ADVANCE(235); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 115: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'a') ADVANCE(198); - if (lookahead == 'h') ADVANCE(109); + if (lookahead == 'a') ADVANCE(168); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 116: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'c') ADVANCE(218); - if (lookahead == 't') ADVANCE(214); + if (lookahead == 'a') ADVANCE(162); + if (lookahead == 'u') ADVANCE(137); + if (lookahead == 'y') ADVANCE(567); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 117: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'c') ADVANCE(218); + if (lookahead == 'a') ADVANCE(181); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 118: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'c') ADVANCE(143); + if (lookahead == 'a') ADVANCE(195); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 119: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'c') ADVANCE(188); - if (lookahead == 't') ADVANCE(118); + if (lookahead == 'a') ADVANCE(129); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 120: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'c') ADVANCE(188); + if (lookahead == 'a') ADVANCE(229); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 121: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'c') ADVANCE(204); + if (lookahead == 'a') ADVANCE(217); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 122: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'c') ADVANCE(205); + if (lookahead == 'a') ADVANCE(225); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 123: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'd') ADVANCE(422); + if (lookahead == 'a') ADVANCE(214); + if (lookahead == 'h') ADVANCE(117); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 124: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(200); + if (lookahead == 'b') ADVANCE(150); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 125: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(159); - if (lookahead == 'p') ADVANCE(106); - if (lookahead == 't') ADVANCE(187); + if (lookahead == 'c') ADVANCE(234); + if (lookahead == 't') ADVANCE(230); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 126: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(159); - if (lookahead == 'p') ADVANCE(106); + if (lookahead == 'c') ADVANCE(234); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 127: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(452); + if (lookahead == 'c') ADVANCE(155); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 128: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(340); + if (lookahead == 'c') ADVANCE(204); + if (lookahead == 't') ADVANCE(127); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 129: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(487); + if (lookahead == 'c') ADVANCE(204); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 130: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(343); + if (lookahead == 'c') ADVANCE(220); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 131: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(475); + if (lookahead == 'c') ADVANCE(221); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 132: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(499); + if (lookahead == 'd') ADVANCE(463); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 133: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(141); + if (lookahead == 'e') ADVANCE(216); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 134: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(429); + if (lookahead == 'e') ADVANCE(173); + if (lookahead == 'p') ADVANCE(114); + if (lookahead == 't') ADVANCE(203); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 135: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(473); + if (lookahead == 'e') ADVANCE(173); + if (lookahead == 'p') ADVANCE(114); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 136: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(194); + if (lookahead == 'e') ADVANCE(517); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 137: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(121); + if (lookahead == 'e') ADVANCE(374); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 138: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'e') ADVANCE(107); + if (lookahead == 'e') ADVANCE(551); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 139: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'f') ADVANCE(416); - if (lookahead == 'm') ADVANCE(182); + if (lookahead == 'e') ADVANCE(377); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 140: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'f') ADVANCE(416); + if (lookahead == 'e') ADVANCE(539); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 141: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'f') ADVANCE(112); + if (lookahead == 'e') ADVANCE(563); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 142: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'g') ADVANCE(438); + if (lookahead == 'e') ADVANCE(153); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 143: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'h') ADVANCE(409); + if (lookahead == 'e') ADVANCE(470); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 144: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'h') ADVANCE(109); - if (lookahead == 'o') ADVANCE(171); + if (lookahead == 'e') ADVANCE(537); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 145: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'h') ADVANCE(109); + if (lookahead == 'e') ADVANCE(507); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 146: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'h') ADVANCE(151); + if (lookahead == 'e') ADVANCE(509); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 147: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'i') ADVANCE(155); + if (lookahead == 'e') ADVANCE(210); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 148: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'i') ADVANCE(197); + if (lookahead == 'e') ADVANCE(130); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 149: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'i') ADVANCE(202); + if (lookahead == 'e') ADVANCE(115); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 150: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'i') ADVANCE(172); + if (lookahead == 'e') ADVANCE(201); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 151: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'i') ADVANCE(161); + if (lookahead == 'f') ADVANCE(457); + if (lookahead == 'm') ADVANCE(197); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 152: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'i') ADVANCE(170); + if (lookahead == 'f') ADVANCE(457); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 153: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'i') ADVANCE(157); + if (lookahead == 'f') ADVANCE(120); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 154: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'k') ADVANCE(497); + if (lookahead == 'g') ADVANCE(479); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 155: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(346); + if (lookahead == 'h') ADVANCE(450); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 156: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(493); + if (lookahead == 'h') ADVANCE(117); + if (lookahead == 'o') ADVANCE(186); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 157: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(442); + if (lookahead == 'h') ADVANCE(117); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 158: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(440); + if (lookahead == 'h') ADVANCE(164); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 159: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(137); - if (lookahead == 'n') ADVANCE(123); + if (lookahead == 'i') ADVANCE(238); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 160: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(179); + if (lookahead == 'i') ADVANCE(169); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 161: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(131); + if (lookahead == 'i') ADVANCE(213); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 162: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(206); + if (lookahead == 'i') ADVANCE(218); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 163: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(136); + if (lookahead == 'i') ADVANCE(187); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 164: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(196); + if (lookahead == 'i') ADVANCE(175); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 165: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'l') ADVANCE(199); - if (lookahead == 'x') ADVANCE(181); + if (lookahead == 'i') ADVANCE(185); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 166: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(420); + if (lookahead == 'i') ADVANCE(171); + if (lookahead == 'u') ADVANCE(180); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 167: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(418); + if (lookahead == 'i') ADVANCE(239); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 168: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(495); + if (lookahead == 'k') ADVANCE(561); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 169: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(221); + if (lookahead == 'l') ADVANCE(380); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 170: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(142); + if (lookahead == 'l') ADVANCE(557); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 171: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(210); + if (lookahead == 'l') ADVANCE(483); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 172: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(215); + if (lookahead == 'l') ADVANCE(481); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 173: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'n') ADVANCE(207); + if (lookahead == 'l') ADVANCE(148); + if (lookahead == 'n') ADVANCE(132); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 174: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'o') ADVANCE(501); + if (lookahead == 'l') ADVANCE(194); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 175: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'o') ADVANCE(102); + if (lookahead == 'l') ADVANCE(140); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 176: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'o') ADVANCE(189); + if (lookahead == 'l') ADVANCE(222); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 177: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'o') ADVANCE(178); + if (lookahead == 'l') ADVANCE(147); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 178: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'o') ADVANCE(158); + if (lookahead == 'l') ADVANCE(212); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 179: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'o') ADVANCE(114); + if (lookahead == 'l') ADVANCE(215); + if (lookahead == 'x') ADVANCE(196); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 180: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'p') ADVANCE(448); + if (lookahead == 'm') ADVANCE(124); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 181: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'p') ADVANCE(176); + if (lookahead == 'n') ADVANCE(461); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 182: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'p') ADVANCE(156); + if (lookahead == 'n') ADVANCE(459); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 183: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'p') ADVANCE(129); + if (lookahead == 'n') ADVANCE(559); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 184: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(108); - if (lookahead == 'y') ADVANCE(183); + if (lookahead == 'n') ADVANCE(237); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 185: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(478); + if (lookahead == 'n') ADVANCE(154); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 186: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(138); + if (lookahead == 'n') ADVANCE(226); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 187: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(216); + if (lookahead == 'n') ADVANCE(231); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 188: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(175); + if (lookahead == 'n') ADVANCE(223); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 189: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(203); + if (lookahead == 'o') ADVANCE(565); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 190: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(217); + if (lookahead == 'o') ADVANCE(110); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 191: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(168); + if (lookahead == 'o') ADVANCE(205); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 192: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(152); + if (lookahead == 'o') ADVANCE(193); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 193: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'r') ADVANCE(212); + if (lookahead == 'o') ADVANCE(172); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 194: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 's') ADVANCE(457); + if (lookahead == 'o') ADVANCE(122); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 195: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 's') ADVANCE(127); + if (lookahead == 'p') ADVANCE(513); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 196: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 's') ADVANCE(130); + if (lookahead == 'p') ADVANCE(191); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 197: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 's') ADVANCE(208); + if (lookahead == 'p') ADVANCE(170); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 198: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 's') ADVANCE(134); + if (lookahead == 'p') ADVANCE(138); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 199: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 's') ADVANCE(135); + if (lookahead == 'r') ADVANCE(116); + if (lookahead == 'y') ADVANCE(198); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 200: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(465); + if (lookahead == 'r') ADVANCE(542); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 201: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(118); + if (lookahead == 'r') ADVANCE(487); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 202: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(490); + if (lookahead == 'r') ADVANCE(149); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 203: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(454); + if (lookahead == 'r') ADVANCE(232); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 204: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(426); + if (lookahead == 'r') ADVANCE(190); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 205: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(484); + if (lookahead == 'r') ADVANCE(219); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 206: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(432); + if (lookahead == 'r') ADVANCE(233); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 207: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(434); + if (lookahead == 'r') ADVANCE(183); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 208: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(446); + if (lookahead == 'r') ADVANCE(165); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 209: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(436); + if (lookahead == 'r') ADVANCE(228); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 210: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(150); + if (lookahead == 's') ADVANCE(522); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 211: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 't') ADVANCE(192); + if (lookahead == 's') ADVANCE(136); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 212: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'u') ADVANCE(128); + if (lookahead == 's') ADVANCE(139); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 213: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'u') ADVANCE(162); + if (lookahead == 's') ADVANCE(224); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 214: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'u') ADVANCE(191); + if (lookahead == 's') ADVANCE(143); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 215: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'u') ADVANCE(132); + if (lookahead == 's') ADVANCE(144); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 216: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'u') ADVANCE(122); + if (lookahead == 't') ADVANCE(529); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 217: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'u') ADVANCE(163); + if (lookahead == 't') ADVANCE(127); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 218: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'v') ADVANCE(424); + if (lookahead == 't') ADVANCE(554); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 219: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'w') ADVANCE(167); + if (lookahead == 't') ADVANCE(519); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 220: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'x') ADVANCE(181); + if (lookahead == 't') ADVANCE(467); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 221: ACCEPT_TOKEN(aux_sym_identifier_token1); - if (lookahead == 'y') ADVANCE(444); + if (lookahead == 't') ADVANCE(548); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 222: ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 't') ADVANCE(473); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 223: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == '"') ADVANCE(17); - if (lookahead == '#') ADVANCE(16); - if (lookahead == 'e') ADVANCE(235); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 't') ADVANCE(475); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 224: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == '_') ADVANCE(307); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 't') ADVANCE(511); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 225: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(295); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 't') ADVANCE(477); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 226: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(237); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 't') ADVANCE(163); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 227: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(331); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 't') ADVANCE(208); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 228: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(268); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'u') ADVANCE(137); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 229: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(282); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'u') ADVANCE(176); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 230: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(279); - if (lookahead == 'n') ADVANCE(480); - if (lookahead == 'o') ADVANCE(300); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'u') ADVANCE(207); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 231: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(326); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'u') ADVANCE(141); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 232: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(267); - if (lookahead == 'u') ADVANCE(246); - if (lookahead == 'y') ADVANCE(502); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'u') ADVANCE(131); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 233: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(317); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'u') ADVANCE(177); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 234: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'a') ADVANCE(311); - if (lookahead == 'h') ADVANCE(229); - if (lookahead == 'o') ADVANCE(288); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'v') ADVANCE(465); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('b' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 235: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'c') ADVANCE(330); - if (lookahead == 't') ADVANCE(325); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'w') ADVANCE(182); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 236: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'c') ADVANCE(259); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'x') ADVANCE(196); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 237: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'c') ADVANCE(304); - if (lookahead == 't') ADVANCE(236); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'y') ADVANCE(485); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 238: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'c') ADVANCE(320); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'z') ADVANCE(145); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'y')) ADVANCE(240); END_STATE(); case 239: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'c') ADVANCE(321); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == 'z') ADVANCE(146); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'y')) ADVANCE(240); END_STATE(); case 240: - ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'd') ADVANCE(421); - if (('0' <= lookahead && lookahead <= '9') || + ACCEPT_TOKEN(aux_sym_identifier_token1); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); case 241: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(257); + if (lookahead == '"') ADVANCE(17); + if (lookahead == '#') ADVANCE(16); + if (lookahead == 'e') ADVANCE(263); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 242: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(274); - if (lookahead == 'p') ADVANCE(227); - if (lookahead == 't') ADVANCE(301); + if (lookahead == '1') ADVANCE(250); + if (lookahead == '3') ADVANCE(244); + if (lookahead == '6') ADVANCE(247); + if (lookahead == '8') ADVANCE(490); + if (lookahead == 'f') ADVANCE(456); + if (lookahead == 'm') ADVANCE(329); + if (lookahead == 's') ADVANCE(292); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 243: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(451); + if (lookahead == '1') ADVANCE(251); + if (lookahead == '3') ADVANCE(245); + if (lookahead == '6') ADVANCE(248); + if (lookahead == '8') ADVANCE(498); + if (lookahead == 's') ADVANCE(271); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 244: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(428); + if (lookahead == '2') ADVANCE(494); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 245: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(472); + if (lookahead == '2') ADVANCE(502); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 246: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(339); + if (lookahead == '4') ADVANCE(488); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 247: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(486); + if (lookahead == '4') ADVANCE(496); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 248: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(342); + if (lookahead == '4') ADVANCE(504); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 249: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(474); + if (lookahead == '6') ADVANCE(246); + if (lookahead == 'a') ADVANCE(310); + if (lookahead == 'n') ADVANCE(544); + if (lookahead == 'o') ADVANCE(332); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 250: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(498); + if (lookahead == '6') ADVANCE(492); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 251: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(238); + if (lookahead == '6') ADVANCE(500); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 252: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(315); + if (lookahead == '_') ADVANCE(340); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || - lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 253: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(308); + if (lookahead == 'a') ADVANCE(327); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 254: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'e') ADVANCE(228); + if (lookahead == 'a') ADVANCE(265); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 255: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'f') ADVANCE(415); - if (lookahead == 'm') ADVANCE(297); + if (lookahead == 'a') ADVANCE(363); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 256: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'f') ADVANCE(415); + if (lookahead == 'a') ADVANCE(299); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 257: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'f') ADVANCE(231); + if (lookahead == 'a') ADVANCE(343); + if (lookahead == 'h') ADVANCE(258); + if (lookahead == 'o') ADVANCE(320); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 258: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'g') ADVANCE(437); + if (lookahead == 'a') ADVANCE(314); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 259: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'h') ADVANCE(408); + if (lookahead == 'a') ADVANCE(358); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 260: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'h') ADVANCE(266); + if (lookahead == 'a') ADVANCE(298); + if (lookahead == 'u') ADVANCE(274); + if (lookahead == 'y') ADVANCE(566); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 261: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'i') ADVANCE(269); + if (lookahead == 'a') ADVANCE(349); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('b' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 262: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'i') ADVANCE(309); + if (lookahead == 'b') ADVANCE(285); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 263: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'i') ADVANCE(283); + if (lookahead == 'c') ADVANCE(362); + if (lookahead == 't') ADVANCE(357); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 264: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'i') ADVANCE(270); + if (lookahead == 'c') ADVANCE(289); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 265: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'i') ADVANCE(287); + if (lookahead == 'c') ADVANCE(337); + if (lookahead == 't') ADVANCE(264); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 266: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'i') ADVANCE(276); + if (lookahead == 'c') ADVANCE(352); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 267: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'i') ADVANCE(318); + if (lookahead == 'c') ADVANCE(353); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 268: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'k') ADVANCE(496); + if (lookahead == 'd') ADVANCE(462); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 269: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(441); + if (lookahead == 'e') ADVANCE(287); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 270: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(345); + if (lookahead == 'e') ADVANCE(305); + if (lookahead == 'p') ADVANCE(255); + if (lookahead == 't') ADVANCE(334); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 271: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(439); + if (lookahead == 'e') ADVANCE(516); + if (lookahead == 'i') ADVANCE(366); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 272: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(492); + if (lookahead == 'e') ADVANCE(469); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 273: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(293); + if (lookahead == 'e') ADVANCE(536); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 274: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(251); - if (lookahead == 'n') ADVANCE(240); + if (lookahead == 'e') ADVANCE(373); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 275: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(322); + if (lookahead == 'e') ADVANCE(550); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 276: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(249); + if (lookahead == 'e') ADVANCE(376); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 277: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(253); + if (lookahead == 'e') ADVANCE(506); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 278: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(312); - if (lookahead == 'x') ADVANCE(296); + if (lookahead == 'e') ADVANCE(508); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 279: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'l') ADVANCE(313); + if (lookahead == 'e') ADVANCE(538); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 280: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(332); + if (lookahead == 'e') ADVANCE(562); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 281: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(480); + if (lookahead == 'e') ADVANCE(266); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 282: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(419); + if (lookahead == 'e') ADVANCE(347); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 283: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(258); + if (lookahead == 'e') ADVANCE(341); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 284: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(417); + if (lookahead == 'e') ADVANCE(256); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 285: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(494); + if (lookahead == 'e') ADVANCE(333); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 286: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(314); + if (lookahead == 'f') ADVANCE(456); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 287: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(327); + if (lookahead == 'f') ADVANCE(259); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 288: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'n') ADVANCE(324); + if (lookahead == 'g') ADVANCE(478); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 289: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'o') ADVANCE(500); + if (lookahead == 'h') ADVANCE(449); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 290: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'o') ADVANCE(224); + if (lookahead == 'h') ADVANCE(297); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 291: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'o') ADVANCE(292); + if (lookahead == 'i') ADVANCE(342); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 292: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'o') ADVANCE(271); + if (lookahead == 'i') ADVANCE(365); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 293: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'o') ADVANCE(233); + if (lookahead == 'i') ADVANCE(300); + if (lookahead == 'u') ADVANCE(311); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 294: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'o') ADVANCE(306); + if (lookahead == 'i') ADVANCE(315); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 295: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'p') ADVANCE(447); + if (lookahead == 'i') ADVANCE(301); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 296: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'p') ADVANCE(294); + if (lookahead == 'i') ADVANCE(319); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 297: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'p') ADVANCE(272); + if (lookahead == 'i') ADVANCE(307); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 298: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'p') ADVANCE(247); + if (lookahead == 'i') ADVANCE(350); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 299: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(232); - if (lookahead == 'y') ADVANCE(298); + if (lookahead == 'k') ADVANCE(560); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 300: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(477); + if (lookahead == 'l') ADVANCE(482); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 301: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(328); + if (lookahead == 'l') ADVANCE(379); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 302: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(254); + if (lookahead == 'l') ADVANCE(480); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 303: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(263); + if (lookahead == 'l') ADVANCE(556); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 304: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(290); + if (lookahead == 'l') ADVANCE(325); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 305: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(285); + if (lookahead == 'l') ADVANCE(281); + if (lookahead == 'n') ADVANCE(268); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 306: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(319); + if (lookahead == 'l') ADVANCE(354); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 307: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'r') ADVANCE(329); + if (lookahead == 'l') ADVANCE(279); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 308: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 's') ADVANCE(456); + if (lookahead == 'l') ADVANCE(283); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 309: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 's') ADVANCE(316); + if (lookahead == 'l') ADVANCE(344); + if (lookahead == 'x') ADVANCE(328); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 310: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 's') ADVANCE(243); + if (lookahead == 'l') ADVANCE(345); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 311: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 's') ADVANCE(244); + if (lookahead == 'm') ADVANCE(262); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 312: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 's') ADVANCE(245); + if (lookahead == 'n') ADVANCE(364); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 313: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 's') ADVANCE(248); + if (lookahead == 'n') ADVANCE(544); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 314: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(433); + if (lookahead == 'n') ADVANCE(460); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 315: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(464); + if (lookahead == 'n') ADVANCE(288); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 316: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(445); + if (lookahead == 'n') ADVANCE(458); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 317: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(435); + if (lookahead == 'n') ADVANCE(558); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 318: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(489); + if (lookahead == 'n') ADVANCE(346); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 319: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(453); + if (lookahead == 'n') ADVANCE(359); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 320: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(425); + if (lookahead == 'n') ADVANCE(356); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 321: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(483); + if (lookahead == 'o') ADVANCE(564); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 322: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(431); + if (lookahead == 'o') ADVANCE(252); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 323: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(303); + if (lookahead == 'o') ADVANCE(324); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 324: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 't') ADVANCE(265); + if (lookahead == 'o') ADVANCE(302); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 325: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'u') ADVANCE(305); + if (lookahead == 'o') ADVANCE(261); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 326: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'u') ADVANCE(275); + if (lookahead == 'o') ADVANCE(339); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 327: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'u') ADVANCE(250); + if (lookahead == 'p') ADVANCE(512); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 328: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'u') ADVANCE(239); + if (lookahead == 'p') ADVANCE(326); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 329: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'u') ADVANCE(277); + if (lookahead == 'p') ADVANCE(303); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 330: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'v') ADVANCE(423); + if (lookahead == 'p') ADVANCE(275); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 331: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'w') ADVANCE(284); + if (lookahead == 'r') ADVANCE(260); + if (lookahead == 'y') ADVANCE(330); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 332: ACCEPT_TOKEN(sym__word_identifier); - if (lookahead == 'y') ADVANCE(443); + if (lookahead == 'r') ADVANCE(541); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 333: ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(486); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 334: - ACCEPT_TOKEN(sym_integer_literal); - if (lookahead == '.') ADVANCE(85); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(334); + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(360); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 335: - ACCEPT_TOKEN(sym_integer_literal); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(335); + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(284); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); case 336: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(294); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 337: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(322); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 338: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(317); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 339: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(351); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 340: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'r') ADVANCE(361); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 341: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 's') ADVANCE(521); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 342: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 's') ADVANCE(348); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 343: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 's') ADVANCE(272); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 344: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 's') ADVANCE(273); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 345: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 's') ADVANCE(276); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 346: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(474); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 347: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(528); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 348: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(510); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 349: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(476); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 350: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(553); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 351: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(518); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 352: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(466); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 353: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(547); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 354: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(472); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 355: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(336); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 356: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 't') ADVANCE(296); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 357: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'u') ADVANCE(338); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 358: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'u') ADVANCE(306); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 359: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'u') ADVANCE(280); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 360: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'u') ADVANCE(267); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 361: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'u') ADVANCE(308); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 362: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'v') ADVANCE(464); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 363: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'w') ADVANCE(316); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 364: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'y') ADVANCE(484); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 365: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'z') ADVANCE(277); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'y')) ADVANCE(367); + END_STATE(); + case 366: + ACCEPT_TOKEN(sym__word_identifier); + if (lookahead == 'z') ADVANCE(278); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'y')) ADVANCE(367); + END_STATE(); + case 367: + ACCEPT_TOKEN(sym__word_identifier); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 368: + ACCEPT_TOKEN(sym_integer_literal); + if (lookahead == '.') ADVANCE(83); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(368); + END_STATE(); + case 369: + ACCEPT_TOKEN(sym_integer_literal); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(369); + END_STATE(); + case 370: ACCEPT_TOKEN(sym_float_literal); if (lookahead == 'E' || - lookahead == 'e') ADVANCE(83); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(336); + lookahead == 'e') ADVANCE(81); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(370); END_STATE(); - case 337: + case 371: ACCEPT_TOKEN(sym_float_literal); - if (('0' <= lookahead && lookahead <= '9')) ADVANCE(337); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(371); END_STATE(); - case 338: + case 372: ACCEPT_TOKEN(anon_sym_true); END_STATE(); - case 339: + case 373: ACCEPT_TOKEN(anon_sym_true); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 340: + case 374: ACCEPT_TOKEN(anon_sym_true); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 341: + case 375: ACCEPT_TOKEN(anon_sym_false); END_STATE(); - case 342: + case 376: ACCEPT_TOKEN(anon_sym_false); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 343: + case 377: ACCEPT_TOKEN(anon_sym_false); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 344: + case 378: ACCEPT_TOKEN(anon_sym_nil); END_STATE(); - case 345: + case 379: ACCEPT_TOKEN(anon_sym_nil); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 346: + case 380: ACCEPT_TOKEN(anon_sym_nil); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 347: + case 381: ACCEPT_TOKEN(anon_sym_DQUOTE); END_STATE(); - case 348: + case 382: ACCEPT_TOKEN(aux_sym_double_string_token1); - if (lookahead == '*') ADVANCE(350); - if (lookahead == '/') ADVANCE(94); + if (lookahead == '*') ADVANCE(384); + if (lookahead == '/') ADVANCE(92); if (lookahead != 0 && lookahead != '"' && lookahead != '$' && - lookahead != '\\') ADVANCE(352); + lookahead != '\\') ADVANCE(386); END_STATE(); - case 349: + case 383: ACCEPT_TOKEN(aux_sym_double_string_token1); - if (lookahead == '*') ADVANCE(349); - if (lookahead == '/') ADVANCE(98); + if (lookahead == '*') ADVANCE(383); + if (lookahead == '/') ADVANCE(96); if (lookahead == '"' || lookahead == '$' || - lookahead == '\\') ADVANCE(27); - if (lookahead != 0) ADVANCE(350); + lookahead == '\\') ADVANCE(26); + if (lookahead != 0) ADVANCE(384); END_STATE(); - case 350: + case 384: ACCEPT_TOKEN(aux_sym_double_string_token1); - if (lookahead == '*') ADVANCE(349); + if (lookahead == '*') ADVANCE(383); if (lookahead == '"' || lookahead == '$' || - lookahead == '\\') ADVANCE(27); - if (lookahead != 0) ADVANCE(350); + lookahead == '\\') ADVANCE(26); + if (lookahead != 0) ADVANCE(384); END_STATE(); - case 351: + case 385: ACCEPT_TOKEN(aux_sym_double_string_token1); - if (lookahead == '/') ADVANCE(348); + if (lookahead == '/') ADVANCE(382); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') ADVANCE(351); + lookahead == ' ') ADVANCE(385); if (lookahead != 0 && lookahead != '"' && lookahead != '$' && - lookahead != '\\') ADVANCE(352); + lookahead != '\\') ADVANCE(386); END_STATE(); - case 352: + case 386: ACCEPT_TOKEN(aux_sym_double_string_token1); if (lookahead != 0 && lookahead != '"' && lookahead != '$' && - lookahead != '\\') ADVANCE(352); + lookahead != '\\') ADVANCE(386); END_STATE(); - case 353: + case 387: ACCEPT_TOKEN(anon_sym_SQUOTE); END_STATE(); - case 354: + case 388: ACCEPT_TOKEN(aux_sym_single_string_token1); - if (lookahead == '*') ADVANCE(356); - if (lookahead == '/') ADVANCE(93); + if (lookahead == '*') ADVANCE(390); + if (lookahead == '/') ADVANCE(91); if (lookahead != 0 && lookahead != '$' && lookahead != '\'' && - lookahead != '\\') ADVANCE(358); + lookahead != '\\') ADVANCE(392); END_STATE(); - case 355: + case 389: ACCEPT_TOKEN(aux_sym_single_string_token1); - if (lookahead == '*') ADVANCE(355); - if (lookahead == '/') ADVANCE(97); + if (lookahead == '*') ADVANCE(389); + if (lookahead == '/') ADVANCE(95); if (lookahead == '$' || lookahead == '\'' || - lookahead == '\\') ADVANCE(27); - if (lookahead != 0) ADVANCE(356); + lookahead == '\\') ADVANCE(26); + if (lookahead != 0) ADVANCE(390); END_STATE(); - case 356: + case 390: ACCEPT_TOKEN(aux_sym_single_string_token1); - if (lookahead == '*') ADVANCE(355); + if (lookahead == '*') ADVANCE(389); if (lookahead == '$' || lookahead == '\'' || - lookahead == '\\') ADVANCE(27); - if (lookahead != 0) ADVANCE(356); + lookahead == '\\') ADVANCE(26); + if (lookahead != 0) ADVANCE(390); END_STATE(); - case 357: + case 391: ACCEPT_TOKEN(aux_sym_single_string_token1); - if (lookahead == '/') ADVANCE(354); + if (lookahead == '/') ADVANCE(388); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') ADVANCE(357); + lookahead == ' ') ADVANCE(391); if (lookahead != 0 && lookahead != '$' && lookahead != '\'' && - lookahead != '\\') ADVANCE(358); + lookahead != '\\') ADVANCE(392); END_STATE(); - case 358: + case 392: ACCEPT_TOKEN(aux_sym_single_string_token1); if (lookahead != 0 && lookahead != '$' && lookahead != '\'' && - lookahead != '\\') ADVANCE(358); + lookahead != '\\') ADVANCE(392); END_STATE(); - case 359: + case 393: ACCEPT_TOKEN(sym_raw_string); - if (lookahead == '#') ADVANCE(359); + if (lookahead == '#') ADVANCE(393); END_STATE(); - case 360: + case 394: ACCEPT_TOKEN(anon_sym_DOLLAR_LBRACE); END_STATE(); - case 361: + case 395: ACCEPT_TOKEN(anon_sym_RBRACE); END_STATE(); - case 362: + case 396: ACCEPT_TOKEN(sym_escape_sequence); END_STATE(); - case 363: + case 397: ACCEPT_TOKEN(anon_sym_LPAREN); END_STATE(); - case 364: + case 398: ACCEPT_TOKEN(anon_sym_RPAREN); END_STATE(); - case 365: + case 399: ACCEPT_TOKEN(anon_sym_COMMA); END_STATE(); - case 366: + case 400: ACCEPT_TOKEN(anon_sym_COLON); END_STATE(); - case 367: + case 401: ACCEPT_TOKEN(anon_sym_COLON); - if (lookahead == ':') ADVANCE(459); + if (lookahead == ':') ADVANCE(524); END_STATE(); - case 368: + case 402: ACCEPT_TOKEN(anon_sym_COLON); - if (lookahead == ':') ADVANCE(459); - if (lookahead == '=') ADVANCE(466); + if (lookahead == ':') ADVANCE(524); + if (lookahead == '=') ADVANCE(530); END_STATE(); - case 369: + case 403: ACCEPT_TOKEN(anon_sym_COLON); - if (lookahead == '=') ADVANCE(466); + if (lookahead == '=') ADVANCE(530); END_STATE(); - case 370: + case 404: ACCEPT_TOKEN(anon_sym_DOT); - if (lookahead == '.') ADVANCE(402); + if (lookahead == '.') ADVANCE(445); END_STATE(); - case 371: + case 405: ACCEPT_TOKEN(anon_sym_QMARK_DOT); END_STATE(); - case 372: + case 406: ACCEPT_TOKEN(anon_sym_LBRACK); END_STATE(); - case 373: + case 407: ACCEPT_TOKEN(anon_sym_RBRACK); END_STATE(); - case 374: + case 408: ACCEPT_TOKEN(anon_sym_QMARK_LBRACK); END_STATE(); - case 375: + case 409: ACCEPT_TOKEN(anon_sym_LBRACE); END_STATE(); - case 376: + case 410: ACCEPT_TOKEN(anon_sym_BANG); END_STATE(); - case 377: + case 411: ACCEPT_TOKEN(anon_sym_BANG); - if (lookahead == '=') ADVANCE(391); + if (lookahead == '=') ADVANCE(426); END_STATE(); - case 378: + case 412: + ACCEPT_TOKEN(anon_sym_TILDE); + END_STATE(); + case 413: ACCEPT_TOKEN(anon_sym_STAR); END_STATE(); - case 379: + case 414: ACCEPT_TOKEN(anon_sym_STAR); - if (lookahead == '=') ADVANCE(469); + if (lookahead == '=') ADVANCE(533); END_STATE(); - case 380: + case 415: ACCEPT_TOKEN(anon_sym_SLASH); - if (lookahead == '*') ADVANCE(27); - if (lookahead == '/') ADVANCE(95); + if (lookahead == '*') ADVANCE(26); + if (lookahead == '/') ADVANCE(93); END_STATE(); - case 381: + case 416: ACCEPT_TOKEN(anon_sym_SLASH); - if (lookahead == '*') ADVANCE(27); - if (lookahead == '/') ADVANCE(95); - if (lookahead == '=') ADVANCE(470); + if (lookahead == '*') ADVANCE(26); + if (lookahead == '/') ADVANCE(93); + if (lookahead == '=') ADVANCE(534); END_STATE(); - case 382: + case 417: ACCEPT_TOKEN(anon_sym_PERCENT); END_STATE(); - case 383: + case 418: ACCEPT_TOKEN(anon_sym_PERCENT); - if (lookahead == '=') ADVANCE(471); + if (lookahead == '=') ADVANCE(535); END_STATE(); - case 384: + case 419: ACCEPT_TOKEN(anon_sym_PLUS); END_STATE(); - case 385: + case 420: ACCEPT_TOKEN(anon_sym_PLUS); - if (lookahead == '=') ADVANCE(467); + if (lookahead == '=') ADVANCE(531); END_STATE(); - case 386: + case 421: ACCEPT_TOKEN(anon_sym_DASH); END_STATE(); - case 387: + case 422: ACCEPT_TOKEN(anon_sym_DASH); - if (lookahead == '=') ADVANCE(468); + if (lookahead == '=') ADVANCE(532); END_STATE(); - case 388: + case 423: ACCEPT_TOKEN(anon_sym_DASH); - if (lookahead == '=') ADVANCE(468); - if (lookahead == '>') ADVANCE(449); + if (lookahead == '=') ADVANCE(532); + if (lookahead == '>') ADVANCE(514); END_STATE(); - case 389: + case 424: ACCEPT_TOKEN(anon_sym_DASH); - if (lookahead == '>') ADVANCE(449); + if (lookahead == '>') ADVANCE(514); END_STATE(); - case 390: + case 425: ACCEPT_TOKEN(anon_sym_EQ_EQ); END_STATE(); - case 391: + case 426: ACCEPT_TOKEN(anon_sym_BANG_EQ); END_STATE(); - case 392: + case 427: ACCEPT_TOKEN(anon_sym_LT); END_STATE(); - case 393: + case 428: ACCEPT_TOKEN(anon_sym_LT); - if (lookahead == '=') ADVANCE(396); + if (lookahead == '<') ADVANCE(441); + if (lookahead == '=') ADVANCE(433); END_STATE(); - case 394: + case 429: + ACCEPT_TOKEN(anon_sym_LT); + if (lookahead == '=') ADVANCE(433); + END_STATE(); + case 430: ACCEPT_TOKEN(anon_sym_GT); END_STATE(); - case 395: + case 431: ACCEPT_TOKEN(anon_sym_GT); - if (lookahead == '=') ADVANCE(397); + if (lookahead == '=') ADVANCE(434); END_STATE(); - case 396: + case 432: + ACCEPT_TOKEN(anon_sym_GT); + if (lookahead == '=') ADVANCE(434); + if (lookahead == '>') ADVANCE(442); + END_STATE(); + case 433: ACCEPT_TOKEN(anon_sym_LT_EQ); END_STATE(); - case 397: + case 434: ACCEPT_TOKEN(anon_sym_GT_EQ); END_STATE(); - case 398: + case 435: ACCEPT_TOKEN(anon_sym_AMP_AMP); END_STATE(); - case 399: + case 436: ACCEPT_TOKEN(anon_sym_PIPE_PIPE); END_STATE(); - case 400: + case 437: + ACCEPT_TOKEN(anon_sym_PIPE); + END_STATE(); + case 438: + ACCEPT_TOKEN(anon_sym_PIPE); + if (lookahead == '|') ADVANCE(436); + END_STATE(); + case 439: + ACCEPT_TOKEN(anon_sym_CARET); + END_STATE(); + case 440: + ACCEPT_TOKEN(anon_sym_AMP); + if (lookahead == '&') ADVANCE(435); + END_STATE(); + case 441: + ACCEPT_TOKEN(anon_sym_LT_LT); + END_STATE(); + case 442: + ACCEPT_TOKEN(anon_sym_GT_GT); + END_STATE(); + case 443: ACCEPT_TOKEN(anon_sym_QMARK_QMARK); END_STATE(); - case 401: + case 444: ACCEPT_TOKEN(anon_sym_DOT_DOT); END_STATE(); - case 402: + case 445: ACCEPT_TOKEN(anon_sym_DOT_DOT); - if (lookahead == '=') ADVANCE(403); + if (lookahead == '=') ADVANCE(446); END_STATE(); - case 403: + case 446: ACCEPT_TOKEN(anon_sym_DOT_DOT_EQ); END_STATE(); - case 404: + case 447: ACCEPT_TOKEN(anon_sym_QMARK); END_STATE(); - case 405: + case 448: ACCEPT_TOKEN(anon_sym_QMARK); - if (lookahead == '.') ADVANCE(371); - if (lookahead == '?') ADVANCE(400); - if (lookahead == '[') ADVANCE(374); - END_STATE(); - case 406: - ACCEPT_TOKEN(anon_sym_PIPE); + if (lookahead == '.') ADVANCE(405); + if (lookahead == '?') ADVANCE(443); + if (lookahead == '[') ADVANCE(408); END_STATE(); - case 407: - ACCEPT_TOKEN(anon_sym_PIPE); - if (lookahead == '|') ADVANCE(399); - END_STATE(); - case 408: + case 449: ACCEPT_TOKEN(anon_sym_match); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 409: + case 450: ACCEPT_TOKEN(anon_sym_match); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 410: + case 451: ACCEPT_TOKEN(anon_sym_EQ_GT); END_STATE(); - case 411: + case 452: ACCEPT_TOKEN(anon_sym_SEMI); END_STATE(); - case 412: + case 453: ACCEPT_TOKEN(anon_sym__); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 413: + case 454: ACCEPT_TOKEN(anon_sym__); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 414: + case 455: ACCEPT_TOKEN(anon_sym_if); END_STATE(); - case 415: + case 456: ACCEPT_TOKEN(anon_sym_if); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 416: + case 457: ACCEPT_TOKEN(anon_sym_if); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 417: + case 458: ACCEPT_TOKEN(anon_sym_spawn); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 418: + case 459: ACCEPT_TOKEN(anon_sym_spawn); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 419: + case 460: ACCEPT_TOKEN(anon_sym_chan); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 420: + case 461: ACCEPT_TOKEN(anon_sym_chan); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 421: + case 462: ACCEPT_TOKEN(anon_sym_send); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 422: + case 463: ACCEPT_TOKEN(anon_sym_send); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 423: + case 464: ACCEPT_TOKEN(anon_sym_recv); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 424: + case 465: ACCEPT_TOKEN(anon_sym_recv); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 425: + case 466: ACCEPT_TOKEN(anon_sym_select); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 426: + case 467: ACCEPT_TOKEN(anon_sym_select); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 427: + case 468: ACCEPT_TOKEN(anon_sym_case); END_STATE(); - case 428: + case 469: ACCEPT_TOKEN(anon_sym_case); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 429: + case 470: ACCEPT_TOKEN(anon_sym_case); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 430: + case 471: ACCEPT_TOKEN(anon_sym_default); END_STATE(); - case 431: + case 472: ACCEPT_TOKEN(anon_sym_default); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 432: + case 473: ACCEPT_TOKEN(anon_sym_default); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 433: + case 474: ACCEPT_TOKEN(anon_sym_Int); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 434: + case 475: ACCEPT_TOKEN(anon_sym_Int); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 435: + case 476: ACCEPT_TOKEN(anon_sym_Float); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 436: + case 477: ACCEPT_TOKEN(anon_sym_Float); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 437: + case 478: ACCEPT_TOKEN(anon_sym_String); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 438: + case 479: ACCEPT_TOKEN(anon_sym_String); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 439: + case 480: ACCEPT_TOKEN(anon_sym_Bool); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 440: + case 481: ACCEPT_TOKEN(anon_sym_Bool); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 441: + case 482: ACCEPT_TOKEN(anon_sym_Nil); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 442: + case 483: ACCEPT_TOKEN(anon_sym_Nil); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 443: + case 484: ACCEPT_TOKEN(anon_sym_Any); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 444: + case 485: ACCEPT_TOKEN(anon_sym_Any); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 445: - ACCEPT_TOKEN(anon_sym_List); + case 486: + ACCEPT_TOKEN(anon_sym_Number); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 446: - ACCEPT_TOKEN(anon_sym_List); + case 487: + ACCEPT_TOKEN(anon_sym_Number); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 447: - ACCEPT_TOKEN(anon_sym_Map); + case 488: + ACCEPT_TOKEN(anon_sym_f64); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 448: - ACCEPT_TOKEN(anon_sym_Map); + case 489: + ACCEPT_TOKEN(anon_sym_f64); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 449: - ACCEPT_TOKEN(anon_sym_DASH_GT); + case 490: + ACCEPT_TOKEN(anon_sym_i8); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 450: - ACCEPT_TOKEN(anon_sym_POUND); + case 491: + ACCEPT_TOKEN(anon_sym_i8); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 451: - ACCEPT_TOKEN(anon_sym_use); + case 492: + ACCEPT_TOKEN(anon_sym_i16); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 452: - ACCEPT_TOKEN(anon_sym_use); + case 493: + ACCEPT_TOKEN(anon_sym_i16); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 453: - ACCEPT_TOKEN(anon_sym_export); + case 494: + ACCEPT_TOKEN(anon_sym_i32); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 454: - ACCEPT_TOKEN(anon_sym_export); + case 495: + ACCEPT_TOKEN(anon_sym_i32); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); - END_STATE(); - case 455: - ACCEPT_TOKEN(anon_sym_macro_rules); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 456: - ACCEPT_TOKEN(anon_sym_macro_rules); + case 496: + ACCEPT_TOKEN(anon_sym_i64); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 457: - ACCEPT_TOKEN(anon_sym_macro_rules); + case 497: + ACCEPT_TOKEN(anon_sym_i64); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 458: + case 498: + ACCEPT_TOKEN(anon_sym_u8); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 499: + ACCEPT_TOKEN(anon_sym_u8); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 500: + ACCEPT_TOKEN(anon_sym_u16); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 501: + ACCEPT_TOKEN(anon_sym_u16); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 502: + ACCEPT_TOKEN(anon_sym_u32); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 503: + ACCEPT_TOKEN(anon_sym_u32); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 504: + ACCEPT_TOKEN(anon_sym_u64); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 505: + ACCEPT_TOKEN(anon_sym_u64); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 506: + ACCEPT_TOKEN(anon_sym_isize); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 507: + ACCEPT_TOKEN(anon_sym_isize); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 508: + ACCEPT_TOKEN(anon_sym_usize); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 509: + ACCEPT_TOKEN(anon_sym_usize); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 510: + ACCEPT_TOKEN(anon_sym_List); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 511: + ACCEPT_TOKEN(anon_sym_List); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 512: + ACCEPT_TOKEN(anon_sym_Map); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 513: + ACCEPT_TOKEN(anon_sym_Map); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 514: + ACCEPT_TOKEN(anon_sym_DASH_GT); + END_STATE(); + case 515: + ACCEPT_TOKEN(anon_sym_POUND); + END_STATE(); + case 516: + ACCEPT_TOKEN(anon_sym_use); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 517: + ACCEPT_TOKEN(anon_sym_use); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 518: + ACCEPT_TOKEN(anon_sym_export); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 519: + ACCEPT_TOKEN(anon_sym_export); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 520: + ACCEPT_TOKEN(anon_sym_macro_rules); + END_STATE(); + case 521: + ACCEPT_TOKEN(anon_sym_macro_rules); + if (('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); + END_STATE(); + case 522: + ACCEPT_TOKEN(anon_sym_macro_rules); + if (lookahead == '-' || + ('0' <= lookahead && lookahead <= '9') || + ('A' <= lookahead && lookahead <= 'Z') || + lookahead == '_' || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); + END_STATE(); + case 523: ACCEPT_TOKEN(anon_sym_DOLLAR); END_STATE(); - case 459: + case 524: ACCEPT_TOKEN(anon_sym_COLON_COLON); END_STATE(); - case 460: + case 525: ACCEPT_TOKEN(anon_sym_EQ); END_STATE(); - case 461: + case 526: ACCEPT_TOKEN(anon_sym_EQ); - if (lookahead == '=') ADVANCE(390); + if (lookahead == '=') ADVANCE(425); END_STATE(); - case 462: + case 527: ACCEPT_TOKEN(anon_sym_EQ); - if (lookahead == '=') ADVANCE(390); - if (lookahead == '>') ADVANCE(410); + if (lookahead == '=') ADVANCE(425); + if (lookahead == '>') ADVANCE(451); END_STATE(); - case 463: - ACCEPT_TOKEN(anon_sym_AMP); - if (lookahead == '&') ADVANCE(398); - END_STATE(); - case 464: + case 528: ACCEPT_TOKEN(anon_sym_let); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 465: + case 529: ACCEPT_TOKEN(anon_sym_let); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 466: + case 530: ACCEPT_TOKEN(anon_sym_COLON_EQ); END_STATE(); - case 467: + case 531: ACCEPT_TOKEN(anon_sym_PLUS_EQ); END_STATE(); - case 468: + case 532: ACCEPT_TOKEN(anon_sym_DASH_EQ); END_STATE(); - case 469: + case 533: ACCEPT_TOKEN(anon_sym_STAR_EQ); END_STATE(); - case 470: + case 534: ACCEPT_TOKEN(anon_sym_SLASH_EQ); END_STATE(); - case 471: + case 535: ACCEPT_TOKEN(anon_sym_PERCENT_EQ); END_STATE(); - case 472: + case 536: ACCEPT_TOKEN(anon_sym_else); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 473: + case 537: ACCEPT_TOKEN(anon_sym_else); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 474: + case 538: ACCEPT_TOKEN(anon_sym_while); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 475: + case 539: ACCEPT_TOKEN(anon_sym_while); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 476: + case 540: ACCEPT_TOKEN(anon_sym_for); END_STATE(); - case 477: + case 541: ACCEPT_TOKEN(anon_sym_for); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 478: + case 542: ACCEPT_TOKEN(anon_sym_for); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 479: + case 543: ACCEPT_TOKEN(anon_sym_fn); END_STATE(); - case 480: + case 544: ACCEPT_TOKEN(anon_sym_fn); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 481: + case 545: ACCEPT_TOKEN(anon_sym_fn); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 482: + case 546: ACCEPT_TOKEN(anon_sym_struct); END_STATE(); - case 483: + case 547: ACCEPT_TOKEN(anon_sym_struct); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 484: + case 548: ACCEPT_TOKEN(anon_sym_struct); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 485: + case 549: ACCEPT_TOKEN(anon_sym_type); END_STATE(); - case 486: + case 550: ACCEPT_TOKEN(anon_sym_type); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 487: + case 551: ACCEPT_TOKEN(anon_sym_type); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 488: + case 552: ACCEPT_TOKEN(anon_sym_trait); END_STATE(); - case 489: + case 553: ACCEPT_TOKEN(anon_sym_trait); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 490: + case 554: ACCEPT_TOKEN(anon_sym_trait); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 491: + case 555: ACCEPT_TOKEN(anon_sym_impl); END_STATE(); - case 492: + case 556: ACCEPT_TOKEN(anon_sym_impl); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 493: + case 557: ACCEPT_TOKEN(anon_sym_impl); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 494: + case 558: ACCEPT_TOKEN(anon_sym_return); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 495: + case 559: ACCEPT_TOKEN(anon_sym_return); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 496: + case 560: ACCEPT_TOKEN(anon_sym_break); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 497: + case 561: ACCEPT_TOKEN(anon_sym_break); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 498: + case 562: ACCEPT_TOKEN(anon_sym_continue); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 499: + case 563: ACCEPT_TOKEN(anon_sym_continue); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 500: + case 564: ACCEPT_TOKEN(anon_sym_go); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 501: + case 565: ACCEPT_TOKEN(anon_sym_go); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); - case 502: + case 566: ACCEPT_TOKEN(anon_sym_try); if (('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(333); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(367); END_STATE(); - case 503: + case 567: ACCEPT_TOKEN(anon_sym_try); if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') || ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' || - ('a' <= lookahead && lookahead <= 'z')) ADVANCE(222); + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(240); END_STATE(); default: return false; @@ -7488,52 +8247,52 @@ static bool ts_lex_keywords(TSLexer *lexer, TSStateId state) { static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [0] = {.lex_state = 0}, - [1] = {.lex_state = 90}, - [2] = {.lex_state = 88}, - [3] = {.lex_state = 88}, - [4] = {.lex_state = 88}, - [5] = {.lex_state = 88}, - [6] = {.lex_state = 88}, - [7] = {.lex_state = 88}, - [8] = {.lex_state = 88}, - [9] = {.lex_state = 88}, - [10] = {.lex_state = 90}, - [11] = {.lex_state = 90}, - [12] = {.lex_state = 90}, - [13] = {.lex_state = 90}, - [14] = {.lex_state = 90}, - [15] = {.lex_state = 90}, - [16] = {.lex_state = 90}, - [17] = {.lex_state = 90}, - [18] = {.lex_state = 90}, - [19] = {.lex_state = 90}, - [20] = {.lex_state = 90}, - [21] = {.lex_state = 90}, - [22] = {.lex_state = 90}, - [23] = {.lex_state = 90}, - [24] = {.lex_state = 90}, - [25] = {.lex_state = 90}, - [26] = {.lex_state = 90}, - [27] = {.lex_state = 90}, - [28] = {.lex_state = 90}, - [29] = {.lex_state = 90}, - [30] = {.lex_state = 90}, - [31] = {.lex_state = 90}, - [32] = {.lex_state = 90}, - [33] = {.lex_state = 90}, - [34] = {.lex_state = 90}, - [35] = {.lex_state = 90}, - [36] = {.lex_state = 90}, - [37] = {.lex_state = 90}, - [38] = {.lex_state = 90}, - [39] = {.lex_state = 90}, - [40] = {.lex_state = 90}, - [41] = {.lex_state = 90}, - [42] = {.lex_state = 90}, - [43] = {.lex_state = 88}, + [1] = {.lex_state = 88}, + [2] = {.lex_state = 86}, + [3] = {.lex_state = 86}, + [4] = {.lex_state = 86}, + [5] = {.lex_state = 86}, + [6] = {.lex_state = 86}, + [7] = {.lex_state = 86}, + [8] = {.lex_state = 86}, + [9] = {.lex_state = 86}, + [10] = {.lex_state = 88}, + [11] = {.lex_state = 88}, + [12] = {.lex_state = 88}, + [13] = {.lex_state = 88}, + [14] = {.lex_state = 88}, + [15] = {.lex_state = 88}, + [16] = {.lex_state = 88}, + [17] = {.lex_state = 88}, + [18] = {.lex_state = 88}, + [19] = {.lex_state = 88}, + [20] = {.lex_state = 88}, + [21] = {.lex_state = 88}, + [22] = {.lex_state = 88}, + [23] = {.lex_state = 88}, + [24] = {.lex_state = 88}, + [25] = {.lex_state = 88}, + [26] = {.lex_state = 88}, + [27] = {.lex_state = 88}, + [28] = {.lex_state = 88}, + [29] = {.lex_state = 88}, + [30] = {.lex_state = 88}, + [31] = {.lex_state = 88}, + [32] = {.lex_state = 88}, + [33] = {.lex_state = 88}, + [34] = {.lex_state = 88}, + [35] = {.lex_state = 88}, + [36] = {.lex_state = 88}, + [37] = {.lex_state = 88}, + [38] = {.lex_state = 88}, + [39] = {.lex_state = 88}, + [40] = {.lex_state = 88}, + [41] = {.lex_state = 88}, + [42] = {.lex_state = 88}, + [43] = {.lex_state = 86}, [44] = {.lex_state = 3}, - [45] = {.lex_state = 6}, - [46] = {.lex_state = 7}, + [45] = {.lex_state = 7}, + [46] = {.lex_state = 6}, [47] = {.lex_state = 4}, [48] = {.lex_state = 4}, [49] = {.lex_state = 4}, @@ -7542,71 +8301,71 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [52] = {.lex_state = 8}, [53] = {.lex_state = 3}, [54] = {.lex_state = 3}, - [55] = {.lex_state = 88}, - [56] = {.lex_state = 88}, - [57] = {.lex_state = 88}, - [58] = {.lex_state = 88}, - [59] = {.lex_state = 88}, - [60] = {.lex_state = 88}, - [61] = {.lex_state = 88}, - [62] = {.lex_state = 88}, - [63] = {.lex_state = 88}, - [64] = {.lex_state = 88}, - [65] = {.lex_state = 88}, - [66] = {.lex_state = 88}, - [67] = {.lex_state = 88}, - [68] = {.lex_state = 88}, - [69] = {.lex_state = 88}, - [70] = {.lex_state = 88}, - [71] = {.lex_state = 88}, - [72] = {.lex_state = 88}, - [73] = {.lex_state = 88}, - [74] = {.lex_state = 88}, - [75] = {.lex_state = 88}, - [76] = {.lex_state = 88}, - [77] = {.lex_state = 88}, - [78] = {.lex_state = 88}, - [79] = {.lex_state = 88}, - [80] = {.lex_state = 88}, - [81] = {.lex_state = 88}, - [82] = {.lex_state = 88}, - [83] = {.lex_state = 88}, - [84] = {.lex_state = 88}, - [85] = {.lex_state = 88}, - [86] = {.lex_state = 88}, - [87] = {.lex_state = 88}, - [88] = {.lex_state = 88}, - [89] = {.lex_state = 88}, - [90] = {.lex_state = 88}, - [91] = {.lex_state = 88}, - [92] = {.lex_state = 88}, - [93] = {.lex_state = 88}, - [94] = {.lex_state = 88}, - [95] = {.lex_state = 88}, - [96] = {.lex_state = 88}, - [97] = {.lex_state = 88}, - [98] = {.lex_state = 88}, - [99] = {.lex_state = 88}, - [100] = {.lex_state = 88}, - [101] = {.lex_state = 88}, - [102] = {.lex_state = 88}, - [103] = {.lex_state = 89}, - [104] = {.lex_state = 89}, - [105] = {.lex_state = 88}, - [106] = {.lex_state = 88}, - [107] = {.lex_state = 88}, - [108] = {.lex_state = 88}, - [109] = {.lex_state = 88}, - [110] = {.lex_state = 88}, - [111] = {.lex_state = 88}, - [112] = {.lex_state = 88}, - [113] = {.lex_state = 88}, - [114] = {.lex_state = 88}, - [115] = {.lex_state = 88}, - [116] = {.lex_state = 11}, - [117] = {.lex_state = 11}, - [118] = {.lex_state = 11}, - [119] = {.lex_state = 11}, + [55] = {.lex_state = 86}, + [56] = {.lex_state = 86}, + [57] = {.lex_state = 86}, + [58] = {.lex_state = 86}, + [59] = {.lex_state = 86}, + [60] = {.lex_state = 86}, + [61] = {.lex_state = 86}, + [62] = {.lex_state = 86}, + [63] = {.lex_state = 86}, + [64] = {.lex_state = 86}, + [65] = {.lex_state = 86}, + [66] = {.lex_state = 86}, + [67] = {.lex_state = 86}, + [68] = {.lex_state = 86}, + [69] = {.lex_state = 86}, + [70] = {.lex_state = 86}, + [71] = {.lex_state = 86}, + [72] = {.lex_state = 86}, + [73] = {.lex_state = 86}, + [74] = {.lex_state = 86}, + [75] = {.lex_state = 86}, + [76] = {.lex_state = 86}, + [77] = {.lex_state = 86}, + [78] = {.lex_state = 86}, + [79] = {.lex_state = 86}, + [80] = {.lex_state = 86}, + [81] = {.lex_state = 86}, + [82] = {.lex_state = 86}, + [83] = {.lex_state = 86}, + [84] = {.lex_state = 86}, + [85] = {.lex_state = 86}, + [86] = {.lex_state = 86}, + [87] = {.lex_state = 86}, + [88] = {.lex_state = 86}, + [89] = {.lex_state = 86}, + [90] = {.lex_state = 86}, + [91] = {.lex_state = 86}, + [92] = {.lex_state = 86}, + [93] = {.lex_state = 86}, + [94] = {.lex_state = 86}, + [95] = {.lex_state = 86}, + [96] = {.lex_state = 86}, + [97] = {.lex_state = 86}, + [98] = {.lex_state = 86}, + [99] = {.lex_state = 86}, + [100] = {.lex_state = 86}, + [101] = {.lex_state = 86}, + [102] = {.lex_state = 87}, + [103] = {.lex_state = 87}, + [104] = {.lex_state = 86}, + [105] = {.lex_state = 86}, + [106] = {.lex_state = 86}, + [107] = {.lex_state = 86}, + [108] = {.lex_state = 86}, + [109] = {.lex_state = 86}, + [110] = {.lex_state = 86}, + [111] = {.lex_state = 86}, + [112] = {.lex_state = 86}, + [113] = {.lex_state = 86}, + [114] = {.lex_state = 86}, + [115] = {.lex_state = 86}, + [116] = {.lex_state = 86}, + [117] = {.lex_state = 86}, + [118] = {.lex_state = 86}, + [119] = {.lex_state = 86}, [120] = {.lex_state = 11}, [121] = {.lex_state = 11}, [122] = {.lex_state = 11}, @@ -7670,7 +8429,7 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [180] = {.lex_state = 11}, [181] = {.lex_state = 11}, [182] = {.lex_state = 11}, - [183] = {.lex_state = 1}, + [183] = {.lex_state = 11}, [184] = {.lex_state = 11}, [185] = {.lex_state = 11}, [186] = {.lex_state = 11}, @@ -7785,18 +8544,18 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [295] = {.lex_state = 11}, [296] = {.lex_state = 11}, [297] = {.lex_state = 1}, - [298] = {.lex_state = 1}, + [298] = {.lex_state = 11}, [299] = {.lex_state = 11}, [300] = {.lex_state = 11}, [301] = {.lex_state = 11}, [302] = {.lex_state = 11}, [303] = {.lex_state = 11}, [304] = {.lex_state = 11}, - [305] = {.lex_state = 1}, + [305] = {.lex_state = 11}, [306] = {.lex_state = 11}, - [307] = {.lex_state = 1}, + [307] = {.lex_state = 11}, [308] = {.lex_state = 11}, - [309] = {.lex_state = 1}, + [309] = {.lex_state = 11}, [310] = {.lex_state = 11}, [311] = {.lex_state = 11}, [312] = {.lex_state = 11}, @@ -7805,14 +8564,14 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [315] = {.lex_state = 11}, [316] = {.lex_state = 11}, [317] = {.lex_state = 11}, - [318] = {.lex_state = 1}, + [318] = {.lex_state = 11}, [319] = {.lex_state = 11}, [320] = {.lex_state = 11}, [321] = {.lex_state = 11}, [322] = {.lex_state = 11}, [323] = {.lex_state = 11}, [324] = {.lex_state = 11}, - [325] = {.lex_state = 1}, + [325] = {.lex_state = 11}, [326] = {.lex_state = 11}, [327] = {.lex_state = 11}, [328] = {.lex_state = 11}, @@ -7826,13 +8585,13 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [336] = {.lex_state = 11}, [337] = {.lex_state = 11}, [338] = {.lex_state = 11}, - [339] = {.lex_state = 1}, - [340] = {.lex_state = 1}, - [341] = {.lex_state = 1}, + [339] = {.lex_state = 11}, + [340] = {.lex_state = 11}, + [341] = {.lex_state = 11}, [342] = {.lex_state = 11}, - [343] = {.lex_state = 1}, - [344] = {.lex_state = 1}, - [345] = {.lex_state = 1}, + [343] = {.lex_state = 11}, + [344] = {.lex_state = 11}, + [345] = {.lex_state = 11}, [346] = {.lex_state = 11}, [347] = {.lex_state = 11}, [348] = {.lex_state = 11}, @@ -7840,46 +8599,46 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [350] = {.lex_state = 11}, [351] = {.lex_state = 11}, [352] = {.lex_state = 11}, - [353] = {.lex_state = 1}, - [354] = {.lex_state = 1}, - [355] = {.lex_state = 1}, + [353] = {.lex_state = 11}, + [354] = {.lex_state = 11}, + [355] = {.lex_state = 11}, [356] = {.lex_state = 11}, - [357] = {.lex_state = 1}, - [358] = {.lex_state = 1}, - [359] = {.lex_state = 1}, - [360] = {.lex_state = 1}, + [357] = {.lex_state = 11}, + [358] = {.lex_state = 11}, + [359] = {.lex_state = 11}, + [360] = {.lex_state = 11}, [361] = {.lex_state = 11}, - [362] = {.lex_state = 1}, - [363] = {.lex_state = 1}, - [364] = {.lex_state = 1}, + [362] = {.lex_state = 11}, + [363] = {.lex_state = 11}, + [364] = {.lex_state = 11}, [365] = {.lex_state = 11}, - [366] = {.lex_state = 1}, - [367] = {.lex_state = 1}, - [368] = {.lex_state = 1}, + [366] = {.lex_state = 11}, + [367] = {.lex_state = 11}, + [368] = {.lex_state = 11}, [369] = {.lex_state = 11}, [370] = {.lex_state = 11}, [371] = {.lex_state = 11}, [372] = {.lex_state = 11}, [373] = {.lex_state = 11}, - [374] = {.lex_state = 1}, - [375] = {.lex_state = 1}, - [376] = {.lex_state = 1}, - [377] = {.lex_state = 1}, - [378] = {.lex_state = 1}, - [379] = {.lex_state = 1}, - [380] = {.lex_state = 1}, - [381] = {.lex_state = 1}, - [382] = {.lex_state = 1}, - [383] = {.lex_state = 1}, - [384] = {.lex_state = 1}, - [385] = {.lex_state = 1}, + [374] = {.lex_state = 11}, + [375] = {.lex_state = 11}, + [376] = {.lex_state = 11}, + [377] = {.lex_state = 11}, + [378] = {.lex_state = 11}, + [379] = {.lex_state = 11}, + [380] = {.lex_state = 11}, + [381] = {.lex_state = 11}, + [382] = {.lex_state = 11}, + [383] = {.lex_state = 11}, + [384] = {.lex_state = 11}, + [385] = {.lex_state = 11}, [386] = {.lex_state = 11}, - [387] = {.lex_state = 1}, - [388] = {.lex_state = 1}, - [389] = {.lex_state = 1}, - [390] = {.lex_state = 1}, - [391] = {.lex_state = 1}, - [392] = {.lex_state = 1}, + [387] = {.lex_state = 11}, + [388] = {.lex_state = 11}, + [389] = {.lex_state = 11}, + [390] = {.lex_state = 11}, + [391] = {.lex_state = 11}, + [392] = {.lex_state = 11}, [393] = {.lex_state = 11}, [394] = {.lex_state = 11}, [395] = {.lex_state = 11}, @@ -7928,120 +8687,120 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [438] = {.lex_state = 11}, [439] = {.lex_state = 11}, [440] = {.lex_state = 11}, - [441] = {.lex_state = 1}, - [442] = {.lex_state = 1}, - [443] = {.lex_state = 1}, - [444] = {.lex_state = 1}, - [445] = {.lex_state = 90}, - [446] = {.lex_state = 1}, - [447] = {.lex_state = 1}, - [448] = {.lex_state = 1}, + [441] = {.lex_state = 11}, + [442] = {.lex_state = 11}, + [443] = {.lex_state = 11}, + [444] = {.lex_state = 11}, + [445] = {.lex_state = 11}, + [446] = {.lex_state = 11}, + [447] = {.lex_state = 11}, + [448] = {.lex_state = 11}, [449] = {.lex_state = 1}, - [450] = {.lex_state = 91}, - [451] = {.lex_state = 91}, - [452] = {.lex_state = 91}, - [453] = {.lex_state = 91}, - [454] = {.lex_state = 91}, - [455] = {.lex_state = 91}, - [456] = {.lex_state = 91}, - [457] = {.lex_state = 91}, - [458] = {.lex_state = 91}, - [459] = {.lex_state = 91}, - [460] = {.lex_state = 91}, - [461] = {.lex_state = 90}, - [462] = {.lex_state = 91}, - [463] = {.lex_state = 90}, - [464] = {.lex_state = 90}, - [465] = {.lex_state = 91}, - [466] = {.lex_state = 91}, - [467] = {.lex_state = 91}, - [468] = {.lex_state = 91}, - [469] = {.lex_state = 91}, - [470] = {.lex_state = 91}, - [471] = {.lex_state = 91}, - [472] = {.lex_state = 91}, - [473] = {.lex_state = 91}, - [474] = {.lex_state = 91}, - [475] = {.lex_state = 91}, - [476] = {.lex_state = 91}, - [477] = {.lex_state = 91}, - [478] = {.lex_state = 91}, - [479] = {.lex_state = 91}, - [480] = {.lex_state = 91}, - [481] = {.lex_state = 91}, - [482] = {.lex_state = 91}, - [483] = {.lex_state = 91}, - [484] = {.lex_state = 91}, - [485] = {.lex_state = 91}, - [486] = {.lex_state = 91}, - [487] = {.lex_state = 91}, - [488] = {.lex_state = 91}, - [489] = {.lex_state = 91}, - [490] = {.lex_state = 91}, - [491] = {.lex_state = 91}, - [492] = {.lex_state = 91}, - [493] = {.lex_state = 91}, - [494] = {.lex_state = 91}, - [495] = {.lex_state = 91}, - [496] = {.lex_state = 91}, - [497] = {.lex_state = 91}, - [498] = {.lex_state = 91}, - [499] = {.lex_state = 91}, - [500] = {.lex_state = 91}, - [501] = {.lex_state = 90}, - [502] = {.lex_state = 90}, - [503] = {.lex_state = 90}, - [504] = {.lex_state = 90}, - [505] = {.lex_state = 90}, - [506] = {.lex_state = 91}, - [507] = {.lex_state = 91}, - [508] = {.lex_state = 90}, - [509] = {.lex_state = 90}, - [510] = {.lex_state = 90}, - [511] = {.lex_state = 90}, - [512] = {.lex_state = 90}, - [513] = {.lex_state = 90}, - [514] = {.lex_state = 91}, - [515] = {.lex_state = 90}, - [516] = {.lex_state = 91}, - [517] = {.lex_state = 90}, - [518] = {.lex_state = 90}, - [519] = {.lex_state = 90}, - [520] = {.lex_state = 91}, - [521] = {.lex_state = 91}, - [522] = {.lex_state = 91}, - [523] = {.lex_state = 90}, - [524] = {.lex_state = 90}, - [525] = {.lex_state = 90}, - [526] = {.lex_state = 90}, - [527] = {.lex_state = 90}, - [528] = {.lex_state = 90}, - [529] = {.lex_state = 90}, - [530] = {.lex_state = 90}, - [531] = {.lex_state = 90}, - [532] = {.lex_state = 90}, - [533] = {.lex_state = 90}, - [534] = {.lex_state = 90}, - [535] = {.lex_state = 90}, - [536] = {.lex_state = 90}, - [537] = {.lex_state = 90}, - [538] = {.lex_state = 90}, - [539] = {.lex_state = 90}, - [540] = {.lex_state = 90}, - [541] = {.lex_state = 90}, - [542] = {.lex_state = 90}, - [543] = {.lex_state = 90}, - [544] = {.lex_state = 90}, - [545] = {.lex_state = 90}, - [546] = {.lex_state = 90}, - [547] = {.lex_state = 91}, - [548] = {.lex_state = 90}, - [549] = {.lex_state = 90}, - [550] = {.lex_state = 90}, - [551] = {.lex_state = 90}, - [552] = {.lex_state = 90}, - [553] = {.lex_state = 90}, - [554] = {.lex_state = 90}, + [450] = {.lex_state = 1}, + [451] = {.lex_state = 1}, + [452] = {.lex_state = 1}, + [453] = {.lex_state = 1}, + [454] = {.lex_state = 1}, + [455] = {.lex_state = 1}, + [456] = {.lex_state = 1}, + [457] = {.lex_state = 1}, + [458] = {.lex_state = 1}, + [459] = {.lex_state = 1}, + [460] = {.lex_state = 1}, + [461] = {.lex_state = 1}, + [462] = {.lex_state = 1}, + [463] = {.lex_state = 1}, + [464] = {.lex_state = 1}, + [465] = {.lex_state = 1}, + [466] = {.lex_state = 1}, + [467] = {.lex_state = 1}, + [468] = {.lex_state = 1}, + [469] = {.lex_state = 1}, + [470] = {.lex_state = 1}, + [471] = {.lex_state = 1}, + [472] = {.lex_state = 1}, + [473] = {.lex_state = 1}, + [474] = {.lex_state = 1}, + [475] = {.lex_state = 1}, + [476] = {.lex_state = 1}, + [477] = {.lex_state = 1}, + [478] = {.lex_state = 1}, + [479] = {.lex_state = 1}, + [480] = {.lex_state = 1}, + [481] = {.lex_state = 1}, + [482] = {.lex_state = 1}, + [483] = {.lex_state = 1}, + [484] = {.lex_state = 1}, + [485] = {.lex_state = 1}, + [486] = {.lex_state = 1}, + [487] = {.lex_state = 1}, + [488] = {.lex_state = 1}, + [489] = {.lex_state = 1}, + [490] = {.lex_state = 1}, + [491] = {.lex_state = 1}, + [492] = {.lex_state = 1}, + [493] = {.lex_state = 88}, + [494] = {.lex_state = 1}, + [495] = {.lex_state = 1}, + [496] = {.lex_state = 1}, + [497] = {.lex_state = 1}, + [498] = {.lex_state = 1}, + [499] = {.lex_state = 1}, + [500] = {.lex_state = 1}, + [501] = {.lex_state = 1}, + [502] = {.lex_state = 89}, + [503] = {.lex_state = 89}, + [504] = {.lex_state = 89}, + [505] = {.lex_state = 5}, + [506] = {.lex_state = 89}, + [507] = {.lex_state = 89}, + [508] = {.lex_state = 5}, + [509] = {.lex_state = 5}, + [510] = {.lex_state = 5}, + [511] = {.lex_state = 5}, + [512] = {.lex_state = 5}, + [513] = {.lex_state = 5}, + [514] = {.lex_state = 5}, + [515] = {.lex_state = 89}, + [516] = {.lex_state = 89}, + [517] = {.lex_state = 5}, + [518] = {.lex_state = 5}, + [519] = {.lex_state = 89}, + [520] = {.lex_state = 89}, + [521] = {.lex_state = 88}, + [522] = {.lex_state = 5}, + [523] = {.lex_state = 5}, + [524] = {.lex_state = 5}, + [525] = {.lex_state = 5}, + [526] = {.lex_state = 5}, + [527] = {.lex_state = 5}, + [528] = {.lex_state = 5}, + [529] = {.lex_state = 5}, + [530] = {.lex_state = 5}, + [531] = {.lex_state = 5}, + [532] = {.lex_state = 5}, + [533] = {.lex_state = 5}, + [534] = {.lex_state = 5}, + [535] = {.lex_state = 5}, + [536] = {.lex_state = 5}, + [537] = {.lex_state = 89}, + [538] = {.lex_state = 89}, + [539] = {.lex_state = 89}, + [540] = {.lex_state = 88}, + [541] = {.lex_state = 89}, + [542] = {.lex_state = 89}, + [543] = {.lex_state = 5}, + [544] = {.lex_state = 5}, + [545] = {.lex_state = 5}, + [546] = {.lex_state = 89}, + [547] = {.lex_state = 5}, + [548] = {.lex_state = 5}, + [549] = {.lex_state = 89}, + [550] = {.lex_state = 5}, + [551] = {.lex_state = 5}, + [552] = {.lex_state = 5}, + [553] = {.lex_state = 5}, + [554] = {.lex_state = 5}, [555] = {.lex_state = 5}, [556] = {.lex_state = 5}, [557] = {.lex_state = 5}, @@ -8056,223 +8815,223 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [566] = {.lex_state = 5}, [567] = {.lex_state = 5}, [568] = {.lex_state = 5}, - [569] = {.lex_state = 5}, - [570] = {.lex_state = 5}, - [571] = {.lex_state = 5}, - [572] = {.lex_state = 5}, - [573] = {.lex_state = 5}, - [574] = {.lex_state = 5}, - [575] = {.lex_state = 5}, - [576] = {.lex_state = 5}, - [577] = {.lex_state = 5}, + [569] = {.lex_state = 89}, + [570] = {.lex_state = 89}, + [571] = {.lex_state = 89}, + [572] = {.lex_state = 89}, + [573] = {.lex_state = 89}, + [574] = {.lex_state = 89}, + [575] = {.lex_state = 89}, + [576] = {.lex_state = 89}, + [577] = {.lex_state = 89}, [578] = {.lex_state = 5}, - [579] = {.lex_state = 5}, - [580] = {.lex_state = 5}, - [581] = {.lex_state = 5}, - [582] = {.lex_state = 5}, - [583] = {.lex_state = 5}, - [584] = {.lex_state = 5}, - [585] = {.lex_state = 5}, - [586] = {.lex_state = 5}, - [587] = {.lex_state = 5}, - [588] = {.lex_state = 5}, + [579] = {.lex_state = 89}, + [580] = {.lex_state = 89}, + [581] = {.lex_state = 89}, + [582] = {.lex_state = 89}, + [583] = {.lex_state = 89}, + [584] = {.lex_state = 89}, + [585] = {.lex_state = 89}, + [586] = {.lex_state = 89}, + [587] = {.lex_state = 89}, + [588] = {.lex_state = 89}, [589] = {.lex_state = 5}, - [590] = {.lex_state = 5}, - [591] = {.lex_state = 5}, - [592] = {.lex_state = 5}, - [593] = {.lex_state = 5}, - [594] = {.lex_state = 5}, - [595] = {.lex_state = 5}, - [596] = {.lex_state = 5}, - [597] = {.lex_state = 5}, - [598] = {.lex_state = 5}, - [599] = {.lex_state = 5}, - [600] = {.lex_state = 5}, + [590] = {.lex_state = 89}, + [591] = {.lex_state = 89}, + [592] = {.lex_state = 89}, + [593] = {.lex_state = 89}, + [594] = {.lex_state = 89}, + [595] = {.lex_state = 89}, + [596] = {.lex_state = 89}, + [597] = {.lex_state = 89}, + [598] = {.lex_state = 89}, + [599] = {.lex_state = 89}, + [600] = {.lex_state = 89}, [601] = {.lex_state = 5}, [602] = {.lex_state = 5}, [603] = {.lex_state = 5}, - [604] = {.lex_state = 5}, - [605] = {.lex_state = 5}, + [604] = {.lex_state = 89}, + [605] = {.lex_state = 89}, [606] = {.lex_state = 5}, [607] = {.lex_state = 5}, [608] = {.lex_state = 5}, [609] = {.lex_state = 5}, [610] = {.lex_state = 5}, [611] = {.lex_state = 5}, - [612] = {.lex_state = 2}, - [613] = {.lex_state = 10}, - [614] = {.lex_state = 10}, - [615] = {.lex_state = 10}, - [616] = {.lex_state = 10}, - [617] = {.lex_state = 10}, - [618] = {.lex_state = 10}, - [619] = {.lex_state = 10}, - [620] = {.lex_state = 9}, - [621] = {.lex_state = 10}, - [622] = {.lex_state = 10}, - [623] = {.lex_state = 10}, - [624] = {.lex_state = 10}, - [625] = {.lex_state = 10}, - [626] = {.lex_state = 10}, - [627] = {.lex_state = 10}, - [628] = {.lex_state = 10}, - [629] = {.lex_state = 10}, - [630] = {.lex_state = 10}, - [631] = {.lex_state = 10}, - [632] = {.lex_state = 10}, - [633] = {.lex_state = 10}, - [634] = {.lex_state = 10}, - [635] = {.lex_state = 10}, - [636] = {.lex_state = 10}, - [637] = {.lex_state = 10}, - [638] = {.lex_state = 10}, - [639] = {.lex_state = 10}, - [640] = {.lex_state = 10}, - [641] = {.lex_state = 10}, - [642] = {.lex_state = 10}, - [643] = {.lex_state = 10}, - [644] = {.lex_state = 10}, - [645] = {.lex_state = 10}, - [646] = {.lex_state = 10}, - [647] = {.lex_state = 10}, - [648] = {.lex_state = 10}, - [649] = {.lex_state = 10}, - [650] = {.lex_state = 10}, - [651] = {.lex_state = 10}, - [652] = {.lex_state = 10}, - [653] = {.lex_state = 10}, - [654] = {.lex_state = 10}, - [655] = {.lex_state = 10}, - [656] = {.lex_state = 10}, - [657] = {.lex_state = 10}, - [658] = {.lex_state = 10}, - [659] = {.lex_state = 10}, - [660] = {.lex_state = 10}, - [661] = {.lex_state = 9}, - [662] = {.lex_state = 9}, - [663] = {.lex_state = 9}, + [612] = {.lex_state = 88}, + [613] = {.lex_state = 5}, + [614] = {.lex_state = 88}, + [615] = {.lex_state = 88}, + [616] = {.lex_state = 88}, + [617] = {.lex_state = 88}, + [618] = {.lex_state = 88}, + [619] = {.lex_state = 88}, + [620] = {.lex_state = 88}, + [621] = {.lex_state = 88}, + [622] = {.lex_state = 88}, + [623] = {.lex_state = 88}, + [624] = {.lex_state = 88}, + [625] = {.lex_state = 88}, + [626] = {.lex_state = 88}, + [627] = {.lex_state = 88}, + [628] = {.lex_state = 88}, + [629] = {.lex_state = 89}, + [630] = {.lex_state = 89}, + [631] = {.lex_state = 88}, + [632] = {.lex_state = 88}, + [633] = {.lex_state = 88}, + [634] = {.lex_state = 89}, + [635] = {.lex_state = 89}, + [636] = {.lex_state = 89}, + [637] = {.lex_state = 88}, + [638] = {.lex_state = 88}, + [639] = {.lex_state = 88}, + [640] = {.lex_state = 88}, + [641] = {.lex_state = 88}, + [642] = {.lex_state = 88}, + [643] = {.lex_state = 88}, + [644] = {.lex_state = 88}, + [645] = {.lex_state = 89}, + [646] = {.lex_state = 88}, + [647] = {.lex_state = 88}, + [648] = {.lex_state = 88}, + [649] = {.lex_state = 89}, + [650] = {.lex_state = 88}, + [651] = {.lex_state = 88}, + [652] = {.lex_state = 88}, + [653] = {.lex_state = 88}, + [654] = {.lex_state = 88}, + [655] = {.lex_state = 88}, + [656] = {.lex_state = 88}, + [657] = {.lex_state = 88}, + [658] = {.lex_state = 88}, + [659] = {.lex_state = 88}, + [660] = {.lex_state = 88}, + [661] = {.lex_state = 88}, + [662] = {.lex_state = 88}, + [663] = {.lex_state = 88}, [664] = {.lex_state = 88}, - [665] = {.lex_state = 5}, - [666] = {.lex_state = 5}, + [665] = {.lex_state = 88}, + [666] = {.lex_state = 89}, [667] = {.lex_state = 88}, - [668] = {.lex_state = 5}, - [669] = {.lex_state = 88}, - [670] = {.lex_state = 5}, - [671] = {.lex_state = 5}, - [672] = {.lex_state = 88}, - [673] = {.lex_state = 88}, - [674] = {.lex_state = 5}, - [675] = {.lex_state = 5}, - [676] = {.lex_state = 5}, - [677] = {.lex_state = 88}, - [678] = {.lex_state = 88}, - [679] = {.lex_state = 5}, - [680] = {.lex_state = 88}, - [681] = {.lex_state = 88}, - [682] = {.lex_state = 14}, - [683] = {.lex_state = 14}, - [684] = {.lex_state = 14}, - [685] = {.lex_state = 14}, - [686] = {.lex_state = 2}, - [687] = {.lex_state = 5}, - [688] = {.lex_state = 2}, - [689] = {.lex_state = 2}, - [690] = {.lex_state = 2}, - [691] = {.lex_state = 5}, - [692] = {.lex_state = 2}, - [693] = {.lex_state = 2}, + [668] = {.lex_state = 2}, + [669] = {.lex_state = 9}, + [670] = {.lex_state = 10}, + [671] = {.lex_state = 10}, + [672] = {.lex_state = 10}, + [673] = {.lex_state = 10}, + [674] = {.lex_state = 10}, + [675] = {.lex_state = 10}, + [676] = {.lex_state = 10}, + [677] = {.lex_state = 10}, + [678] = {.lex_state = 10}, + [679] = {.lex_state = 10}, + [680] = {.lex_state = 10}, + [681] = {.lex_state = 10}, + [682] = {.lex_state = 10}, + [683] = {.lex_state = 10}, + [684] = {.lex_state = 10}, + [685] = {.lex_state = 10}, + [686] = {.lex_state = 10}, + [687] = {.lex_state = 10}, + [688] = {.lex_state = 10}, + [689] = {.lex_state = 10}, + [690] = {.lex_state = 10}, + [691] = {.lex_state = 10}, + [692] = {.lex_state = 9}, + [693] = {.lex_state = 10}, [694] = {.lex_state = 10}, - [695] = {.lex_state = 2}, - [696] = {.lex_state = 2}, - [697] = {.lex_state = 2}, + [695] = {.lex_state = 10}, + [696] = {.lex_state = 10}, + [697] = {.lex_state = 10}, [698] = {.lex_state = 10}, - [699] = {.lex_state = 2}, - [700] = {.lex_state = 2}, - [701] = {.lex_state = 2}, - [702] = {.lex_state = 5}, - [703] = {.lex_state = 2}, - [704] = {.lex_state = 2}, - [705] = {.lex_state = 2}, - [706] = {.lex_state = 2}, - [707] = {.lex_state = 2}, - [708] = {.lex_state = 2}, + [699] = {.lex_state = 10}, + [700] = {.lex_state = 10}, + [701] = {.lex_state = 10}, + [702] = {.lex_state = 10}, + [703] = {.lex_state = 10}, + [704] = {.lex_state = 10}, + [705] = {.lex_state = 10}, + [706] = {.lex_state = 10}, + [707] = {.lex_state = 10}, + [708] = {.lex_state = 10}, [709] = {.lex_state = 10}, - [710] = {.lex_state = 5}, - [711] = {.lex_state = 2}, + [710] = {.lex_state = 10}, + [711] = {.lex_state = 10}, [712] = {.lex_state = 10}, - [713] = {.lex_state = 2}, - [714] = {.lex_state = 5}, - [715] = {.lex_state = 5}, - [716] = {.lex_state = 5}, - [717] = {.lex_state = 5}, - [718] = {.lex_state = 5}, - [719] = {.lex_state = 2}, - [720] = {.lex_state = 5}, - [721] = {.lex_state = 2}, - [722] = {.lex_state = 10}, - [723] = {.lex_state = 10}, - [724] = {.lex_state = 10}, - [725] = {.lex_state = 2}, - [726] = {.lex_state = 2}, - [727] = {.lex_state = 2}, - [728] = {.lex_state = 10}, - [729] = {.lex_state = 10}, - [730] = {.lex_state = 5}, - [731] = {.lex_state = 2}, - [732] = {.lex_state = 5}, - [733] = {.lex_state = 10}, - [734] = {.lex_state = 5}, - [735] = {.lex_state = 5}, - [736] = {.lex_state = 5}, + [713] = {.lex_state = 10}, + [714] = {.lex_state = 10}, + [715] = {.lex_state = 10}, + [716] = {.lex_state = 10}, + [717] = {.lex_state = 10}, + [718] = {.lex_state = 9}, + [719] = {.lex_state = 9}, + [720] = {.lex_state = 86}, + [721] = {.lex_state = 86}, + [722] = {.lex_state = 86}, + [723] = {.lex_state = 86}, + [724] = {.lex_state = 86}, + [725] = {.lex_state = 86}, + [726] = {.lex_state = 86}, + [727] = {.lex_state = 86}, + [728] = {.lex_state = 86}, + [729] = {.lex_state = 86}, + [730] = {.lex_state = 86}, + [731] = {.lex_state = 86}, + [732] = {.lex_state = 86}, + [733] = {.lex_state = 2}, + [734] = {.lex_state = 2}, + [735] = {.lex_state = 2}, + [736] = {.lex_state = 2}, [737] = {.lex_state = 2}, [738] = {.lex_state = 2}, [739] = {.lex_state = 2}, [740] = {.lex_state = 2}, - [741] = {.lex_state = 10}, + [741] = {.lex_state = 2}, [742] = {.lex_state = 2}, [743] = {.lex_state = 2}, [744] = {.lex_state = 2}, [745] = {.lex_state = 2}, [746] = {.lex_state = 2}, - [747] = {.lex_state = 2}, - [748] = {.lex_state = 2}, + [747] = {.lex_state = 10}, + [748] = {.lex_state = 9}, [749] = {.lex_state = 2}, [750] = {.lex_state = 2}, [751] = {.lex_state = 2}, [752] = {.lex_state = 2}, [753] = {.lex_state = 2}, - [754] = {.lex_state = 88}, + [754] = {.lex_state = 2}, [755] = {.lex_state = 2}, [756] = {.lex_state = 2}, [757] = {.lex_state = 2}, [758] = {.lex_state = 2}, [759] = {.lex_state = 2}, [760] = {.lex_state = 2}, - [761] = {.lex_state = 2}, - [762] = {.lex_state = 2}, - [763] = {.lex_state = 2}, + [761] = {.lex_state = 10}, + [762] = {.lex_state = 9}, + [763] = {.lex_state = 9}, [764] = {.lex_state = 2}, - [765] = {.lex_state = 2}, - [766] = {.lex_state = 2}, - [767] = {.lex_state = 2}, - [768] = {.lex_state = 2}, - [769] = {.lex_state = 2}, + [765] = {.lex_state = 9}, + [766] = {.lex_state = 9}, + [767] = {.lex_state = 9}, + [768] = {.lex_state = 9}, + [769] = {.lex_state = 9}, [770] = {.lex_state = 2}, - [771] = {.lex_state = 2}, - [772] = {.lex_state = 2}, - [773] = {.lex_state = 2}, - [774] = {.lex_state = 2}, - [775] = {.lex_state = 10}, - [776] = {.lex_state = 10}, - [777] = {.lex_state = 10}, - [778] = {.lex_state = 2}, + [771] = {.lex_state = 9}, + [772] = {.lex_state = 9}, + [773] = {.lex_state = 9}, + [774] = {.lex_state = 9}, + [775] = {.lex_state = 9}, + [776] = {.lex_state = 9}, + [777] = {.lex_state = 9}, + [778] = {.lex_state = 10}, [779] = {.lex_state = 10}, - [780] = {.lex_state = 2}, - [781] = {.lex_state = 2}, - [782] = {.lex_state = 2}, - [783] = {.lex_state = 2}, + [780] = {.lex_state = 10}, + [781] = {.lex_state = 10}, + [782] = {.lex_state = 10}, + [783] = {.lex_state = 10}, [784] = {.lex_state = 2}, - [785] = {.lex_state = 2}, + [785] = {.lex_state = 9}, [786] = {.lex_state = 2}, [787] = {.lex_state = 2}, [788] = {.lex_state = 2}, @@ -8281,770 +9040,866 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [791] = {.lex_state = 10}, [792] = {.lex_state = 10}, [793] = {.lex_state = 10}, - [794] = {.lex_state = 2}, - [795] = {.lex_state = 2}, - [796] = {.lex_state = 2}, - [797] = {.lex_state = 2}, - [798] = {.lex_state = 2}, - [799] = {.lex_state = 2}, - [800] = {.lex_state = 2}, - [801] = {.lex_state = 2}, - [802] = {.lex_state = 2}, - [803] = {.lex_state = 2}, - [804] = {.lex_state = 2}, - [805] = {.lex_state = 2}, - [806] = {.lex_state = 2}, - [807] = {.lex_state = 2}, - [808] = {.lex_state = 2}, - [809] = {.lex_state = 2}, - [810] = {.lex_state = 88}, - [811] = {.lex_state = 88}, - [812] = {.lex_state = 88}, - [813] = {.lex_state = 88}, - [814] = {.lex_state = 88}, - [815] = {.lex_state = 88}, - [816] = {.lex_state = 88}, - [817] = {.lex_state = 88}, - [818] = {.lex_state = 88}, - [819] = {.lex_state = 88}, - [820] = {.lex_state = 88}, - [821] = {.lex_state = 88}, - [822] = {.lex_state = 88}, - [823] = {.lex_state = 88}, - [824] = {.lex_state = 88}, - [825] = {.lex_state = 88}, - [826] = {.lex_state = 88}, - [827] = {.lex_state = 88}, - [828] = {.lex_state = 88}, - [829] = {.lex_state = 88}, - [830] = {.lex_state = 88}, - [831] = {.lex_state = 88}, - [832] = {.lex_state = 9}, - [833] = {.lex_state = 9}, - [834] = {.lex_state = 9}, - [835] = {.lex_state = 9}, - [836] = {.lex_state = 9}, - [837] = {.lex_state = 9}, - [838] = {.lex_state = 9}, - [839] = {.lex_state = 9}, - [840] = {.lex_state = 9}, - [841] = {.lex_state = 9}, - [842] = {.lex_state = 9}, - [843] = {.lex_state = 9}, - [844] = {.lex_state = 9}, - [845] = {.lex_state = 9}, - [846] = {.lex_state = 9}, - [847] = {.lex_state = 9}, - [848] = {.lex_state = 9}, - [849] = {.lex_state = 9}, - [850] = {.lex_state = 9}, - [851] = {.lex_state = 9}, - [852] = {.lex_state = 9}, - [853] = {.lex_state = 9}, - [854] = {.lex_state = 9}, - [855] = {.lex_state = 9}, - [856] = {.lex_state = 9}, - [857] = {.lex_state = 9}, - [858] = {.lex_state = 9}, - [859] = {.lex_state = 9}, - [860] = {.lex_state = 9}, - [861] = {.lex_state = 9}, - [862] = {.lex_state = 9}, - [863] = {.lex_state = 15}, - [864] = {.lex_state = 15}, - [865] = {.lex_state = 15}, - [866] = {.lex_state = 20}, - [867] = {.lex_state = 5}, + [794] = {.lex_state = 9}, + [795] = {.lex_state = 9}, + [796] = {.lex_state = 9}, + [797] = {.lex_state = 9}, + [798] = {.lex_state = 9}, + [799] = {.lex_state = 9}, + [800] = {.lex_state = 9}, + [801] = {.lex_state = 9}, + [802] = {.lex_state = 9}, + [803] = {.lex_state = 9}, + [804] = {.lex_state = 9}, + [805] = {.lex_state = 9}, + [806] = {.lex_state = 9}, + [807] = {.lex_state = 10}, + [808] = {.lex_state = 9}, + [809] = {.lex_state = 10}, + [810] = {.lex_state = 2}, + [811] = {.lex_state = 2}, + [812] = {.lex_state = 10}, + [813] = {.lex_state = 9}, + [814] = {.lex_state = 2}, + [815] = {.lex_state = 86}, + [816] = {.lex_state = 2}, + [817] = {.lex_state = 2}, + [818] = {.lex_state = 10}, + [819] = {.lex_state = 10}, + [820] = {.lex_state = 10}, + [821] = {.lex_state = 5}, + [822] = {.lex_state = 10}, + [823] = {.lex_state = 10}, + [824] = {.lex_state = 2}, + [825] = {.lex_state = 5}, + [826] = {.lex_state = 5}, + [827] = {.lex_state = 2}, + [828] = {.lex_state = 2}, + [829] = {.lex_state = 2}, + [830] = {.lex_state = 2}, + [831] = {.lex_state = 2}, + [832] = {.lex_state = 2}, + [833] = {.lex_state = 10}, + [834] = {.lex_state = 2}, + [835] = {.lex_state = 2}, + [836] = {.lex_state = 2}, + [837] = {.lex_state = 2}, + [838] = {.lex_state = 2}, + [839] = {.lex_state = 2}, + [840] = {.lex_state = 5}, + [841] = {.lex_state = 2}, + [842] = {.lex_state = 2}, + [843] = {.lex_state = 2}, + [844] = {.lex_state = 2}, + [845] = {.lex_state = 2}, + [846] = {.lex_state = 2}, + [847] = {.lex_state = 10}, + [848] = {.lex_state = 10}, + [849] = {.lex_state = 2}, + [850] = {.lex_state = 5}, + [851] = {.lex_state = 2}, + [852] = {.lex_state = 10}, + [853] = {.lex_state = 2}, + [854] = {.lex_state = 2}, + [855] = {.lex_state = 2}, + [856] = {.lex_state = 2}, + [857] = {.lex_state = 2}, + [858] = {.lex_state = 2}, + [859] = {.lex_state = 2}, + [860] = {.lex_state = 2}, + [861] = {.lex_state = 2}, + [862] = {.lex_state = 2}, + [863] = {.lex_state = 2}, + [864] = {.lex_state = 2}, + [865] = {.lex_state = 2}, + [866] = {.lex_state = 2}, + [867] = {.lex_state = 2}, [868] = {.lex_state = 2}, [869] = {.lex_state = 2}, [870] = {.lex_state = 2}, - [871] = {.lex_state = 5}, + [871] = {.lex_state = 2}, [872] = {.lex_state = 2}, [873] = {.lex_state = 2}, - [874] = {.lex_state = 29}, - [875] = {.lex_state = 13}, - [876] = {.lex_state = 29}, - [877] = {.lex_state = 13}, - [878] = {.lex_state = 13}, - [879] = {.lex_state = 29}, - [880] = {.lex_state = 29}, - [881] = {.lex_state = 13}, - [882] = {.lex_state = 29}, - [883] = {.lex_state = 23}, - [884] = {.lex_state = 11}, - [885] = {.lex_state = 11}, - [886] = {.lex_state = 13}, - [887] = {.lex_state = 11}, - [888] = {.lex_state = 13}, - [889] = {.lex_state = 11}, - [890] = {.lex_state = 13}, - [891] = {.lex_state = 13}, - [892] = {.lex_state = 11}, - [893] = {.lex_state = 11}, - [894] = {.lex_state = 13}, - [895] = {.lex_state = 11}, - [896] = {.lex_state = 11}, - [897] = {.lex_state = 11}, - [898] = {.lex_state = 11}, - [899] = {.lex_state = 23}, - [900] = {.lex_state = 11}, - [901] = {.lex_state = 23}, - [902] = {.lex_state = 13}, - [903] = {.lex_state = 11}, - [904] = {.lex_state = 23}, - [905] = {.lex_state = 11}, - [906] = {.lex_state = 11}, - [907] = {.lex_state = 11}, - [908] = {.lex_state = 13}, - [909] = {.lex_state = 28}, - [910] = {.lex_state = 28}, - [911] = {.lex_state = 13}, - [912] = {.lex_state = 13}, - [913] = {.lex_state = 13}, - [914] = {.lex_state = 13}, - [915] = {.lex_state = 28}, - [916] = {.lex_state = 28}, - [917] = {.lex_state = 2}, - [918] = {.lex_state = 28}, - [919] = {.lex_state = 28}, - [920] = {.lex_state = 13}, - [921] = {.lex_state = 28}, - [922] = {.lex_state = 28}, - [923] = {.lex_state = 28}, - [924] = {.lex_state = 28}, - [925] = {.lex_state = 13}, - [926] = {.lex_state = 28}, - [927] = {.lex_state = 28}, - [928] = {.lex_state = 28}, - [929] = {.lex_state = 28}, - [930] = {.lex_state = 28}, - [931] = {.lex_state = 28}, - [932] = {.lex_state = 21}, - [933] = {.lex_state = 28}, - [934] = {.lex_state = 24}, - [935] = {.lex_state = 28}, - [936] = {.lex_state = 23}, - [937] = {.lex_state = 24}, - [938] = {.lex_state = 21}, - [939] = {.lex_state = 21}, - [940] = {.lex_state = 21}, - [941] = {.lex_state = 28}, - [942] = {.lex_state = 21}, - [943] = {.lex_state = 28}, - [944] = {.lex_state = 21}, - [945] = {.lex_state = 21}, - [946] = {.lex_state = 21}, - [947] = {.lex_state = 28}, - [948] = {.lex_state = 18}, - [949] = {.lex_state = 28}, - [950] = {.lex_state = 12}, - [951] = {.lex_state = 12}, - [952] = {.lex_state = 12}, - [953] = {.lex_state = 18}, - [954] = {.lex_state = 18}, - [955] = {.lex_state = 18}, - [956] = {.lex_state = 22}, - [957] = {.lex_state = 28}, - [958] = {.lex_state = 28}, - [959] = {.lex_state = 28}, - [960] = {.lex_state = 28}, - [961] = {.lex_state = 12}, - [962] = {.lex_state = 22}, - [963] = {.lex_state = 23}, - [964] = {.lex_state = 22}, - [965] = {.lex_state = 12}, - [966] = {.lex_state = 12}, - [967] = {.lex_state = 18}, - [968] = {.lex_state = 12}, - [969] = {.lex_state = 18}, - [970] = {.lex_state = 12}, - [971] = {.lex_state = 12}, - [972] = {.lex_state = 18}, - [973] = {.lex_state = 28}, + [874] = {.lex_state = 2}, + [875] = {.lex_state = 2}, + [876] = {.lex_state = 2}, + [877] = {.lex_state = 2}, + [878] = {.lex_state = 5}, + [879] = {.lex_state = 2}, + [880] = {.lex_state = 5}, + [881] = {.lex_state = 2}, + [882] = {.lex_state = 2}, + [883] = {.lex_state = 2}, + [884] = {.lex_state = 2}, + [885] = {.lex_state = 2}, + [886] = {.lex_state = 2}, + [887] = {.lex_state = 10}, + [888] = {.lex_state = 2}, + [889] = {.lex_state = 2}, + [890] = {.lex_state = 2}, + [891] = {.lex_state = 5}, + [892] = {.lex_state = 2}, + [893] = {.lex_state = 2}, + [894] = {.lex_state = 10}, + [895] = {.lex_state = 5}, + [896] = {.lex_state = 10}, + [897] = {.lex_state = 2}, + [898] = {.lex_state = 2}, + [899] = {.lex_state = 86}, + [900] = {.lex_state = 2}, + [901] = {.lex_state = 2}, + [902] = {.lex_state = 14}, + [903] = {.lex_state = 14}, + [904] = {.lex_state = 2}, + [905] = {.lex_state = 2}, + [906] = {.lex_state = 2}, + [907] = {.lex_state = 86}, + [908] = {.lex_state = 2}, + [909] = {.lex_state = 2}, + [910] = {.lex_state = 86}, + [911] = {.lex_state = 2}, + [912] = {.lex_state = 2}, + [913] = {.lex_state = 14}, + [914] = {.lex_state = 86}, + [915] = {.lex_state = 86}, + [916] = {.lex_state = 86}, + [917] = {.lex_state = 86}, + [918] = {.lex_state = 86}, + [919] = {.lex_state = 86}, + [920] = {.lex_state = 86}, + [921] = {.lex_state = 2}, + [922] = {.lex_state = 14}, + [923] = {.lex_state = 2}, + [924] = {.lex_state = 86}, + [925] = {.lex_state = 86}, + [926] = {.lex_state = 86}, + [927] = {.lex_state = 86}, + [928] = {.lex_state = 86}, + [929] = {.lex_state = 5}, + [930] = {.lex_state = 86}, + [931] = {.lex_state = 86}, + [932] = {.lex_state = 86}, + [933] = {.lex_state = 5}, + [934] = {.lex_state = 86}, + [935] = {.lex_state = 86}, + [936] = {.lex_state = 86}, + [937] = {.lex_state = 86}, + [938] = {.lex_state = 86}, + [939] = {.lex_state = 86}, + [940] = {.lex_state = 86}, + [941] = {.lex_state = 86}, + [942] = {.lex_state = 86}, + [943] = {.lex_state = 86}, + [944] = {.lex_state = 86}, + [945] = {.lex_state = 86}, + [946] = {.lex_state = 5}, + [947] = {.lex_state = 5}, + [948] = {.lex_state = 5}, + [949] = {.lex_state = 5}, + [950] = {.lex_state = 5}, + [951] = {.lex_state = 5}, + [952] = {.lex_state = 5}, + [953] = {.lex_state = 5}, + [954] = {.lex_state = 5}, + [955] = {.lex_state = 5}, + [956] = {.lex_state = 5}, + [957] = {.lex_state = 5}, + [958] = {.lex_state = 5}, + [959] = {.lex_state = 15}, + [960] = {.lex_state = 15}, + [961] = {.lex_state = 15}, + [962] = {.lex_state = 19}, + [963] = {.lex_state = 5}, + [964] = {.lex_state = 2}, + [965] = {.lex_state = 2}, + [966] = {.lex_state = 2}, + [967] = {.lex_state = 2}, + [968] = {.lex_state = 2}, + [969] = {.lex_state = 5}, + [970] = {.lex_state = 22}, + [971] = {.lex_state = 28}, + [972] = {.lex_state = 13}, + [973] = {.lex_state = 13}, [974] = {.lex_state = 28}, - [975] = {.lex_state = 28}, + [975] = {.lex_state = 13}, [976] = {.lex_state = 28}, - [977] = {.lex_state = 22}, - [978] = {.lex_state = 18}, - [979] = {.lex_state = 12}, - [980] = {.lex_state = 18}, - [981] = {.lex_state = 12}, - [982] = {.lex_state = 18}, - [983] = {.lex_state = 12}, - [984] = {.lex_state = 18}, - [985] = {.lex_state = 12}, - [986] = {.lex_state = 2}, - [987] = {.lex_state = 18}, - [988] = {.lex_state = 28}, - [989] = {.lex_state = 12}, - [990] = {.lex_state = 12}, - [991] = {.lex_state = 18}, - [992] = {.lex_state = 12}, - [993] = {.lex_state = 2}, - [994] = {.lex_state = 18}, - [995] = {.lex_state = 12}, - [996] = {.lex_state = 22}, - [997] = {.lex_state = 18}, - [998] = {.lex_state = 18}, - [999] = {.lex_state = 18}, - [1000] = {.lex_state = 32}, - [1001] = {.lex_state = 24}, - [1002] = {.lex_state = 24}, - [1003] = {.lex_state = 11}, - [1004] = {.lex_state = 24}, - [1005] = {.lex_state = 28}, - [1006] = {.lex_state = 24}, - [1007] = {.lex_state = 24}, - [1008] = {.lex_state = 32}, - [1009] = {.lex_state = 24}, - [1010] = {.lex_state = 32}, - [1011] = {.lex_state = 24}, - [1012] = {.lex_state = 32}, - [1013] = {.lex_state = 32}, - [1014] = {.lex_state = 90}, - [1015] = {.lex_state = 90}, - [1016] = {.lex_state = 24}, - [1017] = {.lex_state = 24}, - [1018] = {.lex_state = 24}, - [1019] = {.lex_state = 2}, - [1020] = {.lex_state = 32}, - [1021] = {.lex_state = 2}, - [1022] = {.lex_state = 24}, - [1023] = {.lex_state = 24}, - [1024] = {.lex_state = 2}, - [1025] = {.lex_state = 23}, - [1026] = {.lex_state = 24}, - [1027] = {.lex_state = 23}, - [1028] = {.lex_state = 24}, - [1029] = {.lex_state = 2}, - [1030] = {.lex_state = 23}, - [1031] = {.lex_state = 32}, - [1032] = {.lex_state = 28}, - [1033] = {.lex_state = 90}, - [1034] = {.lex_state = 23}, - [1035] = {.lex_state = 32}, - [1036] = {.lex_state = 2}, - [1037] = {.lex_state = 90}, - [1038] = {.lex_state = 24}, - [1039] = {.lex_state = 24}, - [1040] = {.lex_state = 32}, - [1041] = {.lex_state = 11}, - [1042] = {.lex_state = 2}, - [1043] = {.lex_state = 24}, - [1044] = {.lex_state = 24}, - [1045] = {.lex_state = 24}, - [1046] = {.lex_state = 24}, - [1047] = {.lex_state = 24}, - [1048] = {.lex_state = 24}, - [1049] = {.lex_state = 24}, - [1050] = {.lex_state = 90}, - [1051] = {.lex_state = 24}, - [1052] = {.lex_state = 2}, - [1053] = {.lex_state = 2}, - [1054] = {.lex_state = 24}, - [1055] = {.lex_state = 2}, - [1056] = {.lex_state = 2}, - [1057] = {.lex_state = 0}, - [1058] = {.lex_state = 24}, - [1059] = {.lex_state = 90}, - [1060] = {.lex_state = 90}, - [1061] = {.lex_state = 2}, - [1062] = {.lex_state = 2}, - [1063] = {.lex_state = 24}, - [1064] = {.lex_state = 0}, - [1065] = {.lex_state = 24}, - [1066] = {.lex_state = 24}, - [1067] = {.lex_state = 32}, - [1068] = {.lex_state = 90}, - [1069] = {.lex_state = 23}, - [1070] = {.lex_state = 90}, - [1071] = {.lex_state = 24}, - [1072] = {.lex_state = 90}, - [1073] = {.lex_state = 24}, - [1074] = {.lex_state = 90}, - [1075] = {.lex_state = 24}, - [1076] = {.lex_state = 24}, - [1077] = {.lex_state = 24}, - [1078] = {.lex_state = 90}, - [1079] = {.lex_state = 2}, - [1080] = {.lex_state = 90}, - [1081] = {.lex_state = 90}, - [1082] = {.lex_state = 12}, - [1083] = {.lex_state = 90}, - [1084] = {.lex_state = 90}, - [1085] = {.lex_state = 0}, - [1086] = {.lex_state = 24}, - [1087] = {.lex_state = 2}, - [1088] = {.lex_state = 24}, - [1089] = {.lex_state = 2}, - [1090] = {.lex_state = 23}, - [1091] = {.lex_state = 2}, - [1092] = {.lex_state = 2}, - [1093] = {.lex_state = 24}, - [1094] = {.lex_state = 18}, + [977] = {.lex_state = 13}, + [978] = {.lex_state = 28}, + [979] = {.lex_state = 28}, + [980] = {.lex_state = 13}, + [981] = {.lex_state = 11}, + [982] = {.lex_state = 11}, + [983] = {.lex_state = 11}, + [984] = {.lex_state = 13}, + [985] = {.lex_state = 11}, + [986] = {.lex_state = 13}, + [987] = {.lex_state = 11}, + [988] = {.lex_state = 13}, + [989] = {.lex_state = 11}, + [990] = {.lex_state = 13}, + [991] = {.lex_state = 22}, + [992] = {.lex_state = 11}, + [993] = {.lex_state = 11}, + [994] = {.lex_state = 11}, + [995] = {.lex_state = 11}, + [996] = {.lex_state = 11}, + [997] = {.lex_state = 11}, + [998] = {.lex_state = 11}, + [999] = {.lex_state = 13}, + [1000] = {.lex_state = 11}, + [1001] = {.lex_state = 11}, + [1002] = {.lex_state = 22}, + [1003] = {.lex_state = 22}, + [1004] = {.lex_state = 13}, + [1005] = {.lex_state = 13}, + [1006] = {.lex_state = 27}, + [1007] = {.lex_state = 27}, + [1008] = {.lex_state = 13}, + [1009] = {.lex_state = 27}, + [1010] = {.lex_state = 27}, + [1011] = {.lex_state = 27}, + [1012] = {.lex_state = 13}, + [1013] = {.lex_state = 27}, + [1014] = {.lex_state = 13}, + [1015] = {.lex_state = 27}, + [1016] = {.lex_state = 13}, + [1017] = {.lex_state = 27}, + [1018] = {.lex_state = 27}, + [1019] = {.lex_state = 27}, + [1020] = {.lex_state = 27}, + [1021] = {.lex_state = 13}, + [1022] = {.lex_state = 2}, + [1023] = {.lex_state = 27}, + [1024] = {.lex_state = 27}, + [1025] = {.lex_state = 27}, + [1026] = {.lex_state = 27}, + [1027] = {.lex_state = 27}, + [1028] = {.lex_state = 20}, + [1029] = {.lex_state = 27}, + [1030] = {.lex_state = 27}, + [1031] = {.lex_state = 22}, + [1032] = {.lex_state = 20}, + [1033] = {.lex_state = 20}, + [1034] = {.lex_state = 20}, + [1035] = {.lex_state = 23}, + [1036] = {.lex_state = 20}, + [1037] = {.lex_state = 27}, + [1038] = {.lex_state = 20}, + [1039] = {.lex_state = 23}, + [1040] = {.lex_state = 27}, + [1041] = {.lex_state = 20}, + [1042] = {.lex_state = 20}, + [1043] = {.lex_state = 18}, + [1044] = {.lex_state = 12}, + [1045] = {.lex_state = 12}, + [1046] = {.lex_state = 27}, + [1047] = {.lex_state = 18}, + [1048] = {.lex_state = 21}, + [1049] = {.lex_state = 12}, + [1050] = {.lex_state = 18}, + [1051] = {.lex_state = 12}, + [1052] = {.lex_state = 18}, + [1053] = {.lex_state = 27}, + [1054] = {.lex_state = 18}, + [1055] = {.lex_state = 21}, + [1056] = {.lex_state = 12}, + [1057] = {.lex_state = 18}, + [1058] = {.lex_state = 12}, + [1059] = {.lex_state = 27}, + [1060] = {.lex_state = 12}, + [1061] = {.lex_state = 18}, + [1062] = {.lex_state = 22}, + [1063] = {.lex_state = 12}, + [1064] = {.lex_state = 18}, + [1065] = {.lex_state = 18}, + [1066] = {.lex_state = 27}, + [1067] = {.lex_state = 27}, + [1068] = {.lex_state = 27}, + [1069] = {.lex_state = 21}, + [1070] = {.lex_state = 12}, + [1071] = {.lex_state = 18}, + [1072] = {.lex_state = 12}, + [1073] = {.lex_state = 18}, + [1074] = {.lex_state = 21}, + [1075] = {.lex_state = 27}, + [1076] = {.lex_state = 27}, + [1077] = {.lex_state = 12}, + [1078] = {.lex_state = 18}, + [1079] = {.lex_state = 18}, + [1080] = {.lex_state = 12}, + [1081] = {.lex_state = 2}, + [1082] = {.lex_state = 21}, + [1083] = {.lex_state = 12}, + [1084] = {.lex_state = 12}, + [1085] = {.lex_state = 18}, + [1086] = {.lex_state = 27}, + [1087] = {.lex_state = 27}, + [1088] = {.lex_state = 12}, + [1089] = {.lex_state = 18}, + [1090] = {.lex_state = 12}, + [1091] = {.lex_state = 18}, + [1092] = {.lex_state = 12}, + [1093] = {.lex_state = 18}, + [1094] = {.lex_state = 27}, [1095] = {.lex_state = 2}, - [1096] = {.lex_state = 90}, - [1097] = {.lex_state = 0}, - [1098] = {.lex_state = 24}, - [1099] = {.lex_state = 24}, - [1100] = {.lex_state = 32}, - [1101] = {.lex_state = 24}, - [1102] = {.lex_state = 24}, - [1103] = {.lex_state = 23}, - [1104] = {.lex_state = 0}, - [1105] = {.lex_state = 90}, - [1106] = {.lex_state = 90}, - [1107] = {.lex_state = 32}, - [1108] = {.lex_state = 24}, - [1109] = {.lex_state = 2}, - [1110] = {.lex_state = 2}, - [1111] = {.lex_state = 90}, - [1112] = {.lex_state = 24}, - [1113] = {.lex_state = 90}, - [1114] = {.lex_state = 90}, - [1115] = {.lex_state = 90}, - [1116] = {.lex_state = 24}, - [1117] = {.lex_state = 24}, - [1118] = {.lex_state = 24}, - [1119] = {.lex_state = 24}, - [1120] = {.lex_state = 2}, - [1121] = {.lex_state = 90}, - [1122] = {.lex_state = 0}, - [1123] = {.lex_state = 32}, - [1124] = {.lex_state = 24}, - [1125] = {.lex_state = 24}, - [1126] = {.lex_state = 0}, - [1127] = {.lex_state = 2}, - [1128] = {.lex_state = 24}, - [1129] = {.lex_state = 24}, - [1130] = {.lex_state = 24}, - [1131] = {.lex_state = 90}, - [1132] = {.lex_state = 0}, - [1133] = {.lex_state = 0}, - [1134] = {.lex_state = 24}, - [1135] = {.lex_state = 0}, - [1136] = {.lex_state = 0}, - [1137] = {.lex_state = 0}, - [1138] = {.lex_state = 11}, - [1139] = {.lex_state = 0}, - [1140] = {.lex_state = 0}, - [1141] = {.lex_state = 90}, - [1142] = {.lex_state = 90}, - [1143] = {.lex_state = 0}, - [1144] = {.lex_state = 0}, - [1145] = {.lex_state = 90}, - [1146] = {.lex_state = 0}, - [1147] = {.lex_state = 0}, - [1148] = {.lex_state = 32}, - [1149] = {.lex_state = 24}, - [1150] = {.lex_state = 90}, - [1151] = {.lex_state = 24}, - [1152] = {.lex_state = 90}, - [1153] = {.lex_state = 0}, - [1154] = {.lex_state = 0}, - [1155] = {.lex_state = 90}, - [1156] = {.lex_state = 0}, - [1157] = {.lex_state = 0}, - [1158] = {.lex_state = 0}, + [1096] = {.lex_state = 31}, + [1097] = {.lex_state = 31}, + [1098] = {.lex_state = 23}, + [1099] = {.lex_state = 23}, + [1100] = {.lex_state = 23}, + [1101] = {.lex_state = 27}, + [1102] = {.lex_state = 31}, + [1103] = {.lex_state = 31}, + [1104] = {.lex_state = 31}, + [1105] = {.lex_state = 23}, + [1106] = {.lex_state = 27}, + [1107] = {.lex_state = 22}, + [1108] = {.lex_state = 2}, + [1109] = {.lex_state = 31}, + [1110] = {.lex_state = 22}, + [1111] = {.lex_state = 31}, + [1112] = {.lex_state = 31}, + [1113] = {.lex_state = 2}, + [1114] = {.lex_state = 23}, + [1115] = {.lex_state = 23}, + [1116] = {.lex_state = 23}, + [1117] = {.lex_state = 88}, + [1118] = {.lex_state = 23}, + [1119] = {.lex_state = 2}, + [1120] = {.lex_state = 22}, + [1121] = {.lex_state = 2}, + [1122] = {.lex_state = 23}, + [1123] = {.lex_state = 23}, + [1124] = {.lex_state = 23}, + [1125] = {.lex_state = 23}, + [1126] = {.lex_state = 23}, + [1127] = {.lex_state = 88}, + [1128] = {.lex_state = 22}, + [1129] = {.lex_state = 23}, + [1130] = {.lex_state = 31}, + [1131] = {.lex_state = 23}, + [1132] = {.lex_state = 23}, + [1133] = {.lex_state = 11}, + [1134] = {.lex_state = 2}, + [1135] = {.lex_state = 88}, + [1136] = {.lex_state = 88}, + [1137] = {.lex_state = 23}, + [1138] = {.lex_state = 88}, + [1139] = {.lex_state = 23}, + [1140] = {.lex_state = 18}, + [1141] = {.lex_state = 88}, + [1142] = {.lex_state = 88}, + [1143] = {.lex_state = 88}, + [1144] = {.lex_state = 88}, + [1145] = {.lex_state = 0}, + [1146] = {.lex_state = 23}, + [1147] = {.lex_state = 2}, + [1148] = {.lex_state = 0}, + [1149] = {.lex_state = 88}, + [1150] = {.lex_state = 31}, + [1151] = {.lex_state = 88}, + [1152] = {.lex_state = 23}, + [1153] = {.lex_state = 23}, + [1154] = {.lex_state = 88}, + [1155] = {.lex_state = 88}, + [1156] = {.lex_state = 22}, + [1157] = {.lex_state = 11}, + [1158] = {.lex_state = 88}, [1159] = {.lex_state = 0}, - [1160] = {.lex_state = 0}, - [1161] = {.lex_state = 22}, - [1162] = {.lex_state = 24}, - [1163] = {.lex_state = 24}, - [1164] = {.lex_state = 0}, - [1165] = {.lex_state = 0}, - [1166] = {.lex_state = 0}, - [1167] = {.lex_state = 0}, - [1168] = {.lex_state = 0}, - [1169] = {.lex_state = 90}, - [1170] = {.lex_state = 0}, - [1171] = {.lex_state = 2}, - [1172] = {.lex_state = 0}, - [1173] = {.lex_state = 0}, - [1174] = {.lex_state = 90}, - [1175] = {.lex_state = 24}, - [1176] = {.lex_state = 24}, - [1177] = {.lex_state = 24}, - [1178] = {.lex_state = 24}, - [1179] = {.lex_state = 24}, - [1180] = {.lex_state = 0}, - [1181] = {.lex_state = 0}, - [1182] = {.lex_state = 0}, - [1183] = {.lex_state = 0}, - [1184] = {.lex_state = 24}, - [1185] = {.lex_state = 0}, - [1186] = {.lex_state = 2}, - [1187] = {.lex_state = 0}, - [1188] = {.lex_state = 90}, - [1189] = {.lex_state = 0}, - [1190] = {.lex_state = 90}, - [1191] = {.lex_state = 90}, - [1192] = {.lex_state = 0}, - [1193] = {.lex_state = 24}, - [1194] = {.lex_state = 0}, + [1160] = {.lex_state = 23}, + [1161] = {.lex_state = 23}, + [1162] = {.lex_state = 88}, + [1163] = {.lex_state = 2}, + [1164] = {.lex_state = 31}, + [1165] = {.lex_state = 2}, + [1166] = {.lex_state = 23}, + [1167] = {.lex_state = 23}, + [1168] = {.lex_state = 23}, + [1169] = {.lex_state = 31}, + [1170] = {.lex_state = 31}, + [1171] = {.lex_state = 23}, + [1172] = {.lex_state = 2}, + [1173] = {.lex_state = 23}, + [1174] = {.lex_state = 2}, + [1175] = {.lex_state = 2}, + [1176] = {.lex_state = 23}, + [1177] = {.lex_state = 23}, + [1178] = {.lex_state = 2}, + [1179] = {.lex_state = 23}, + [1180] = {.lex_state = 23}, + [1181] = {.lex_state = 88}, + [1182] = {.lex_state = 23}, + [1183] = {.lex_state = 23}, + [1184] = {.lex_state = 0}, + [1185] = {.lex_state = 2}, + [1186] = {.lex_state = 23}, + [1187] = {.lex_state = 2}, + [1188] = {.lex_state = 23}, + [1189] = {.lex_state = 12}, + [1190] = {.lex_state = 2}, + [1191] = {.lex_state = 0}, + [1192] = {.lex_state = 23}, + [1193] = {.lex_state = 22}, + [1194] = {.lex_state = 23}, [1195] = {.lex_state = 0}, - [1196] = {.lex_state = 24}, - [1197] = {.lex_state = 90}, - [1198] = {.lex_state = 24}, - [1199] = {.lex_state = 32}, - [1200] = {.lex_state = 0}, - [1201] = {.lex_state = 90}, - [1202] = {.lex_state = 90}, - [1203] = {.lex_state = 24}, - [1204] = {.lex_state = 24}, - [1205] = {.lex_state = 32}, - [1206] = {.lex_state = 0}, - [1207] = {.lex_state = 0}, - [1208] = {.lex_state = 32}, - [1209] = {.lex_state = 0}, + [1196] = {.lex_state = 23}, + [1197] = {.lex_state = 0}, + [1198] = {.lex_state = 2}, + [1199] = {.lex_state = 23}, + [1200] = {.lex_state = 88}, + [1201] = {.lex_state = 23}, + [1202] = {.lex_state = 2}, + [1203] = {.lex_state = 88}, + [1204] = {.lex_state = 2}, + [1205] = {.lex_state = 2}, + [1206] = {.lex_state = 23}, + [1207] = {.lex_state = 23}, + [1208] = {.lex_state = 22}, + [1209] = {.lex_state = 23}, [1210] = {.lex_state = 0}, - [1211] = {.lex_state = 0}, - [1212] = {.lex_state = 32}, - [1213] = {.lex_state = 32}, - [1214] = {.lex_state = 0}, - [1215] = {.lex_state = 0}, - [1216] = {.lex_state = 0}, - [1217] = {.lex_state = 90}, - [1218] = {.lex_state = 32}, - [1219] = {.lex_state = 0}, + [1211] = {.lex_state = 88}, + [1212] = {.lex_state = 88}, + [1213] = {.lex_state = 88}, + [1214] = {.lex_state = 88}, + [1215] = {.lex_state = 2}, + [1216] = {.lex_state = 23}, + [1217] = {.lex_state = 23}, + [1218] = {.lex_state = 23}, + [1219] = {.lex_state = 23}, [1220] = {.lex_state = 0}, - [1221] = {.lex_state = 22}, - [1222] = {.lex_state = 33}, - [1223] = {.lex_state = 90}, - [1224] = {.lex_state = 0}, - [1225] = {.lex_state = 0}, - [1226] = {.lex_state = 0}, - [1227] = {.lex_state = 0}, - [1228] = {.lex_state = 0}, - [1229] = {.lex_state = 0}, - [1230] = {.lex_state = 33}, - [1231] = {.lex_state = 32}, - [1232] = {.lex_state = 32}, - [1233] = {.lex_state = 90}, - [1234] = {.lex_state = 0}, - [1235] = {.lex_state = 0}, - [1236] = {.lex_state = 0}, - [1237] = {.lex_state = 0}, - [1238] = {.lex_state = 0}, - [1239] = {.lex_state = 24}, - [1240] = {.lex_state = 90}, - [1241] = {.lex_state = 32}, - [1242] = {.lex_state = 32}, - [1243] = {.lex_state = 0}, - [1244] = {.lex_state = 32}, - [1245] = {.lex_state = 90}, - [1246] = {.lex_state = 90}, + [1221] = {.lex_state = 2}, + [1222] = {.lex_state = 23}, + [1223] = {.lex_state = 88}, + [1224] = {.lex_state = 23}, + [1225] = {.lex_state = 23}, + [1226] = {.lex_state = 2}, + [1227] = {.lex_state = 88}, + [1228] = {.lex_state = 23}, + [1229] = {.lex_state = 88}, + [1230] = {.lex_state = 0}, + [1231] = {.lex_state = 0}, + [1232] = {.lex_state = 31}, + [1233] = {.lex_state = 0}, + [1234] = {.lex_state = 31}, + [1235] = {.lex_state = 31}, + [1236] = {.lex_state = 31}, + [1237] = {.lex_state = 31}, + [1238] = {.lex_state = 31}, + [1239] = {.lex_state = 11}, + [1240] = {.lex_state = 0}, + [1241] = {.lex_state = 21}, + [1242] = {.lex_state = 0}, + [1243] = {.lex_state = 88}, + [1244] = {.lex_state = 0}, + [1245] = {.lex_state = 0}, + [1246] = {.lex_state = 0}, [1247] = {.lex_state = 0}, - [1248] = {.lex_state = 24}, - [1249] = {.lex_state = 0}, - [1250] = {.lex_state = 11}, - [1251] = {.lex_state = 0}, - [1252] = {.lex_state = 32}, - [1253] = {.lex_state = 0}, - [1254] = {.lex_state = 24}, - [1255] = {.lex_state = 32}, - [1256] = {.lex_state = 0}, + [1248] = {.lex_state = 0}, + [1249] = {.lex_state = 88}, + [1250] = {.lex_state = 23}, + [1251] = {.lex_state = 88}, + [1252] = {.lex_state = 0}, + [1253] = {.lex_state = 23}, + [1254] = {.lex_state = 0}, + [1255] = {.lex_state = 2}, + [1256] = {.lex_state = 88}, [1257] = {.lex_state = 0}, - [1258] = {.lex_state = 0}, - [1259] = {.lex_state = 24}, - [1260] = {.lex_state = 0}, - [1261] = {.lex_state = 0}, - [1262] = {.lex_state = 32}, - [1263] = {.lex_state = 32}, - [1264] = {.lex_state = 32}, - [1265] = {.lex_state = 32}, + [1258] = {.lex_state = 21}, + [1259] = {.lex_state = 0}, + [1260] = {.lex_state = 88}, + [1261] = {.lex_state = 88}, + [1262] = {.lex_state = 23}, + [1263] = {.lex_state = 0}, + [1264] = {.lex_state = 23}, + [1265] = {.lex_state = 23}, [1266] = {.lex_state = 0}, - [1267] = {.lex_state = 90}, + [1267] = {.lex_state = 0}, [1268] = {.lex_state = 0}, - [1269] = {.lex_state = 0}, - [1270] = {.lex_state = 0}, - [1271] = {.lex_state = 24}, - [1272] = {.lex_state = 0}, - [1273] = {.lex_state = 24}, - [1274] = {.lex_state = 24}, + [1269] = {.lex_state = 23}, + [1270] = {.lex_state = 88}, + [1271] = {.lex_state = 23}, + [1272] = {.lex_state = 23}, + [1273] = {.lex_state = 0}, + [1274] = {.lex_state = 23}, [1275] = {.lex_state = 0}, - [1276] = {.lex_state = 2}, - [1277] = {.lex_state = 24}, - [1278] = {.lex_state = 32}, - [1279] = {.lex_state = 22}, + [1276] = {.lex_state = 0}, + [1277] = {.lex_state = 0}, + [1278] = {.lex_state = 88}, + [1279] = {.lex_state = 88}, [1280] = {.lex_state = 0}, - [1281] = {.lex_state = 0}, - [1282] = {.lex_state = 2}, - [1283] = {.lex_state = 0}, + [1281] = {.lex_state = 31}, + [1282] = {.lex_state = 31}, + [1283] = {.lex_state = 31}, [1284] = {.lex_state = 0}, - [1285] = {.lex_state = 24}, + [1285] = {.lex_state = 0}, [1286] = {.lex_state = 0}, - [1287] = {.lex_state = 2}, + [1287] = {.lex_state = 0}, [1288] = {.lex_state = 0}, - [1289] = {.lex_state = 24}, - [1290] = {.lex_state = 24}, + [1289] = {.lex_state = 0}, + [1290] = {.lex_state = 2}, [1291] = {.lex_state = 0}, - [1292] = {.lex_state = 2}, - [1293] = {.lex_state = 24}, - [1294] = {.lex_state = 24}, - [1295] = {.lex_state = 24}, - [1296] = {.lex_state = 24}, - [1297] = {.lex_state = 24}, + [1292] = {.lex_state = 23}, + [1293] = {.lex_state = 0}, + [1294] = {.lex_state = 0}, + [1295] = {.lex_state = 23}, + [1296] = {.lex_state = 23}, + [1297] = {.lex_state = 23}, [1298] = {.lex_state = 0}, - [1299] = {.lex_state = 0}, - [1300] = {.lex_state = 24}, - [1301] = {.lex_state = 24}, + [1299] = {.lex_state = 23}, + [1300] = {.lex_state = 88}, + [1301] = {.lex_state = 23}, [1302] = {.lex_state = 0}, - [1303] = {.lex_state = 24}, + [1303] = {.lex_state = 88}, [1304] = {.lex_state = 0}, - [1305] = {.lex_state = 22}, - [1306] = {.lex_state = 24}, - [1307] = {.lex_state = 0}, - [1308] = {.lex_state = 24}, - [1309] = {.lex_state = 24}, - [1310] = {.lex_state = 0}, - [1311] = {.lex_state = 2}, - [1312] = {.lex_state = 0}, - [1313] = {.lex_state = 24}, - [1314] = {.lex_state = 24}, - [1315] = {.lex_state = 24}, - [1316] = {.lex_state = 24}, + [1305] = {.lex_state = 0}, + [1306] = {.lex_state = 0}, + [1307] = {.lex_state = 23}, + [1308] = {.lex_state = 0}, + [1309] = {.lex_state = 0}, + [1310] = {.lex_state = 23}, + [1311] = {.lex_state = 23}, + [1312] = {.lex_state = 88}, + [1313] = {.lex_state = 23}, + [1314] = {.lex_state = 88}, + [1315] = {.lex_state = 0}, + [1316] = {.lex_state = 88}, [1317] = {.lex_state = 0}, - [1318] = {.lex_state = 0}, - [1319] = {.lex_state = 90}, + [1318] = {.lex_state = 31}, + [1319] = {.lex_state = 32}, [1320] = {.lex_state = 0}, - [1321] = {.lex_state = 24}, - [1322] = {.lex_state = 24}, - [1323] = {.lex_state = 24}, - [1324] = {.lex_state = 2}, - [1325] = {.lex_state = 2}, - [1326] = {.lex_state = 2}, - [1327] = {.lex_state = 24}, - [1328] = {.lex_state = 24}, - [1329] = {.lex_state = 24}, - [1330] = {.lex_state = 0}, - [1331] = {.lex_state = 24}, - [1332] = {.lex_state = 0}, - [1333] = {.lex_state = 2}, + [1321] = {.lex_state = 0}, + [1322] = {.lex_state = 0}, + [1323] = {.lex_state = 23}, + [1324] = {.lex_state = 23}, + [1325] = {.lex_state = 31}, + [1326] = {.lex_state = 31}, + [1327] = {.lex_state = 31}, + [1328] = {.lex_state = 31}, + [1329] = {.lex_state = 0}, + [1330] = {.lex_state = 23}, + [1331] = {.lex_state = 23}, + [1332] = {.lex_state = 88}, + [1333] = {.lex_state = 23}, [1334] = {.lex_state = 0}, [1335] = {.lex_state = 0}, - [1336] = {.lex_state = 0}, - [1337] = {.lex_state = 2}, - [1338] = {.lex_state = 24}, - [1339] = {.lex_state = 24}, - [1340] = {.lex_state = 0}, - [1341] = {.lex_state = 2}, - [1342] = {.lex_state = 0}, - [1343] = {.lex_state = 24}, + [1336] = {.lex_state = 88}, + [1337] = {.lex_state = 0}, + [1338] = {.lex_state = 0}, + [1339] = {.lex_state = 0}, + [1340] = {.lex_state = 11}, + [1341] = {.lex_state = 88}, + [1342] = {.lex_state = 88}, + [1343] = {.lex_state = 32}, [1344] = {.lex_state = 0}, - [1345] = {.lex_state = 24}, - [1346] = {.lex_state = 24}, - [1347] = {.lex_state = 24}, - [1348] = {.lex_state = 24}, - [1349] = {.lex_state = 24}, - [1350] = {.lex_state = 0}, - [1351] = {.lex_state = 24}, + [1345] = {.lex_state = 0}, + [1346] = {.lex_state = 31}, + [1347] = {.lex_state = 0}, + [1348] = {.lex_state = 0}, + [1349] = {.lex_state = 88}, + [1350] = {.lex_state = 31}, + [1351] = {.lex_state = 31}, [1352] = {.lex_state = 0}, - [1353] = {.lex_state = 24}, + [1353] = {.lex_state = 0}, [1354] = {.lex_state = 0}, - [1355] = {.lex_state = 24}, - [1356] = {.lex_state = 24}, + [1355] = {.lex_state = 0}, + [1356] = {.lex_state = 0}, [1357] = {.lex_state = 0}, - [1358] = {.lex_state = 24}, + [1358] = {.lex_state = 0}, [1359] = {.lex_state = 0}, [1360] = {.lex_state = 0}, [1361] = {.lex_state = 0}, - [1362] = {.lex_state = 90}, - [1363] = {.lex_state = 90}, - [1364] = {.lex_state = 0}, + [1362] = {.lex_state = 0}, + [1363] = {.lex_state = 31}, + [1364] = {.lex_state = 31}, [1365] = {.lex_state = 0}, [1366] = {.lex_state = 0}, - [1367] = {.lex_state = 90}, - [1368] = {.lex_state = 0}, - [1369] = {.lex_state = 0}, - [1370] = {.lex_state = 0}, + [1367] = {.lex_state = 0}, + [1368] = {.lex_state = 88}, + [1369] = {.lex_state = 88}, + [1370] = {.lex_state = 2}, [1371] = {.lex_state = 0}, [1372] = {.lex_state = 0}, [1373] = {.lex_state = 0}, [1374] = {.lex_state = 0}, [1375] = {.lex_state = 0}, - [1376] = {.lex_state = 0}, - [1377] = {.lex_state = 0}, - [1378] = {.lex_state = 0}, - [1379] = {.lex_state = 0}, - [1380] = {.lex_state = 0}, + [1376] = {.lex_state = 23}, + [1377] = {.lex_state = 23}, + [1378] = {.lex_state = 2}, + [1379] = {.lex_state = 23}, + [1380] = {.lex_state = 23}, [1381] = {.lex_state = 0}, [1382] = {.lex_state = 0}, - [1383] = {.lex_state = 0}, - [1384] = {.lex_state = 0}, - [1385] = {.lex_state = 0}, + [1383] = {.lex_state = 23}, + [1384] = {.lex_state = 23}, + [1385] = {.lex_state = 2}, [1386] = {.lex_state = 0}, - [1387] = {.lex_state = 0}, - [1388] = {.lex_state = 90}, - [1389] = {.lex_state = 0}, + [1387] = {.lex_state = 2}, + [1388] = {.lex_state = 23}, + [1389] = {.lex_state = 23}, [1390] = {.lex_state = 0}, [1391] = {.lex_state = 0}, - [1392] = {.lex_state = 22}, + [1392] = {.lex_state = 0}, [1393] = {.lex_state = 0}, [1394] = {.lex_state = 0}, - [1395] = {.lex_state = 22}, + [1395] = {.lex_state = 23}, [1396] = {.lex_state = 0}, - [1397] = {.lex_state = 0}, - [1398] = {.lex_state = 0}, - [1399] = {.lex_state = 11}, + [1397] = {.lex_state = 88}, + [1398] = {.lex_state = 21}, + [1399] = {.lex_state = 0}, [1400] = {.lex_state = 0}, [1401] = {.lex_state = 0}, - [1402] = {.lex_state = 0}, + [1402] = {.lex_state = 23}, [1403] = {.lex_state = 0}, - [1404] = {.lex_state = 90}, + [1404] = {.lex_state = 23}, [1405] = {.lex_state = 0}, - [1406] = {.lex_state = 90}, - [1407] = {.lex_state = 0}, + [1406] = {.lex_state = 0}, + [1407] = {.lex_state = 23}, [1408] = {.lex_state = 0}, - [1409] = {.lex_state = 0}, - [1410] = {.lex_state = 0}, - [1411] = {.lex_state = 0}, - [1412] = {.lex_state = 2}, - [1413] = {.lex_state = 0}, - [1414] = {.lex_state = 0}, - [1415] = {.lex_state = 0}, - [1416] = {.lex_state = 0}, - [1417] = {.lex_state = 90}, + [1409] = {.lex_state = 2}, + [1410] = {.lex_state = 23}, + [1411] = {.lex_state = 23}, + [1412] = {.lex_state = 23}, + [1413] = {.lex_state = 2}, + [1414] = {.lex_state = 23}, + [1415] = {.lex_state = 2}, + [1416] = {.lex_state = 2}, + [1417] = {.lex_state = 23}, [1418] = {.lex_state = 0}, - [1419] = {.lex_state = 90}, - [1420] = {.lex_state = 0}, - [1421] = {.lex_state = 0}, + [1419] = {.lex_state = 23}, + [1420] = {.lex_state = 23}, + [1421] = {.lex_state = 23}, [1422] = {.lex_state = 0}, - [1423] = {.lex_state = 0}, + [1423] = {.lex_state = 21}, [1424] = {.lex_state = 0}, - [1425] = {.lex_state = 0}, - [1426] = {.lex_state = 0}, - [1427] = {.lex_state = 0}, - [1428] = {.lex_state = 90}, - [1429] = {.lex_state = 0}, - [1430] = {.lex_state = 0}, + [1425] = {.lex_state = 23}, + [1426] = {.lex_state = 23}, + [1427] = {.lex_state = 23}, + [1428] = {.lex_state = 2}, + [1429] = {.lex_state = 23}, + [1430] = {.lex_state = 23}, [1431] = {.lex_state = 0}, [1432] = {.lex_state = 0}, - [1433] = {.lex_state = 0}, - [1434] = {.lex_state = 90}, + [1433] = {.lex_state = 2}, + [1434] = {.lex_state = 0}, [1435] = {.lex_state = 0}, - [1436] = {.lex_state = 0}, + [1436] = {.lex_state = 23}, [1437] = {.lex_state = 0}, - [1438] = {.lex_state = 90}, - [1439] = {.lex_state = 0}, - [1440] = {.lex_state = 0}, + [1438] = {.lex_state = 0}, + [1439] = {.lex_state = 23}, + [1440] = {.lex_state = 23}, [1441] = {.lex_state = 0}, - [1442] = {.lex_state = 0}, - [1443] = {.lex_state = 0}, + [1442] = {.lex_state = 23}, + [1443] = {.lex_state = 23}, [1444] = {.lex_state = 0}, - [1445] = {.lex_state = 0}, - [1446] = {.lex_state = 90}, - [1447] = {.lex_state = 0}, - [1448] = {.lex_state = 0}, - [1449] = {.lex_state = 90}, - [1450] = {.lex_state = 90}, - [1451] = {.lex_state = 11}, - [1452] = {.lex_state = 11}, - [1453] = {.lex_state = 90}, - [1454] = {.lex_state = 0}, - [1455] = {.lex_state = 90}, + [1445] = {.lex_state = 23}, + [1446] = {.lex_state = 23}, + [1447] = {.lex_state = 23}, + [1448] = {.lex_state = 23}, + [1449] = {.lex_state = 0}, + [1450] = {.lex_state = 0}, + [1451] = {.lex_state = 23}, + [1452] = {.lex_state = 23}, + [1453] = {.lex_state = 23}, + [1454] = {.lex_state = 23}, + [1455] = {.lex_state = 2}, [1456] = {.lex_state = 0}, - [1457] = {.lex_state = 90}, - [1458] = {.lex_state = 22}, + [1457] = {.lex_state = 0}, + [1458] = {.lex_state = 0}, [1459] = {.lex_state = 0}, - [1460] = {.lex_state = 90}, - [1461] = {.lex_state = 90}, - [1462] = {.lex_state = 90}, - [1463] = {.lex_state = 0}, - [1464] = {.lex_state = 0}, + [1460] = {.lex_state = 0}, + [1461] = {.lex_state = 0}, + [1462] = {.lex_state = 0}, + [1463] = {.lex_state = 2}, + [1464] = {.lex_state = 88}, [1465] = {.lex_state = 0}, [1466] = {.lex_state = 0}, [1467] = {.lex_state = 0}, [1468] = {.lex_state = 0}, [1469] = {.lex_state = 0}, [1470] = {.lex_state = 0}, - [1471] = {.lex_state = 90}, + [1471] = {.lex_state = 88}, [1472] = {.lex_state = 0}, - [1473] = {.lex_state = 90}, - [1474] = {.lex_state = 22}, + [1473] = {.lex_state = 0}, + [1474] = {.lex_state = 0}, [1475] = {.lex_state = 0}, - [1476] = {.lex_state = 22}, + [1476] = {.lex_state = 0}, [1477] = {.lex_state = 0}, - [1478] = {.lex_state = 11}, - [1479] = {.lex_state = 22}, - [1480] = {.lex_state = 0}, - [1481] = {.lex_state = 0}, - [1482] = {.lex_state = 0}, + [1478] = {.lex_state = 0}, + [1479] = {.lex_state = 0}, + [1480] = {.lex_state = 11}, + [1481] = {.lex_state = 88}, + [1482] = {.lex_state = 11}, [1483] = {.lex_state = 0}, - [1484] = {.lex_state = 0}, - [1485] = {.lex_state = 22}, - [1486] = {.lex_state = 0}, - [1487] = {.lex_state = 22}, - [1488] = {.lex_state = 22}, + [1484] = {.lex_state = 11}, + [1485] = {.lex_state = 0}, + [1486] = {.lex_state = 88}, + [1487] = {.lex_state = 0}, + [1488] = {.lex_state = 88}, [1489] = {.lex_state = 0}, - [1490] = {.lex_state = 90}, - [1491] = {.lex_state = 90}, - [1492] = {.lex_state = 0}, - [1493] = {.lex_state = 90}, - [1494] = {.lex_state = 90}, + [1490] = {.lex_state = 0}, + [1491] = {.lex_state = 0}, + [1492] = {.lex_state = 88}, + [1493] = {.lex_state = 21}, + [1494] = {.lex_state = 0}, [1495] = {.lex_state = 0}, [1496] = {.lex_state = 0}, - [1497] = {.lex_state = 0}, - [1498] = {.lex_state = 90}, - [1499] = {.lex_state = 0}, - [1500] = {.lex_state = 0}, - [1501] = {.lex_state = 0}, + [1497] = {.lex_state = 88}, + [1498] = {.lex_state = 0}, + [1499] = {.lex_state = 88}, + [1500] = {.lex_state = 88}, + [1501] = {.lex_state = 88}, [1502] = {.lex_state = 0}, [1503] = {.lex_state = 0}, - [1504] = {.lex_state = 0}, + [1504] = {.lex_state = 21}, [1505] = {.lex_state = 0}, [1506] = {.lex_state = 0}, - [1507] = {.lex_state = 90}, + [1507] = {.lex_state = 0}, [1508] = {.lex_state = 0}, [1509] = {.lex_state = 0}, [1510] = {.lex_state = 0}, [1511] = {.lex_state = 0}, [1512] = {.lex_state = 0}, [1513] = {.lex_state = 0}, - [1514] = {.lex_state = 0}, - [1515] = {.lex_state = 90}, + [1514] = {.lex_state = 88}, + [1515] = {.lex_state = 0}, [1516] = {.lex_state = 0}, - [1517] = {.lex_state = 22}, + [1517] = {.lex_state = 0}, [1518] = {.lex_state = 0}, - [1519] = {.lex_state = 90}, + [1519] = {.lex_state = 88}, [1520] = {.lex_state = 0}, - [1521] = {.lex_state = 90}, + [1521] = {.lex_state = 0}, [1522] = {.lex_state = 0}, [1523] = {.lex_state = 0}, [1524] = {.lex_state = 0}, [1525] = {.lex_state = 0}, - [1526] = {.lex_state = 22}, - [1527] = {.lex_state = 90}, + [1526] = {.lex_state = 88}, + [1527] = {.lex_state = 0}, [1528] = {.lex_state = 0}, [1529] = {.lex_state = 0}, - [1530] = {.lex_state = 0}, - [1531] = {.lex_state = 90}, + [1530] = {.lex_state = 88}, + [1531] = {.lex_state = 88}, [1532] = {.lex_state = 0}, - [1533] = {.lex_state = 90}, - [1534] = {.lex_state = 90}, + [1533] = {.lex_state = 21}, + [1534] = {.lex_state = 0}, [1535] = {.lex_state = 0}, [1536] = {.lex_state = 0}, - [1537] = {.lex_state = 0}, - [1538] = {.lex_state = 0}, - [1539] = {.lex_state = 2}, - [1540] = {.lex_state = 0}, - [1541] = {.lex_state = 0}, + [1537] = {.lex_state = 88}, + [1538] = {.lex_state = 88}, + [1539] = {.lex_state = 88}, + [1540] = {.lex_state = 88}, + [1541] = {.lex_state = 88}, [1542] = {.lex_state = 0}, - [1543] = {.lex_state = 0}, + [1543] = {.lex_state = 88}, [1544] = {.lex_state = 0}, - [1545] = {.lex_state = 0}, + [1545] = {.lex_state = 88}, [1546] = {.lex_state = 0}, [1547] = {.lex_state = 0}, - [1548] = {.lex_state = 22}, + [1548] = {.lex_state = 0}, [1549] = {.lex_state = 0}, - [1550] = {.lex_state = 90}, + [1550] = {.lex_state = 88}, [1551] = {.lex_state = 0}, [1552] = {.lex_state = 0}, - [1553] = {.lex_state = 22}, - [1554] = {.lex_state = 22}, - [1555] = {.lex_state = 0}, - [1556] = {.lex_state = 0}, - [1557] = {.lex_state = 90}, + [1553] = {.lex_state = 0}, + [1554] = {.lex_state = 0}, + [1555] = {.lex_state = 88}, + [1556] = {.lex_state = 88}, + [1557] = {.lex_state = 0}, + [1558] = {.lex_state = 0}, + [1559] = {.lex_state = 88}, + [1560] = {.lex_state = 21}, + [1561] = {.lex_state = 88}, + [1562] = {.lex_state = 0}, + [1563] = {.lex_state = 0}, + [1564] = {.lex_state = 0}, + [1565] = {.lex_state = 0}, + [1566] = {.lex_state = 11}, + [1567] = {.lex_state = 21}, + [1568] = {.lex_state = 88}, + [1569] = {.lex_state = 0}, + [1570] = {.lex_state = 0}, + [1571] = {.lex_state = 0}, + [1572] = {.lex_state = 0}, + [1573] = {.lex_state = 21}, + [1574] = {.lex_state = 0}, + [1575] = {.lex_state = 21}, + [1576] = {.lex_state = 21}, + [1577] = {.lex_state = 0}, + [1578] = {.lex_state = 0}, + [1579] = {.lex_state = 0}, + [1580] = {.lex_state = 0}, + [1581] = {.lex_state = 88}, + [1582] = {.lex_state = 88}, + [1583] = {.lex_state = 0}, + [1584] = {.lex_state = 88}, + [1585] = {.lex_state = 0}, + [1586] = {.lex_state = 88}, + [1587] = {.lex_state = 0}, + [1588] = {.lex_state = 0}, + [1589] = {.lex_state = 21}, + [1590] = {.lex_state = 0}, + [1591] = {.lex_state = 0}, + [1592] = {.lex_state = 0}, + [1593] = {.lex_state = 88}, + [1594] = {.lex_state = 0}, + [1595] = {.lex_state = 0}, + [1596] = {.lex_state = 0}, + [1597] = {.lex_state = 0}, + [1598] = {.lex_state = 0}, + [1599] = {.lex_state = 0}, + [1600] = {.lex_state = 0}, + [1601] = {.lex_state = 0}, + [1602] = {.lex_state = 0}, + [1603] = {.lex_state = 0}, + [1604] = {.lex_state = 0}, + [1605] = {.lex_state = 0}, + [1606] = {.lex_state = 0}, + [1607] = {.lex_state = 0}, + [1608] = {.lex_state = 21}, + [1609] = {.lex_state = 0}, + [1610] = {.lex_state = 0}, + [1611] = {.lex_state = 0}, + [1612] = {.lex_state = 0}, + [1613] = {.lex_state = 0}, + [1614] = {.lex_state = 0}, + [1615] = {.lex_state = 0}, + [1616] = {.lex_state = 88}, + [1617] = {.lex_state = 21}, + [1618] = {.lex_state = 0}, + [1619] = {.lex_state = 0}, + [1620] = {.lex_state = 0}, + [1621] = {.lex_state = 88}, + [1622] = {.lex_state = 0}, + [1623] = {.lex_state = 0}, + [1624] = {.lex_state = 0}, + [1625] = {.lex_state = 0}, + [1626] = {.lex_state = 0}, + [1627] = {.lex_state = 0}, + [1628] = {.lex_state = 0}, + [1629] = {.lex_state = 0}, + [1630] = {.lex_state = 2}, + [1631] = {.lex_state = 0}, + [1632] = {.lex_state = 21}, + [1633] = {.lex_state = 0}, + [1634] = {.lex_state = 0}, + [1635] = {.lex_state = 0}, + [1636] = {.lex_state = 0}, + [1637] = {.lex_state = 0}, + [1638] = {.lex_state = 0}, + [1639] = {.lex_state = 0}, + [1640] = {.lex_state = 0}, + [1641] = {.lex_state = 0}, + [1642] = {.lex_state = 88}, + [1643] = {.lex_state = 88}, + [1644] = {.lex_state = 0}, + [1645] = {.lex_state = 0}, + [1646] = {.lex_state = 0}, + [1647] = {.lex_state = 0}, + [1648] = {.lex_state = 0}, + [1649] = {.lex_state = 0}, + [1650] = {.lex_state = 21}, + [1651] = {.lex_state = 0}, + [1652] = {.lex_state = 0}, + [1653] = {.lex_state = 21}, }; static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { @@ -9074,6 +9929,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(1), [anon_sym_LBRACE] = ACTIONS(1), [anon_sym_BANG] = ACTIONS(1), + [anon_sym_TILDE] = ACTIONS(1), [anon_sym_STAR] = ACTIONS(1), [anon_sym_SLASH] = ACTIONS(1), [anon_sym_PERCENT] = ACTIONS(1), @@ -9087,11 +9943,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(1), [anon_sym_AMP_AMP] = ACTIONS(1), [anon_sym_PIPE_PIPE] = ACTIONS(1), + [anon_sym_PIPE] = ACTIONS(1), + [anon_sym_CARET] = ACTIONS(1), + [anon_sym_AMP] = ACTIONS(1), + [anon_sym_LT_LT] = ACTIONS(1), + [anon_sym_GT_GT] = ACTIONS(1), [anon_sym_QMARK_QMARK] = ACTIONS(1), [anon_sym_DOT_DOT] = ACTIONS(1), [anon_sym_DOT_DOT_EQ] = ACTIONS(1), [anon_sym_QMARK] = ACTIONS(1), - [anon_sym_PIPE] = ACTIONS(1), [anon_sym_match] = ACTIONS(1), [anon_sym_EQ_GT] = ACTIONS(1), [anon_sym_SEMI] = ACTIONS(1), @@ -9110,6 +9970,18 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_Bool] = ACTIONS(1), [anon_sym_Nil] = ACTIONS(1), [anon_sym_Any] = ACTIONS(1), + [anon_sym_Number] = ACTIONS(1), + [anon_sym_f64] = ACTIONS(1), + [anon_sym_i8] = ACTIONS(1), + [anon_sym_i16] = ACTIONS(1), + [anon_sym_i32] = ACTIONS(1), + [anon_sym_i64] = ACTIONS(1), + [anon_sym_u8] = ACTIONS(1), + [anon_sym_u16] = ACTIONS(1), + [anon_sym_u32] = ACTIONS(1), + [anon_sym_u64] = ACTIONS(1), + [anon_sym_isize] = ACTIONS(1), + [anon_sym_usize] = ACTIONS(1), [anon_sym_List] = ACTIONS(1), [anon_sym_Map] = ACTIONS(1), [anon_sym_DASH_GT] = ACTIONS(1), @@ -9122,7 +9994,6 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DOLLAR] = ACTIONS(1), [anon_sym_COLON_COLON] = ACTIONS(1), [anon_sym_EQ] = ACTIONS(1), - [anon_sym_AMP] = ACTIONS(1), [anon_sym_let] = ACTIONS(1), [anon_sym_COLON_EQ] = ACTIONS(1), [anon_sym_PLUS_EQ] = ACTIONS(1), @@ -9147,67 +10018,67 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_catch] = ACTIONS(1), }, [STATE(1)] = { - [sym_program] = STATE(1442), - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(17), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(17), - [sym_import_statement] = STATE(554), - [sym_macro_export] = STATE(554), - [sym_macro_definition] = STATE(554), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(17), - [sym_define_statement] = STATE(17), - [sym_assignment_statement] = STATE(17), - [sym_compound_assignment_statement] = STATE(17), - [sym_if_statement] = STATE(17), - [sym_while_statement] = STATE(17), - [sym_for_statement] = STATE(17), - [sym_function_definition] = STATE(17), - [sym_struct_definition] = STATE(17), - [sym_type_alias_definition] = STATE(17), - [sym_trait_definition] = STATE(17), - [sym_impl_definition] = STATE(17), - [sym_return_statement] = STATE(17), - [sym_break_statement] = STATE(17), - [sym_continue_statement] = STATE(17), - [sym_expression_statement] = STATE(17), - [sym_go_statement] = STATE(17), - [sym_try_statement] = STATE(17), - [sym_block] = STATE(17), - [aux_sym_program_repeat1] = STATE(17), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_program] = STATE(1508), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(22), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(22), + [sym_import_statement] = STATE(667), + [sym_macro_export] = STATE(667), + [sym_macro_definition] = STATE(667), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(22), + [sym_define_statement] = STATE(22), + [sym_assignment_statement] = STATE(22), + [sym_compound_assignment_statement] = STATE(22), + [sym_if_statement] = STATE(22), + [sym_while_statement] = STATE(22), + [sym_for_statement] = STATE(22), + [sym_function_definition] = STATE(22), + [sym_struct_definition] = STATE(22), + [sym_type_alias_definition] = STATE(22), + [sym_trait_definition] = STATE(22), + [sym_impl_definition] = STATE(22), + [sym_return_statement] = STATE(22), + [sym_break_statement] = STATE(22), + [sym_continue_statement] = STATE(22), + [sym_expression_statement] = STATE(22), + [sym_go_statement] = STATE(22), + [sym_try_statement] = STATE(22), + [sym_block] = STATE(22), + [aux_sym_program_repeat1] = STATE(22), + [aux_sym_attributed_item_repeat1] = STATE(968), [ts_builtin_sym_end] = ACTIONS(5), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), @@ -9224,6 +10095,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_LBRACK] = ACTIONS(25), [anon_sym_LBRACE] = ACTIONS(27), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), [anon_sym_if] = ACTIONS(35), @@ -9251,65 +10123,65 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(2)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(532), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(532), - [sym_import_statement] = STATE(532), - [sym_macro_export] = STATE(532), - [sym_macro_definition] = STATE(532), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(532), - [sym_define_statement] = STATE(532), - [sym_assignment_statement] = STATE(532), - [sym_compound_assignment_statement] = STATE(532), - [sym_if_statement] = STATE(532), - [sym_while_statement] = STATE(532), - [sym_for_statement] = STATE(532), - [sym_function_definition] = STATE(532), - [sym_struct_definition] = STATE(532), - [sym_type_alias_definition] = STATE(532), - [sym_trait_definition] = STATE(532), - [sym_impl_definition] = STATE(532), - [sym_return_statement] = STATE(532), - [sym_break_statement] = STATE(532), - [sym_continue_statement] = STATE(532), - [sym_expression_statement] = STATE(532), - [sym_go_statement] = STATE(532), - [sym_try_statement] = STATE(532), - [sym_block] = STATE(532), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(645), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(645), + [sym_import_statement] = STATE(645), + [sym_macro_export] = STATE(645), + [sym_macro_definition] = STATE(645), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(645), + [sym_define_statement] = STATE(645), + [sym_assignment_statement] = STATE(645), + [sym_compound_assignment_statement] = STATE(645), + [sym_if_statement] = STATE(645), + [sym_while_statement] = STATE(645), + [sym_for_statement] = STATE(645), + [sym_function_definition] = STATE(645), + [sym_struct_definition] = STATE(645), + [sym_type_alias_definition] = STATE(645), + [sym_trait_definition] = STATE(645), + [sym_impl_definition] = STATE(645), + [sym_return_statement] = STATE(645), + [sym_break_statement] = STATE(645), + [sym_continue_statement] = STATE(645), + [sym_expression_statement] = STATE(645), + [sym_go_statement] = STATE(645), + [sym_try_statement] = STATE(645), + [sym_block] = STATE(645), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -9328,6 +10200,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(110), [anon_sym_LBRACE] = ACTIONS(115), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -9341,96 +10214,100 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(127), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(148), - [anon_sym_export] = ACTIONS(151), - [anon_sym_macro_rules] = ACTIONS(154), - [anon_sym_let] = ACTIONS(157), - [anon_sym_while] = ACTIONS(160), - [anon_sym_for] = ACTIONS(163), - [anon_sym_fn] = ACTIONS(166), - [anon_sym_struct] = ACTIONS(169), - [anon_sym_type] = ACTIONS(172), - [anon_sym_trait] = ACTIONS(175), - [anon_sym_impl] = ACTIONS(178), - [anon_sym_return] = ACTIONS(181), - [anon_sym_break] = ACTIONS(184), - [anon_sym_continue] = ACTIONS(187), - [anon_sym_go] = ACTIONS(190), - [anon_sym_try] = ACTIONS(193), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(130), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(151), + [anon_sym_export] = ACTIONS(154), + [anon_sym_macro_rules] = ACTIONS(157), + [anon_sym_let] = ACTIONS(160), + [anon_sym_while] = ACTIONS(163), + [anon_sym_for] = ACTIONS(166), + [anon_sym_fn] = ACTIONS(169), + [anon_sym_struct] = ACTIONS(172), + [anon_sym_type] = ACTIONS(175), + [anon_sym_trait] = ACTIONS(178), + [anon_sym_impl] = ACTIONS(181), + [anon_sym_return] = ACTIONS(184), + [anon_sym_break] = ACTIONS(187), + [anon_sym_continue] = ACTIONS(190), + [anon_sym_go] = ACTIONS(193), + [anon_sym_try] = ACTIONS(196), }, [STATE(3)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(547), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(547), - [sym_import_statement] = STATE(547), - [sym_macro_export] = STATE(547), - [sym_macro_definition] = STATE(547), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(547), - [sym_define_statement] = STATE(547), - [sym_assignment_statement] = STATE(547), - [sym_compound_assignment_statement] = STATE(547), - [sym_if_statement] = STATE(547), - [sym_while_statement] = STATE(547), - [sym_for_statement] = STATE(547), - [sym_function_definition] = STATE(547), - [sym_struct_definition] = STATE(547), - [sym_type_alias_definition] = STATE(547), - [sym_trait_definition] = STATE(547), - [sym_impl_definition] = STATE(547), - [sym_return_statement] = STATE(547), - [sym_break_statement] = STATE(547), - [sym_continue_statement] = STATE(547), - [sym_expression_statement] = STATE(547), - [sym_go_statement] = STATE(547), - [sym_try_statement] = STATE(547), - [sym_block] = STATE(547), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(630), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(630), + [sym_import_statement] = STATE(630), + [sym_macro_export] = STATE(630), + [sym_macro_definition] = STATE(630), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(630), + [sym_define_statement] = STATE(630), + [sym_assignment_statement] = STATE(630), + [sym_compound_assignment_statement] = STATE(630), + [sym_if_statement] = STATE(630), + [sym_while_statement] = STATE(630), + [sym_for_statement] = STATE(630), + [sym_function_definition] = STATE(630), + [sym_struct_definition] = STATE(630), + [sym_type_alias_definition] = STATE(630), + [sym_trait_definition] = STATE(630), + [sym_impl_definition] = STATE(630), + [sym_return_statement] = STATE(630), + [sym_break_statement] = STATE(630), + [sym_continue_statement] = STATE(630), + [sym_expression_statement] = STATE(630), + [sym_go_statement] = STATE(630), + [sym_try_statement] = STATE(630), + [sym_block] = STATE(630), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -9447,8 +10324,9 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_DOT] = ACTIONS(110), [anon_sym_LBRACK] = ACTIONS(112), [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(196), + [anon_sym_LBRACE] = ACTIONS(199), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -9462,96 +10340,100 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(199), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(202), - [anon_sym_export] = ACTIONS(205), - [anon_sym_macro_rules] = ACTIONS(208), - [anon_sym_let] = ACTIONS(211), - [anon_sym_while] = ACTIONS(214), - [anon_sym_for] = ACTIONS(217), - [anon_sym_fn] = ACTIONS(220), - [anon_sym_struct] = ACTIONS(223), - [anon_sym_type] = ACTIONS(226), - [anon_sym_trait] = ACTIONS(229), - [anon_sym_impl] = ACTIONS(232), - [anon_sym_return] = ACTIONS(235), - [anon_sym_break] = ACTIONS(238), - [anon_sym_continue] = ACTIONS(241), - [anon_sym_go] = ACTIONS(244), - [anon_sym_try] = ACTIONS(247), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(202), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(151), + [anon_sym_export] = ACTIONS(154), + [anon_sym_macro_rules] = ACTIONS(157), + [anon_sym_let] = ACTIONS(160), + [anon_sym_while] = ACTIONS(205), + [anon_sym_for] = ACTIONS(208), + [anon_sym_fn] = ACTIONS(169), + [anon_sym_struct] = ACTIONS(172), + [anon_sym_type] = ACTIONS(175), + [anon_sym_trait] = ACTIONS(178), + [anon_sym_impl] = ACTIONS(181), + [anon_sym_return] = ACTIONS(184), + [anon_sym_break] = ACTIONS(187), + [anon_sym_continue] = ACTIONS(190), + [anon_sym_go] = ACTIONS(193), + [anon_sym_try] = ACTIONS(196), }, [STATE(4)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(514), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(514), - [sym_import_statement] = STATE(514), - [sym_macro_export] = STATE(514), - [sym_macro_definition] = STATE(514), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(514), - [sym_define_statement] = STATE(514), - [sym_assignment_statement] = STATE(514), - [sym_compound_assignment_statement] = STATE(514), - [sym_if_statement] = STATE(514), - [sym_while_statement] = STATE(514), - [sym_for_statement] = STATE(514), - [sym_function_definition] = STATE(514), - [sym_struct_definition] = STATE(514), - [sym_type_alias_definition] = STATE(514), - [sym_trait_definition] = STATE(514), - [sym_impl_definition] = STATE(514), - [sym_return_statement] = STATE(514), - [sym_break_statement] = STATE(514), - [sym_continue_statement] = STATE(514), - [sym_expression_statement] = STATE(514), - [sym_go_statement] = STATE(514), - [sym_try_statement] = STATE(514), - [sym_block] = STATE(514), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(581), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(581), + [sym_import_statement] = STATE(581), + [sym_macro_export] = STATE(581), + [sym_macro_definition] = STATE(581), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(581), + [sym_define_statement] = STATE(581), + [sym_assignment_statement] = STATE(581), + [sym_compound_assignment_statement] = STATE(581), + [sym_if_statement] = STATE(581), + [sym_while_statement] = STATE(581), + [sym_for_statement] = STATE(581), + [sym_function_definition] = STATE(581), + [sym_struct_definition] = STATE(581), + [sym_type_alias_definition] = STATE(581), + [sym_trait_definition] = STATE(581), + [sym_impl_definition] = STATE(581), + [sym_return_statement] = STATE(581), + [sym_break_statement] = STATE(581), + [sym_continue_statement] = STATE(581), + [sym_expression_statement] = STATE(581), + [sym_go_statement] = STATE(581), + [sym_try_statement] = STATE(581), + [sym_block] = STATE(581), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -9568,8 +10450,9 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_DOT] = ACTIONS(110), [anon_sym_LBRACK] = ACTIONS(112), [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(196), + [anon_sym_LBRACE] = ACTIONS(199), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -9583,96 +10466,100 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(199), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(202), - [anon_sym_export] = ACTIONS(205), - [anon_sym_macro_rules] = ACTIONS(208), - [anon_sym_let] = ACTIONS(211), - [anon_sym_while] = ACTIONS(214), - [anon_sym_for] = ACTIONS(217), - [anon_sym_fn] = ACTIONS(220), - [anon_sym_struct] = ACTIONS(223), - [anon_sym_type] = ACTIONS(226), - [anon_sym_trait] = ACTIONS(229), - [anon_sym_impl] = ACTIONS(232), - [anon_sym_return] = ACTIONS(235), - [anon_sym_break] = ACTIONS(238), - [anon_sym_continue] = ACTIONS(241), - [anon_sym_go] = ACTIONS(244), - [anon_sym_try] = ACTIONS(247), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(202), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(151), + [anon_sym_export] = ACTIONS(154), + [anon_sym_macro_rules] = ACTIONS(157), + [anon_sym_let] = ACTIONS(160), + [anon_sym_while] = ACTIONS(205), + [anon_sym_for] = ACTIONS(208), + [anon_sym_fn] = ACTIONS(169), + [anon_sym_struct] = ACTIONS(172), + [anon_sym_type] = ACTIONS(175), + [anon_sym_trait] = ACTIONS(178), + [anon_sym_impl] = ACTIONS(181), + [anon_sym_return] = ACTIONS(184), + [anon_sym_break] = ACTIONS(187), + [anon_sym_continue] = ACTIONS(190), + [anon_sym_go] = ACTIONS(193), + [anon_sym_try] = ACTIONS(196), }, [STATE(5)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(480), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(480), - [sym_import_statement] = STATE(480), - [sym_macro_export] = STATE(480), - [sym_macro_definition] = STATE(480), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(480), - [sym_define_statement] = STATE(480), - [sym_assignment_statement] = STATE(480), - [sym_compound_assignment_statement] = STATE(480), - [sym_if_statement] = STATE(480), - [sym_while_statement] = STATE(480), - [sym_for_statement] = STATE(480), - [sym_function_definition] = STATE(480), - [sym_struct_definition] = STATE(480), - [sym_type_alias_definition] = STATE(480), - [sym_trait_definition] = STATE(480), - [sym_impl_definition] = STATE(480), - [sym_return_statement] = STATE(480), - [sym_break_statement] = STATE(480), - [sym_continue_statement] = STATE(480), - [sym_expression_statement] = STATE(480), - [sym_go_statement] = STATE(480), - [sym_try_statement] = STATE(480), - [sym_block] = STATE(480), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(636), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(636), + [sym_import_statement] = STATE(636), + [sym_macro_export] = STATE(636), + [sym_macro_definition] = STATE(636), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(636), + [sym_define_statement] = STATE(636), + [sym_assignment_statement] = STATE(636), + [sym_compound_assignment_statement] = STATE(636), + [sym_if_statement] = STATE(636), + [sym_while_statement] = STATE(636), + [sym_for_statement] = STATE(636), + [sym_function_definition] = STATE(636), + [sym_struct_definition] = STATE(636), + [sym_type_alias_definition] = STATE(636), + [sym_trait_definition] = STATE(636), + [sym_impl_definition] = STATE(636), + [sym_return_statement] = STATE(636), + [sym_break_statement] = STATE(636), + [sym_continue_statement] = STATE(636), + [sym_expression_statement] = STATE(636), + [sym_go_statement] = STATE(636), + [sym_try_statement] = STATE(636), + [sym_block] = STATE(636), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -9689,8 +10576,9 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_DOT] = ACTIONS(110), [anon_sym_LBRACK] = ACTIONS(112), [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(196), + [anon_sym_LBRACE] = ACTIONS(115), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -9704,96 +10592,100 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(199), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(202), - [anon_sym_export] = ACTIONS(205), - [anon_sym_macro_rules] = ACTIONS(208), - [anon_sym_let] = ACTIONS(211), - [anon_sym_while] = ACTIONS(214), - [anon_sym_for] = ACTIONS(217), - [anon_sym_fn] = ACTIONS(220), - [anon_sym_struct] = ACTIONS(223), - [anon_sym_type] = ACTIONS(226), - [anon_sym_trait] = ACTIONS(229), - [anon_sym_impl] = ACTIONS(232), - [anon_sym_return] = ACTIONS(235), - [anon_sym_break] = ACTIONS(238), - [anon_sym_continue] = ACTIONS(241), - [anon_sym_go] = ACTIONS(244), - [anon_sym_try] = ACTIONS(247), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(130), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(151), + [anon_sym_export] = ACTIONS(154), + [anon_sym_macro_rules] = ACTIONS(157), + [anon_sym_let] = ACTIONS(160), + [anon_sym_while] = ACTIONS(163), + [anon_sym_for] = ACTIONS(166), + [anon_sym_fn] = ACTIONS(169), + [anon_sym_struct] = ACTIONS(172), + [anon_sym_type] = ACTIONS(175), + [anon_sym_trait] = ACTIONS(178), + [anon_sym_impl] = ACTIONS(181), + [anon_sym_return] = ACTIONS(184), + [anon_sym_break] = ACTIONS(187), + [anon_sym_continue] = ACTIONS(190), + [anon_sym_go] = ACTIONS(193), + [anon_sym_try] = ACTIONS(196), }, [STATE(6)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(532), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(532), - [sym_import_statement] = STATE(532), - [sym_macro_export] = STATE(532), - [sym_macro_definition] = STATE(532), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(532), - [sym_define_statement] = STATE(532), - [sym_assignment_statement] = STATE(532), - [sym_compound_assignment_statement] = STATE(532), - [sym_if_statement] = STATE(532), - [sym_while_statement] = STATE(532), - [sym_for_statement] = STATE(532), - [sym_function_definition] = STATE(532), - [sym_struct_definition] = STATE(532), - [sym_type_alias_definition] = STATE(532), - [sym_trait_definition] = STATE(532), - [sym_impl_definition] = STATE(532), - [sym_return_statement] = STATE(532), - [sym_break_statement] = STATE(532), - [sym_continue_statement] = STATE(532), - [sym_expression_statement] = STATE(532), - [sym_go_statement] = STATE(532), - [sym_try_statement] = STATE(532), - [sym_block] = STATE(532), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(661), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(661), + [sym_import_statement] = STATE(661), + [sym_macro_export] = STATE(661), + [sym_macro_definition] = STATE(661), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(661), + [sym_define_statement] = STATE(661), + [sym_assignment_statement] = STATE(661), + [sym_compound_assignment_statement] = STATE(661), + [sym_if_statement] = STATE(661), + [sym_while_statement] = STATE(661), + [sym_for_statement] = STATE(661), + [sym_function_definition] = STATE(661), + [sym_struct_definition] = STATE(661), + [sym_type_alias_definition] = STATE(661), + [sym_trait_definition] = STATE(661), + [sym_impl_definition] = STATE(661), + [sym_return_statement] = STATE(661), + [sym_break_statement] = STATE(661), + [sym_continue_statement] = STATE(661), + [sym_expression_statement] = STATE(661), + [sym_go_statement] = STATE(661), + [sym_try_statement] = STATE(661), + [sym_block] = STATE(661), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -9810,8 +10702,9 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_DOT] = ACTIONS(110), [anon_sym_LBRACK] = ACTIONS(112), [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(250), + [anon_sym_LBRACE] = ACTIONS(211), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -9825,96 +10718,100 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(253), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(148), - [anon_sym_export] = ACTIONS(151), - [anon_sym_macro_rules] = ACTIONS(154), - [anon_sym_let] = ACTIONS(157), - [anon_sym_while] = ACTIONS(256), - [anon_sym_for] = ACTIONS(259), - [anon_sym_fn] = ACTIONS(166), - [anon_sym_struct] = ACTIONS(169), - [anon_sym_type] = ACTIONS(172), - [anon_sym_trait] = ACTIONS(175), - [anon_sym_impl] = ACTIONS(178), - [anon_sym_return] = ACTIONS(181), - [anon_sym_break] = ACTIONS(184), - [anon_sym_continue] = ACTIONS(187), - [anon_sym_go] = ACTIONS(190), - [anon_sym_try] = ACTIONS(193), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(214), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(217), + [anon_sym_export] = ACTIONS(220), + [anon_sym_macro_rules] = ACTIONS(223), + [anon_sym_let] = ACTIONS(226), + [anon_sym_while] = ACTIONS(229), + [anon_sym_for] = ACTIONS(232), + [anon_sym_fn] = ACTIONS(235), + [anon_sym_struct] = ACTIONS(238), + [anon_sym_type] = ACTIONS(241), + [anon_sym_trait] = ACTIONS(244), + [anon_sym_impl] = ACTIONS(247), + [anon_sym_return] = ACTIONS(250), + [anon_sym_break] = ACTIONS(253), + [anon_sym_continue] = ACTIONS(256), + [anon_sym_go] = ACTIONS(259), + [anon_sym_try] = ACTIONS(262), }, [STATE(7)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(480), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(480), - [sym_import_statement] = STATE(480), - [sym_macro_export] = STATE(480), - [sym_macro_definition] = STATE(480), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(480), - [sym_define_statement] = STATE(480), - [sym_assignment_statement] = STATE(480), - [sym_compound_assignment_statement] = STATE(480), - [sym_if_statement] = STATE(480), - [sym_while_statement] = STATE(480), - [sym_for_statement] = STATE(480), - [sym_function_definition] = STATE(480), - [sym_struct_definition] = STATE(480), - [sym_type_alias_definition] = STATE(480), - [sym_trait_definition] = STATE(480), - [sym_impl_definition] = STATE(480), - [sym_return_statement] = STATE(480), - [sym_break_statement] = STATE(480), - [sym_continue_statement] = STATE(480), - [sym_expression_statement] = STATE(480), - [sym_go_statement] = STATE(480), - [sym_try_statement] = STATE(480), - [sym_block] = STATE(480), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(581), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(581), + [sym_import_statement] = STATE(581), + [sym_macro_export] = STATE(581), + [sym_macro_definition] = STATE(581), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(581), + [sym_define_statement] = STATE(581), + [sym_assignment_statement] = STATE(581), + [sym_compound_assignment_statement] = STATE(581), + [sym_if_statement] = STATE(581), + [sym_while_statement] = STATE(581), + [sym_for_statement] = STATE(581), + [sym_function_definition] = STATE(581), + [sym_struct_definition] = STATE(581), + [sym_type_alias_definition] = STATE(581), + [sym_trait_definition] = STATE(581), + [sym_impl_definition] = STATE(581), + [sym_return_statement] = STATE(581), + [sym_break_statement] = STATE(581), + [sym_continue_statement] = STATE(581), + [sym_expression_statement] = STATE(581), + [sym_go_statement] = STATE(581), + [sym_try_statement] = STATE(581), + [sym_block] = STATE(581), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -9931,8 +10828,9 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_DOT] = ACTIONS(110), [anon_sym_LBRACK] = ACTIONS(112), [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(262), + [anon_sym_LBRACE] = ACTIONS(115), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -9946,96 +10844,100 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(265), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(202), - [anon_sym_export] = ACTIONS(205), - [anon_sym_macro_rules] = ACTIONS(208), - [anon_sym_let] = ACTIONS(211), - [anon_sym_while] = ACTIONS(268), - [anon_sym_for] = ACTIONS(271), - [anon_sym_fn] = ACTIONS(220), - [anon_sym_struct] = ACTIONS(223), - [anon_sym_type] = ACTIONS(226), - [anon_sym_trait] = ACTIONS(229), - [anon_sym_impl] = ACTIONS(232), - [anon_sym_return] = ACTIONS(235), - [anon_sym_break] = ACTIONS(238), - [anon_sym_continue] = ACTIONS(241), - [anon_sym_go] = ACTIONS(244), - [anon_sym_try] = ACTIONS(247), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(130), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(151), + [anon_sym_export] = ACTIONS(154), + [anon_sym_macro_rules] = ACTIONS(157), + [anon_sym_let] = ACTIONS(160), + [anon_sym_while] = ACTIONS(163), + [anon_sym_for] = ACTIONS(166), + [anon_sym_fn] = ACTIONS(169), + [anon_sym_struct] = ACTIONS(172), + [anon_sym_type] = ACTIONS(175), + [anon_sym_trait] = ACTIONS(178), + [anon_sym_impl] = ACTIONS(181), + [anon_sym_return] = ACTIONS(184), + [anon_sym_break] = ACTIONS(187), + [anon_sym_continue] = ACTIONS(190), + [anon_sym_go] = ACTIONS(193), + [anon_sym_try] = ACTIONS(196), }, [STATE(8)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(507), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(507), - [sym_import_statement] = STATE(507), - [sym_macro_export] = STATE(507), - [sym_macro_definition] = STATE(507), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(507), - [sym_define_statement] = STATE(507), - [sym_assignment_statement] = STATE(507), - [sym_compound_assignment_statement] = STATE(507), - [sym_if_statement] = STATE(507), - [sym_while_statement] = STATE(507), - [sym_for_statement] = STATE(507), - [sym_function_definition] = STATE(507), - [sym_struct_definition] = STATE(507), - [sym_type_alias_definition] = STATE(507), - [sym_trait_definition] = STATE(507), - [sym_impl_definition] = STATE(507), - [sym_return_statement] = STATE(507), - [sym_break_statement] = STATE(507), - [sym_continue_statement] = STATE(507), - [sym_expression_statement] = STATE(507), - [sym_go_statement] = STATE(507), - [sym_try_statement] = STATE(507), - [sym_block] = STATE(507), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(661), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(661), + [sym_import_statement] = STATE(661), + [sym_macro_export] = STATE(661), + [sym_macro_definition] = STATE(661), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(661), + [sym_define_statement] = STATE(661), + [sym_assignment_statement] = STATE(661), + [sym_compound_assignment_statement] = STATE(661), + [sym_if_statement] = STATE(661), + [sym_while_statement] = STATE(661), + [sym_for_statement] = STATE(661), + [sym_function_definition] = STATE(661), + [sym_struct_definition] = STATE(661), + [sym_type_alias_definition] = STATE(661), + [sym_trait_definition] = STATE(661), + [sym_impl_definition] = STATE(661), + [sym_return_statement] = STATE(661), + [sym_break_statement] = STATE(661), + [sym_continue_statement] = STATE(661), + [sym_expression_statement] = STATE(661), + [sym_go_statement] = STATE(661), + [sym_try_statement] = STATE(661), + [sym_block] = STATE(661), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -10052,8 +10954,9 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_DOT] = ACTIONS(110), [anon_sym_LBRACK] = ACTIONS(112), [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(262), + [anon_sym_LBRACE] = ACTIONS(265), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -10067,96 +10970,100 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(265), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(202), - [anon_sym_export] = ACTIONS(205), - [anon_sym_macro_rules] = ACTIONS(208), - [anon_sym_let] = ACTIONS(211), - [anon_sym_while] = ACTIONS(268), - [anon_sym_for] = ACTIONS(271), - [anon_sym_fn] = ACTIONS(220), - [anon_sym_struct] = ACTIONS(223), - [anon_sym_type] = ACTIONS(226), - [anon_sym_trait] = ACTIONS(229), - [anon_sym_impl] = ACTIONS(232), - [anon_sym_return] = ACTIONS(235), - [anon_sym_break] = ACTIONS(238), - [anon_sym_continue] = ACTIONS(241), - [anon_sym_go] = ACTIONS(244), - [anon_sym_try] = ACTIONS(247), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(268), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(217), + [anon_sym_export] = ACTIONS(220), + [anon_sym_macro_rules] = ACTIONS(223), + [anon_sym_let] = ACTIONS(226), + [anon_sym_while] = ACTIONS(271), + [anon_sym_for] = ACTIONS(274), + [anon_sym_fn] = ACTIONS(235), + [anon_sym_struct] = ACTIONS(238), + [anon_sym_type] = ACTIONS(241), + [anon_sym_trait] = ACTIONS(244), + [anon_sym_impl] = ACTIONS(247), + [anon_sym_return] = ACTIONS(250), + [anon_sym_break] = ACTIONS(253), + [anon_sym_continue] = ACTIONS(256), + [anon_sym_go] = ACTIONS(259), + [anon_sym_try] = ACTIONS(262), }, [STATE(9)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(521), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(521), - [sym_import_statement] = STATE(521), - [sym_macro_export] = STATE(521), - [sym_macro_definition] = STATE(521), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(521), - [sym_define_statement] = STATE(521), - [sym_assignment_statement] = STATE(521), - [sym_compound_assignment_statement] = STATE(521), - [sym_if_statement] = STATE(521), - [sym_while_statement] = STATE(521), - [sym_for_statement] = STATE(521), - [sym_function_definition] = STATE(521), - [sym_struct_definition] = STATE(521), - [sym_type_alias_definition] = STATE(521), - [sym_trait_definition] = STATE(521), - [sym_impl_definition] = STATE(521), - [sym_return_statement] = STATE(521), - [sym_break_statement] = STATE(521), - [sym_continue_statement] = STATE(521), - [sym_expression_statement] = STATE(521), - [sym_go_statement] = STATE(521), - [sym_try_statement] = STATE(521), - [sym_block] = STATE(521), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(635), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(635), + [sym_import_statement] = STATE(635), + [sym_macro_export] = STATE(635), + [sym_macro_definition] = STATE(635), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(635), + [sym_define_statement] = STATE(635), + [sym_assignment_statement] = STATE(635), + [sym_compound_assignment_statement] = STATE(635), + [sym_if_statement] = STATE(635), + [sym_while_statement] = STATE(635), + [sym_for_statement] = STATE(635), + [sym_function_definition] = STATE(635), + [sym_struct_definition] = STATE(635), + [sym_type_alias_definition] = STATE(635), + [sym_trait_definition] = STATE(635), + [sym_impl_definition] = STATE(635), + [sym_return_statement] = STATE(635), + [sym_break_statement] = STATE(635), + [sym_continue_statement] = STATE(635), + [sym_expression_statement] = STATE(635), + [sym_go_statement] = STATE(635), + [sym_try_statement] = STATE(635), + [sym_block] = STATE(635), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(81), @@ -10173,8 +11080,9 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_DOT] = ACTIONS(110), [anon_sym_LBRACK] = ACTIONS(112), [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(262), + [anon_sym_LBRACE] = ACTIONS(199), [anon_sym_BANG] = ACTIONS(118), + [anon_sym_TILDE] = ACTIONS(121), [anon_sym_STAR] = ACTIONS(110), [anon_sym_SLASH] = ACTIONS(108), [anon_sym_PERCENT] = ACTIONS(110), @@ -10188,98 +11096,102 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(110), [anon_sym_AMP_AMP] = ACTIONS(110), [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(124), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), [anon_sym_QMARK_QMARK] = ACTIONS(110), [anon_sym_DOT_DOT] = ACTIONS(108), [anon_sym_DOT_DOT_EQ] = ACTIONS(110), [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(121), - [anon_sym_match] = ACTIONS(124), - [anon_sym_if] = ACTIONS(265), - [anon_sym_spawn] = ACTIONS(130), - [anon_sym_chan] = ACTIONS(133), - [anon_sym_send] = ACTIONS(136), - [anon_sym_recv] = ACTIONS(139), - [anon_sym_select] = ACTIONS(142), - [anon_sym_POUND] = ACTIONS(145), - [anon_sym_use] = ACTIONS(202), - [anon_sym_export] = ACTIONS(205), - [anon_sym_macro_rules] = ACTIONS(208), - [anon_sym_let] = ACTIONS(211), - [anon_sym_while] = ACTIONS(268), - [anon_sym_for] = ACTIONS(271), - [anon_sym_fn] = ACTIONS(220), - [anon_sym_struct] = ACTIONS(223), - [anon_sym_type] = ACTIONS(226), - [anon_sym_trait] = ACTIONS(229), - [anon_sym_impl] = ACTIONS(232), - [anon_sym_return] = ACTIONS(235), - [anon_sym_break] = ACTIONS(238), - [anon_sym_continue] = ACTIONS(241), - [anon_sym_go] = ACTIONS(244), - [anon_sym_try] = ACTIONS(247), + [anon_sym_match] = ACTIONS(127), + [anon_sym_if] = ACTIONS(202), + [anon_sym_spawn] = ACTIONS(133), + [anon_sym_chan] = ACTIONS(136), + [anon_sym_send] = ACTIONS(139), + [anon_sym_recv] = ACTIONS(142), + [anon_sym_select] = ACTIONS(145), + [anon_sym_POUND] = ACTIONS(148), + [anon_sym_use] = ACTIONS(151), + [anon_sym_export] = ACTIONS(154), + [anon_sym_macro_rules] = ACTIONS(157), + [anon_sym_let] = ACTIONS(160), + [anon_sym_while] = ACTIONS(205), + [anon_sym_for] = ACTIONS(208), + [anon_sym_fn] = ACTIONS(169), + [anon_sym_struct] = ACTIONS(172), + [anon_sym_type] = ACTIONS(175), + [anon_sym_trait] = ACTIONS(178), + [anon_sym_impl] = ACTIONS(181), + [anon_sym_return] = ACTIONS(184), + [anon_sym_break] = ACTIONS(187), + [anon_sym_continue] = ACTIONS(190), + [anon_sym_go] = ACTIONS(193), + [anon_sym_try] = ACTIONS(196), }, [STATE(10)] = { - [sym_identifier] = STATE(661), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1319), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_map_entry] = STATE(1137), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1319), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(15), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(15), - [sym_import_statement] = STATE(15), - [sym_macro_export] = STATE(15), - [sym_macro_definition] = STATE(15), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(15), - [sym_define_statement] = STATE(15), - [sym_assignment_statement] = STATE(15), - [sym_compound_assignment_statement] = STATE(15), - [sym_if_statement] = STATE(15), - [sym_while_statement] = STATE(15), - [sym_for_statement] = STATE(15), - [sym_function_definition] = STATE(15), - [sym_struct_definition] = STATE(15), - [sym_type_alias_definition] = STATE(15), - [sym_trait_definition] = STATE(15), - [sym_impl_definition] = STATE(15), - [sym_return_statement] = STATE(15), - [sym_break_statement] = STATE(15), - [sym_continue_statement] = STATE(15), - [sym_expression_statement] = STATE(15), - [sym_go_statement] = STATE(15), - [sym_try_statement] = STATE(15), - [sym_block] = STATE(15), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(15), + [sym_identifier] = STATE(692), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1397), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_map_entry] = STATE(1306), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1397), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(19), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(19), + [sym_import_statement] = STATE(19), + [sym_macro_export] = STATE(19), + [sym_macro_definition] = STATE(19), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(19), + [sym_define_statement] = STATE(19), + [sym_assignment_statement] = STATE(19), + [sym_compound_assignment_statement] = STATE(19), + [sym_if_statement] = STATE(19), + [sym_while_statement] = STATE(19), + [sym_for_statement] = STATE(19), + [sym_function_definition] = STATE(19), + [sym_struct_definition] = STATE(19), + [sym_type_alias_definition] = STATE(19), + [sym_trait_definition] = STATE(19), + [sym_impl_definition] = STATE(19), + [sym_return_statement] = STATE(19), + [sym_break_statement] = STATE(19), + [sym_continue_statement] = STATE(19), + [sym_expression_statement] = STATE(19), + [sym_go_statement] = STATE(19), + [sym_try_statement] = STATE(19), + [sym_block] = STATE(19), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(19), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -10291,14 +11203,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(274), + [anon_sym_RBRACE] = ACTIONS(277), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -10309,8 +11222,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -10323,67 +11236,67 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(11)] = { - [sym_identifier] = STATE(661), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1319), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_map_entry] = STATE(1137), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1319), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(15), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(15), - [sym_import_statement] = STATE(15), - [sym_macro_export] = STATE(15), - [sym_macro_definition] = STATE(15), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(15), - [sym_define_statement] = STATE(15), - [sym_assignment_statement] = STATE(15), - [sym_compound_assignment_statement] = STATE(15), - [sym_if_statement] = STATE(15), - [sym_while_statement] = STATE(15), - [sym_for_statement] = STATE(15), - [sym_function_definition] = STATE(15), - [sym_struct_definition] = STATE(15), - [sym_type_alias_definition] = STATE(15), - [sym_trait_definition] = STATE(15), - [sym_impl_definition] = STATE(15), - [sym_return_statement] = STATE(15), - [sym_break_statement] = STATE(15), - [sym_continue_statement] = STATE(15), - [sym_expression_statement] = STATE(15), - [sym_go_statement] = STATE(15), - [sym_try_statement] = STATE(15), - [sym_block] = STATE(15), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(15), + [sym_identifier] = STATE(692), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1397), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_map_entry] = STATE(1306), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1397), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(19), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(19), + [sym_import_statement] = STATE(19), + [sym_macro_export] = STATE(19), + [sym_macro_definition] = STATE(19), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(19), + [sym_define_statement] = STATE(19), + [sym_assignment_statement] = STATE(19), + [sym_compound_assignment_statement] = STATE(19), + [sym_if_statement] = STATE(19), + [sym_while_statement] = STATE(19), + [sym_for_statement] = STATE(19), + [sym_function_definition] = STATE(19), + [sym_struct_definition] = STATE(19), + [sym_type_alias_definition] = STATE(19), + [sym_trait_definition] = STATE(19), + [sym_impl_definition] = STATE(19), + [sym_return_statement] = STATE(19), + [sym_break_statement] = STATE(19), + [sym_continue_statement] = STATE(19), + [sym_expression_statement] = STATE(19), + [sym_go_statement] = STATE(19), + [sym_try_statement] = STATE(19), + [sym_block] = STATE(19), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(19), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -10395,14 +11308,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(284), + [anon_sym_RBRACE] = ACTIONS(287), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -10413,8 +11327,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -10427,67 +11341,67 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(12)] = { - [sym_identifier] = STATE(661), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1319), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_map_entry] = STATE(1137), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1319), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(21), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(21), - [sym_import_statement] = STATE(21), - [sym_macro_export] = STATE(21), - [sym_macro_definition] = STATE(21), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(21), - [sym_define_statement] = STATE(21), - [sym_assignment_statement] = STATE(21), - [sym_compound_assignment_statement] = STATE(21), - [sym_if_statement] = STATE(21), - [sym_while_statement] = STATE(21), - [sym_for_statement] = STATE(21), - [sym_function_definition] = STATE(21), - [sym_struct_definition] = STATE(21), - [sym_type_alias_definition] = STATE(21), - [sym_trait_definition] = STATE(21), - [sym_impl_definition] = STATE(21), - [sym_return_statement] = STATE(21), - [sym_break_statement] = STATE(21), - [sym_continue_statement] = STATE(21), - [sym_expression_statement] = STATE(21), - [sym_go_statement] = STATE(21), - [sym_try_statement] = STATE(21), - [sym_block] = STATE(21), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(21), + [sym_identifier] = STATE(692), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1397), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_map_entry] = STATE(1306), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1397), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(15), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(15), + [sym_import_statement] = STATE(15), + [sym_macro_export] = STATE(15), + [sym_macro_definition] = STATE(15), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(15), + [sym_define_statement] = STATE(15), + [sym_assignment_statement] = STATE(15), + [sym_compound_assignment_statement] = STATE(15), + [sym_if_statement] = STATE(15), + [sym_while_statement] = STATE(15), + [sym_for_statement] = STATE(15), + [sym_function_definition] = STATE(15), + [sym_struct_definition] = STATE(15), + [sym_type_alias_definition] = STATE(15), + [sym_trait_definition] = STATE(15), + [sym_impl_definition] = STATE(15), + [sym_return_statement] = STATE(15), + [sym_break_statement] = STATE(15), + [sym_continue_statement] = STATE(15), + [sym_expression_statement] = STATE(15), + [sym_go_statement] = STATE(15), + [sym_try_statement] = STATE(15), + [sym_block] = STATE(15), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(15), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -10499,14 +11413,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(286), + [anon_sym_RBRACE] = ACTIONS(289), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -10517,8 +11432,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -10531,46 +11446,150 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(13)] = { - [sym_identifier] = STATE(661), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1319), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_map_entry] = STATE(1137), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1319), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), + [sym_identifier] = STATE(692), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1397), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_map_entry] = STATE(1306), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1397), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(15), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(15), + [sym_import_statement] = STATE(15), + [sym_macro_export] = STATE(15), + [sym_macro_definition] = STATE(15), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(15), + [sym_define_statement] = STATE(15), + [sym_assignment_statement] = STATE(15), + [sym_compound_assignment_statement] = STATE(15), + [sym_if_statement] = STATE(15), + [sym_while_statement] = STATE(15), + [sym_for_statement] = STATE(15), + [sym_function_definition] = STATE(15), + [sym_struct_definition] = STATE(15), + [sym_type_alias_definition] = STATE(15), + [sym_trait_definition] = STATE(15), + [sym_impl_definition] = STATE(15), + [sym_return_statement] = STATE(15), + [sym_break_statement] = STATE(15), + [sym_continue_statement] = STATE(15), + [sym_expression_statement] = STATE(15), + [sym_go_statement] = STATE(15), + [sym_try_statement] = STATE(15), + [sym_block] = STATE(15), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(15), + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(7), + [sym_integer_literal] = ACTIONS(9), + [sym_float_literal] = ACTIONS(11), + [anon_sym_true] = ACTIONS(13), + [anon_sym_false] = ACTIONS(13), + [anon_sym_nil] = ACTIONS(15), + [anon_sym_DQUOTE] = ACTIONS(17), + [anon_sym_SQUOTE] = ACTIONS(19), + [sym_raw_string] = ACTIONS(21), + [anon_sym_RBRACE] = ACTIONS(291), + [anon_sym_LPAREN] = ACTIONS(23), + [anon_sym_LBRACK] = ACTIONS(25), + [anon_sym_LBRACE] = ACTIONS(279), + [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), + [anon_sym_PIPE] = ACTIONS(31), + [anon_sym_match] = ACTIONS(33), + [anon_sym_if] = ACTIONS(281), + [anon_sym_spawn] = ACTIONS(37), + [anon_sym_chan] = ACTIONS(39), + [anon_sym_send] = ACTIONS(41), + [anon_sym_recv] = ACTIONS(43), + [anon_sym_select] = ACTIONS(45), + [anon_sym_POUND] = ACTIONS(47), + [anon_sym_use] = ACTIONS(49), + [anon_sym_export] = ACTIONS(51), + [anon_sym_macro_rules] = ACTIONS(53), + [anon_sym_let] = ACTIONS(55), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), + [anon_sym_fn] = ACTIONS(61), + [anon_sym_struct] = ACTIONS(63), + [anon_sym_type] = ACTIONS(65), + [anon_sym_trait] = ACTIONS(67), + [anon_sym_impl] = ACTIONS(69), + [anon_sym_return] = ACTIONS(71), + [anon_sym_break] = ACTIONS(73), + [anon_sym_continue] = ACTIONS(75), + [anon_sym_go] = ACTIONS(77), + [anon_sym_try] = ACTIONS(79), + }, + [STATE(14)] = { + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), [sym__statement] = STATE(21), - [sym_attribute] = STATE(873), + [sym_attribute] = STATE(968), [sym_attributed_item] = STATE(21), [sym_import_statement] = STATE(21), [sym_macro_export] = STATE(21), [sym_macro_definition] = STATE(21), - [sym_macro_invocation] = STATE(678), + [sym_macro_invocation] = STATE(731), [sym_let_statement] = STATE(21), [sym_define_statement] = STATE(21), [sym_assignment_statement] = STATE(21), @@ -10590,7 +11609,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_go_statement] = STATE(21), [sym_try_statement] = STATE(21), [sym_block] = STATE(21), - [aux_sym_attributed_item_repeat1] = STATE(873), + [aux_sym_attributed_item_repeat1] = STATE(968), [aux_sym_block_repeat1] = STATE(21), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), @@ -10603,14 +11622,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(288), + [anon_sym_RBRACE] = ACTIONS(293), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -10621,8 +11641,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -10634,67 +11654,67 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(77), [anon_sym_try] = ACTIONS(79), }, - [STATE(14)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(19), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(19), - [sym_import_statement] = STATE(19), - [sym_macro_export] = STATE(19), - [sym_macro_definition] = STATE(19), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(19), - [sym_define_statement] = STATE(19), - [sym_assignment_statement] = STATE(19), - [sym_compound_assignment_statement] = STATE(19), - [sym_if_statement] = STATE(19), - [sym_while_statement] = STATE(19), - [sym_for_statement] = STATE(19), - [sym_function_definition] = STATE(19), - [sym_struct_definition] = STATE(19), - [sym_type_alias_definition] = STATE(19), - [sym_trait_definition] = STATE(19), - [sym_impl_definition] = STATE(19), - [sym_return_statement] = STATE(19), - [sym_break_statement] = STATE(19), - [sym_continue_statement] = STATE(19), - [sym_expression_statement] = STATE(19), - [sym_go_statement] = STATE(19), - [sym_try_statement] = STATE(19), - [sym_block] = STATE(19), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(19), + [STATE(15)] = { + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(21), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(21), + [sym_import_statement] = STATE(21), + [sym_macro_export] = STATE(21), + [sym_macro_definition] = STATE(21), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(21), + [sym_define_statement] = STATE(21), + [sym_assignment_statement] = STATE(21), + [sym_compound_assignment_statement] = STATE(21), + [sym_if_statement] = STATE(21), + [sym_while_statement] = STATE(21), + [sym_for_statement] = STATE(21), + [sym_function_definition] = STATE(21), + [sym_struct_definition] = STATE(21), + [sym_type_alias_definition] = STATE(21), + [sym_trait_definition] = STATE(21), + [sym_impl_definition] = STATE(21), + [sym_return_statement] = STATE(21), + [sym_break_statement] = STATE(21), + [sym_continue_statement] = STATE(21), + [sym_expression_statement] = STATE(21), + [sym_go_statement] = STATE(21), + [sym_try_statement] = STATE(21), + [sym_block] = STATE(21), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(21), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -10706,14 +11726,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(290), + [anon_sym_RBRACE] = ACTIONS(295), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -10724,8 +11745,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -10737,67 +11758,67 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(77), [anon_sym_try] = ACTIONS(79), }, - [STATE(15)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(16), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(16), - [sym_import_statement] = STATE(16), - [sym_macro_export] = STATE(16), - [sym_macro_definition] = STATE(16), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(16), - [sym_define_statement] = STATE(16), - [sym_assignment_statement] = STATE(16), - [sym_compound_assignment_statement] = STATE(16), - [sym_if_statement] = STATE(16), - [sym_while_statement] = STATE(16), - [sym_for_statement] = STATE(16), - [sym_function_definition] = STATE(16), - [sym_struct_definition] = STATE(16), - [sym_type_alias_definition] = STATE(16), - [sym_trait_definition] = STATE(16), - [sym_impl_definition] = STATE(16), - [sym_return_statement] = STATE(16), - [sym_break_statement] = STATE(16), - [sym_continue_statement] = STATE(16), - [sym_expression_statement] = STATE(16), - [sym_go_statement] = STATE(16), - [sym_try_statement] = STATE(16), - [sym_block] = STATE(16), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(16), + [STATE(16)] = { + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(15), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(15), + [sym_import_statement] = STATE(15), + [sym_macro_export] = STATE(15), + [sym_macro_definition] = STATE(15), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(15), + [sym_define_statement] = STATE(15), + [sym_assignment_statement] = STATE(15), + [sym_compound_assignment_statement] = STATE(15), + [sym_if_statement] = STATE(15), + [sym_while_statement] = STATE(15), + [sym_for_statement] = STATE(15), + [sym_function_definition] = STATE(15), + [sym_struct_definition] = STATE(15), + [sym_type_alias_definition] = STATE(15), + [sym_trait_definition] = STATE(15), + [sym_impl_definition] = STATE(15), + [sym_return_statement] = STATE(15), + [sym_break_statement] = STATE(15), + [sym_continue_statement] = STATE(15), + [sym_expression_statement] = STATE(15), + [sym_go_statement] = STATE(15), + [sym_try_statement] = STATE(15), + [sym_block] = STATE(15), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(15), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -10809,14 +11830,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(292), + [anon_sym_RBRACE] = ACTIONS(297), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -10827,8 +11849,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -10840,171 +11862,67 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(77), [anon_sym_try] = ACTIONS(79), }, - [STATE(16)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(16), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(16), - [sym_import_statement] = STATE(16), - [sym_macro_export] = STATE(16), - [sym_macro_definition] = STATE(16), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(16), - [sym_define_statement] = STATE(16), - [sym_assignment_statement] = STATE(16), - [sym_compound_assignment_statement] = STATE(16), - [sym_if_statement] = STATE(16), - [sym_while_statement] = STATE(16), - [sym_for_statement] = STATE(16), - [sym_function_definition] = STATE(16), - [sym_struct_definition] = STATE(16), - [sym_type_alias_definition] = STATE(16), - [sym_trait_definition] = STATE(16), - [sym_impl_definition] = STATE(16), - [sym_return_statement] = STATE(16), - [sym_break_statement] = STATE(16), - [sym_continue_statement] = STATE(16), - [sym_expression_statement] = STATE(16), - [sym_go_statement] = STATE(16), - [sym_try_statement] = STATE(16), - [sym_block] = STATE(16), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(16), - [sym_line_comment] = ACTIONS(3), - [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(294), - [sym_integer_literal] = ACTIONS(297), - [sym_float_literal] = ACTIONS(300), - [anon_sym_true] = ACTIONS(303), - [anon_sym_false] = ACTIONS(303), - [anon_sym_nil] = ACTIONS(306), - [anon_sym_DQUOTE] = ACTIONS(309), - [anon_sym_SQUOTE] = ACTIONS(312), - [sym_raw_string] = ACTIONS(315), - [anon_sym_RBRACE] = ACTIONS(318), - [anon_sym_LPAREN] = ACTIONS(320), - [anon_sym_LBRACK] = ACTIONS(323), - [anon_sym_LBRACE] = ACTIONS(326), - [anon_sym_BANG] = ACTIONS(329), - [anon_sym_PIPE] = ACTIONS(332), - [anon_sym_match] = ACTIONS(335), - [anon_sym_if] = ACTIONS(338), - [anon_sym_spawn] = ACTIONS(341), - [anon_sym_chan] = ACTIONS(344), - [anon_sym_send] = ACTIONS(347), - [anon_sym_recv] = ACTIONS(350), - [anon_sym_select] = ACTIONS(353), - [anon_sym_POUND] = ACTIONS(356), - [anon_sym_use] = ACTIONS(359), - [anon_sym_export] = ACTIONS(362), - [anon_sym_macro_rules] = ACTIONS(365), - [anon_sym_let] = ACTIONS(368), - [anon_sym_while] = ACTIONS(371), - [anon_sym_for] = ACTIONS(374), - [anon_sym_fn] = ACTIONS(377), - [anon_sym_struct] = ACTIONS(380), - [anon_sym_type] = ACTIONS(383), - [anon_sym_trait] = ACTIONS(386), - [anon_sym_impl] = ACTIONS(389), - [anon_sym_return] = ACTIONS(392), - [anon_sym_break] = ACTIONS(395), - [anon_sym_continue] = ACTIONS(398), - [anon_sym_go] = ACTIONS(401), - [anon_sym_try] = ACTIONS(404), - }, [STATE(17)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(18), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(18), - [sym_import_statement] = STATE(554), - [sym_macro_export] = STATE(554), - [sym_macro_definition] = STATE(554), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(18), - [sym_define_statement] = STATE(18), - [sym_assignment_statement] = STATE(18), - [sym_compound_assignment_statement] = STATE(18), - [sym_if_statement] = STATE(18), - [sym_while_statement] = STATE(18), - [sym_for_statement] = STATE(18), - [sym_function_definition] = STATE(18), - [sym_struct_definition] = STATE(18), - [sym_type_alias_definition] = STATE(18), - [sym_trait_definition] = STATE(18), - [sym_impl_definition] = STATE(18), - [sym_return_statement] = STATE(18), - [sym_break_statement] = STATE(18), - [sym_continue_statement] = STATE(18), - [sym_expression_statement] = STATE(18), - [sym_go_statement] = STATE(18), - [sym_try_statement] = STATE(18), - [sym_block] = STATE(18), - [aux_sym_program_repeat1] = STATE(18), - [aux_sym_attributed_item_repeat1] = STATE(873), - [ts_builtin_sym_end] = ACTIONS(407), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(14), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(14), + [sym_import_statement] = STATE(14), + [sym_macro_export] = STATE(14), + [sym_macro_definition] = STATE(14), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(14), + [sym_define_statement] = STATE(14), + [sym_assignment_statement] = STATE(14), + [sym_compound_assignment_statement] = STATE(14), + [sym_if_statement] = STATE(14), + [sym_while_statement] = STATE(14), + [sym_for_statement] = STATE(14), + [sym_function_definition] = STATE(14), + [sym_struct_definition] = STATE(14), + [sym_type_alias_definition] = STATE(14), + [sym_trait_definition] = STATE(14), + [sym_impl_definition] = STATE(14), + [sym_return_statement] = STATE(14), + [sym_break_statement] = STATE(14), + [sym_continue_statement] = STATE(14), + [sym_expression_statement] = STATE(14), + [sym_go_statement] = STATE(14), + [sym_try_statement] = STATE(14), + [sym_block] = STATE(14), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(14), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -11016,13 +11934,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), + [anon_sym_RBRACE] = ACTIONS(299), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(27), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(35), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -11033,8 +11953,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(57), - [anon_sym_for] = ACTIONS(59), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -11047,45 +11967,45 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(18)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), [sym__statement] = STATE(18), - [sym_attribute] = STATE(873), + [sym_attribute] = STATE(968), [sym_attributed_item] = STATE(18), - [sym_import_statement] = STATE(554), - [sym_macro_export] = STATE(554), - [sym_macro_definition] = STATE(554), - [sym_macro_invocation] = STATE(678), + [sym_import_statement] = STATE(667), + [sym_macro_export] = STATE(667), + [sym_macro_definition] = STATE(667), + [sym_macro_invocation] = STATE(731), [sym_let_statement] = STATE(18), [sym_define_statement] = STATE(18), [sym_assignment_statement] = STATE(18), @@ -11106,110 +12026,111 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_try_statement] = STATE(18), [sym_block] = STATE(18), [aux_sym_program_repeat1] = STATE(18), - [aux_sym_attributed_item_repeat1] = STATE(873), - [ts_builtin_sym_end] = ACTIONS(409), + [aux_sym_attributed_item_repeat1] = STATE(968), + [ts_builtin_sym_end] = ACTIONS(301), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(411), - [sym_integer_literal] = ACTIONS(414), - [sym_float_literal] = ACTIONS(417), - [anon_sym_true] = ACTIONS(420), - [anon_sym_false] = ACTIONS(420), - [anon_sym_nil] = ACTIONS(423), - [anon_sym_DQUOTE] = ACTIONS(426), - [anon_sym_SQUOTE] = ACTIONS(429), - [sym_raw_string] = ACTIONS(432), - [anon_sym_LPAREN] = ACTIONS(435), - [anon_sym_LBRACK] = ACTIONS(438), - [anon_sym_LBRACE] = ACTIONS(441), - [anon_sym_BANG] = ACTIONS(444), - [anon_sym_PIPE] = ACTIONS(447), - [anon_sym_match] = ACTIONS(450), - [anon_sym_if] = ACTIONS(453), - [anon_sym_spawn] = ACTIONS(456), - [anon_sym_chan] = ACTIONS(459), - [anon_sym_send] = ACTIONS(462), - [anon_sym_recv] = ACTIONS(465), - [anon_sym_select] = ACTIONS(468), - [anon_sym_POUND] = ACTIONS(471), - [anon_sym_use] = ACTIONS(474), - [anon_sym_export] = ACTIONS(477), - [anon_sym_macro_rules] = ACTIONS(480), - [anon_sym_let] = ACTIONS(483), - [anon_sym_while] = ACTIONS(486), - [anon_sym_for] = ACTIONS(489), - [anon_sym_fn] = ACTIONS(492), - [anon_sym_struct] = ACTIONS(495), - [anon_sym_type] = ACTIONS(498), - [anon_sym_trait] = ACTIONS(501), - [anon_sym_impl] = ACTIONS(504), - [anon_sym_return] = ACTIONS(507), - [anon_sym_break] = ACTIONS(510), - [anon_sym_continue] = ACTIONS(513), - [anon_sym_go] = ACTIONS(516), - [anon_sym_try] = ACTIONS(519), + [aux_sym_identifier_token1] = ACTIONS(303), + [sym_integer_literal] = ACTIONS(306), + [sym_float_literal] = ACTIONS(309), + [anon_sym_true] = ACTIONS(312), + [anon_sym_false] = ACTIONS(312), + [anon_sym_nil] = ACTIONS(315), + [anon_sym_DQUOTE] = ACTIONS(318), + [anon_sym_SQUOTE] = ACTIONS(321), + [sym_raw_string] = ACTIONS(324), + [anon_sym_LPAREN] = ACTIONS(327), + [anon_sym_LBRACK] = ACTIONS(330), + [anon_sym_LBRACE] = ACTIONS(333), + [anon_sym_BANG] = ACTIONS(336), + [anon_sym_TILDE] = ACTIONS(336), + [anon_sym_PIPE] = ACTIONS(339), + [anon_sym_match] = ACTIONS(342), + [anon_sym_if] = ACTIONS(345), + [anon_sym_spawn] = ACTIONS(348), + [anon_sym_chan] = ACTIONS(351), + [anon_sym_send] = ACTIONS(354), + [anon_sym_recv] = ACTIONS(357), + [anon_sym_select] = ACTIONS(360), + [anon_sym_POUND] = ACTIONS(363), + [anon_sym_use] = ACTIONS(366), + [anon_sym_export] = ACTIONS(369), + [anon_sym_macro_rules] = ACTIONS(372), + [anon_sym_let] = ACTIONS(375), + [anon_sym_while] = ACTIONS(378), + [anon_sym_for] = ACTIONS(381), + [anon_sym_fn] = ACTIONS(384), + [anon_sym_struct] = ACTIONS(387), + [anon_sym_type] = ACTIONS(390), + [anon_sym_trait] = ACTIONS(393), + [anon_sym_impl] = ACTIONS(396), + [anon_sym_return] = ACTIONS(399), + [anon_sym_break] = ACTIONS(402), + [anon_sym_continue] = ACTIONS(405), + [anon_sym_go] = ACTIONS(408), + [anon_sym_try] = ACTIONS(411), }, [STATE(19)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(16), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(16), - [sym_import_statement] = STATE(16), - [sym_macro_export] = STATE(16), - [sym_macro_definition] = STATE(16), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(16), - [sym_define_statement] = STATE(16), - [sym_assignment_statement] = STATE(16), - [sym_compound_assignment_statement] = STATE(16), - [sym_if_statement] = STATE(16), - [sym_while_statement] = STATE(16), - [sym_for_statement] = STATE(16), - [sym_function_definition] = STATE(16), - [sym_struct_definition] = STATE(16), - [sym_type_alias_definition] = STATE(16), - [sym_trait_definition] = STATE(16), - [sym_impl_definition] = STATE(16), - [sym_return_statement] = STATE(16), - [sym_break_statement] = STATE(16), - [sym_continue_statement] = STATE(16), - [sym_expression_statement] = STATE(16), - [sym_go_statement] = STATE(16), - [sym_try_statement] = STATE(16), - [sym_block] = STATE(16), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(16), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(21), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(21), + [sym_import_statement] = STATE(21), + [sym_macro_export] = STATE(21), + [sym_macro_definition] = STATE(21), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(21), + [sym_define_statement] = STATE(21), + [sym_assignment_statement] = STATE(21), + [sym_compound_assignment_statement] = STATE(21), + [sym_if_statement] = STATE(21), + [sym_while_statement] = STATE(21), + [sym_for_statement] = STATE(21), + [sym_function_definition] = STATE(21), + [sym_struct_definition] = STATE(21), + [sym_type_alias_definition] = STATE(21), + [sym_trait_definition] = STATE(21), + [sym_impl_definition] = STATE(21), + [sym_return_statement] = STATE(21), + [sym_break_statement] = STATE(21), + [sym_continue_statement] = STATE(21), + [sym_expression_statement] = STATE(21), + [sym_go_statement] = STATE(21), + [sym_try_statement] = STATE(21), + [sym_block] = STATE(21), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(21), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -11221,14 +12142,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(522), + [anon_sym_RBRACE] = ACTIONS(414), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -11239,8 +12161,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -11253,66 +12175,66 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(20)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(15), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(15), - [sym_import_statement] = STATE(15), - [sym_macro_export] = STATE(15), - [sym_macro_definition] = STATE(15), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(15), - [sym_define_statement] = STATE(15), - [sym_assignment_statement] = STATE(15), - [sym_compound_assignment_statement] = STATE(15), - [sym_if_statement] = STATE(15), - [sym_while_statement] = STATE(15), - [sym_for_statement] = STATE(15), - [sym_function_definition] = STATE(15), - [sym_struct_definition] = STATE(15), - [sym_type_alias_definition] = STATE(15), - [sym_trait_definition] = STATE(15), - [sym_impl_definition] = STATE(15), - [sym_return_statement] = STATE(15), - [sym_break_statement] = STATE(15), - [sym_continue_statement] = STATE(15), - [sym_expression_statement] = STATE(15), - [sym_go_statement] = STATE(15), - [sym_try_statement] = STATE(15), - [sym_block] = STATE(15), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(15), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(19), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(19), + [sym_import_statement] = STATE(19), + [sym_macro_export] = STATE(19), + [sym_macro_definition] = STATE(19), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(19), + [sym_define_statement] = STATE(19), + [sym_assignment_statement] = STATE(19), + [sym_compound_assignment_statement] = STATE(19), + [sym_if_statement] = STATE(19), + [sym_while_statement] = STATE(19), + [sym_for_statement] = STATE(19), + [sym_function_definition] = STATE(19), + [sym_struct_definition] = STATE(19), + [sym_type_alias_definition] = STATE(19), + [sym_trait_definition] = STATE(19), + [sym_impl_definition] = STATE(19), + [sym_return_statement] = STATE(19), + [sym_break_statement] = STATE(19), + [sym_continue_statement] = STATE(19), + [sym_expression_statement] = STATE(19), + [sym_go_statement] = STATE(19), + [sym_try_statement] = STATE(19), + [sym_block] = STATE(19), + [aux_sym_attributed_item_repeat1] = STATE(968), + [aux_sym_block_repeat1] = STATE(19), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -11324,14 +12246,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(524), + [anon_sym_RBRACE] = ACTIONS(416), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -11342,8 +12265,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -11356,148 +12279,45 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(21)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(16), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(16), - [sym_import_statement] = STATE(16), - [sym_macro_export] = STATE(16), - [sym_macro_definition] = STATE(16), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(16), - [sym_define_statement] = STATE(16), - [sym_assignment_statement] = STATE(16), - [sym_compound_assignment_statement] = STATE(16), - [sym_if_statement] = STATE(16), - [sym_while_statement] = STATE(16), - [sym_for_statement] = STATE(16), - [sym_function_definition] = STATE(16), - [sym_struct_definition] = STATE(16), - [sym_type_alias_definition] = STATE(16), - [sym_trait_definition] = STATE(16), - [sym_impl_definition] = STATE(16), - [sym_return_statement] = STATE(16), - [sym_break_statement] = STATE(16), - [sym_continue_statement] = STATE(16), - [sym_expression_statement] = STATE(16), - [sym_go_statement] = STATE(16), - [sym_try_statement] = STATE(16), - [sym_block] = STATE(16), - [aux_sym_attributed_item_repeat1] = STATE(873), - [aux_sym_block_repeat1] = STATE(16), - [sym_line_comment] = ACTIONS(3), - [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(7), - [sym_integer_literal] = ACTIONS(9), - [sym_float_literal] = ACTIONS(11), - [anon_sym_true] = ACTIONS(13), - [anon_sym_false] = ACTIONS(13), - [anon_sym_nil] = ACTIONS(15), - [anon_sym_DQUOTE] = ACTIONS(17), - [anon_sym_SQUOTE] = ACTIONS(19), - [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(526), - [anon_sym_LPAREN] = ACTIONS(23), - [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), - [anon_sym_BANG] = ACTIONS(29), - [anon_sym_PIPE] = ACTIONS(31), - [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), - [anon_sym_spawn] = ACTIONS(37), - [anon_sym_chan] = ACTIONS(39), - [anon_sym_send] = ACTIONS(41), - [anon_sym_recv] = ACTIONS(43), - [anon_sym_select] = ACTIONS(45), - [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(49), - [anon_sym_export] = ACTIONS(51), - [anon_sym_macro_rules] = ACTIONS(53), - [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), - [anon_sym_fn] = ACTIONS(61), - [anon_sym_struct] = ACTIONS(63), - [anon_sym_type] = ACTIONS(65), - [anon_sym_trait] = ACTIONS(67), - [anon_sym_impl] = ACTIONS(69), - [anon_sym_return] = ACTIONS(71), - [anon_sym_break] = ACTIONS(73), - [anon_sym_continue] = ACTIONS(75), - [anon_sym_go] = ACTIONS(77), - [anon_sym_try] = ACTIONS(79), - }, - [STATE(22)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), [sym__statement] = STATE(21), - [sym_attribute] = STATE(873), + [sym_attribute] = STATE(968), [sym_attributed_item] = STATE(21), [sym_import_statement] = STATE(21), [sym_macro_export] = STATE(21), [sym_macro_definition] = STATE(21), - [sym_macro_invocation] = STATE(678), + [sym_macro_invocation] = STATE(731), [sym_let_statement] = STATE(21), [sym_define_statement] = STATE(21), [sym_assignment_statement] = STATE(21), @@ -11517,10 +12337,115 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_go_statement] = STATE(21), [sym_try_statement] = STATE(21), [sym_block] = STATE(21), - [aux_sym_attributed_item_repeat1] = STATE(873), + [aux_sym_attributed_item_repeat1] = STATE(968), [aux_sym_block_repeat1] = STATE(21), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(418), + [sym_integer_literal] = ACTIONS(421), + [sym_float_literal] = ACTIONS(424), + [anon_sym_true] = ACTIONS(427), + [anon_sym_false] = ACTIONS(427), + [anon_sym_nil] = ACTIONS(430), + [anon_sym_DQUOTE] = ACTIONS(433), + [anon_sym_SQUOTE] = ACTIONS(436), + [sym_raw_string] = ACTIONS(439), + [anon_sym_RBRACE] = ACTIONS(442), + [anon_sym_LPAREN] = ACTIONS(444), + [anon_sym_LBRACK] = ACTIONS(447), + [anon_sym_LBRACE] = ACTIONS(450), + [anon_sym_BANG] = ACTIONS(453), + [anon_sym_TILDE] = ACTIONS(453), + [anon_sym_PIPE] = ACTIONS(456), + [anon_sym_match] = ACTIONS(459), + [anon_sym_if] = ACTIONS(462), + [anon_sym_spawn] = ACTIONS(465), + [anon_sym_chan] = ACTIONS(468), + [anon_sym_send] = ACTIONS(471), + [anon_sym_recv] = ACTIONS(474), + [anon_sym_select] = ACTIONS(477), + [anon_sym_POUND] = ACTIONS(480), + [anon_sym_use] = ACTIONS(483), + [anon_sym_export] = ACTIONS(486), + [anon_sym_macro_rules] = ACTIONS(489), + [anon_sym_let] = ACTIONS(492), + [anon_sym_while] = ACTIONS(495), + [anon_sym_for] = ACTIONS(498), + [anon_sym_fn] = ACTIONS(501), + [anon_sym_struct] = ACTIONS(504), + [anon_sym_type] = ACTIONS(507), + [anon_sym_trait] = ACTIONS(510), + [anon_sym_impl] = ACTIONS(513), + [anon_sym_return] = ACTIONS(516), + [anon_sym_break] = ACTIONS(519), + [anon_sym_continue] = ACTIONS(522), + [anon_sym_go] = ACTIONS(525), + [anon_sym_try] = ACTIONS(528), + }, + [STATE(22)] = { + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(18), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(18), + [sym_import_statement] = STATE(667), + [sym_macro_export] = STATE(667), + [sym_macro_definition] = STATE(667), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(18), + [sym_define_statement] = STATE(18), + [sym_assignment_statement] = STATE(18), + [sym_compound_assignment_statement] = STATE(18), + [sym_if_statement] = STATE(18), + [sym_while_statement] = STATE(18), + [sym_for_statement] = STATE(18), + [sym_function_definition] = STATE(18), + [sym_struct_definition] = STATE(18), + [sym_type_alias_definition] = STATE(18), + [sym_trait_definition] = STATE(18), + [sym_impl_definition] = STATE(18), + [sym_return_statement] = STATE(18), + [sym_break_statement] = STATE(18), + [sym_continue_statement] = STATE(18), + [sym_expression_statement] = STATE(18), + [sym_go_statement] = STATE(18), + [sym_try_statement] = STATE(18), + [sym_block] = STATE(18), + [aux_sym_program_repeat1] = STATE(18), + [aux_sym_attributed_item_repeat1] = STATE(968), + [ts_builtin_sym_end] = ACTIONS(531), + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), [sym_integer_literal] = ACTIONS(9), [sym_float_literal] = ACTIONS(11), @@ -11530,14 +12455,14 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(528), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(27), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(35), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -11548,8 +12473,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(57), + [anon_sym_for] = ACTIONS(59), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -11562,65 +12487,65 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(23)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(475), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(475), - [sym_import_statement] = STATE(475), - [sym_macro_export] = STATE(475), - [sym_macro_definition] = STATE(475), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(475), - [sym_define_statement] = STATE(475), - [sym_assignment_statement] = STATE(475), - [sym_compound_assignment_statement] = STATE(475), - [sym_if_statement] = STATE(475), - [sym_while_statement] = STATE(475), - [sym_for_statement] = STATE(475), - [sym_function_definition] = STATE(475), - [sym_struct_definition] = STATE(475), - [sym_type_alias_definition] = STATE(475), - [sym_trait_definition] = STATE(475), - [sym_impl_definition] = STATE(475), - [sym_return_statement] = STATE(475), - [sym_break_statement] = STATE(475), - [sym_continue_statement] = STATE(475), - [sym_expression_statement] = STATE(475), - [sym_go_statement] = STATE(475), - [sym_try_statement] = STATE(475), - [sym_block] = STATE(475), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(618), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(618), + [sym_import_statement] = STATE(618), + [sym_macro_export] = STATE(618), + [sym_macro_definition] = STATE(618), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(618), + [sym_define_statement] = STATE(618), + [sym_assignment_statement] = STATE(618), + [sym_compound_assignment_statement] = STATE(618), + [sym_if_statement] = STATE(618), + [sym_while_statement] = STATE(618), + [sym_for_statement] = STATE(618), + [sym_function_definition] = STATE(618), + [sym_struct_definition] = STATE(618), + [sym_type_alias_definition] = STATE(618), + [sym_trait_definition] = STATE(618), + [sym_impl_definition] = STATE(618), + [sym_return_statement] = STATE(618), + [sym_break_statement] = STATE(618), + [sym_continue_statement] = STATE(618), + [sym_expression_statement] = STATE(618), + [sym_go_statement] = STATE(618), + [sym_try_statement] = STATE(618), + [sym_block] = STATE(618), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -11634,94 +12559,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(530), + [anon_sym_LBRACE] = ACTIONS(27), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(532), + [anon_sym_if] = ACTIONS(35), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(542), - [anon_sym_for] = ACTIONS(544), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(49), + [anon_sym_export] = ACTIONS(51), + [anon_sym_macro_rules] = ACTIONS(53), + [anon_sym_let] = ACTIONS(55), + [anon_sym_while] = ACTIONS(57), + [anon_sym_for] = ACTIONS(59), + [anon_sym_fn] = ACTIONS(61), + [anon_sym_struct] = ACTIONS(63), + [anon_sym_type] = ACTIONS(65), + [anon_sym_trait] = ACTIONS(67), + [anon_sym_impl] = ACTIONS(69), + [anon_sym_return] = ACTIONS(71), + [anon_sym_break] = ACTIONS(73), + [anon_sym_continue] = ACTIONS(75), + [anon_sym_go] = ACTIONS(77), + [anon_sym_try] = ACTIONS(79), }, [STATE(24)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(522), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(522), - [sym_import_statement] = STATE(522), - [sym_macro_export] = STATE(522), - [sym_macro_definition] = STATE(522), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(522), - [sym_define_statement] = STATE(522), - [sym_assignment_statement] = STATE(522), - [sym_compound_assignment_statement] = STATE(522), - [sym_if_statement] = STATE(522), - [sym_while_statement] = STATE(522), - [sym_for_statement] = STATE(522), - [sym_function_definition] = STATE(522), - [sym_struct_definition] = STATE(522), - [sym_type_alias_definition] = STATE(522), - [sym_trait_definition] = STATE(522), - [sym_impl_definition] = STATE(522), - [sym_return_statement] = STATE(522), - [sym_break_statement] = STATE(522), - [sym_continue_statement] = STATE(522), - [sym_expression_statement] = STATE(522), - [sym_go_statement] = STATE(522), - [sym_try_statement] = STATE(522), - [sym_block] = STATE(522), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(539), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(539), + [sym_import_statement] = STATE(539), + [sym_macro_export] = STATE(539), + [sym_macro_definition] = STATE(539), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(539), + [sym_define_statement] = STATE(539), + [sym_assignment_statement] = STATE(539), + [sym_compound_assignment_statement] = STATE(539), + [sym_if_statement] = STATE(539), + [sym_while_statement] = STATE(539), + [sym_for_statement] = STATE(539), + [sym_function_definition] = STATE(539), + [sym_struct_definition] = STATE(539), + [sym_type_alias_definition] = STATE(539), + [sym_trait_definition] = STATE(539), + [sym_impl_definition] = STATE(539), + [sym_return_statement] = STATE(539), + [sym_break_statement] = STATE(539), + [sym_continue_statement] = STATE(539), + [sym_expression_statement] = STATE(539), + [sym_go_statement] = STATE(539), + [sym_try_statement] = STATE(539), + [sym_block] = STATE(539), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -11735,94 +12661,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(566), + [anon_sym_LBRACE] = ACTIONS(533), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(568), + [anon_sym_if] = ACTIONS(535), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(570), - [anon_sym_for] = ACTIONS(572), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(545), + [anon_sym_for] = ACTIONS(547), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(25)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(518), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(518), - [sym_import_statement] = STATE(518), - [sym_macro_export] = STATE(518), - [sym_macro_definition] = STATE(518), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(518), - [sym_define_statement] = STATE(518), - [sym_assignment_statement] = STATE(518), - [sym_compound_assignment_statement] = STATE(518), - [sym_if_statement] = STATE(518), - [sym_while_statement] = STATE(518), - [sym_for_statement] = STATE(518), - [sym_function_definition] = STATE(518), - [sym_struct_definition] = STATE(518), - [sym_type_alias_definition] = STATE(518), - [sym_trait_definition] = STATE(518), - [sym_impl_definition] = STATE(518), - [sym_return_statement] = STATE(518), - [sym_break_statement] = STATE(518), - [sym_continue_statement] = STATE(518), - [sym_expression_statement] = STATE(518), - [sym_go_statement] = STATE(518), - [sym_try_statement] = STATE(518), - [sym_block] = STATE(518), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(666), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(666), + [sym_import_statement] = STATE(666), + [sym_macro_export] = STATE(666), + [sym_macro_definition] = STATE(666), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(666), + [sym_define_statement] = STATE(666), + [sym_assignment_statement] = STATE(666), + [sym_compound_assignment_statement] = STATE(666), + [sym_if_statement] = STATE(666), + [sym_while_statement] = STATE(666), + [sym_for_statement] = STATE(666), + [sym_function_definition] = STATE(666), + [sym_struct_definition] = STATE(666), + [sym_type_alias_definition] = STATE(666), + [sym_trait_definition] = STATE(666), + [sym_impl_definition] = STATE(666), + [sym_return_statement] = STATE(666), + [sym_break_statement] = STATE(666), + [sym_continue_statement] = STATE(666), + [sym_expression_statement] = STATE(666), + [sym_go_statement] = STATE(666), + [sym_try_statement] = STATE(666), + [sym_block] = STATE(666), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -11836,94 +12763,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(533), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(535), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(49), - [anon_sym_export] = ACTIONS(51), - [anon_sym_macro_rules] = ACTIONS(53), - [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), - [anon_sym_fn] = ACTIONS(61), - [anon_sym_struct] = ACTIONS(63), - [anon_sym_type] = ACTIONS(65), - [anon_sym_trait] = ACTIONS(67), - [anon_sym_impl] = ACTIONS(69), - [anon_sym_return] = ACTIONS(71), - [anon_sym_break] = ACTIONS(73), - [anon_sym_continue] = ACTIONS(75), - [anon_sym_go] = ACTIONS(77), - [anon_sym_try] = ACTIONS(79), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(545), + [anon_sym_for] = ACTIONS(547), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(26)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(551), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(551), - [sym_import_statement] = STATE(551), - [sym_macro_export] = STATE(551), - [sym_macro_definition] = STATE(551), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(551), - [sym_define_statement] = STATE(551), - [sym_assignment_statement] = STATE(551), - [sym_compound_assignment_statement] = STATE(551), - [sym_if_statement] = STATE(551), - [sym_while_statement] = STATE(551), - [sym_for_statement] = STATE(551), - [sym_function_definition] = STATE(551), - [sym_struct_definition] = STATE(551), - [sym_type_alias_definition] = STATE(551), - [sym_trait_definition] = STATE(551), - [sym_impl_definition] = STATE(551), - [sym_return_statement] = STATE(551), - [sym_break_statement] = STATE(551), - [sym_continue_statement] = STATE(551), - [sym_expression_statement] = STATE(551), - [sym_go_statement] = STATE(551), - [sym_try_statement] = STATE(551), - [sym_block] = STATE(551), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(638), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(638), + [sym_import_statement] = STATE(638), + [sym_macro_export] = STATE(638), + [sym_macro_definition] = STATE(638), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(638), + [sym_define_statement] = STATE(638), + [sym_assignment_statement] = STATE(638), + [sym_compound_assignment_statement] = STATE(638), + [sym_if_statement] = STATE(638), + [sym_while_statement] = STATE(638), + [sym_for_statement] = STATE(638), + [sym_function_definition] = STATE(638), + [sym_struct_definition] = STATE(638), + [sym_type_alias_definition] = STATE(638), + [sym_trait_definition] = STATE(638), + [sym_impl_definition] = STATE(638), + [sym_return_statement] = STATE(638), + [sym_break_statement] = STATE(638), + [sym_continue_statement] = STATE(638), + [sym_expression_statement] = STATE(638), + [sym_go_statement] = STATE(638), + [sym_try_statement] = STATE(638), + [sym_block] = STATE(638), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -11937,11 +12865,12 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(27), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(35), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -11952,8 +12881,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(57), - [anon_sym_for] = ACTIONS(59), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -11966,65 +12895,65 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(27)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(518), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(518), - [sym_import_statement] = STATE(518), - [sym_macro_export] = STATE(518), - [sym_macro_definition] = STATE(518), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(518), - [sym_define_statement] = STATE(518), - [sym_assignment_statement] = STATE(518), - [sym_compound_assignment_statement] = STATE(518), - [sym_if_statement] = STATE(518), - [sym_while_statement] = STATE(518), - [sym_for_statement] = STATE(518), - [sym_function_definition] = STATE(518), - [sym_struct_definition] = STATE(518), - [sym_type_alias_definition] = STATE(518), - [sym_trait_definition] = STATE(518), - [sym_impl_definition] = STATE(518), - [sym_return_statement] = STATE(518), - [sym_break_statement] = STATE(518), - [sym_continue_statement] = STATE(518), - [sym_expression_statement] = STATE(518), - [sym_go_statement] = STATE(518), - [sym_try_statement] = STATE(518), - [sym_block] = STATE(518), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(664), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(664), + [sym_import_statement] = STATE(664), + [sym_macro_export] = STATE(664), + [sym_macro_definition] = STATE(664), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(664), + [sym_define_statement] = STATE(664), + [sym_assignment_statement] = STATE(664), + [sym_compound_assignment_statement] = STATE(664), + [sym_if_statement] = STATE(664), + [sym_while_statement] = STATE(664), + [sym_for_statement] = STATE(664), + [sym_function_definition] = STATE(664), + [sym_struct_definition] = STATE(664), + [sym_type_alias_definition] = STATE(664), + [sym_trait_definition] = STATE(664), + [sym_impl_definition] = STATE(664), + [sym_return_statement] = STATE(664), + [sym_break_statement] = STATE(664), + [sym_continue_statement] = STATE(664), + [sym_expression_statement] = STATE(664), + [sym_go_statement] = STATE(664), + [sym_try_statement] = STATE(664), + [sym_block] = STATE(664), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12038,11 +12967,12 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(27), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(35), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -12053,8 +12983,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(57), - [anon_sym_for] = ACTIONS(59), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -12067,65 +12997,65 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(28)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(551), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(551), - [sym_import_statement] = STATE(551), - [sym_macro_export] = STATE(551), - [sym_macro_definition] = STATE(551), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(551), - [sym_define_statement] = STATE(551), - [sym_assignment_statement] = STATE(551), - [sym_compound_assignment_statement] = STATE(551), - [sym_if_statement] = STATE(551), - [sym_while_statement] = STATE(551), - [sym_for_statement] = STATE(551), - [sym_function_definition] = STATE(551), - [sym_struct_definition] = STATE(551), - [sym_type_alias_definition] = STATE(551), - [sym_trait_definition] = STATE(551), - [sym_impl_definition] = STATE(551), - [sym_return_statement] = STATE(551), - [sym_break_statement] = STATE(551), - [sym_continue_statement] = STATE(551), - [sym_expression_statement] = STATE(551), - [sym_go_statement] = STATE(551), - [sym_try_statement] = STATE(551), - [sym_block] = STATE(551), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(638), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(638), + [sym_import_statement] = STATE(638), + [sym_macro_export] = STATE(638), + [sym_macro_definition] = STATE(638), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(638), + [sym_define_statement] = STATE(638), + [sym_assignment_statement] = STATE(638), + [sym_compound_assignment_statement] = STATE(638), + [sym_if_statement] = STATE(638), + [sym_while_statement] = STATE(638), + [sym_for_statement] = STATE(638), + [sym_function_definition] = STATE(638), + [sym_struct_definition] = STATE(638), + [sym_type_alias_definition] = STATE(638), + [sym_trait_definition] = STATE(638), + [sym_impl_definition] = STATE(638), + [sym_return_statement] = STATE(638), + [sym_break_statement] = STATE(638), + [sym_continue_statement] = STATE(638), + [sym_expression_statement] = STATE(638), + [sym_go_statement] = STATE(638), + [sym_try_statement] = STATE(638), + [sym_block] = STATE(638), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12139,11 +13069,12 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(27), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(35), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -12154,8 +13085,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(57), + [anon_sym_for] = ACTIONS(59), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -12168,65 +13099,65 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(29)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(537), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(537), - [sym_import_statement] = STATE(537), - [sym_macro_export] = STATE(537), - [sym_macro_definition] = STATE(537), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(537), - [sym_define_statement] = STATE(537), - [sym_assignment_statement] = STATE(537), - [sym_compound_assignment_statement] = STATE(537), - [sym_if_statement] = STATE(537), - [sym_while_statement] = STATE(537), - [sym_for_statement] = STATE(537), - [sym_function_definition] = STATE(537), - [sym_struct_definition] = STATE(537), - [sym_type_alias_definition] = STATE(537), - [sym_trait_definition] = STATE(537), - [sym_impl_definition] = STATE(537), - [sym_return_statement] = STATE(537), - [sym_break_statement] = STATE(537), - [sym_continue_statement] = STATE(537), - [sym_expression_statement] = STATE(537), - [sym_go_statement] = STATE(537), - [sym_try_statement] = STATE(537), - [sym_block] = STATE(537), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(650), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(650), + [sym_import_statement] = STATE(650), + [sym_macro_export] = STATE(650), + [sym_macro_definition] = STATE(650), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(650), + [sym_define_statement] = STATE(650), + [sym_assignment_statement] = STATE(650), + [sym_compound_assignment_statement] = STATE(650), + [sym_if_statement] = STATE(650), + [sym_while_statement] = STATE(650), + [sym_for_statement] = STATE(650), + [sym_function_definition] = STATE(650), + [sym_struct_definition] = STATE(650), + [sym_type_alias_definition] = STATE(650), + [sym_trait_definition] = STATE(650), + [sym_impl_definition] = STATE(650), + [sym_return_statement] = STATE(650), + [sym_break_statement] = STATE(650), + [sym_continue_statement] = STATE(650), + [sym_expression_statement] = STATE(650), + [sym_go_statement] = STATE(650), + [sym_try_statement] = STATE(650), + [sym_block] = STATE(650), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12240,11 +13171,12 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(27), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(35), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -12255,8 +13187,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(57), - [anon_sym_for] = ACTIONS(59), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -12269,65 +13201,65 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(30)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(537), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(537), - [sym_import_statement] = STATE(537), - [sym_macro_export] = STATE(537), - [sym_macro_definition] = STATE(537), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(537), - [sym_define_statement] = STATE(537), - [sym_assignment_statement] = STATE(537), - [sym_compound_assignment_statement] = STATE(537), - [sym_if_statement] = STATE(537), - [sym_while_statement] = STATE(537), - [sym_for_statement] = STATE(537), - [sym_function_definition] = STATE(537), - [sym_struct_definition] = STATE(537), - [sym_type_alias_definition] = STATE(537), - [sym_trait_definition] = STATE(537), - [sym_impl_definition] = STATE(537), - [sym_return_statement] = STATE(537), - [sym_break_statement] = STATE(537), - [sym_continue_statement] = STATE(537), - [sym_expression_statement] = STATE(537), - [sym_go_statement] = STATE(537), - [sym_try_statement] = STATE(537), - [sym_block] = STATE(537), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(618), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(618), + [sym_import_statement] = STATE(618), + [sym_macro_export] = STATE(618), + [sym_macro_definition] = STATE(618), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(618), + [sym_define_statement] = STATE(618), + [sym_assignment_statement] = STATE(618), + [sym_compound_assignment_statement] = STATE(618), + [sym_if_statement] = STATE(618), + [sym_while_statement] = STATE(618), + [sym_for_statement] = STATE(618), + [sym_function_definition] = STATE(618), + [sym_struct_definition] = STATE(618), + [sym_type_alias_definition] = STATE(618), + [sym_trait_definition] = STATE(618), + [sym_impl_definition] = STATE(618), + [sym_return_statement] = STATE(618), + [sym_break_statement] = STATE(618), + [sym_continue_statement] = STATE(618), + [sym_expression_statement] = STATE(618), + [sym_go_statement] = STATE(618), + [sym_try_statement] = STATE(618), + [sym_block] = STATE(618), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12341,11 +13273,12 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(279), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(281), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -12356,8 +13289,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_export] = ACTIONS(51), [anon_sym_macro_rules] = ACTIONS(53), [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), + [anon_sym_while] = ACTIONS(283), + [anon_sym_for] = ACTIONS(285), [anon_sym_fn] = ACTIONS(61), [anon_sym_struct] = ACTIONS(63), [anon_sym_type] = ACTIONS(65), @@ -12370,65 +13303,65 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_try] = ACTIONS(79), }, [STATE(31)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(515), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(515), - [sym_import_statement] = STATE(515), - [sym_macro_export] = STATE(515), - [sym_macro_definition] = STATE(515), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(515), - [sym_define_statement] = STATE(515), - [sym_assignment_statement] = STATE(515), - [sym_compound_assignment_statement] = STATE(515), - [sym_if_statement] = STATE(515), - [sym_while_statement] = STATE(515), - [sym_for_statement] = STATE(515), - [sym_function_definition] = STATE(515), - [sym_struct_definition] = STATE(515), - [sym_type_alias_definition] = STATE(515), - [sym_trait_definition] = STATE(515), - [sym_impl_definition] = STATE(515), - [sym_return_statement] = STATE(515), - [sym_break_statement] = STATE(515), - [sym_continue_statement] = STATE(515), - [sym_expression_statement] = STATE(515), - [sym_go_statement] = STATE(515), - [sym_try_statement] = STATE(515), - [sym_block] = STATE(515), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(575), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(575), + [sym_import_statement] = STATE(575), + [sym_macro_export] = STATE(575), + [sym_macro_definition] = STATE(575), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(575), + [sym_define_statement] = STATE(575), + [sym_assignment_statement] = STATE(575), + [sym_compound_assignment_statement] = STATE(575), + [sym_if_statement] = STATE(575), + [sym_while_statement] = STATE(575), + [sym_for_statement] = STATE(575), + [sym_function_definition] = STATE(575), + [sym_struct_definition] = STATE(575), + [sym_type_alias_definition] = STATE(575), + [sym_trait_definition] = STATE(575), + [sym_impl_definition] = STATE(575), + [sym_return_statement] = STATE(575), + [sym_break_statement] = STATE(575), + [sym_continue_statement] = STATE(575), + [sym_expression_statement] = STATE(575), + [sym_go_statement] = STATE(575), + [sym_try_statement] = STATE(575), + [sym_block] = STATE(575), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12442,94 +13375,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(276), + [anon_sym_LBRACE] = ACTIONS(533), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(278), + [anon_sym_if] = ACTIONS(535), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(49), - [anon_sym_export] = ACTIONS(51), - [anon_sym_macro_rules] = ACTIONS(53), - [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(280), - [anon_sym_for] = ACTIONS(282), - [anon_sym_fn] = ACTIONS(61), - [anon_sym_struct] = ACTIONS(63), - [anon_sym_type] = ACTIONS(65), - [anon_sym_trait] = ACTIONS(67), - [anon_sym_impl] = ACTIONS(69), - [anon_sym_return] = ACTIONS(71), - [anon_sym_break] = ACTIONS(73), - [anon_sym_continue] = ACTIONS(75), - [anon_sym_go] = ACTIONS(77), - [anon_sym_try] = ACTIONS(79), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(545), + [anon_sym_for] = ACTIONS(547), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(32)] = { - [sym_identifier] = STATE(662), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1413), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1413), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(515), - [sym_attribute] = STATE(873), - [sym_attributed_item] = STATE(515), - [sym_import_statement] = STATE(515), - [sym_macro_export] = STATE(515), - [sym_macro_definition] = STATE(515), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(515), - [sym_define_statement] = STATE(515), - [sym_assignment_statement] = STATE(515), - [sym_compound_assignment_statement] = STATE(515), - [sym_if_statement] = STATE(515), - [sym_while_statement] = STATE(515), - [sym_for_statement] = STATE(515), - [sym_function_definition] = STATE(515), - [sym_struct_definition] = STATE(515), - [sym_type_alias_definition] = STATE(515), - [sym_trait_definition] = STATE(515), - [sym_impl_definition] = STATE(515), - [sym_return_statement] = STATE(515), - [sym_break_statement] = STATE(515), - [sym_continue_statement] = STATE(515), - [sym_expression_statement] = STATE(515), - [sym_go_statement] = STATE(515), - [sym_try_statement] = STATE(515), - [sym_block] = STATE(515), - [aux_sym_attributed_item_repeat1] = STATE(873), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(539), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(539), + [sym_import_statement] = STATE(539), + [sym_macro_export] = STATE(539), + [sym_macro_definition] = STATE(539), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(539), + [sym_define_statement] = STATE(539), + [sym_assignment_statement] = STATE(539), + [sym_compound_assignment_statement] = STATE(539), + [sym_if_statement] = STATE(539), + [sym_while_statement] = STATE(539), + [sym_for_statement] = STATE(539), + [sym_function_definition] = STATE(539), + [sym_struct_definition] = STATE(539), + [sym_type_alias_definition] = STATE(539), + [sym_trait_definition] = STATE(539), + [sym_impl_definition] = STATE(539), + [sym_return_statement] = STATE(539), + [sym_break_statement] = STATE(539), + [sym_continue_statement] = STATE(539), + [sym_expression_statement] = STATE(539), + [sym_go_statement] = STATE(539), + [sym_try_statement] = STATE(539), + [sym_block] = STATE(539), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12543,94 +13477,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(27), + [anon_sym_LBRACE] = ACTIONS(569), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(35), + [anon_sym_if] = ACTIONS(571), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(49), - [anon_sym_export] = ACTIONS(51), - [anon_sym_macro_rules] = ACTIONS(53), - [anon_sym_let] = ACTIONS(55), - [anon_sym_while] = ACTIONS(57), - [anon_sym_for] = ACTIONS(59), - [anon_sym_fn] = ACTIONS(61), - [anon_sym_struct] = ACTIONS(63), - [anon_sym_type] = ACTIONS(65), - [anon_sym_trait] = ACTIONS(67), - [anon_sym_impl] = ACTIONS(69), - [anon_sym_return] = ACTIONS(71), - [anon_sym_break] = ACTIONS(73), - [anon_sym_continue] = ACTIONS(75), - [anon_sym_go] = ACTIONS(77), - [anon_sym_try] = ACTIONS(79), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(573), + [anon_sym_for] = ACTIONS(575), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(33)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(458), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(458), - [sym_import_statement] = STATE(458), - [sym_macro_export] = STATE(458), - [sym_macro_definition] = STATE(458), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(458), - [sym_define_statement] = STATE(458), - [sym_assignment_statement] = STATE(458), - [sym_compound_assignment_statement] = STATE(458), - [sym_if_statement] = STATE(458), - [sym_while_statement] = STATE(458), - [sym_for_statement] = STATE(458), - [sym_function_definition] = STATE(458), - [sym_struct_definition] = STATE(458), - [sym_type_alias_definition] = STATE(458), - [sym_trait_definition] = STATE(458), - [sym_impl_definition] = STATE(458), - [sym_return_statement] = STATE(458), - [sym_break_statement] = STATE(458), - [sym_continue_statement] = STATE(458), - [sym_expression_statement] = STATE(458), - [sym_go_statement] = STATE(458), - [sym_try_statement] = STATE(458), - [sym_block] = STATE(458), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(582), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(582), + [sym_import_statement] = STATE(582), + [sym_macro_export] = STATE(582), + [sym_macro_definition] = STATE(582), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(582), + [sym_define_statement] = STATE(582), + [sym_assignment_statement] = STATE(582), + [sym_compound_assignment_statement] = STATE(582), + [sym_if_statement] = STATE(582), + [sym_while_statement] = STATE(582), + [sym_for_statement] = STATE(582), + [sym_function_definition] = STATE(582), + [sym_struct_definition] = STATE(582), + [sym_type_alias_definition] = STATE(582), + [sym_trait_definition] = STATE(582), + [sym_impl_definition] = STATE(582), + [sym_return_statement] = STATE(582), + [sym_break_statement] = STATE(582), + [sym_continue_statement] = STATE(582), + [sym_expression_statement] = STATE(582), + [sym_go_statement] = STATE(582), + [sym_try_statement] = STATE(582), + [sym_block] = STATE(582), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12644,94 +13579,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(566), + [anon_sym_LBRACE] = ACTIONS(533), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(568), + [anon_sym_if] = ACTIONS(535), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(570), - [anon_sym_for] = ACTIONS(572), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(545), + [anon_sym_for] = ACTIONS(547), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(34)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(475), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(475), - [sym_import_statement] = STATE(475), - [sym_macro_export] = STATE(475), - [sym_macro_definition] = STATE(475), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(475), - [sym_define_statement] = STATE(475), - [sym_assignment_statement] = STATE(475), - [sym_compound_assignment_statement] = STATE(475), - [sym_if_statement] = STATE(475), - [sym_while_statement] = STATE(475), - [sym_for_statement] = STATE(475), - [sym_function_definition] = STATE(475), - [sym_struct_definition] = STATE(475), - [sym_type_alias_definition] = STATE(475), - [sym_trait_definition] = STATE(475), - [sym_impl_definition] = STATE(475), - [sym_return_statement] = STATE(475), - [sym_break_statement] = STATE(475), - [sym_continue_statement] = STATE(475), - [sym_expression_statement] = STATE(475), - [sym_go_statement] = STATE(475), - [sym_try_statement] = STATE(475), - [sym_block] = STATE(475), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(593), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(593), + [sym_import_statement] = STATE(593), + [sym_macro_export] = STATE(593), + [sym_macro_definition] = STATE(593), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(593), + [sym_define_statement] = STATE(593), + [sym_assignment_statement] = STATE(593), + [sym_compound_assignment_statement] = STATE(593), + [sym_if_statement] = STATE(593), + [sym_while_statement] = STATE(593), + [sym_for_statement] = STATE(593), + [sym_function_definition] = STATE(593), + [sym_struct_definition] = STATE(593), + [sym_type_alias_definition] = STATE(593), + [sym_trait_definition] = STATE(593), + [sym_impl_definition] = STATE(593), + [sym_return_statement] = STATE(593), + [sym_break_statement] = STATE(593), + [sym_continue_statement] = STATE(593), + [sym_expression_statement] = STATE(593), + [sym_go_statement] = STATE(593), + [sym_try_statement] = STATE(593), + [sym_block] = STATE(593), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12745,94 +13681,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(566), + [anon_sym_LBRACE] = ACTIONS(533), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(568), + [anon_sym_if] = ACTIONS(535), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(570), - [anon_sym_for] = ACTIONS(572), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(545), + [anon_sym_for] = ACTIONS(547), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(35)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(481), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(481), - [sym_import_statement] = STATE(481), - [sym_macro_export] = STATE(481), - [sym_macro_definition] = STATE(481), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(481), - [sym_define_statement] = STATE(481), - [sym_assignment_statement] = STATE(481), - [sym_compound_assignment_statement] = STATE(481), - [sym_if_statement] = STATE(481), - [sym_while_statement] = STATE(481), - [sym_for_statement] = STATE(481), - [sym_function_definition] = STATE(481), - [sym_struct_definition] = STATE(481), - [sym_type_alias_definition] = STATE(481), - [sym_trait_definition] = STATE(481), - [sym_impl_definition] = STATE(481), - [sym_return_statement] = STATE(481), - [sym_break_statement] = STATE(481), - [sym_continue_statement] = STATE(481), - [sym_expression_statement] = STATE(481), - [sym_go_statement] = STATE(481), - [sym_try_statement] = STATE(481), - [sym_block] = STATE(481), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(649), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(649), + [sym_import_statement] = STATE(649), + [sym_macro_export] = STATE(649), + [sym_macro_definition] = STATE(649), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(649), + [sym_define_statement] = STATE(649), + [sym_assignment_statement] = STATE(649), + [sym_compound_assignment_statement] = STATE(649), + [sym_if_statement] = STATE(649), + [sym_while_statement] = STATE(649), + [sym_for_statement] = STATE(649), + [sym_function_definition] = STATE(649), + [sym_struct_definition] = STATE(649), + [sym_type_alias_definition] = STATE(649), + [sym_trait_definition] = STATE(649), + [sym_impl_definition] = STATE(649), + [sym_return_statement] = STATE(649), + [sym_break_statement] = STATE(649), + [sym_continue_statement] = STATE(649), + [sym_expression_statement] = STATE(649), + [sym_go_statement] = STATE(649), + [sym_try_statement] = STATE(649), + [sym_block] = STATE(649), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12846,94 +13783,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(566), + [anon_sym_LBRACE] = ACTIONS(533), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(568), + [anon_sym_if] = ACTIONS(535), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(570), - [anon_sym_for] = ACTIONS(572), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(545), + [anon_sym_for] = ACTIONS(547), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(36)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(492), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(492), - [sym_import_statement] = STATE(492), - [sym_macro_export] = STATE(492), - [sym_macro_definition] = STATE(492), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(492), - [sym_define_statement] = STATE(492), - [sym_assignment_statement] = STATE(492), - [sym_compound_assignment_statement] = STATE(492), - [sym_if_statement] = STATE(492), - [sym_while_statement] = STATE(492), - [sym_for_statement] = STATE(492), - [sym_function_definition] = STATE(492), - [sym_struct_definition] = STATE(492), - [sym_type_alias_definition] = STATE(492), - [sym_trait_definition] = STATE(492), - [sym_impl_definition] = STATE(492), - [sym_return_statement] = STATE(492), - [sym_break_statement] = STATE(492), - [sym_continue_statement] = STATE(492), - [sym_expression_statement] = STATE(492), - [sym_go_statement] = STATE(492), - [sym_try_statement] = STATE(492), - [sym_block] = STATE(492), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(650), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(650), + [sym_import_statement] = STATE(650), + [sym_macro_export] = STATE(650), + [sym_macro_definition] = STATE(650), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(650), + [sym_define_statement] = STATE(650), + [sym_assignment_statement] = STATE(650), + [sym_compound_assignment_statement] = STATE(650), + [sym_if_statement] = STATE(650), + [sym_while_statement] = STATE(650), + [sym_for_statement] = STATE(650), + [sym_function_definition] = STATE(650), + [sym_struct_definition] = STATE(650), + [sym_type_alias_definition] = STATE(650), + [sym_trait_definition] = STATE(650), + [sym_impl_definition] = STATE(650), + [sym_return_statement] = STATE(650), + [sym_break_statement] = STATE(650), + [sym_continue_statement] = STATE(650), + [sym_expression_statement] = STATE(650), + [sym_go_statement] = STATE(650), + [sym_try_statement] = STATE(650), + [sym_block] = STATE(650), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -12947,94 +13885,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(566), + [anon_sym_LBRACE] = ACTIONS(27), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(568), + [anon_sym_if] = ACTIONS(35), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(570), - [anon_sym_for] = ACTIONS(572), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(49), + [anon_sym_export] = ACTIONS(51), + [anon_sym_macro_rules] = ACTIONS(53), + [anon_sym_let] = ACTIONS(55), + [anon_sym_while] = ACTIONS(57), + [anon_sym_for] = ACTIONS(59), + [anon_sym_fn] = ACTIONS(61), + [anon_sym_struct] = ACTIONS(63), + [anon_sym_type] = ACTIONS(65), + [anon_sym_trait] = ACTIONS(67), + [anon_sym_impl] = ACTIONS(69), + [anon_sym_return] = ACTIONS(71), + [anon_sym_break] = ACTIONS(73), + [anon_sym_continue] = ACTIONS(75), + [anon_sym_go] = ACTIONS(77), + [anon_sym_try] = ACTIONS(79), }, [STATE(37)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(516), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(516), - [sym_import_statement] = STATE(516), - [sym_macro_export] = STATE(516), - [sym_macro_definition] = STATE(516), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(516), - [sym_define_statement] = STATE(516), - [sym_assignment_statement] = STATE(516), - [sym_compound_assignment_statement] = STATE(516), - [sym_if_statement] = STATE(516), - [sym_while_statement] = STATE(516), - [sym_for_statement] = STATE(516), - [sym_function_definition] = STATE(516), - [sym_struct_definition] = STATE(516), - [sym_type_alias_definition] = STATE(516), - [sym_trait_definition] = STATE(516), - [sym_impl_definition] = STATE(516), - [sym_return_statement] = STATE(516), - [sym_break_statement] = STATE(516), - [sym_continue_statement] = STATE(516), - [sym_expression_statement] = STATE(516), - [sym_go_statement] = STATE(516), - [sym_try_statement] = STATE(516), - [sym_block] = STATE(516), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(575), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(575), + [sym_import_statement] = STATE(575), + [sym_macro_export] = STATE(575), + [sym_macro_definition] = STATE(575), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(575), + [sym_define_statement] = STATE(575), + [sym_assignment_statement] = STATE(575), + [sym_compound_assignment_statement] = STATE(575), + [sym_if_statement] = STATE(575), + [sym_while_statement] = STATE(575), + [sym_for_statement] = STATE(575), + [sym_function_definition] = STATE(575), + [sym_struct_definition] = STATE(575), + [sym_type_alias_definition] = STATE(575), + [sym_trait_definition] = STATE(575), + [sym_impl_definition] = STATE(575), + [sym_return_statement] = STATE(575), + [sym_break_statement] = STATE(575), + [sym_continue_statement] = STATE(575), + [sym_expression_statement] = STATE(575), + [sym_go_statement] = STATE(575), + [sym_try_statement] = STATE(575), + [sym_block] = STATE(575), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -13048,94 +13987,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(566), + [anon_sym_LBRACE] = ACTIONS(569), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(568), + [anon_sym_if] = ACTIONS(571), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(570), - [anon_sym_for] = ACTIONS(572), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(573), + [anon_sym_for] = ACTIONS(575), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(38)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(458), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(458), - [sym_import_statement] = STATE(458), - [sym_macro_export] = STATE(458), - [sym_macro_definition] = STATE(458), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(458), - [sym_define_statement] = STATE(458), - [sym_assignment_statement] = STATE(458), - [sym_compound_assignment_statement] = STATE(458), - [sym_if_statement] = STATE(458), - [sym_while_statement] = STATE(458), - [sym_for_statement] = STATE(458), - [sym_function_definition] = STATE(458), - [sym_struct_definition] = STATE(458), - [sym_type_alias_definition] = STATE(458), - [sym_trait_definition] = STATE(458), - [sym_impl_definition] = STATE(458), - [sym_return_statement] = STATE(458), - [sym_break_statement] = STATE(458), - [sym_continue_statement] = STATE(458), - [sym_expression_statement] = STATE(458), - [sym_go_statement] = STATE(458), - [sym_try_statement] = STATE(458), - [sym_block] = STATE(458), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(629), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(629), + [sym_import_statement] = STATE(629), + [sym_macro_export] = STATE(629), + [sym_macro_definition] = STATE(629), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(629), + [sym_define_statement] = STATE(629), + [sym_assignment_statement] = STATE(629), + [sym_compound_assignment_statement] = STATE(629), + [sym_if_statement] = STATE(629), + [sym_while_statement] = STATE(629), + [sym_for_statement] = STATE(629), + [sym_function_definition] = STATE(629), + [sym_struct_definition] = STATE(629), + [sym_type_alias_definition] = STATE(629), + [sym_trait_definition] = STATE(629), + [sym_impl_definition] = STATE(629), + [sym_return_statement] = STATE(629), + [sym_break_statement] = STATE(629), + [sym_continue_statement] = STATE(629), + [sym_expression_statement] = STATE(629), + [sym_go_statement] = STATE(629), + [sym_try_statement] = STATE(629), + [sym_block] = STATE(629), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -13149,94 +14089,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(530), + [anon_sym_LBRACE] = ACTIONS(569), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(532), + [anon_sym_if] = ACTIONS(571), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(542), - [anon_sym_for] = ACTIONS(544), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(573), + [anon_sym_for] = ACTIONS(575), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(39)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(506), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(506), - [sym_import_statement] = STATE(506), - [sym_macro_export] = STATE(506), - [sym_macro_definition] = STATE(506), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(506), - [sym_define_statement] = STATE(506), - [sym_assignment_statement] = STATE(506), - [sym_compound_assignment_statement] = STATE(506), - [sym_if_statement] = STATE(506), - [sym_while_statement] = STATE(506), - [sym_for_statement] = STATE(506), - [sym_function_definition] = STATE(506), - [sym_struct_definition] = STATE(506), - [sym_type_alias_definition] = STATE(506), - [sym_trait_definition] = STATE(506), - [sym_impl_definition] = STATE(506), - [sym_return_statement] = STATE(506), - [sym_break_statement] = STATE(506), - [sym_continue_statement] = STATE(506), - [sym_expression_statement] = STATE(506), - [sym_go_statement] = STATE(506), - [sym_try_statement] = STATE(506), - [sym_block] = STATE(506), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(582), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(582), + [sym_import_statement] = STATE(582), + [sym_macro_export] = STATE(582), + [sym_macro_definition] = STATE(582), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(582), + [sym_define_statement] = STATE(582), + [sym_assignment_statement] = STATE(582), + [sym_compound_assignment_statement] = STATE(582), + [sym_if_statement] = STATE(582), + [sym_while_statement] = STATE(582), + [sym_for_statement] = STATE(582), + [sym_function_definition] = STATE(582), + [sym_struct_definition] = STATE(582), + [sym_type_alias_definition] = STATE(582), + [sym_trait_definition] = STATE(582), + [sym_impl_definition] = STATE(582), + [sym_return_statement] = STATE(582), + [sym_break_statement] = STATE(582), + [sym_continue_statement] = STATE(582), + [sym_expression_statement] = STATE(582), + [sym_go_statement] = STATE(582), + [sym_try_statement] = STATE(582), + [sym_block] = STATE(582), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -13250,94 +14191,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(530), + [anon_sym_LBRACE] = ACTIONS(569), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(532), + [anon_sym_if] = ACTIONS(571), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(542), - [anon_sym_for] = ACTIONS(544), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(573), + [anon_sym_for] = ACTIONS(575), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(40)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(492), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(492), - [sym_import_statement] = STATE(492), - [sym_macro_export] = STATE(492), - [sym_macro_definition] = STATE(492), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(492), - [sym_define_statement] = STATE(492), - [sym_assignment_statement] = STATE(492), - [sym_compound_assignment_statement] = STATE(492), - [sym_if_statement] = STATE(492), - [sym_while_statement] = STATE(492), - [sym_for_statement] = STATE(492), - [sym_function_definition] = STATE(492), - [sym_struct_definition] = STATE(492), - [sym_type_alias_definition] = STATE(492), - [sym_trait_definition] = STATE(492), - [sym_impl_definition] = STATE(492), - [sym_return_statement] = STATE(492), - [sym_break_statement] = STATE(492), - [sym_continue_statement] = STATE(492), - [sym_expression_statement] = STATE(492), - [sym_go_statement] = STATE(492), - [sym_try_statement] = STATE(492), - [sym_block] = STATE(492), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(593), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(593), + [sym_import_statement] = STATE(593), + [sym_macro_export] = STATE(593), + [sym_macro_definition] = STATE(593), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(593), + [sym_define_statement] = STATE(593), + [sym_assignment_statement] = STATE(593), + [sym_compound_assignment_statement] = STATE(593), + [sym_if_statement] = STATE(593), + [sym_while_statement] = STATE(593), + [sym_for_statement] = STATE(593), + [sym_function_definition] = STATE(593), + [sym_struct_definition] = STATE(593), + [sym_type_alias_definition] = STATE(593), + [sym_trait_definition] = STATE(593), + [sym_impl_definition] = STATE(593), + [sym_return_statement] = STATE(593), + [sym_break_statement] = STATE(593), + [sym_continue_statement] = STATE(593), + [sym_expression_statement] = STATE(593), + [sym_go_statement] = STATE(593), + [sym_try_statement] = STATE(593), + [sym_block] = STATE(593), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -13351,94 +14293,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(530), + [anon_sym_LBRACE] = ACTIONS(569), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(532), + [anon_sym_if] = ACTIONS(571), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(542), - [anon_sym_for] = ACTIONS(544), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(573), + [anon_sym_for] = ACTIONS(575), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(41)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(481), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(481), - [sym_import_statement] = STATE(481), - [sym_macro_export] = STATE(481), - [sym_macro_definition] = STATE(481), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(481), - [sym_define_statement] = STATE(481), - [sym_assignment_statement] = STATE(481), - [sym_compound_assignment_statement] = STATE(481), - [sym_if_statement] = STATE(481), - [sym_while_statement] = STATE(481), - [sym_for_statement] = STATE(481), - [sym_function_definition] = STATE(481), - [sym_struct_definition] = STATE(481), - [sym_type_alias_definition] = STATE(481), - [sym_trait_definition] = STATE(481), - [sym_impl_definition] = STATE(481), - [sym_return_statement] = STATE(481), - [sym_break_statement] = STATE(481), - [sym_continue_statement] = STATE(481), - [sym_expression_statement] = STATE(481), - [sym_go_statement] = STATE(481), - [sym_try_statement] = STATE(481), - [sym_block] = STATE(481), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(719), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1521), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1521), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(634), + [sym_attribute] = STATE(967), + [sym_attributed_item] = STATE(634), + [sym_import_statement] = STATE(634), + [sym_macro_export] = STATE(634), + [sym_macro_definition] = STATE(634), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(634), + [sym_define_statement] = STATE(634), + [sym_assignment_statement] = STATE(634), + [sym_compound_assignment_statement] = STATE(634), + [sym_if_statement] = STATE(634), + [sym_while_statement] = STATE(634), + [sym_for_statement] = STATE(634), + [sym_function_definition] = STATE(634), + [sym_struct_definition] = STATE(634), + [sym_type_alias_definition] = STATE(634), + [sym_trait_definition] = STATE(634), + [sym_impl_definition] = STATE(634), + [sym_return_statement] = STATE(634), + [sym_break_statement] = STATE(634), + [sym_continue_statement] = STATE(634), + [sym_expression_statement] = STATE(634), + [sym_go_statement] = STATE(634), + [sym_try_statement] = STATE(634), + [sym_block] = STATE(634), + [aux_sym_attributed_item_repeat1] = STATE(967), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -13452,94 +14395,95 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(530), + [anon_sym_LBRACE] = ACTIONS(569), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(532), + [anon_sym_if] = ACTIONS(571), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(542), - [anon_sym_for] = ACTIONS(544), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(537), + [anon_sym_export] = ACTIONS(539), + [anon_sym_macro_rules] = ACTIONS(541), + [anon_sym_let] = ACTIONS(543), + [anon_sym_while] = ACTIONS(573), + [anon_sym_for] = ACTIONS(575), + [anon_sym_fn] = ACTIONS(549), + [anon_sym_struct] = ACTIONS(551), + [anon_sym_type] = ACTIONS(553), + [anon_sym_trait] = ACTIONS(555), + [anon_sym_impl] = ACTIONS(557), + [anon_sym_return] = ACTIONS(559), + [anon_sym_break] = ACTIONS(561), + [anon_sym_continue] = ACTIONS(563), + [anon_sym_go] = ACTIONS(565), + [anon_sym_try] = ACTIONS(567), }, [STATE(42)] = { - [sym_identifier] = STATE(663), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__full_expression] = STATE(1425), - [sym__expression] = STATE(678), - [sym_primary_expression] = STATE(678), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(678), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(678), - [sym_nullish_coalescing_expression] = STATE(678), - [sym_range_expression] = STATE(678), - [sym_ternary_expression] = STATE(1425), - [sym_closure] = STATE(678), - [sym_match_expression] = STATE(678), - [sym_spawn_expression] = STATE(678), - [sym_chan_expression] = STATE(678), - [sym_send_expression] = STATE(678), - [sym_recv_expression] = STATE(678), - [sym_select_expression] = STATE(678), - [sym__statement] = STATE(520), - [sym_attribute] = STATE(872), - [sym_attributed_item] = STATE(520), - [sym_import_statement] = STATE(520), - [sym_macro_export] = STATE(520), - [sym_macro_definition] = STATE(520), - [sym_macro_invocation] = STATE(678), - [sym_let_statement] = STATE(520), - [sym_define_statement] = STATE(520), - [sym_assignment_statement] = STATE(520), - [sym_compound_assignment_statement] = STATE(520), - [sym_if_statement] = STATE(520), - [sym_while_statement] = STATE(520), - [sym_for_statement] = STATE(520), - [sym_function_definition] = STATE(520), - [sym_struct_definition] = STATE(520), - [sym_type_alias_definition] = STATE(520), - [sym_trait_definition] = STATE(520), - [sym_impl_definition] = STATE(520), - [sym_return_statement] = STATE(520), - [sym_break_statement] = STATE(520), - [sym_continue_statement] = STATE(520), - [sym_expression_statement] = STATE(520), - [sym_go_statement] = STATE(520), - [sym_try_statement] = STATE(520), - [sym_block] = STATE(520), - [aux_sym_attributed_item_repeat1] = STATE(872), + [sym_identifier] = STATE(718), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__full_expression] = STATE(1648), + [sym__expression] = STATE(731), + [sym_primary_expression] = STATE(731), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(731), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(731), + [sym_nullish_coalescing_expression] = STATE(731), + [sym_range_expression] = STATE(731), + [sym_ternary_expression] = STATE(1648), + [sym_closure] = STATE(731), + [sym_match_expression] = STATE(731), + [sym_spawn_expression] = STATE(731), + [sym_chan_expression] = STATE(731), + [sym_send_expression] = STATE(731), + [sym_recv_expression] = STATE(731), + [sym_select_expression] = STATE(731), + [sym__statement] = STATE(664), + [sym_attribute] = STATE(968), + [sym_attributed_item] = STATE(664), + [sym_import_statement] = STATE(664), + [sym_macro_export] = STATE(664), + [sym_macro_definition] = STATE(664), + [sym_macro_invocation] = STATE(731), + [sym_let_statement] = STATE(664), + [sym_define_statement] = STATE(664), + [sym_assignment_statement] = STATE(664), + [sym_compound_assignment_statement] = STATE(664), + [sym_if_statement] = STATE(664), + [sym_while_statement] = STATE(664), + [sym_for_statement] = STATE(664), + [sym_function_definition] = STATE(664), + [sym_struct_definition] = STATE(664), + [sym_type_alias_definition] = STATE(664), + [sym_trait_definition] = STATE(664), + [sym_impl_definition] = STATE(664), + [sym_return_statement] = STATE(664), + [sym_break_statement] = STATE(664), + [sym_continue_statement] = STATE(664), + [sym_expression_statement] = STATE(664), + [sym_go_statement] = STATE(664), + [sym_try_statement] = STATE(664), + [sym_block] = STATE(664), + [aux_sym_attributed_item_repeat1] = STATE(968), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(7), @@ -13553,162 +14497,168 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [sym_raw_string] = ACTIONS(21), [anon_sym_LPAREN] = ACTIONS(23), [anon_sym_LBRACK] = ACTIONS(25), - [anon_sym_LBRACE] = ACTIONS(530), + [anon_sym_LBRACE] = ACTIONS(27), [anon_sym_BANG] = ACTIONS(29), + [anon_sym_TILDE] = ACTIONS(29), [anon_sym_PIPE] = ACTIONS(31), [anon_sym_match] = ACTIONS(33), - [anon_sym_if] = ACTIONS(532), + [anon_sym_if] = ACTIONS(35), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), [anon_sym_recv] = ACTIONS(43), [anon_sym_select] = ACTIONS(45), [anon_sym_POUND] = ACTIONS(47), - [anon_sym_use] = ACTIONS(534), - [anon_sym_export] = ACTIONS(536), - [anon_sym_macro_rules] = ACTIONS(538), - [anon_sym_let] = ACTIONS(540), - [anon_sym_while] = ACTIONS(542), - [anon_sym_for] = ACTIONS(544), - [anon_sym_fn] = ACTIONS(546), - [anon_sym_struct] = ACTIONS(548), - [anon_sym_type] = ACTIONS(550), - [anon_sym_trait] = ACTIONS(552), - [anon_sym_impl] = ACTIONS(554), - [anon_sym_return] = ACTIONS(556), - [anon_sym_break] = ACTIONS(558), - [anon_sym_continue] = ACTIONS(560), - [anon_sym_go] = ACTIONS(562), - [anon_sym_try] = ACTIONS(564), + [anon_sym_use] = ACTIONS(49), + [anon_sym_export] = ACTIONS(51), + [anon_sym_macro_rules] = ACTIONS(53), + [anon_sym_let] = ACTIONS(55), + [anon_sym_while] = ACTIONS(57), + [anon_sym_for] = ACTIONS(59), + [anon_sym_fn] = ACTIONS(61), + [anon_sym_struct] = ACTIONS(63), + [anon_sym_type] = ACTIONS(65), + [anon_sym_trait] = ACTIONS(67), + [anon_sym_impl] = ACTIONS(69), + [anon_sym_return] = ACTIONS(71), + [anon_sym_break] = ACTIONS(73), + [anon_sym_continue] = ACTIONS(75), + [anon_sym_go] = ACTIONS(77), + [anon_sym_try] = ACTIONS(79), }, [STATE(43)] = { - [sym_identifier] = STATE(86), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__expression] = STATE(114), - [sym_primary_expression] = STATE(114), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(114), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(114), - [sym_nullish_coalescing_expression] = STATE(114), - [sym_range_expression] = STATE(114), - [sym_closure] = STATE(114), - [sym_match_expression] = STATE(114), - [sym_spawn_expression] = STATE(114), - [sym_chan_expression] = STATE(114), - [sym_send_expression] = STATE(114), - [sym_recv_expression] = STATE(114), - [sym_select_expression] = STATE(114), - [sym_macro_invocation] = STATE(114), + [sym_identifier] = STATE(60), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__expression] = STATE(117), + [sym_primary_expression] = STATE(117), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(117), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(117), + [sym_nullish_coalescing_expression] = STATE(117), + [sym_range_expression] = STATE(117), + [sym_closure] = STATE(117), + [sym_match_expression] = STATE(117), + [sym_spawn_expression] = STATE(117), + [sym_chan_expression] = STATE(117), + [sym_send_expression] = STATE(117), + [sym_recv_expression] = STATE(117), + [sym_select_expression] = STATE(117), + [sym_macro_invocation] = STATE(117), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(574), - [sym_integer_literal] = ACTIONS(574), - [sym_float_literal] = ACTIONS(576), - [anon_sym_true] = ACTIONS(574), - [anon_sym_false] = ACTIONS(574), - [anon_sym_nil] = ACTIONS(574), - [anon_sym_DQUOTE] = ACTIONS(576), - [anon_sym_SQUOTE] = ACTIONS(576), - [sym_raw_string] = ACTIONS(576), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(576), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(574), - [anon_sym_match] = ACTIONS(574), - [anon_sym_if] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(574), - [anon_sym_chan] = ACTIONS(574), - [anon_sym_send] = ACTIONS(574), - [anon_sym_recv] = ACTIONS(574), - [anon_sym_select] = ACTIONS(574), - [anon_sym_POUND] = ACTIONS(576), - [anon_sym_use] = ACTIONS(574), - [anon_sym_export] = ACTIONS(574), - [anon_sym_macro_rules] = ACTIONS(574), - [anon_sym_let] = ACTIONS(574), - [anon_sym_while] = ACTIONS(574), - [anon_sym_for] = ACTIONS(574), - [anon_sym_fn] = ACTIONS(574), - [anon_sym_struct] = ACTIONS(574), - [anon_sym_type] = ACTIONS(574), - [anon_sym_trait] = ACTIONS(574), - [anon_sym_impl] = ACTIONS(574), - [anon_sym_return] = ACTIONS(574), - [anon_sym_break] = ACTIONS(574), - [anon_sym_continue] = ACTIONS(574), - [anon_sym_go] = ACTIONS(574), - [anon_sym_try] = ACTIONS(574), + [aux_sym_identifier_token1] = ACTIONS(577), + [sym_integer_literal] = ACTIONS(577), + [sym_float_literal] = ACTIONS(579), + [anon_sym_true] = ACTIONS(577), + [anon_sym_false] = ACTIONS(577), + [anon_sym_nil] = ACTIONS(577), + [anon_sym_DQUOTE] = ACTIONS(579), + [anon_sym_SQUOTE] = ACTIONS(579), + [sym_raw_string] = ACTIONS(579), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(579), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(579), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), + [anon_sym_match] = ACTIONS(577), + [anon_sym_if] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(577), + [anon_sym_chan] = ACTIONS(577), + [anon_sym_send] = ACTIONS(577), + [anon_sym_recv] = ACTIONS(577), + [anon_sym_select] = ACTIONS(577), + [anon_sym_POUND] = ACTIONS(579), + [anon_sym_use] = ACTIONS(577), + [anon_sym_export] = ACTIONS(577), + [anon_sym_macro_rules] = ACTIONS(577), + [anon_sym_let] = ACTIONS(577), + [anon_sym_while] = ACTIONS(577), + [anon_sym_for] = ACTIONS(577), + [anon_sym_fn] = ACTIONS(577), + [anon_sym_struct] = ACTIONS(577), + [anon_sym_type] = ACTIONS(577), + [anon_sym_trait] = ACTIONS(577), + [anon_sym_impl] = ACTIONS(577), + [anon_sym_return] = ACTIONS(577), + [anon_sym_break] = ACTIONS(577), + [anon_sym_continue] = ACTIONS(577), + [anon_sym_go] = ACTIONS(577), + [anon_sym_try] = ACTIONS(577), }, [STATE(44)] = { - [sym_identifier] = STATE(86), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__expression] = STATE(669), - [sym_primary_expression] = STATE(669), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(669), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(669), - [sym_nullish_coalescing_expression] = STATE(669), - [sym_range_expression] = STATE(669), - [sym_closure] = STATE(669), - [sym_match_expression] = STATE(669), - [sym_spawn_expression] = STATE(669), - [sym_chan_expression] = STATE(669), - [sym_send_expression] = STATE(669), - [sym_recv_expression] = STATE(669), - [sym_select_expression] = STATE(669), - [sym_macro_invocation] = STATE(669), + [sym_identifier] = STATE(60), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__expression] = STATE(727), + [sym_primary_expression] = STATE(727), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(727), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(727), + [sym_nullish_coalescing_expression] = STATE(727), + [sym_range_expression] = STATE(727), + [sym_closure] = STATE(727), + [sym_match_expression] = STATE(727), + [sym_spawn_expression] = STATE(727), + [sym_chan_expression] = STATE(727), + [sym_send_expression] = STATE(727), + [sym_recv_expression] = STATE(727), + [sym_select_expression] = STATE(727), + [sym_macro_invocation] = STATE(727), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(578), + [aux_sym_identifier_token1] = ACTIONS(581), [sym_integer_literal] = ACTIONS(9), [sym_float_literal] = ACTIONS(11), [anon_sym_true] = ACTIONS(13), @@ -13717,39 +14667,44 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_RBRACE] = ACTIONS(576), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_RPAREN] = ACTIONS(576), - [anon_sym_COMMA] = ACTIONS(576), - [anon_sym_COLON] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_RBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(580), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(582), + [anon_sym_RBRACE] = ACTIONS(579), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_RPAREN] = ACTIONS(579), + [anon_sym_COMMA] = ACTIONS(579), + [anon_sym_COLON] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_RBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(583), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(29), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), [anon_sym_match] = ACTIONS(33), - [anon_sym_EQ_GT] = ACTIONS(576), - [anon_sym_SEMI] = ACTIONS(576), + [anon_sym_EQ_GT] = ACTIONS(579), + [anon_sym_SEMI] = ACTIONS(579), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), [anon_sym_send] = ACTIONS(41), @@ -13757,663 +14712,703 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_select] = ACTIONS(45), }, [STATE(45)] = { - [sym_identifier] = STATE(600), - [sym_type_identifier] = STATE(1405), - [sym_boolean_literal] = STATE(557), - [sym_nil_literal] = STATE(557), - [sym_string_literal] = STATE(557), - [sym_double_string] = STATE(611), - [sym_single_string] = STATE(611), - [sym__expression] = STATE(556), - [sym_primary_expression] = STATE(556), - [sym_parenthesized_expression] = STATE(557), - [sym_call_expression] = STATE(557), - [sym_field_access] = STATE(557), - [sym_optional_field_access] = STATE(557), - [sym_index_access] = STATE(557), - [sym_optional_index_access] = STATE(557), - [sym_list_expression] = STATE(557), - [sym_map_expression] = STATE(557), - [sym_struct_literal] = STATE(557), - [sym_unary_expression] = STATE(556), - [sym_unwrap_expression] = STATE(557), - [sym_binary_expression] = STATE(556), - [sym_nullish_coalescing_expression] = STATE(556), - [sym_range_expression] = STATE(556), - [sym_closure] = STATE(556), - [sym_match_expression] = STATE(556), - [sym_spawn_expression] = STATE(556), - [sym_chan_expression] = STATE(556), - [sym_send_expression] = STATE(556), - [sym_recv_expression] = STATE(556), - [sym_select_expression] = STATE(556), - [sym_macro_invocation] = STATE(556), + [sym_identifier] = STATE(677), + [sym_type_identifier] = STATE(1578), + [sym_boolean_literal] = STATE(717), + [sym_nil_literal] = STATE(717), + [sym_string_literal] = STATE(717), + [sym_double_string] = STATE(688), + [sym_single_string] = STATE(688), + [sym__expression] = STATE(746), + [sym_primary_expression] = STATE(746), + [sym_parenthesized_expression] = STATE(717), + [sym_call_expression] = STATE(717), + [sym_field_access] = STATE(717), + [sym_optional_field_access] = STATE(717), + [sym_index_access] = STATE(717), + [sym_optional_index_access] = STATE(717), + [sym_list_expression] = STATE(717), + [sym_map_expression] = STATE(717), + [sym_struct_literal] = STATE(717), + [sym_unary_expression] = STATE(746), + [sym_unwrap_expression] = STATE(717), + [sym_binary_expression] = STATE(746), + [sym_nullish_coalescing_expression] = STATE(746), + [sym_range_expression] = STATE(746), + [sym_closure] = STATE(746), + [sym_match_expression] = STATE(746), + [sym_spawn_expression] = STATE(746), + [sym_chan_expression] = STATE(746), + [sym_send_expression] = STATE(746), + [sym_recv_expression] = STATE(746), + [sym_select_expression] = STATE(746), + [sym_macro_invocation] = STATE(746), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(574), - [sym_integer_literal] = ACTIONS(574), - [sym_float_literal] = ACTIONS(576), - [anon_sym_true] = ACTIONS(574), - [anon_sym_false] = ACTIONS(574), - [anon_sym_nil] = ACTIONS(574), - [anon_sym_DQUOTE] = ACTIONS(576), - [anon_sym_SQUOTE] = ACTIONS(576), - [sym_raw_string] = ACTIONS(576), - [anon_sym_RBRACE] = ACTIONS(576), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_COMMA] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(576), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(584), - [anon_sym_match] = ACTIONS(586), - [anon_sym_SEMI] = ACTIONS(576), - [anon_sym__] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(588), - [anon_sym_chan] = ACTIONS(590), - [anon_sym_send] = ACTIONS(592), - [anon_sym_recv] = ACTIONS(594), - [anon_sym_select] = ACTIONS(596), + [aux_sym_identifier_token1] = ACTIONS(581), + [sym_integer_literal] = ACTIONS(585), + [sym_float_literal] = ACTIONS(587), + [anon_sym_true] = ACTIONS(589), + [anon_sym_false] = ACTIONS(589), + [anon_sym_nil] = ACTIONS(591), + [anon_sym_DQUOTE] = ACTIONS(593), + [anon_sym_SQUOTE] = ACTIONS(595), + [sym_raw_string] = ACTIONS(597), + [anon_sym_RBRACE] = ACTIONS(579), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(599), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(601), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), + [anon_sym_match] = ACTIONS(603), + [anon_sym_SEMI] = ACTIONS(579), + [anon_sym_spawn] = ACTIONS(605), + [anon_sym_chan] = ACTIONS(607), + [anon_sym_send] = ACTIONS(609), + [anon_sym_recv] = ACTIONS(611), + [anon_sym_select] = ACTIONS(613), + [anon_sym_case] = ACTIONS(577), + [anon_sym_default] = ACTIONS(577), }, [STATE(46)] = { - [sym_identifier] = STATE(640), - [sym_type_identifier] = STATE(1470), - [sym_boolean_literal] = STATE(638), - [sym_nil_literal] = STATE(638), - [sym_string_literal] = STATE(638), - [sym_double_string] = STATE(658), - [sym_single_string] = STATE(658), - [sym__expression] = STATE(701), - [sym_primary_expression] = STATE(701), - [sym_parenthesized_expression] = STATE(638), - [sym_call_expression] = STATE(638), - [sym_field_access] = STATE(638), - [sym_optional_field_access] = STATE(638), - [sym_index_access] = STATE(638), - [sym_optional_index_access] = STATE(638), - [sym_list_expression] = STATE(638), - [sym_map_expression] = STATE(638), - [sym_struct_literal] = STATE(638), - [sym_unary_expression] = STATE(701), - [sym_unwrap_expression] = STATE(638), - [sym_binary_expression] = STATE(701), - [sym_nullish_coalescing_expression] = STATE(701), - [sym_range_expression] = STATE(701), - [sym_closure] = STATE(701), - [sym_match_expression] = STATE(701), - [sym_spawn_expression] = STATE(701), - [sym_chan_expression] = STATE(701), - [sym_send_expression] = STATE(701), - [sym_recv_expression] = STATE(701), - [sym_select_expression] = STATE(701), - [sym_macro_invocation] = STATE(701), + [sym_identifier] = STATE(609), + [sym_type_identifier] = STATE(1485), + [sym_boolean_literal] = STATE(606), + [sym_nil_literal] = STATE(606), + [sym_string_literal] = STATE(606), + [sym_double_string] = STATE(527), + [sym_single_string] = STATE(527), + [sym__expression] = STATE(514), + [sym_primary_expression] = STATE(514), + [sym_parenthesized_expression] = STATE(606), + [sym_call_expression] = STATE(606), + [sym_field_access] = STATE(606), + [sym_optional_field_access] = STATE(606), + [sym_index_access] = STATE(606), + [sym_optional_index_access] = STATE(606), + [sym_list_expression] = STATE(606), + [sym_map_expression] = STATE(606), + [sym_struct_literal] = STATE(606), + [sym_unary_expression] = STATE(514), + [sym_unwrap_expression] = STATE(606), + [sym_binary_expression] = STATE(514), + [sym_nullish_coalescing_expression] = STATE(514), + [sym_range_expression] = STATE(514), + [sym_closure] = STATE(514), + [sym_match_expression] = STATE(514), + [sym_spawn_expression] = STATE(514), + [sym_chan_expression] = STATE(514), + [sym_send_expression] = STATE(514), + [sym_recv_expression] = STATE(514), + [sym_select_expression] = STATE(514), + [sym_macro_invocation] = STATE(514), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(578), - [sym_integer_literal] = ACTIONS(598), - [sym_float_literal] = ACTIONS(600), - [anon_sym_true] = ACTIONS(602), - [anon_sym_false] = ACTIONS(602), - [anon_sym_nil] = ACTIONS(604), - [anon_sym_DQUOTE] = ACTIONS(606), - [anon_sym_SQUOTE] = ACTIONS(608), - [sym_raw_string] = ACTIONS(610), - [anon_sym_RBRACE] = ACTIONS(576), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(612), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(614), - [anon_sym_match] = ACTIONS(616), - [anon_sym_SEMI] = ACTIONS(576), - [anon_sym_spawn] = ACTIONS(618), - [anon_sym_chan] = ACTIONS(620), - [anon_sym_send] = ACTIONS(622), - [anon_sym_recv] = ACTIONS(624), - [anon_sym_select] = ACTIONS(626), - [anon_sym_case] = ACTIONS(574), - [anon_sym_default] = ACTIONS(574), + [aux_sym_identifier_token1] = ACTIONS(577), + [sym_integer_literal] = ACTIONS(577), + [sym_float_literal] = ACTIONS(579), + [anon_sym_true] = ACTIONS(577), + [anon_sym_false] = ACTIONS(577), + [anon_sym_nil] = ACTIONS(577), + [anon_sym_DQUOTE] = ACTIONS(579), + [anon_sym_SQUOTE] = ACTIONS(579), + [sym_raw_string] = ACTIONS(579), + [anon_sym_RBRACE] = ACTIONS(579), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_COMMA] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(579), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(615), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), + [anon_sym_match] = ACTIONS(617), + [anon_sym_SEMI] = ACTIONS(579), + [anon_sym__] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(619), + [anon_sym_chan] = ACTIONS(621), + [anon_sym_send] = ACTIONS(623), + [anon_sym_recv] = ACTIONS(625), + [anon_sym_select] = ACTIONS(627), }, [STATE(47)] = { - [sym_identifier] = STATE(640), - [sym_type_identifier] = STATE(1470), - [sym_boolean_literal] = STATE(638), - [sym_nil_literal] = STATE(638), - [sym_string_literal] = STATE(638), - [sym_double_string] = STATE(658), - [sym_single_string] = STATE(658), - [sym__expression] = STATE(692), - [sym_primary_expression] = STATE(692), - [sym_parenthesized_expression] = STATE(638), - [sym_call_expression] = STATE(638), - [sym_field_access] = STATE(638), - [sym_optional_field_access] = STATE(638), - [sym_index_access] = STATE(638), - [sym_optional_index_access] = STATE(638), - [sym_list_expression] = STATE(638), - [sym_map_expression] = STATE(638), - [sym_struct_literal] = STATE(638), - [sym_unary_expression] = STATE(692), - [sym_unwrap_expression] = STATE(638), - [sym_binary_expression] = STATE(692), - [sym_nullish_coalescing_expression] = STATE(692), - [sym_range_expression] = STATE(692), - [sym_closure] = STATE(692), - [sym_match_expression] = STATE(692), - [sym_spawn_expression] = STATE(692), - [sym_chan_expression] = STATE(692), - [sym_send_expression] = STATE(692), - [sym_recv_expression] = STATE(692), - [sym_select_expression] = STATE(692), - [sym_macro_invocation] = STATE(692), + [sym_identifier] = STATE(677), + [sym_type_identifier] = STATE(1578), + [sym_boolean_literal] = STATE(717), + [sym_nil_literal] = STATE(717), + [sym_string_literal] = STATE(717), + [sym_double_string] = STATE(688), + [sym_single_string] = STATE(688), + [sym__expression] = STATE(733), + [sym_primary_expression] = STATE(733), + [sym_parenthesized_expression] = STATE(717), + [sym_call_expression] = STATE(717), + [sym_field_access] = STATE(717), + [sym_optional_field_access] = STATE(717), + [sym_index_access] = STATE(717), + [sym_optional_index_access] = STATE(717), + [sym_list_expression] = STATE(717), + [sym_map_expression] = STATE(717), + [sym_struct_literal] = STATE(717), + [sym_unary_expression] = STATE(733), + [sym_unwrap_expression] = STATE(717), + [sym_binary_expression] = STATE(733), + [sym_nullish_coalescing_expression] = STATE(733), + [sym_range_expression] = STATE(733), + [sym_closure] = STATE(733), + [sym_match_expression] = STATE(733), + [sym_spawn_expression] = STATE(733), + [sym_chan_expression] = STATE(733), + [sym_send_expression] = STATE(733), + [sym_recv_expression] = STATE(733), + [sym_select_expression] = STATE(733), + [sym_macro_invocation] = STATE(733), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(578), - [sym_integer_literal] = ACTIONS(598), - [sym_float_literal] = ACTIONS(600), - [anon_sym_true] = ACTIONS(602), - [anon_sym_false] = ACTIONS(602), - [anon_sym_nil] = ACTIONS(604), - [anon_sym_DQUOTE] = ACTIONS(606), - [anon_sym_SQUOTE] = ACTIONS(608), - [sym_raw_string] = ACTIONS(610), - [anon_sym_RBRACE] = ACTIONS(576), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_COMMA] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_RBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(612), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(574), - [anon_sym_match] = ACTIONS(616), - [anon_sym_if] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(618), - [anon_sym_chan] = ACTIONS(620), - [anon_sym_send] = ACTIONS(622), - [anon_sym_recv] = ACTIONS(624), - [anon_sym_select] = ACTIONS(626), + [aux_sym_identifier_token1] = ACTIONS(581), + [sym_integer_literal] = ACTIONS(585), + [sym_float_literal] = ACTIONS(587), + [anon_sym_true] = ACTIONS(589), + [anon_sym_false] = ACTIONS(589), + [anon_sym_nil] = ACTIONS(591), + [anon_sym_DQUOTE] = ACTIONS(593), + [anon_sym_SQUOTE] = ACTIONS(595), + [sym_raw_string] = ACTIONS(597), + [anon_sym_RBRACE] = ACTIONS(579), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_COMMA] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_RBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(599), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(629), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), + [anon_sym_match] = ACTIONS(603), + [anon_sym_if] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(605), + [anon_sym_chan] = ACTIONS(607), + [anon_sym_send] = ACTIONS(609), + [anon_sym_recv] = ACTIONS(611), + [anon_sym_select] = ACTIONS(613), }, [STATE(48)] = { - [sym_identifier] = STATE(640), - [sym_type_identifier] = STATE(1470), - [sym_boolean_literal] = STATE(638), - [sym_nil_literal] = STATE(638), - [sym_string_literal] = STATE(638), - [sym_double_string] = STATE(658), - [sym_single_string] = STATE(658), - [sym__expression] = STATE(724), - [sym_primary_expression] = STATE(724), - [sym_parenthesized_expression] = STATE(638), - [sym_call_expression] = STATE(638), - [sym_field_access] = STATE(638), - [sym_optional_field_access] = STATE(638), - [sym_index_access] = STATE(638), - [sym_optional_index_access] = STATE(638), - [sym_list_expression] = STATE(638), - [sym_map_expression] = STATE(638), - [sym_struct_literal] = STATE(638), - [sym_unary_expression] = STATE(724), - [sym_unwrap_expression] = STATE(638), - [sym_binary_expression] = STATE(724), - [sym_nullish_coalescing_expression] = STATE(724), - [sym_range_expression] = STATE(724), - [sym_closure] = STATE(724), - [sym_match_expression] = STATE(724), - [sym_spawn_expression] = STATE(724), - [sym_chan_expression] = STATE(724), - [sym_send_expression] = STATE(724), - [sym_recv_expression] = STATE(724), - [sym_select_expression] = STATE(724), - [sym_macro_invocation] = STATE(724), + [sym_identifier] = STATE(677), + [sym_type_identifier] = STATE(1578), + [sym_boolean_literal] = STATE(717), + [sym_nil_literal] = STATE(717), + [sym_string_literal] = STATE(717), + [sym_double_string] = STATE(688), + [sym_single_string] = STATE(688), + [sym__expression] = STATE(790), + [sym_primary_expression] = STATE(790), + [sym_parenthesized_expression] = STATE(717), + [sym_call_expression] = STATE(717), + [sym_field_access] = STATE(717), + [sym_optional_field_access] = STATE(717), + [sym_index_access] = STATE(717), + [sym_optional_index_access] = STATE(717), + [sym_list_expression] = STATE(717), + [sym_map_expression] = STATE(717), + [sym_struct_literal] = STATE(717), + [sym_unary_expression] = STATE(790), + [sym_unwrap_expression] = STATE(717), + [sym_binary_expression] = STATE(790), + [sym_nullish_coalescing_expression] = STATE(790), + [sym_range_expression] = STATE(790), + [sym_closure] = STATE(790), + [sym_match_expression] = STATE(790), + [sym_spawn_expression] = STATE(790), + [sym_chan_expression] = STATE(790), + [sym_send_expression] = STATE(790), + [sym_recv_expression] = STATE(790), + [sym_select_expression] = STATE(790), + [sym_macro_invocation] = STATE(790), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(628), - [sym_integer_literal] = ACTIONS(598), - [sym_float_literal] = ACTIONS(600), - [anon_sym_true] = ACTIONS(602), - [anon_sym_false] = ACTIONS(602), - [anon_sym_nil] = ACTIONS(604), - [anon_sym_DQUOTE] = ACTIONS(606), - [anon_sym_SQUOTE] = ACTIONS(608), - [sym_raw_string] = ACTIONS(610), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_COLON] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(612), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(574), - [anon_sym_match] = ACTIONS(616), - [anon_sym_if] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(618), - [anon_sym_chan] = ACTIONS(620), - [anon_sym_send] = ACTIONS(622), - [anon_sym_recv] = ACTIONS(624), - [anon_sym_select] = ACTIONS(626), - [anon_sym_EQ] = ACTIONS(574), + [aux_sym_identifier_token1] = ACTIONS(581), + [sym_integer_literal] = ACTIONS(585), + [sym_float_literal] = ACTIONS(587), + [anon_sym_true] = ACTIONS(589), + [anon_sym_false] = ACTIONS(589), + [anon_sym_nil] = ACTIONS(591), + [anon_sym_DQUOTE] = ACTIONS(593), + [anon_sym_SQUOTE] = ACTIONS(595), + [sym_raw_string] = ACTIONS(597), + [anon_sym_RBRACE] = ACTIONS(579), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_COMMA] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_RBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(599), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(631), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_match] = ACTIONS(603), + [anon_sym_if] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(605), + [anon_sym_chan] = ACTIONS(607), + [anon_sym_send] = ACTIONS(609), + [anon_sym_recv] = ACTIONS(611), + [anon_sym_select] = ACTIONS(613), }, [STATE(49)] = { - [sym_identifier] = STATE(640), - [sym_type_identifier] = STATE(1470), - [sym_boolean_literal] = STATE(638), - [sym_nil_literal] = STATE(638), - [sym_string_literal] = STATE(638), - [sym_double_string] = STATE(658), - [sym_single_string] = STATE(658), - [sym__expression] = STATE(727), - [sym_primary_expression] = STATE(727), - [sym_parenthesized_expression] = STATE(638), - [sym_call_expression] = STATE(638), - [sym_field_access] = STATE(638), - [sym_optional_field_access] = STATE(638), - [sym_index_access] = STATE(638), - [sym_optional_index_access] = STATE(638), - [sym_list_expression] = STATE(638), - [sym_map_expression] = STATE(638), - [sym_struct_literal] = STATE(638), - [sym_unary_expression] = STATE(727), - [sym_unwrap_expression] = STATE(638), - [sym_binary_expression] = STATE(727), - [sym_nullish_coalescing_expression] = STATE(727), - [sym_range_expression] = STATE(727), - [sym_closure] = STATE(727), - [sym_match_expression] = STATE(727), - [sym_spawn_expression] = STATE(727), - [sym_chan_expression] = STATE(727), - [sym_send_expression] = STATE(727), - [sym_recv_expression] = STATE(727), - [sym_select_expression] = STATE(727), - [sym_macro_invocation] = STATE(727), + [sym_identifier] = STATE(677), + [sym_type_identifier] = STATE(1578), + [sym_boolean_literal] = STATE(717), + [sym_nil_literal] = STATE(717), + [sym_string_literal] = STATE(717), + [sym_double_string] = STATE(688), + [sym_single_string] = STATE(688), + [sym__expression] = STATE(783), + [sym_primary_expression] = STATE(783), + [sym_parenthesized_expression] = STATE(717), + [sym_call_expression] = STATE(717), + [sym_field_access] = STATE(717), + [sym_optional_field_access] = STATE(717), + [sym_index_access] = STATE(717), + [sym_optional_index_access] = STATE(717), + [sym_list_expression] = STATE(717), + [sym_map_expression] = STATE(717), + [sym_struct_literal] = STATE(717), + [sym_unary_expression] = STATE(783), + [sym_unwrap_expression] = STATE(717), + [sym_binary_expression] = STATE(783), + [sym_nullish_coalescing_expression] = STATE(783), + [sym_range_expression] = STATE(783), + [sym_closure] = STATE(783), + [sym_match_expression] = STATE(783), + [sym_spawn_expression] = STATE(783), + [sym_chan_expression] = STATE(783), + [sym_send_expression] = STATE(783), + [sym_recv_expression] = STATE(783), + [sym_select_expression] = STATE(783), + [sym_macro_invocation] = STATE(783), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(578), - [sym_integer_literal] = ACTIONS(598), - [sym_float_literal] = ACTIONS(600), - [anon_sym_true] = ACTIONS(602), - [anon_sym_false] = ACTIONS(602), - [anon_sym_nil] = ACTIONS(604), - [anon_sym_DQUOTE] = ACTIONS(606), - [anon_sym_SQUOTE] = ACTIONS(608), - [sym_raw_string] = ACTIONS(610), - [anon_sym_RBRACE] = ACTIONS(576), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_COMMA] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_RBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(612), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_PIPE] = ACTIONS(574), - [anon_sym_match] = ACTIONS(616), - [anon_sym_if] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(618), - [anon_sym_chan] = ACTIONS(620), - [anon_sym_send] = ACTIONS(622), - [anon_sym_recv] = ACTIONS(624), - [anon_sym_select] = ACTIONS(626), + [aux_sym_identifier_token1] = ACTIONS(633), + [sym_integer_literal] = ACTIONS(585), + [sym_float_literal] = ACTIONS(587), + [anon_sym_true] = ACTIONS(589), + [anon_sym_false] = ACTIONS(589), + [anon_sym_nil] = ACTIONS(591), + [anon_sym_DQUOTE] = ACTIONS(593), + [anon_sym_SQUOTE] = ACTIONS(595), + [sym_raw_string] = ACTIONS(597), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_COLON] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(599), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(635), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), + [anon_sym_match] = ACTIONS(603), + [anon_sym_if] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(605), + [anon_sym_chan] = ACTIONS(607), + [anon_sym_send] = ACTIONS(609), + [anon_sym_recv] = ACTIONS(611), + [anon_sym_select] = ACTIONS(613), + [anon_sym_EQ] = ACTIONS(577), }, [STATE(50)] = { - [sym_identifier] = STATE(640), - [sym_type_identifier] = STATE(1470), - [sym_boolean_literal] = STATE(638), - [sym_nil_literal] = STATE(638), - [sym_string_literal] = STATE(638), - [sym_double_string] = STATE(658), - [sym_single_string] = STATE(658), - [sym__expression] = STATE(793), - [sym_primary_expression] = STATE(793), - [sym_parenthesized_expression] = STATE(638), - [sym_call_expression] = STATE(638), - [sym_field_access] = STATE(638), - [sym_optional_field_access] = STATE(638), - [sym_index_access] = STATE(638), - [sym_optional_index_access] = STATE(638), - [sym_list_expression] = STATE(638), - [sym_map_expression] = STATE(638), - [sym_struct_literal] = STATE(638), - [sym_unary_expression] = STATE(793), - [sym_unwrap_expression] = STATE(638), - [sym_binary_expression] = STATE(793), - [sym_nullish_coalescing_expression] = STATE(793), - [sym_range_expression] = STATE(793), - [sym_closure] = STATE(793), - [sym_match_expression] = STATE(793), - [sym_spawn_expression] = STATE(793), - [sym_chan_expression] = STATE(793), - [sym_send_expression] = STATE(793), - [sym_recv_expression] = STATE(793), - [sym_select_expression] = STATE(793), - [sym_macro_invocation] = STATE(793), + [sym_identifier] = STATE(677), + [sym_type_identifier] = STATE(1578), + [sym_boolean_literal] = STATE(717), + [sym_nil_literal] = STATE(717), + [sym_string_literal] = STATE(717), + [sym_double_string] = STATE(688), + [sym_single_string] = STATE(688), + [sym__expression] = STATE(823), + [sym_primary_expression] = STATE(823), + [sym_parenthesized_expression] = STATE(717), + [sym_call_expression] = STATE(717), + [sym_field_access] = STATE(717), + [sym_optional_field_access] = STATE(717), + [sym_index_access] = STATE(717), + [sym_optional_index_access] = STATE(717), + [sym_list_expression] = STATE(717), + [sym_map_expression] = STATE(717), + [sym_struct_literal] = STATE(717), + [sym_unary_expression] = STATE(823), + [sym_unwrap_expression] = STATE(717), + [sym_binary_expression] = STATE(823), + [sym_nullish_coalescing_expression] = STATE(823), + [sym_range_expression] = STATE(823), + [sym_closure] = STATE(823), + [sym_match_expression] = STATE(823), + [sym_spawn_expression] = STATE(823), + [sym_chan_expression] = STATE(823), + [sym_send_expression] = STATE(823), + [sym_recv_expression] = STATE(823), + [sym_select_expression] = STATE(823), + [sym_macro_invocation] = STATE(823), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(628), - [sym_integer_literal] = ACTIONS(598), - [sym_float_literal] = ACTIONS(600), - [anon_sym_true] = ACTIONS(602), - [anon_sym_false] = ACTIONS(602), - [anon_sym_nil] = ACTIONS(604), - [anon_sym_DQUOTE] = ACTIONS(606), - [anon_sym_SQUOTE] = ACTIONS(608), - [sym_raw_string] = ACTIONS(610), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_COLON] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(612), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_PIPE] = ACTIONS(574), - [anon_sym_match] = ACTIONS(616), - [anon_sym_if] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(618), - [anon_sym_chan] = ACTIONS(620), - [anon_sym_send] = ACTIONS(622), - [anon_sym_recv] = ACTIONS(624), - [anon_sym_select] = ACTIONS(626), - [anon_sym_EQ] = ACTIONS(574), + [aux_sym_identifier_token1] = ACTIONS(633), + [sym_integer_literal] = ACTIONS(585), + [sym_float_literal] = ACTIONS(587), + [anon_sym_true] = ACTIONS(589), + [anon_sym_false] = ACTIONS(589), + [anon_sym_nil] = ACTIONS(591), + [anon_sym_DQUOTE] = ACTIONS(593), + [anon_sym_SQUOTE] = ACTIONS(595), + [sym_raw_string] = ACTIONS(597), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_COLON] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(599), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(637), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_match] = ACTIONS(603), + [anon_sym_if] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(605), + [anon_sym_chan] = ACTIONS(607), + [anon_sym_send] = ACTIONS(609), + [anon_sym_recv] = ACTIONS(611), + [anon_sym_select] = ACTIONS(613), + [anon_sym_EQ] = ACTIONS(577), }, [STATE(51)] = { - [sym_identifier] = STATE(749), - [sym_type_identifier] = STATE(1437), - [sym_boolean_literal] = STATE(747), - [sym_nil_literal] = STATE(747), - [sym_string_literal] = STATE(747), - [sym_double_string] = STATE(757), - [sym_single_string] = STATE(757), - [sym__expression] = STATE(786), - [sym_primary_expression] = STATE(786), - [sym_parenthesized_expression] = STATE(747), - [sym_call_expression] = STATE(747), - [sym_field_access] = STATE(747), - [sym_optional_field_access] = STATE(747), - [sym_index_access] = STATE(747), - [sym_optional_index_access] = STATE(747), - [sym_list_expression] = STATE(747), - [sym_map_expression] = STATE(747), - [sym_struct_literal] = STATE(747), - [sym_unary_expression] = STATE(786), - [sym_unwrap_expression] = STATE(747), - [sym_binary_expression] = STATE(786), - [sym_nullish_coalescing_expression] = STATE(786), - [sym_range_expression] = STATE(786), - [sym_closure] = STATE(786), - [sym_match_expression] = STATE(786), - [sym_spawn_expression] = STATE(786), - [sym_chan_expression] = STATE(786), - [sym_send_expression] = STATE(786), - [sym_recv_expression] = STATE(786), - [sym_select_expression] = STATE(786), - [sym_macro_invocation] = STATE(786), + [sym_identifier] = STATE(829), + [sym_type_identifier] = STATE(1525), + [sym_boolean_literal] = STATE(828), + [sym_nil_literal] = STATE(828), + [sym_string_literal] = STATE(828), + [sym_double_string] = STATE(886), + [sym_single_string] = STATE(886), + [sym__expression] = STATE(863), + [sym_primary_expression] = STATE(863), + [sym_parenthesized_expression] = STATE(828), + [sym_call_expression] = STATE(828), + [sym_field_access] = STATE(828), + [sym_optional_field_access] = STATE(828), + [sym_index_access] = STATE(828), + [sym_optional_index_access] = STATE(828), + [sym_list_expression] = STATE(828), + [sym_map_expression] = STATE(828), + [sym_struct_literal] = STATE(828), + [sym_unary_expression] = STATE(863), + [sym_unwrap_expression] = STATE(828), + [sym_binary_expression] = STATE(863), + [sym_nullish_coalescing_expression] = STATE(863), + [sym_range_expression] = STATE(863), + [sym_closure] = STATE(863), + [sym_match_expression] = STATE(863), + [sym_spawn_expression] = STATE(863), + [sym_chan_expression] = STATE(863), + [sym_send_expression] = STATE(863), + [sym_recv_expression] = STATE(863), + [sym_select_expression] = STATE(863), + [sym_macro_invocation] = STATE(863), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(578), - [sym_integer_literal] = ACTIONS(630), - [sym_float_literal] = ACTIONS(632), - [anon_sym_true] = ACTIONS(634), - [anon_sym_false] = ACTIONS(634), - [anon_sym_nil] = ACTIONS(636), - [anon_sym_DQUOTE] = ACTIONS(638), - [anon_sym_SQUOTE] = ACTIONS(640), - [sym_raw_string] = ACTIONS(642), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(644), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(574), - [anon_sym_match] = ACTIONS(646), - [anon_sym_EQ_GT] = ACTIONS(576), - [anon_sym_if] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(648), - [anon_sym_chan] = ACTIONS(650), - [anon_sym_send] = ACTIONS(652), - [anon_sym_recv] = ACTIONS(654), - [anon_sym_select] = ACTIONS(656), + [aux_sym_identifier_token1] = ACTIONS(581), + [sym_integer_literal] = ACTIONS(639), + [sym_float_literal] = ACTIONS(641), + [anon_sym_true] = ACTIONS(643), + [anon_sym_false] = ACTIONS(643), + [anon_sym_nil] = ACTIONS(645), + [anon_sym_DQUOTE] = ACTIONS(647), + [anon_sym_SQUOTE] = ACTIONS(649), + [sym_raw_string] = ACTIONS(651), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(653), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(655), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), + [anon_sym_match] = ACTIONS(657), + [anon_sym_EQ_GT] = ACTIONS(579), + [anon_sym_if] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(659), + [anon_sym_chan] = ACTIONS(661), + [anon_sym_send] = ACTIONS(663), + [anon_sym_recv] = ACTIONS(665), + [anon_sym_select] = ACTIONS(667), }, [STATE(52)] = { - [sym_identifier] = STATE(749), - [sym_type_identifier] = STATE(1437), - [sym_boolean_literal] = STATE(747), - [sym_nil_literal] = STATE(747), - [sym_string_literal] = STATE(747), - [sym_double_string] = STATE(757), - [sym_single_string] = STATE(757), - [sym__expression] = STATE(806), - [sym_primary_expression] = STATE(806), - [sym_parenthesized_expression] = STATE(747), - [sym_call_expression] = STATE(747), - [sym_field_access] = STATE(747), - [sym_optional_field_access] = STATE(747), - [sym_index_access] = STATE(747), - [sym_optional_index_access] = STATE(747), - [sym_list_expression] = STATE(747), - [sym_map_expression] = STATE(747), - [sym_struct_literal] = STATE(747), - [sym_unary_expression] = STATE(806), - [sym_unwrap_expression] = STATE(747), - [sym_binary_expression] = STATE(806), - [sym_nullish_coalescing_expression] = STATE(806), - [sym_range_expression] = STATE(806), - [sym_closure] = STATE(806), - [sym_match_expression] = STATE(806), - [sym_spawn_expression] = STATE(806), - [sym_chan_expression] = STATE(806), - [sym_send_expression] = STATE(806), - [sym_recv_expression] = STATE(806), - [sym_select_expression] = STATE(806), - [sym_macro_invocation] = STATE(806), + [sym_identifier] = STATE(829), + [sym_type_identifier] = STATE(1525), + [sym_boolean_literal] = STATE(828), + [sym_nil_literal] = STATE(828), + [sym_string_literal] = STATE(828), + [sym_double_string] = STATE(886), + [sym_single_string] = STATE(886), + [sym__expression] = STATE(921), + [sym_primary_expression] = STATE(921), + [sym_parenthesized_expression] = STATE(828), + [sym_call_expression] = STATE(828), + [sym_field_access] = STATE(828), + [sym_optional_field_access] = STATE(828), + [sym_index_access] = STATE(828), + [sym_optional_index_access] = STATE(828), + [sym_list_expression] = STATE(828), + [sym_map_expression] = STATE(828), + [sym_struct_literal] = STATE(828), + [sym_unary_expression] = STATE(921), + [sym_unwrap_expression] = STATE(828), + [sym_binary_expression] = STATE(921), + [sym_nullish_coalescing_expression] = STATE(921), + [sym_range_expression] = STATE(921), + [sym_closure] = STATE(921), + [sym_match_expression] = STATE(921), + [sym_spawn_expression] = STATE(921), + [sym_chan_expression] = STATE(921), + [sym_send_expression] = STATE(921), + [sym_recv_expression] = STATE(921), + [sym_select_expression] = STATE(921), + [sym_macro_invocation] = STATE(921), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(578), - [sym_integer_literal] = ACTIONS(630), - [sym_float_literal] = ACTIONS(632), - [anon_sym_true] = ACTIONS(634), - [anon_sym_false] = ACTIONS(634), - [anon_sym_nil] = ACTIONS(636), - [anon_sym_DQUOTE] = ACTIONS(638), - [anon_sym_SQUOTE] = ACTIONS(640), - [sym_raw_string] = ACTIONS(642), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(644), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_PIPE] = ACTIONS(574), - [anon_sym_match] = ACTIONS(646), - [anon_sym_EQ_GT] = ACTIONS(576), - [anon_sym_if] = ACTIONS(574), - [anon_sym_spawn] = ACTIONS(648), - [anon_sym_chan] = ACTIONS(650), - [anon_sym_send] = ACTIONS(652), - [anon_sym_recv] = ACTIONS(654), - [anon_sym_select] = ACTIONS(656), + [aux_sym_identifier_token1] = ACTIONS(581), + [sym_integer_literal] = ACTIONS(639), + [sym_float_literal] = ACTIONS(641), + [anon_sym_true] = ACTIONS(643), + [anon_sym_false] = ACTIONS(643), + [anon_sym_nil] = ACTIONS(645), + [anon_sym_DQUOTE] = ACTIONS(647), + [anon_sym_SQUOTE] = ACTIONS(649), + [sym_raw_string] = ACTIONS(651), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(653), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(669), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_match] = ACTIONS(657), + [anon_sym_EQ_GT] = ACTIONS(579), + [anon_sym_if] = ACTIONS(577), + [anon_sym_spawn] = ACTIONS(659), + [anon_sym_chan] = ACTIONS(661), + [anon_sym_send] = ACTIONS(663), + [anon_sym_recv] = ACTIONS(665), + [anon_sym_select] = ACTIONS(667), }, [STATE(53)] = { - [sym_identifier] = STATE(86), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__expression] = STATE(811), - [sym_primary_expression] = STATE(811), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(811), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(811), - [sym_nullish_coalescing_expression] = STATE(811), - [sym_range_expression] = STATE(811), - [sym_closure] = STATE(811), - [sym_match_expression] = STATE(811), - [sym_spawn_expression] = STATE(811), - [sym_chan_expression] = STATE(811), - [sym_send_expression] = STATE(811), - [sym_recv_expression] = STATE(811), - [sym_select_expression] = STATE(811), - [sym_macro_invocation] = STATE(811), + [sym_identifier] = STATE(60), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__expression] = STATE(920), + [sym_primary_expression] = STATE(920), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(920), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(920), + [sym_nullish_coalescing_expression] = STATE(920), + [sym_range_expression] = STATE(920), + [sym_closure] = STATE(920), + [sym_match_expression] = STATE(920), + [sym_spawn_expression] = STATE(920), + [sym_chan_expression] = STATE(920), + [sym_send_expression] = STATE(920), + [sym_recv_expression] = STATE(920), + [sym_select_expression] = STATE(920), + [sym_macro_invocation] = STATE(920), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(658), + [aux_sym_identifier_token1] = ACTIONS(671), [sym_integer_literal] = ACTIONS(9), [sym_float_literal] = ACTIONS(11), [anon_sym_true] = ACTIONS(13), @@ -14422,31 +15417,36 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(576), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_QMARK] = ACTIONS(574), - [anon_sym_PIPE] = ACTIONS(660), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(579), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(673), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), + [anon_sym_QMARK] = ACTIONS(577), [anon_sym_match] = ACTIONS(33), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), @@ -14455,40 +15455,40 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_select] = ACTIONS(45), }, [STATE(54)] = { - [sym_identifier] = STATE(86), - [sym_type_identifier] = STATE(1369), - [sym_boolean_literal] = STATE(97), - [sym_nil_literal] = STATE(97), - [sym_string_literal] = STATE(97), - [sym_double_string] = STATE(67), - [sym_single_string] = STATE(67), - [sym__expression] = STATE(821), - [sym_primary_expression] = STATE(821), - [sym_parenthesized_expression] = STATE(97), - [sym_call_expression] = STATE(97), - [sym_field_access] = STATE(97), - [sym_optional_field_access] = STATE(97), - [sym_index_access] = STATE(97), - [sym_optional_index_access] = STATE(97), - [sym_list_expression] = STATE(97), - [sym_map_expression] = STATE(97), - [sym_struct_literal] = STATE(97), - [sym_unary_expression] = STATE(821), - [sym_unwrap_expression] = STATE(97), - [sym_binary_expression] = STATE(821), - [sym_nullish_coalescing_expression] = STATE(821), - [sym_range_expression] = STATE(821), - [sym_closure] = STATE(821), - [sym_match_expression] = STATE(821), - [sym_spawn_expression] = STATE(821), - [sym_chan_expression] = STATE(821), - [sym_send_expression] = STATE(821), - [sym_recv_expression] = STATE(821), - [sym_select_expression] = STATE(821), - [sym_macro_invocation] = STATE(821), + [sym_identifier] = STATE(60), + [sym_type_identifier] = STATE(1606), + [sym_boolean_literal] = STATE(101), + [sym_nil_literal] = STATE(101), + [sym_string_literal] = STATE(101), + [sym_double_string] = STATE(84), + [sym_single_string] = STATE(84), + [sym__expression] = STATE(943), + [sym_primary_expression] = STATE(943), + [sym_parenthesized_expression] = STATE(101), + [sym_call_expression] = STATE(101), + [sym_field_access] = STATE(101), + [sym_optional_field_access] = STATE(101), + [sym_index_access] = STATE(101), + [sym_optional_index_access] = STATE(101), + [sym_list_expression] = STATE(101), + [sym_map_expression] = STATE(101), + [sym_struct_literal] = STATE(101), + [sym_unary_expression] = STATE(943), + [sym_unwrap_expression] = STATE(101), + [sym_binary_expression] = STATE(943), + [sym_nullish_coalescing_expression] = STATE(943), + [sym_range_expression] = STATE(943), + [sym_closure] = STATE(943), + [sym_match_expression] = STATE(943), + [sym_spawn_expression] = STATE(943), + [sym_chan_expression] = STATE(943), + [sym_send_expression] = STATE(943), + [sym_recv_expression] = STATE(943), + [sym_select_expression] = STATE(943), + [sym_macro_invocation] = STATE(943), [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(658), + [aux_sym_identifier_token1] = ACTIONS(671), [sym_integer_literal] = ACTIONS(9), [sym_float_literal] = ACTIONS(11), [anon_sym_true] = ACTIONS(13), @@ -14497,30 +15497,35 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_DQUOTE] = ACTIONS(17), [anon_sym_SQUOTE] = ACTIONS(19), [sym_raw_string] = ACTIONS(21), - [anon_sym_LPAREN] = ACTIONS(576), - [anon_sym_DOT] = ACTIONS(574), - [anon_sym_QMARK_DOT] = ACTIONS(576), - [anon_sym_LBRACK] = ACTIONS(576), - [anon_sym_QMARK_LBRACK] = ACTIONS(576), - [anon_sym_LBRACE] = ACTIONS(576), - [anon_sym_BANG] = ACTIONS(574), - [anon_sym_STAR] = ACTIONS(576), - [anon_sym_SLASH] = ACTIONS(574), - [anon_sym_PERCENT] = ACTIONS(576), - [anon_sym_PLUS] = ACTIONS(576), - [anon_sym_DASH] = ACTIONS(576), - [anon_sym_EQ_EQ] = ACTIONS(576), - [anon_sym_BANG_EQ] = ACTIONS(576), - [anon_sym_LT] = ACTIONS(574), - [anon_sym_GT] = ACTIONS(574), - [anon_sym_LT_EQ] = ACTIONS(576), - [anon_sym_GT_EQ] = ACTIONS(576), - [anon_sym_AMP_AMP] = ACTIONS(576), - [anon_sym_PIPE_PIPE] = ACTIONS(576), - [anon_sym_QMARK_QMARK] = ACTIONS(576), - [anon_sym_DOT_DOT] = ACTIONS(574), - [anon_sym_DOT_DOT_EQ] = ACTIONS(576), - [anon_sym_PIPE] = ACTIONS(662), + [anon_sym_LPAREN] = ACTIONS(579), + [anon_sym_DOT] = ACTIONS(577), + [anon_sym_QMARK_DOT] = ACTIONS(579), + [anon_sym_LBRACK] = ACTIONS(579), + [anon_sym_QMARK_LBRACK] = ACTIONS(579), + [anon_sym_LBRACE] = ACTIONS(579), + [anon_sym_BANG] = ACTIONS(577), + [anon_sym_TILDE] = ACTIONS(675), + [anon_sym_STAR] = ACTIONS(579), + [anon_sym_SLASH] = ACTIONS(577), + [anon_sym_PERCENT] = ACTIONS(579), + [anon_sym_PLUS] = ACTIONS(579), + [anon_sym_DASH] = ACTIONS(579), + [anon_sym_EQ_EQ] = ACTIONS(579), + [anon_sym_BANG_EQ] = ACTIONS(579), + [anon_sym_LT] = ACTIONS(577), + [anon_sym_GT] = ACTIONS(577), + [anon_sym_LT_EQ] = ACTIONS(579), + [anon_sym_GT_EQ] = ACTIONS(579), + [anon_sym_AMP_AMP] = ACTIONS(579), + [anon_sym_PIPE_PIPE] = ACTIONS(579), + [anon_sym_PIPE] = ACTIONS(577), + [anon_sym_CARET] = ACTIONS(579), + [anon_sym_AMP] = ACTIONS(577), + [anon_sym_LT_LT] = ACTIONS(579), + [anon_sym_GT_GT] = ACTIONS(579), + [anon_sym_QMARK_QMARK] = ACTIONS(579), + [anon_sym_DOT_DOT] = ACTIONS(577), + [anon_sym_DOT_DOT_EQ] = ACTIONS(579), [anon_sym_match] = ACTIONS(33), [anon_sym_spawn] = ACTIONS(37), [anon_sym_chan] = ACTIONS(39), @@ -14531,763 +15536,448 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [STATE(55)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(664), - [sym_integer_literal] = ACTIONS(664), - [sym_float_literal] = ACTIONS(666), - [anon_sym_true] = ACTIONS(664), - [anon_sym_false] = ACTIONS(664), - [anon_sym_nil] = ACTIONS(664), - [anon_sym_DQUOTE] = ACTIONS(666), - [anon_sym_SQUOTE] = ACTIONS(666), - [sym_raw_string] = ACTIONS(666), - [anon_sym_RBRACE] = ACTIONS(666), - [anon_sym_LPAREN] = ACTIONS(666), - [anon_sym_RPAREN] = ACTIONS(666), - [anon_sym_COMMA] = ACTIONS(666), - [anon_sym_COLON] = ACTIONS(666), - [anon_sym_DOT] = ACTIONS(664), - [anon_sym_QMARK_DOT] = ACTIONS(666), - [anon_sym_LBRACK] = ACTIONS(666), - [anon_sym_RBRACK] = ACTIONS(666), - [anon_sym_QMARK_LBRACK] = ACTIONS(666), - [anon_sym_LBRACE] = ACTIONS(666), - [anon_sym_BANG] = ACTIONS(664), - [anon_sym_STAR] = ACTIONS(666), - [anon_sym_SLASH] = ACTIONS(664), - [anon_sym_PERCENT] = ACTIONS(666), - [anon_sym_PLUS] = ACTIONS(666), - [anon_sym_DASH] = ACTIONS(666), - [anon_sym_EQ_EQ] = ACTIONS(666), - [anon_sym_BANG_EQ] = ACTIONS(666), - [anon_sym_LT] = ACTIONS(664), - [anon_sym_GT] = ACTIONS(664), - [anon_sym_LT_EQ] = ACTIONS(666), - [anon_sym_GT_EQ] = ACTIONS(666), - [anon_sym_AMP_AMP] = ACTIONS(666), - [anon_sym_PIPE_PIPE] = ACTIONS(666), - [anon_sym_QMARK_QMARK] = ACTIONS(666), - [anon_sym_DOT_DOT] = ACTIONS(664), - [anon_sym_DOT_DOT_EQ] = ACTIONS(666), - [anon_sym_QMARK] = ACTIONS(664), - [anon_sym_PIPE] = ACTIONS(664), - [anon_sym_match] = ACTIONS(664), - [anon_sym_EQ_GT] = ACTIONS(666), - [anon_sym_SEMI] = ACTIONS(666), - [anon_sym_if] = ACTIONS(664), - [anon_sym_spawn] = ACTIONS(664), - [anon_sym_chan] = ACTIONS(664), - [anon_sym_send] = ACTIONS(664), - [anon_sym_recv] = ACTIONS(664), - [anon_sym_select] = ACTIONS(664), - [anon_sym_POUND] = ACTIONS(666), - [anon_sym_use] = ACTIONS(664), - [anon_sym_export] = ACTIONS(664), - [anon_sym_macro_rules] = ACTIONS(664), - [anon_sym_let] = ACTIONS(664), - [anon_sym_while] = ACTIONS(664), - [anon_sym_for] = ACTIONS(664), - [anon_sym_fn] = ACTIONS(664), - [anon_sym_struct] = ACTIONS(664), - [anon_sym_type] = ACTIONS(664), - [anon_sym_trait] = ACTIONS(664), - [anon_sym_impl] = ACTIONS(664), - [anon_sym_return] = ACTIONS(664), - [anon_sym_break] = ACTIONS(664), - [anon_sym_continue] = ACTIONS(664), - [anon_sym_go] = ACTIONS(664), - [anon_sym_try] = ACTIONS(664), + [aux_sym_identifier_token1] = ACTIONS(677), + [sym_integer_literal] = ACTIONS(677), + [sym_float_literal] = ACTIONS(679), + [anon_sym_true] = ACTIONS(677), + [anon_sym_false] = ACTIONS(677), + [anon_sym_nil] = ACTIONS(677), + [anon_sym_DQUOTE] = ACTIONS(679), + [anon_sym_SQUOTE] = ACTIONS(679), + [sym_raw_string] = ACTIONS(679), + [anon_sym_RBRACE] = ACTIONS(679), + [anon_sym_LPAREN] = ACTIONS(679), + [anon_sym_RPAREN] = ACTIONS(679), + [anon_sym_COMMA] = ACTIONS(679), + [anon_sym_COLON] = ACTIONS(679), + [anon_sym_DOT] = ACTIONS(677), + [anon_sym_QMARK_DOT] = ACTIONS(679), + [anon_sym_LBRACK] = ACTIONS(679), + [anon_sym_RBRACK] = ACTIONS(679), + [anon_sym_QMARK_LBRACK] = ACTIONS(679), + [anon_sym_LBRACE] = ACTIONS(679), + [anon_sym_BANG] = ACTIONS(677), + [anon_sym_TILDE] = ACTIONS(679), + [anon_sym_STAR] = ACTIONS(679), + [anon_sym_SLASH] = ACTIONS(677), + [anon_sym_PERCENT] = ACTIONS(679), + [anon_sym_PLUS] = ACTIONS(679), + [anon_sym_DASH] = ACTIONS(679), + [anon_sym_EQ_EQ] = ACTIONS(679), + [anon_sym_BANG_EQ] = ACTIONS(679), + [anon_sym_LT] = ACTIONS(677), + [anon_sym_GT] = ACTIONS(677), + [anon_sym_LT_EQ] = ACTIONS(679), + [anon_sym_GT_EQ] = ACTIONS(679), + [anon_sym_AMP_AMP] = ACTIONS(679), + [anon_sym_PIPE_PIPE] = ACTIONS(679), + [anon_sym_PIPE] = ACTIONS(677), + [anon_sym_CARET] = ACTIONS(679), + [anon_sym_AMP] = ACTIONS(677), + [anon_sym_LT_LT] = ACTIONS(679), + [anon_sym_GT_GT] = ACTIONS(679), + [anon_sym_QMARK_QMARK] = ACTIONS(679), + [anon_sym_DOT_DOT] = ACTIONS(677), + [anon_sym_DOT_DOT_EQ] = ACTIONS(679), + [anon_sym_QMARK] = ACTIONS(677), + [anon_sym_match] = ACTIONS(677), + [anon_sym_EQ_GT] = ACTIONS(679), + [anon_sym_SEMI] = ACTIONS(679), + [anon_sym_if] = ACTIONS(677), + [anon_sym_spawn] = ACTIONS(677), + [anon_sym_chan] = ACTIONS(677), + [anon_sym_send] = ACTIONS(677), + [anon_sym_recv] = ACTIONS(677), + [anon_sym_select] = ACTIONS(677), + [anon_sym_POUND] = ACTIONS(679), + [anon_sym_use] = ACTIONS(677), + [anon_sym_export] = ACTIONS(677), + [anon_sym_macro_rules] = ACTIONS(677), + [anon_sym_let] = ACTIONS(677), + [anon_sym_while] = ACTIONS(677), + [anon_sym_for] = ACTIONS(677), + [anon_sym_fn] = ACTIONS(677), + [anon_sym_struct] = ACTIONS(677), + [anon_sym_type] = ACTIONS(677), + [anon_sym_trait] = ACTIONS(677), + [anon_sym_impl] = ACTIONS(677), + [anon_sym_return] = ACTIONS(677), + [anon_sym_break] = ACTIONS(677), + [anon_sym_continue] = ACTIONS(677), + [anon_sym_go] = ACTIONS(677), + [anon_sym_try] = ACTIONS(677), }, [STATE(56)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(668), - [sym_integer_literal] = ACTIONS(668), - [sym_float_literal] = ACTIONS(670), - [anon_sym_true] = ACTIONS(668), - [anon_sym_false] = ACTIONS(668), - [anon_sym_nil] = ACTIONS(668), - [anon_sym_DQUOTE] = ACTIONS(670), - [anon_sym_SQUOTE] = ACTIONS(670), - [sym_raw_string] = ACTIONS(670), - [anon_sym_RBRACE] = ACTIONS(670), - [anon_sym_LPAREN] = ACTIONS(670), - [anon_sym_RPAREN] = ACTIONS(670), - [anon_sym_COMMA] = ACTIONS(670), - [anon_sym_COLON] = ACTIONS(670), - [anon_sym_DOT] = ACTIONS(668), - [anon_sym_QMARK_DOT] = ACTIONS(670), - [anon_sym_LBRACK] = ACTIONS(670), - [anon_sym_RBRACK] = ACTIONS(670), - [anon_sym_QMARK_LBRACK] = ACTIONS(670), - [anon_sym_LBRACE] = ACTIONS(670), - [anon_sym_BANG] = ACTIONS(668), - [anon_sym_STAR] = ACTIONS(670), - [anon_sym_SLASH] = ACTIONS(668), - [anon_sym_PERCENT] = ACTIONS(670), - [anon_sym_PLUS] = ACTIONS(670), - [anon_sym_DASH] = ACTIONS(670), - [anon_sym_EQ_EQ] = ACTIONS(670), - [anon_sym_BANG_EQ] = ACTIONS(670), - [anon_sym_LT] = ACTIONS(668), - [anon_sym_GT] = ACTIONS(668), - [anon_sym_LT_EQ] = ACTIONS(670), - [anon_sym_GT_EQ] = ACTIONS(670), - [anon_sym_AMP_AMP] = ACTIONS(670), - [anon_sym_PIPE_PIPE] = ACTIONS(670), - [anon_sym_QMARK_QMARK] = ACTIONS(670), - [anon_sym_DOT_DOT] = ACTIONS(668), - [anon_sym_DOT_DOT_EQ] = ACTIONS(670), - [anon_sym_QMARK] = ACTIONS(668), - [anon_sym_PIPE] = ACTIONS(668), - [anon_sym_match] = ACTIONS(668), - [anon_sym_EQ_GT] = ACTIONS(670), - [anon_sym_SEMI] = ACTIONS(670), - [anon_sym_if] = ACTIONS(668), - [anon_sym_spawn] = ACTIONS(668), - [anon_sym_chan] = ACTIONS(668), - [anon_sym_send] = ACTIONS(668), - [anon_sym_recv] = ACTIONS(668), - [anon_sym_select] = ACTIONS(668), - [anon_sym_POUND] = ACTIONS(670), - [anon_sym_use] = ACTIONS(668), - [anon_sym_export] = ACTIONS(668), - [anon_sym_macro_rules] = ACTIONS(668), - [anon_sym_let] = ACTIONS(668), - [anon_sym_while] = ACTIONS(668), - [anon_sym_for] = ACTIONS(668), - [anon_sym_fn] = ACTIONS(668), - [anon_sym_struct] = ACTIONS(668), - [anon_sym_type] = ACTIONS(668), - [anon_sym_trait] = ACTIONS(668), - [anon_sym_impl] = ACTIONS(668), - [anon_sym_return] = ACTIONS(668), - [anon_sym_break] = ACTIONS(668), - [anon_sym_continue] = ACTIONS(668), - [anon_sym_go] = ACTIONS(668), - [anon_sym_try] = ACTIONS(668), + [aux_sym_identifier_token1] = ACTIONS(681), + [sym_integer_literal] = ACTIONS(681), + [sym_float_literal] = ACTIONS(683), + [anon_sym_true] = ACTIONS(681), + [anon_sym_false] = ACTIONS(681), + [anon_sym_nil] = ACTIONS(681), + [anon_sym_DQUOTE] = ACTIONS(683), + [anon_sym_SQUOTE] = ACTIONS(683), + [sym_raw_string] = ACTIONS(683), + [anon_sym_RBRACE] = ACTIONS(683), + [anon_sym_LPAREN] = ACTIONS(683), + [anon_sym_RPAREN] = ACTIONS(683), + [anon_sym_COMMA] = ACTIONS(683), + [anon_sym_COLON] = ACTIONS(683), + [anon_sym_DOT] = ACTIONS(681), + [anon_sym_QMARK_DOT] = ACTIONS(683), + [anon_sym_LBRACK] = ACTIONS(683), + [anon_sym_RBRACK] = ACTIONS(683), + [anon_sym_QMARK_LBRACK] = ACTIONS(683), + [anon_sym_LBRACE] = ACTIONS(683), + [anon_sym_BANG] = ACTIONS(681), + [anon_sym_TILDE] = ACTIONS(683), + [anon_sym_STAR] = ACTIONS(683), + [anon_sym_SLASH] = ACTIONS(681), + [anon_sym_PERCENT] = ACTIONS(683), + [anon_sym_PLUS] = ACTIONS(683), + [anon_sym_DASH] = ACTIONS(683), + [anon_sym_EQ_EQ] = ACTIONS(683), + [anon_sym_BANG_EQ] = ACTIONS(683), + [anon_sym_LT] = ACTIONS(681), + [anon_sym_GT] = ACTIONS(681), + [anon_sym_LT_EQ] = ACTIONS(683), + [anon_sym_GT_EQ] = ACTIONS(683), + [anon_sym_AMP_AMP] = ACTIONS(683), + [anon_sym_PIPE_PIPE] = ACTIONS(683), + [anon_sym_PIPE] = ACTIONS(681), + [anon_sym_CARET] = ACTIONS(683), + [anon_sym_AMP] = ACTIONS(681), + [anon_sym_LT_LT] = ACTIONS(683), + [anon_sym_GT_GT] = ACTIONS(683), + [anon_sym_QMARK_QMARK] = ACTIONS(683), + [anon_sym_DOT_DOT] = ACTIONS(681), + [anon_sym_DOT_DOT_EQ] = ACTIONS(683), + [anon_sym_QMARK] = ACTIONS(681), + [anon_sym_match] = ACTIONS(681), + [anon_sym_EQ_GT] = ACTIONS(683), + [anon_sym_SEMI] = ACTIONS(683), + [anon_sym_if] = ACTIONS(681), + [anon_sym_spawn] = ACTIONS(681), + [anon_sym_chan] = ACTIONS(681), + [anon_sym_send] = ACTIONS(681), + [anon_sym_recv] = ACTIONS(681), + [anon_sym_select] = ACTIONS(681), + [anon_sym_POUND] = ACTIONS(683), + [anon_sym_use] = ACTIONS(681), + [anon_sym_export] = ACTIONS(681), + [anon_sym_macro_rules] = ACTIONS(681), + [anon_sym_let] = ACTIONS(681), + [anon_sym_while] = ACTIONS(681), + [anon_sym_for] = ACTIONS(681), + [anon_sym_fn] = ACTIONS(681), + [anon_sym_struct] = ACTIONS(681), + [anon_sym_type] = ACTIONS(681), + [anon_sym_trait] = ACTIONS(681), + [anon_sym_impl] = ACTIONS(681), + [anon_sym_return] = ACTIONS(681), + [anon_sym_break] = ACTIONS(681), + [anon_sym_continue] = ACTIONS(681), + [anon_sym_go] = ACTIONS(681), + [anon_sym_try] = ACTIONS(681), }, [STATE(57)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(672), - [sym_integer_literal] = ACTIONS(672), - [sym_float_literal] = ACTIONS(674), - [anon_sym_true] = ACTIONS(672), - [anon_sym_false] = ACTIONS(672), - [anon_sym_nil] = ACTIONS(672), - [anon_sym_DQUOTE] = ACTIONS(674), - [anon_sym_SQUOTE] = ACTIONS(674), - [sym_raw_string] = ACTIONS(674), - [anon_sym_RBRACE] = ACTIONS(674), - [anon_sym_LPAREN] = ACTIONS(674), - [anon_sym_RPAREN] = ACTIONS(674), - [anon_sym_COMMA] = ACTIONS(674), - [anon_sym_COLON] = ACTIONS(674), - [anon_sym_DOT] = ACTIONS(672), - [anon_sym_QMARK_DOT] = ACTIONS(674), - [anon_sym_LBRACK] = ACTIONS(674), - [anon_sym_RBRACK] = ACTIONS(674), - [anon_sym_QMARK_LBRACK] = ACTIONS(674), - [anon_sym_LBRACE] = ACTIONS(674), - [anon_sym_BANG] = ACTIONS(672), - [anon_sym_STAR] = ACTIONS(674), - [anon_sym_SLASH] = ACTIONS(672), - [anon_sym_PERCENT] = ACTIONS(674), - [anon_sym_PLUS] = ACTIONS(674), - [anon_sym_DASH] = ACTIONS(674), - [anon_sym_EQ_EQ] = ACTIONS(674), - [anon_sym_BANG_EQ] = ACTIONS(674), - [anon_sym_LT] = ACTIONS(672), - [anon_sym_GT] = ACTIONS(672), - [anon_sym_LT_EQ] = ACTIONS(674), - [anon_sym_GT_EQ] = ACTIONS(674), - [anon_sym_AMP_AMP] = ACTIONS(674), - [anon_sym_PIPE_PIPE] = ACTIONS(674), - [anon_sym_QMARK_QMARK] = ACTIONS(674), - [anon_sym_DOT_DOT] = ACTIONS(672), - [anon_sym_DOT_DOT_EQ] = ACTIONS(674), - [anon_sym_QMARK] = ACTIONS(672), - [anon_sym_PIPE] = ACTIONS(672), - [anon_sym_match] = ACTIONS(672), - [anon_sym_EQ_GT] = ACTIONS(674), - [anon_sym_SEMI] = ACTIONS(674), - [anon_sym_if] = ACTIONS(672), - [anon_sym_spawn] = ACTIONS(672), - [anon_sym_chan] = ACTIONS(672), - [anon_sym_send] = ACTIONS(672), - [anon_sym_recv] = ACTIONS(672), - [anon_sym_select] = ACTIONS(672), - [anon_sym_POUND] = ACTIONS(674), - [anon_sym_use] = ACTIONS(672), - [anon_sym_export] = ACTIONS(672), - [anon_sym_macro_rules] = ACTIONS(672), - [anon_sym_let] = ACTIONS(672), - [anon_sym_while] = ACTIONS(672), - [anon_sym_for] = ACTIONS(672), - [anon_sym_fn] = ACTIONS(672), - [anon_sym_struct] = ACTIONS(672), - [anon_sym_type] = ACTIONS(672), - [anon_sym_trait] = ACTIONS(672), - [anon_sym_impl] = ACTIONS(672), - [anon_sym_return] = ACTIONS(672), - [anon_sym_break] = ACTIONS(672), - [anon_sym_continue] = ACTIONS(672), - [anon_sym_go] = ACTIONS(672), - [anon_sym_try] = ACTIONS(672), + [aux_sym_identifier_token1] = ACTIONS(685), + [sym_integer_literal] = ACTIONS(685), + [sym_float_literal] = ACTIONS(687), + [anon_sym_true] = ACTIONS(685), + [anon_sym_false] = ACTIONS(685), + [anon_sym_nil] = ACTIONS(685), + [anon_sym_DQUOTE] = ACTIONS(687), + [anon_sym_SQUOTE] = ACTIONS(687), + [sym_raw_string] = ACTIONS(687), + [anon_sym_RBRACE] = ACTIONS(687), + [anon_sym_LPAREN] = ACTIONS(687), + [anon_sym_RPAREN] = ACTIONS(687), + [anon_sym_COMMA] = ACTIONS(687), + [anon_sym_COLON] = ACTIONS(687), + [anon_sym_DOT] = ACTIONS(685), + [anon_sym_QMARK_DOT] = ACTIONS(687), + [anon_sym_LBRACK] = ACTIONS(687), + [anon_sym_RBRACK] = ACTIONS(687), + [anon_sym_QMARK_LBRACK] = ACTIONS(687), + [anon_sym_LBRACE] = ACTIONS(687), + [anon_sym_BANG] = ACTIONS(685), + [anon_sym_TILDE] = ACTIONS(687), + [anon_sym_STAR] = ACTIONS(687), + [anon_sym_SLASH] = ACTIONS(685), + [anon_sym_PERCENT] = ACTIONS(687), + [anon_sym_PLUS] = ACTIONS(687), + [anon_sym_DASH] = ACTIONS(687), + [anon_sym_EQ_EQ] = ACTIONS(687), + [anon_sym_BANG_EQ] = ACTIONS(687), + [anon_sym_LT] = ACTIONS(685), + [anon_sym_GT] = ACTIONS(685), + [anon_sym_LT_EQ] = ACTIONS(687), + [anon_sym_GT_EQ] = ACTIONS(687), + [anon_sym_AMP_AMP] = ACTIONS(687), + [anon_sym_PIPE_PIPE] = ACTIONS(687), + [anon_sym_PIPE] = ACTIONS(685), + [anon_sym_CARET] = ACTIONS(687), + [anon_sym_AMP] = ACTIONS(685), + [anon_sym_LT_LT] = ACTIONS(687), + [anon_sym_GT_GT] = ACTIONS(687), + [anon_sym_QMARK_QMARK] = ACTIONS(687), + [anon_sym_DOT_DOT] = ACTIONS(685), + [anon_sym_DOT_DOT_EQ] = ACTIONS(687), + [anon_sym_QMARK] = ACTIONS(685), + [anon_sym_match] = ACTIONS(685), + [anon_sym_EQ_GT] = ACTIONS(687), + [anon_sym_SEMI] = ACTIONS(687), + [anon_sym_if] = ACTIONS(685), + [anon_sym_spawn] = ACTIONS(685), + [anon_sym_chan] = ACTIONS(685), + [anon_sym_send] = ACTIONS(685), + [anon_sym_recv] = ACTIONS(685), + [anon_sym_select] = ACTIONS(685), + [anon_sym_POUND] = ACTIONS(687), + [anon_sym_use] = ACTIONS(685), + [anon_sym_export] = ACTIONS(685), + [anon_sym_macro_rules] = ACTIONS(685), + [anon_sym_let] = ACTIONS(685), + [anon_sym_while] = ACTIONS(685), + [anon_sym_for] = ACTIONS(685), + [anon_sym_fn] = ACTIONS(685), + [anon_sym_struct] = ACTIONS(685), + [anon_sym_type] = ACTIONS(685), + [anon_sym_trait] = ACTIONS(685), + [anon_sym_impl] = ACTIONS(685), + [anon_sym_return] = ACTIONS(685), + [anon_sym_break] = ACTIONS(685), + [anon_sym_continue] = ACTIONS(685), + [anon_sym_go] = ACTIONS(685), + [anon_sym_try] = ACTIONS(685), }, [STATE(58)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(676), - [sym_integer_literal] = ACTIONS(676), - [sym_float_literal] = ACTIONS(678), - [anon_sym_true] = ACTIONS(676), - [anon_sym_false] = ACTIONS(676), - [anon_sym_nil] = ACTIONS(676), - [anon_sym_DQUOTE] = ACTIONS(678), - [anon_sym_SQUOTE] = ACTIONS(678), - [sym_raw_string] = ACTIONS(678), - [anon_sym_RBRACE] = ACTIONS(678), - [anon_sym_LPAREN] = ACTIONS(678), - [anon_sym_RPAREN] = ACTIONS(678), - [anon_sym_COMMA] = ACTIONS(678), - [anon_sym_COLON] = ACTIONS(678), - [anon_sym_DOT] = ACTIONS(676), - [anon_sym_QMARK_DOT] = ACTIONS(678), - [anon_sym_LBRACK] = ACTIONS(678), - [anon_sym_RBRACK] = ACTIONS(678), - [anon_sym_QMARK_LBRACK] = ACTIONS(678), - [anon_sym_LBRACE] = ACTIONS(678), - [anon_sym_BANG] = ACTIONS(676), - [anon_sym_STAR] = ACTIONS(678), - [anon_sym_SLASH] = ACTIONS(676), - [anon_sym_PERCENT] = ACTIONS(678), - [anon_sym_PLUS] = ACTIONS(678), - [anon_sym_DASH] = ACTIONS(678), - [anon_sym_EQ_EQ] = ACTIONS(678), - [anon_sym_BANG_EQ] = ACTIONS(678), - [anon_sym_LT] = ACTIONS(676), - [anon_sym_GT] = ACTIONS(676), - [anon_sym_LT_EQ] = ACTIONS(678), - [anon_sym_GT_EQ] = ACTIONS(678), - [anon_sym_AMP_AMP] = ACTIONS(678), - [anon_sym_PIPE_PIPE] = ACTIONS(678), - [anon_sym_QMARK_QMARK] = ACTIONS(678), - [anon_sym_DOT_DOT] = ACTIONS(676), - [anon_sym_DOT_DOT_EQ] = ACTIONS(678), - [anon_sym_QMARK] = ACTIONS(676), - [anon_sym_PIPE] = ACTIONS(676), - [anon_sym_match] = ACTIONS(676), - [anon_sym_EQ_GT] = ACTIONS(678), - [anon_sym_SEMI] = ACTIONS(678), - [anon_sym_if] = ACTIONS(676), - [anon_sym_spawn] = ACTIONS(676), - [anon_sym_chan] = ACTIONS(676), - [anon_sym_send] = ACTIONS(676), - [anon_sym_recv] = ACTIONS(676), - [anon_sym_select] = ACTIONS(676), - [anon_sym_POUND] = ACTIONS(678), - [anon_sym_use] = ACTIONS(676), - [anon_sym_export] = ACTIONS(676), - [anon_sym_macro_rules] = ACTIONS(676), - [anon_sym_let] = ACTIONS(676), - [anon_sym_while] = ACTIONS(676), - [anon_sym_for] = ACTIONS(676), - [anon_sym_fn] = ACTIONS(676), - [anon_sym_struct] = ACTIONS(676), - [anon_sym_type] = ACTIONS(676), - [anon_sym_trait] = ACTIONS(676), - [anon_sym_impl] = ACTIONS(676), - [anon_sym_return] = ACTIONS(676), - [anon_sym_break] = ACTIONS(676), - [anon_sym_continue] = ACTIONS(676), - [anon_sym_go] = ACTIONS(676), - [anon_sym_try] = ACTIONS(676), + [aux_sym_identifier_token1] = ACTIONS(689), + [sym_integer_literal] = ACTIONS(689), + [sym_float_literal] = ACTIONS(691), + [anon_sym_true] = ACTIONS(689), + [anon_sym_false] = ACTIONS(689), + [anon_sym_nil] = ACTIONS(689), + [anon_sym_DQUOTE] = ACTIONS(691), + [anon_sym_SQUOTE] = ACTIONS(691), + [sym_raw_string] = ACTIONS(691), + [anon_sym_RBRACE] = ACTIONS(691), + [anon_sym_LPAREN] = ACTIONS(691), + [anon_sym_RPAREN] = ACTIONS(691), + [anon_sym_COMMA] = ACTIONS(691), + [anon_sym_COLON] = ACTIONS(691), + [anon_sym_DOT] = ACTIONS(689), + [anon_sym_QMARK_DOT] = ACTIONS(691), + [anon_sym_LBRACK] = ACTIONS(691), + [anon_sym_RBRACK] = ACTIONS(691), + [anon_sym_QMARK_LBRACK] = ACTIONS(691), + [anon_sym_LBRACE] = ACTIONS(691), + [anon_sym_BANG] = ACTIONS(689), + [anon_sym_TILDE] = ACTIONS(691), + [anon_sym_STAR] = ACTIONS(691), + [anon_sym_SLASH] = ACTIONS(689), + [anon_sym_PERCENT] = ACTIONS(691), + [anon_sym_PLUS] = ACTIONS(691), + [anon_sym_DASH] = ACTIONS(691), + [anon_sym_EQ_EQ] = ACTIONS(691), + [anon_sym_BANG_EQ] = ACTIONS(691), + [anon_sym_LT] = ACTIONS(689), + [anon_sym_GT] = ACTIONS(689), + [anon_sym_LT_EQ] = ACTIONS(691), + [anon_sym_GT_EQ] = ACTIONS(691), + [anon_sym_AMP_AMP] = ACTIONS(691), + [anon_sym_PIPE_PIPE] = ACTIONS(691), + [anon_sym_PIPE] = ACTIONS(689), + [anon_sym_CARET] = ACTIONS(691), + [anon_sym_AMP] = ACTIONS(689), + [anon_sym_LT_LT] = ACTIONS(691), + [anon_sym_GT_GT] = ACTIONS(691), + [anon_sym_QMARK_QMARK] = ACTIONS(691), + [anon_sym_DOT_DOT] = ACTIONS(689), + [anon_sym_DOT_DOT_EQ] = ACTIONS(691), + [anon_sym_QMARK] = ACTIONS(689), + [anon_sym_match] = ACTIONS(689), + [anon_sym_EQ_GT] = ACTIONS(691), + [anon_sym_SEMI] = ACTIONS(691), + [anon_sym_if] = ACTIONS(689), + [anon_sym_spawn] = ACTIONS(689), + [anon_sym_chan] = ACTIONS(689), + [anon_sym_send] = ACTIONS(689), + [anon_sym_recv] = ACTIONS(689), + [anon_sym_select] = ACTIONS(689), + [anon_sym_POUND] = ACTIONS(691), + [anon_sym_use] = ACTIONS(689), + [anon_sym_export] = ACTIONS(689), + [anon_sym_macro_rules] = ACTIONS(689), + [anon_sym_let] = ACTIONS(689), + [anon_sym_while] = ACTIONS(689), + [anon_sym_for] = ACTIONS(689), + [anon_sym_fn] = ACTIONS(689), + [anon_sym_struct] = ACTIONS(689), + [anon_sym_type] = ACTIONS(689), + [anon_sym_trait] = ACTIONS(689), + [anon_sym_impl] = ACTIONS(689), + [anon_sym_return] = ACTIONS(689), + [anon_sym_break] = ACTIONS(689), + [anon_sym_continue] = ACTIONS(689), + [anon_sym_go] = ACTIONS(689), + [anon_sym_try] = ACTIONS(689), }, [STATE(59)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(680), - [sym_integer_literal] = ACTIONS(680), - [sym_float_literal] = ACTIONS(682), - [anon_sym_true] = ACTIONS(680), - [anon_sym_false] = ACTIONS(680), - [anon_sym_nil] = ACTIONS(680), - [anon_sym_DQUOTE] = ACTIONS(682), - [anon_sym_SQUOTE] = ACTIONS(682), - [sym_raw_string] = ACTIONS(682), - [anon_sym_RBRACE] = ACTIONS(682), - [anon_sym_LPAREN] = ACTIONS(682), - [anon_sym_RPAREN] = ACTIONS(682), - [anon_sym_COMMA] = ACTIONS(682), - [anon_sym_COLON] = ACTIONS(682), - [anon_sym_DOT] = ACTIONS(680), - [anon_sym_QMARK_DOT] = ACTIONS(682), - [anon_sym_LBRACK] = ACTIONS(682), - [anon_sym_RBRACK] = ACTIONS(682), - [anon_sym_QMARK_LBRACK] = ACTIONS(682), - [anon_sym_LBRACE] = ACTIONS(682), - [anon_sym_BANG] = ACTIONS(680), - [anon_sym_STAR] = ACTIONS(682), - [anon_sym_SLASH] = ACTIONS(680), - [anon_sym_PERCENT] = ACTIONS(682), - [anon_sym_PLUS] = ACTIONS(682), - [anon_sym_DASH] = ACTIONS(682), - [anon_sym_EQ_EQ] = ACTIONS(682), - [anon_sym_BANG_EQ] = ACTIONS(682), - [anon_sym_LT] = ACTIONS(680), - [anon_sym_GT] = ACTIONS(680), - [anon_sym_LT_EQ] = ACTIONS(682), - [anon_sym_GT_EQ] = ACTIONS(682), - [anon_sym_AMP_AMP] = ACTIONS(682), - [anon_sym_PIPE_PIPE] = ACTIONS(682), - [anon_sym_QMARK_QMARK] = ACTIONS(682), - [anon_sym_DOT_DOT] = ACTIONS(680), - [anon_sym_DOT_DOT_EQ] = ACTIONS(682), - [anon_sym_QMARK] = ACTIONS(680), - [anon_sym_PIPE] = ACTIONS(680), - [anon_sym_match] = ACTIONS(680), - [anon_sym_EQ_GT] = ACTIONS(682), - [anon_sym_SEMI] = ACTIONS(682), - [anon_sym_if] = ACTIONS(680), - [anon_sym_spawn] = ACTIONS(680), - [anon_sym_chan] = ACTIONS(680), - [anon_sym_send] = ACTIONS(680), - [anon_sym_recv] = ACTIONS(680), - [anon_sym_select] = ACTIONS(680), - [anon_sym_POUND] = ACTIONS(682), - [anon_sym_use] = ACTIONS(680), - [anon_sym_export] = ACTIONS(680), - [anon_sym_macro_rules] = ACTIONS(680), - [anon_sym_let] = ACTIONS(680), - [anon_sym_while] = ACTIONS(680), - [anon_sym_for] = ACTIONS(680), - [anon_sym_fn] = ACTIONS(680), - [anon_sym_struct] = ACTIONS(680), - [anon_sym_type] = ACTIONS(680), - [anon_sym_trait] = ACTIONS(680), - [anon_sym_impl] = ACTIONS(680), - [anon_sym_return] = ACTIONS(680), - [anon_sym_break] = ACTIONS(680), - [anon_sym_continue] = ACTIONS(680), - [anon_sym_go] = ACTIONS(680), - [anon_sym_try] = ACTIONS(680), + [aux_sym_identifier_token1] = ACTIONS(693), + [sym_integer_literal] = ACTIONS(693), + [sym_float_literal] = ACTIONS(695), + [anon_sym_true] = ACTIONS(693), + [anon_sym_false] = ACTIONS(693), + [anon_sym_nil] = ACTIONS(693), + [anon_sym_DQUOTE] = ACTIONS(695), + [anon_sym_SQUOTE] = ACTIONS(695), + [sym_raw_string] = ACTIONS(695), + [anon_sym_RBRACE] = ACTIONS(695), + [anon_sym_LPAREN] = ACTIONS(695), + [anon_sym_RPAREN] = ACTIONS(695), + [anon_sym_COMMA] = ACTIONS(695), + [anon_sym_COLON] = ACTIONS(695), + [anon_sym_DOT] = ACTIONS(693), + [anon_sym_QMARK_DOT] = ACTIONS(695), + [anon_sym_LBRACK] = ACTIONS(695), + [anon_sym_RBRACK] = ACTIONS(695), + [anon_sym_QMARK_LBRACK] = ACTIONS(695), + [anon_sym_LBRACE] = ACTIONS(695), + [anon_sym_BANG] = ACTIONS(693), + [anon_sym_TILDE] = ACTIONS(695), + [anon_sym_STAR] = ACTIONS(695), + [anon_sym_SLASH] = ACTIONS(693), + [anon_sym_PERCENT] = ACTIONS(695), + [anon_sym_PLUS] = ACTIONS(695), + [anon_sym_DASH] = ACTIONS(695), + [anon_sym_EQ_EQ] = ACTIONS(695), + [anon_sym_BANG_EQ] = ACTIONS(695), + [anon_sym_LT] = ACTIONS(693), + [anon_sym_GT] = ACTIONS(693), + [anon_sym_LT_EQ] = ACTIONS(695), + [anon_sym_GT_EQ] = ACTIONS(695), + [anon_sym_AMP_AMP] = ACTIONS(695), + [anon_sym_PIPE_PIPE] = ACTIONS(695), + [anon_sym_PIPE] = ACTIONS(693), + [anon_sym_CARET] = ACTIONS(695), + [anon_sym_AMP] = ACTIONS(693), + [anon_sym_LT_LT] = ACTIONS(695), + [anon_sym_GT_GT] = ACTIONS(695), + [anon_sym_QMARK_QMARK] = ACTIONS(695), + [anon_sym_DOT_DOT] = ACTIONS(693), + [anon_sym_DOT_DOT_EQ] = ACTIONS(695), + [anon_sym_QMARK] = ACTIONS(693), + [anon_sym_match] = ACTIONS(693), + [anon_sym_EQ_GT] = ACTIONS(695), + [anon_sym_SEMI] = ACTIONS(695), + [anon_sym_if] = ACTIONS(693), + [anon_sym_spawn] = ACTIONS(693), + [anon_sym_chan] = ACTIONS(693), + [anon_sym_send] = ACTIONS(693), + [anon_sym_recv] = ACTIONS(693), + [anon_sym_select] = ACTIONS(693), + [anon_sym_POUND] = ACTIONS(695), + [anon_sym_use] = ACTIONS(693), + [anon_sym_export] = ACTIONS(693), + [anon_sym_macro_rules] = ACTIONS(693), + [anon_sym_let] = ACTIONS(693), + [anon_sym_while] = ACTIONS(693), + [anon_sym_for] = ACTIONS(693), + [anon_sym_fn] = ACTIONS(693), + [anon_sym_struct] = ACTIONS(693), + [anon_sym_type] = ACTIONS(693), + [anon_sym_trait] = ACTIONS(693), + [anon_sym_impl] = ACTIONS(693), + [anon_sym_return] = ACTIONS(693), + [anon_sym_break] = ACTIONS(693), + [anon_sym_continue] = ACTIONS(693), + [anon_sym_go] = ACTIONS(693), + [anon_sym_try] = ACTIONS(693), }, [STATE(60)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(684), - [sym_integer_literal] = ACTIONS(684), - [sym_float_literal] = ACTIONS(686), - [anon_sym_true] = ACTIONS(684), - [anon_sym_false] = ACTIONS(684), - [anon_sym_nil] = ACTIONS(684), - [anon_sym_DQUOTE] = ACTIONS(686), - [anon_sym_SQUOTE] = ACTIONS(686), - [sym_raw_string] = ACTIONS(686), - [anon_sym_RBRACE] = ACTIONS(686), - [anon_sym_LPAREN] = ACTIONS(686), - [anon_sym_RPAREN] = ACTIONS(686), - [anon_sym_COMMA] = ACTIONS(686), - [anon_sym_COLON] = ACTIONS(686), - [anon_sym_DOT] = ACTIONS(684), - [anon_sym_QMARK_DOT] = ACTIONS(686), - [anon_sym_LBRACK] = ACTIONS(686), - [anon_sym_RBRACK] = ACTIONS(686), - [anon_sym_QMARK_LBRACK] = ACTIONS(686), - [anon_sym_LBRACE] = ACTIONS(686), - [anon_sym_BANG] = ACTIONS(684), - [anon_sym_STAR] = ACTIONS(686), - [anon_sym_SLASH] = ACTIONS(684), - [anon_sym_PERCENT] = ACTIONS(686), - [anon_sym_PLUS] = ACTIONS(686), - [anon_sym_DASH] = ACTIONS(686), - [anon_sym_EQ_EQ] = ACTIONS(686), - [anon_sym_BANG_EQ] = ACTIONS(686), - [anon_sym_LT] = ACTIONS(684), - [anon_sym_GT] = ACTIONS(684), - [anon_sym_LT_EQ] = ACTIONS(686), - [anon_sym_GT_EQ] = ACTIONS(686), - [anon_sym_AMP_AMP] = ACTIONS(686), - [anon_sym_PIPE_PIPE] = ACTIONS(686), - [anon_sym_QMARK_QMARK] = ACTIONS(686), - [anon_sym_DOT_DOT] = ACTIONS(684), - [anon_sym_DOT_DOT_EQ] = ACTIONS(686), - [anon_sym_QMARK] = ACTIONS(684), - [anon_sym_PIPE] = ACTIONS(684), - [anon_sym_match] = ACTIONS(684), - [anon_sym_EQ_GT] = ACTIONS(686), - [anon_sym_SEMI] = ACTIONS(686), - [anon_sym_if] = ACTIONS(684), - [anon_sym_spawn] = ACTIONS(684), - [anon_sym_chan] = ACTIONS(684), - [anon_sym_send] = ACTIONS(684), - [anon_sym_recv] = ACTIONS(684), - [anon_sym_select] = ACTIONS(684), - [anon_sym_POUND] = ACTIONS(686), - [anon_sym_use] = ACTIONS(684), - [anon_sym_export] = ACTIONS(684), - [anon_sym_macro_rules] = ACTIONS(684), - [anon_sym_let] = ACTIONS(684), - [anon_sym_while] = ACTIONS(684), - [anon_sym_for] = ACTIONS(684), - [anon_sym_fn] = ACTIONS(684), - [anon_sym_struct] = ACTIONS(684), - [anon_sym_type] = ACTIONS(684), - [anon_sym_trait] = ACTIONS(684), - [anon_sym_impl] = ACTIONS(684), - [anon_sym_return] = ACTIONS(684), - [anon_sym_break] = ACTIONS(684), - [anon_sym_continue] = ACTIONS(684), - [anon_sym_go] = ACTIONS(684), - [anon_sym_try] = ACTIONS(684), + [aux_sym_identifier_token1] = ACTIONS(697), + [sym_integer_literal] = ACTIONS(697), + [sym_float_literal] = ACTIONS(699), + [anon_sym_true] = ACTIONS(697), + [anon_sym_false] = ACTIONS(697), + [anon_sym_nil] = ACTIONS(697), + [anon_sym_DQUOTE] = ACTIONS(699), + [anon_sym_SQUOTE] = ACTIONS(699), + [sym_raw_string] = ACTIONS(699), + [anon_sym_RBRACE] = ACTIONS(699), + [anon_sym_LPAREN] = ACTIONS(699), + [anon_sym_RPAREN] = ACTIONS(699), + [anon_sym_COMMA] = ACTIONS(699), + [anon_sym_COLON] = ACTIONS(699), + [anon_sym_DOT] = ACTIONS(697), + [anon_sym_QMARK_DOT] = ACTIONS(699), + [anon_sym_LBRACK] = ACTIONS(699), + [anon_sym_RBRACK] = ACTIONS(699), + [anon_sym_QMARK_LBRACK] = ACTIONS(699), + [anon_sym_LBRACE] = ACTIONS(699), + [anon_sym_BANG] = ACTIONS(701), + [anon_sym_TILDE] = ACTIONS(699), + [anon_sym_STAR] = ACTIONS(699), + [anon_sym_SLASH] = ACTIONS(697), + [anon_sym_PERCENT] = ACTIONS(699), + [anon_sym_PLUS] = ACTIONS(699), + [anon_sym_DASH] = ACTIONS(699), + [anon_sym_EQ_EQ] = ACTIONS(699), + [anon_sym_BANG_EQ] = ACTIONS(699), + [anon_sym_LT] = ACTIONS(697), + [anon_sym_GT] = ACTIONS(697), + [anon_sym_LT_EQ] = ACTIONS(699), + [anon_sym_GT_EQ] = ACTIONS(699), + [anon_sym_AMP_AMP] = ACTIONS(699), + [anon_sym_PIPE_PIPE] = ACTIONS(699), + [anon_sym_PIPE] = ACTIONS(697), + [anon_sym_CARET] = ACTIONS(699), + [anon_sym_AMP] = ACTIONS(697), + [anon_sym_LT_LT] = ACTIONS(699), + [anon_sym_GT_GT] = ACTIONS(699), + [anon_sym_QMARK_QMARK] = ACTIONS(699), + [anon_sym_DOT_DOT] = ACTIONS(697), + [anon_sym_DOT_DOT_EQ] = ACTIONS(699), + [anon_sym_QMARK] = ACTIONS(697), + [anon_sym_match] = ACTIONS(697), + [anon_sym_EQ_GT] = ACTIONS(699), + [anon_sym_SEMI] = ACTIONS(699), + [anon_sym_if] = ACTIONS(697), + [anon_sym_spawn] = ACTIONS(697), + [anon_sym_chan] = ACTIONS(697), + [anon_sym_send] = ACTIONS(697), + [anon_sym_recv] = ACTIONS(697), + [anon_sym_select] = ACTIONS(697), + [anon_sym_POUND] = ACTIONS(699), + [anon_sym_use] = ACTIONS(697), + [anon_sym_export] = ACTIONS(697), + [anon_sym_macro_rules] = ACTIONS(697), + [anon_sym_let] = ACTIONS(697), + [anon_sym_while] = ACTIONS(697), + [anon_sym_for] = ACTIONS(697), + [anon_sym_fn] = ACTIONS(697), + [anon_sym_struct] = ACTIONS(697), + [anon_sym_type] = ACTIONS(697), + [anon_sym_trait] = ACTIONS(697), + [anon_sym_impl] = ACTIONS(697), + [anon_sym_return] = ACTIONS(697), + [anon_sym_break] = ACTIONS(697), + [anon_sym_continue] = ACTIONS(697), + [anon_sym_go] = ACTIONS(697), + [anon_sym_try] = ACTIONS(697), }, [STATE(61)] = { - [sym_line_comment] = ACTIONS(3), - [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(688), - [sym_integer_literal] = ACTIONS(688), - [sym_float_literal] = ACTIONS(690), - [anon_sym_true] = ACTIONS(688), - [anon_sym_false] = ACTIONS(688), - [anon_sym_nil] = ACTIONS(688), - [anon_sym_DQUOTE] = ACTIONS(690), - [anon_sym_SQUOTE] = ACTIONS(690), - [sym_raw_string] = ACTIONS(690), - [anon_sym_RBRACE] = ACTIONS(690), - [anon_sym_LPAREN] = ACTIONS(690), - [anon_sym_RPAREN] = ACTIONS(690), - [anon_sym_COMMA] = ACTIONS(690), - [anon_sym_COLON] = ACTIONS(690), - [anon_sym_DOT] = ACTIONS(688), - [anon_sym_QMARK_DOT] = ACTIONS(690), - [anon_sym_LBRACK] = ACTIONS(690), - [anon_sym_RBRACK] = ACTIONS(690), - [anon_sym_QMARK_LBRACK] = ACTIONS(690), - [anon_sym_LBRACE] = ACTIONS(690), - [anon_sym_BANG] = ACTIONS(688), - [anon_sym_STAR] = ACTIONS(690), - [anon_sym_SLASH] = ACTIONS(688), - [anon_sym_PERCENT] = ACTIONS(690), - [anon_sym_PLUS] = ACTIONS(690), - [anon_sym_DASH] = ACTIONS(690), - [anon_sym_EQ_EQ] = ACTIONS(690), - [anon_sym_BANG_EQ] = ACTIONS(690), - [anon_sym_LT] = ACTIONS(688), - [anon_sym_GT] = ACTIONS(688), - [anon_sym_LT_EQ] = ACTIONS(690), - [anon_sym_GT_EQ] = ACTIONS(690), - [anon_sym_AMP_AMP] = ACTIONS(690), - [anon_sym_PIPE_PIPE] = ACTIONS(690), - [anon_sym_QMARK_QMARK] = ACTIONS(690), - [anon_sym_DOT_DOT] = ACTIONS(688), - [anon_sym_DOT_DOT_EQ] = ACTIONS(690), - [anon_sym_QMARK] = ACTIONS(688), - [anon_sym_PIPE] = ACTIONS(688), - [anon_sym_match] = ACTIONS(688), - [anon_sym_EQ_GT] = ACTIONS(690), - [anon_sym_SEMI] = ACTIONS(690), - [anon_sym_if] = ACTIONS(688), - [anon_sym_spawn] = ACTIONS(688), - [anon_sym_chan] = ACTIONS(688), - [anon_sym_send] = ACTIONS(688), - [anon_sym_recv] = ACTIONS(688), - [anon_sym_select] = ACTIONS(688), - [anon_sym_POUND] = ACTIONS(690), - [anon_sym_use] = ACTIONS(688), - [anon_sym_export] = ACTIONS(688), - [anon_sym_macro_rules] = ACTIONS(688), - [anon_sym_let] = ACTIONS(688), - [anon_sym_while] = ACTIONS(688), - [anon_sym_for] = ACTIONS(688), - [anon_sym_fn] = ACTIONS(688), - [anon_sym_struct] = ACTIONS(688), - [anon_sym_type] = ACTIONS(688), - [anon_sym_trait] = ACTIONS(688), - [anon_sym_impl] = ACTIONS(688), - [anon_sym_return] = ACTIONS(688), - [anon_sym_break] = ACTIONS(688), - [anon_sym_continue] = ACTIONS(688), - [anon_sym_go] = ACTIONS(688), - [anon_sym_try] = ACTIONS(688), - }, - [STATE(62)] = { - [sym_line_comment] = ACTIONS(3), - [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(692), - [sym_integer_literal] = ACTIONS(692), - [sym_float_literal] = ACTIONS(694), - [anon_sym_true] = ACTIONS(692), - [anon_sym_false] = ACTIONS(692), - [anon_sym_nil] = ACTIONS(692), - [anon_sym_DQUOTE] = ACTIONS(694), - [anon_sym_SQUOTE] = ACTIONS(694), - [sym_raw_string] = ACTIONS(694), - [anon_sym_RBRACE] = ACTIONS(694), - [anon_sym_LPAREN] = ACTIONS(694), - [anon_sym_RPAREN] = ACTIONS(694), - [anon_sym_COMMA] = ACTIONS(694), - [anon_sym_COLON] = ACTIONS(694), - [anon_sym_DOT] = ACTIONS(692), - [anon_sym_QMARK_DOT] = ACTIONS(694), - [anon_sym_LBRACK] = ACTIONS(694), - [anon_sym_RBRACK] = ACTIONS(694), - [anon_sym_QMARK_LBRACK] = ACTIONS(694), - [anon_sym_LBRACE] = ACTIONS(694), - [anon_sym_BANG] = ACTIONS(692), - [anon_sym_STAR] = ACTIONS(694), - [anon_sym_SLASH] = ACTIONS(692), - [anon_sym_PERCENT] = ACTIONS(694), - [anon_sym_PLUS] = ACTIONS(694), - [anon_sym_DASH] = ACTIONS(694), - [anon_sym_EQ_EQ] = ACTIONS(694), - [anon_sym_BANG_EQ] = ACTIONS(694), - [anon_sym_LT] = ACTIONS(692), - [anon_sym_GT] = ACTIONS(692), - [anon_sym_LT_EQ] = ACTIONS(694), - [anon_sym_GT_EQ] = ACTIONS(694), - [anon_sym_AMP_AMP] = ACTIONS(694), - [anon_sym_PIPE_PIPE] = ACTIONS(694), - [anon_sym_QMARK_QMARK] = ACTIONS(694), - [anon_sym_DOT_DOT] = ACTIONS(692), - [anon_sym_DOT_DOT_EQ] = ACTIONS(694), - [anon_sym_QMARK] = ACTIONS(692), - [anon_sym_PIPE] = ACTIONS(692), - [anon_sym_match] = ACTIONS(692), - [anon_sym_EQ_GT] = ACTIONS(694), - [anon_sym_SEMI] = ACTIONS(694), - [anon_sym_if] = ACTIONS(692), - [anon_sym_spawn] = ACTIONS(692), - [anon_sym_chan] = ACTIONS(692), - [anon_sym_send] = ACTIONS(692), - [anon_sym_recv] = ACTIONS(692), - [anon_sym_select] = ACTIONS(692), - [anon_sym_POUND] = ACTIONS(694), - [anon_sym_use] = ACTIONS(692), - [anon_sym_export] = ACTIONS(692), - [anon_sym_macro_rules] = ACTIONS(692), - [anon_sym_let] = ACTIONS(692), - [anon_sym_while] = ACTIONS(692), - [anon_sym_for] = ACTIONS(692), - [anon_sym_fn] = ACTIONS(692), - [anon_sym_struct] = ACTIONS(692), - [anon_sym_type] = ACTIONS(692), - [anon_sym_trait] = ACTIONS(692), - [anon_sym_impl] = ACTIONS(692), - [anon_sym_return] = ACTIONS(692), - [anon_sym_break] = ACTIONS(692), - [anon_sym_continue] = ACTIONS(692), - [anon_sym_go] = ACTIONS(692), - [anon_sym_try] = ACTIONS(692), - }, - [STATE(63)] = { - [sym_line_comment] = ACTIONS(3), - [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(696), - [sym_integer_literal] = ACTIONS(696), - [sym_float_literal] = ACTIONS(698), - [anon_sym_true] = ACTIONS(696), - [anon_sym_false] = ACTIONS(696), - [anon_sym_nil] = ACTIONS(696), - [anon_sym_DQUOTE] = ACTIONS(698), - [anon_sym_SQUOTE] = ACTIONS(698), - [sym_raw_string] = ACTIONS(698), - [anon_sym_RBRACE] = ACTIONS(698), - [anon_sym_LPAREN] = ACTIONS(698), - [anon_sym_RPAREN] = ACTIONS(698), - [anon_sym_COMMA] = ACTIONS(698), - [anon_sym_COLON] = ACTIONS(698), - [anon_sym_DOT] = ACTIONS(696), - [anon_sym_QMARK_DOT] = ACTIONS(698), - [anon_sym_LBRACK] = ACTIONS(698), - [anon_sym_RBRACK] = ACTIONS(698), - [anon_sym_QMARK_LBRACK] = ACTIONS(698), - [anon_sym_LBRACE] = ACTIONS(698), - [anon_sym_BANG] = ACTIONS(696), - [anon_sym_STAR] = ACTIONS(698), - [anon_sym_SLASH] = ACTIONS(696), - [anon_sym_PERCENT] = ACTIONS(698), - [anon_sym_PLUS] = ACTIONS(698), - [anon_sym_DASH] = ACTIONS(698), - [anon_sym_EQ_EQ] = ACTIONS(698), - [anon_sym_BANG_EQ] = ACTIONS(698), - [anon_sym_LT] = ACTIONS(696), - [anon_sym_GT] = ACTIONS(696), - [anon_sym_LT_EQ] = ACTIONS(698), - [anon_sym_GT_EQ] = ACTIONS(698), - [anon_sym_AMP_AMP] = ACTIONS(698), - [anon_sym_PIPE_PIPE] = ACTIONS(698), - [anon_sym_QMARK_QMARK] = ACTIONS(698), - [anon_sym_DOT_DOT] = ACTIONS(696), - [anon_sym_DOT_DOT_EQ] = ACTIONS(698), - [anon_sym_QMARK] = ACTIONS(696), - [anon_sym_PIPE] = ACTIONS(696), - [anon_sym_match] = ACTIONS(696), - [anon_sym_EQ_GT] = ACTIONS(698), - [anon_sym_SEMI] = ACTIONS(698), - [anon_sym_if] = ACTIONS(696), - [anon_sym_spawn] = ACTIONS(696), - [anon_sym_chan] = ACTIONS(696), - [anon_sym_send] = ACTIONS(696), - [anon_sym_recv] = ACTIONS(696), - [anon_sym_select] = ACTIONS(696), - [anon_sym_POUND] = ACTIONS(698), - [anon_sym_use] = ACTIONS(696), - [anon_sym_export] = ACTIONS(696), - [anon_sym_macro_rules] = ACTIONS(696), - [anon_sym_let] = ACTIONS(696), - [anon_sym_while] = ACTIONS(696), - [anon_sym_for] = ACTIONS(696), - [anon_sym_fn] = ACTIONS(696), - [anon_sym_struct] = ACTIONS(696), - [anon_sym_type] = ACTIONS(696), - [anon_sym_trait] = ACTIONS(696), - [anon_sym_impl] = ACTIONS(696), - [anon_sym_return] = ACTIONS(696), - [anon_sym_break] = ACTIONS(696), - [anon_sym_continue] = ACTIONS(696), - [anon_sym_go] = ACTIONS(696), - [anon_sym_try] = ACTIONS(696), - }, - [STATE(64)] = { - [sym_line_comment] = ACTIONS(3), - [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(108), - [sym_integer_literal] = ACTIONS(108), - [sym_float_literal] = ACTIONS(110), - [anon_sym_true] = ACTIONS(108), - [anon_sym_false] = ACTIONS(108), - [anon_sym_nil] = ACTIONS(108), - [anon_sym_DQUOTE] = ACTIONS(110), - [anon_sym_SQUOTE] = ACTIONS(110), - [sym_raw_string] = ACTIONS(110), - [anon_sym_RBRACE] = ACTIONS(110), - [anon_sym_LPAREN] = ACTIONS(110), - [anon_sym_RPAREN] = ACTIONS(110), - [anon_sym_COMMA] = ACTIONS(110), - [anon_sym_COLON] = ACTIONS(110), - [anon_sym_DOT] = ACTIONS(108), - [anon_sym_QMARK_DOT] = ACTIONS(110), - [anon_sym_LBRACK] = ACTIONS(110), - [anon_sym_RBRACK] = ACTIONS(110), - [anon_sym_QMARK_LBRACK] = ACTIONS(110), - [anon_sym_LBRACE] = ACTIONS(110), - [anon_sym_BANG] = ACTIONS(108), - [anon_sym_STAR] = ACTIONS(110), - [anon_sym_SLASH] = ACTIONS(108), - [anon_sym_PERCENT] = ACTIONS(110), - [anon_sym_PLUS] = ACTIONS(110), - [anon_sym_DASH] = ACTIONS(110), - [anon_sym_EQ_EQ] = ACTIONS(110), - [anon_sym_BANG_EQ] = ACTIONS(110), - [anon_sym_LT] = ACTIONS(108), - [anon_sym_GT] = ACTIONS(108), - [anon_sym_LT_EQ] = ACTIONS(110), - [anon_sym_GT_EQ] = ACTIONS(110), - [anon_sym_AMP_AMP] = ACTIONS(110), - [anon_sym_PIPE_PIPE] = ACTIONS(110), - [anon_sym_QMARK_QMARK] = ACTIONS(110), - [anon_sym_DOT_DOT] = ACTIONS(108), - [anon_sym_DOT_DOT_EQ] = ACTIONS(110), - [anon_sym_QMARK] = ACTIONS(108), - [anon_sym_PIPE] = ACTIONS(108), - [anon_sym_match] = ACTIONS(108), - [anon_sym_EQ_GT] = ACTIONS(110), - [anon_sym_SEMI] = ACTIONS(110), - [anon_sym_if] = ACTIONS(108), - [anon_sym_spawn] = ACTIONS(108), - [anon_sym_chan] = ACTIONS(108), - [anon_sym_send] = ACTIONS(108), - [anon_sym_recv] = ACTIONS(108), - [anon_sym_select] = ACTIONS(108), - [anon_sym_POUND] = ACTIONS(110), - [anon_sym_use] = ACTIONS(108), - [anon_sym_export] = ACTIONS(108), - [anon_sym_macro_rules] = ACTIONS(108), - [anon_sym_let] = ACTIONS(108), - [anon_sym_while] = ACTIONS(108), - [anon_sym_for] = ACTIONS(108), - [anon_sym_fn] = ACTIONS(108), - [anon_sym_struct] = ACTIONS(108), - [anon_sym_type] = ACTIONS(108), - [anon_sym_trait] = ACTIONS(108), - [anon_sym_impl] = ACTIONS(108), - [anon_sym_return] = ACTIONS(108), - [anon_sym_break] = ACTIONS(108), - [anon_sym_continue] = ACTIONS(108), - [anon_sym_go] = ACTIONS(108), - [anon_sym_try] = ACTIONS(108), - }, - [STATE(65)] = { - [sym_line_comment] = ACTIONS(3), - [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(700), - [sym_integer_literal] = ACTIONS(700), - [sym_float_literal] = ACTIONS(702), - [anon_sym_true] = ACTIONS(700), - [anon_sym_false] = ACTIONS(700), - [anon_sym_nil] = ACTIONS(700), - [anon_sym_DQUOTE] = ACTIONS(702), - [anon_sym_SQUOTE] = ACTIONS(702), - [sym_raw_string] = ACTIONS(702), - [anon_sym_RBRACE] = ACTIONS(702), - [anon_sym_LPAREN] = ACTIONS(702), - [anon_sym_RPAREN] = ACTIONS(702), - [anon_sym_COMMA] = ACTIONS(702), - [anon_sym_COLON] = ACTIONS(702), - [anon_sym_DOT] = ACTIONS(700), - [anon_sym_QMARK_DOT] = ACTIONS(702), - [anon_sym_LBRACK] = ACTIONS(702), - [anon_sym_RBRACK] = ACTIONS(702), - [anon_sym_QMARK_LBRACK] = ACTIONS(702), - [anon_sym_LBRACE] = ACTIONS(702), - [anon_sym_BANG] = ACTIONS(700), - [anon_sym_STAR] = ACTIONS(702), - [anon_sym_SLASH] = ACTIONS(700), - [anon_sym_PERCENT] = ACTIONS(702), - [anon_sym_PLUS] = ACTIONS(702), - [anon_sym_DASH] = ACTIONS(702), - [anon_sym_EQ_EQ] = ACTIONS(702), - [anon_sym_BANG_EQ] = ACTIONS(702), - [anon_sym_LT] = ACTIONS(700), - [anon_sym_GT] = ACTIONS(700), - [anon_sym_LT_EQ] = ACTIONS(702), - [anon_sym_GT_EQ] = ACTIONS(702), - [anon_sym_AMP_AMP] = ACTIONS(702), - [anon_sym_PIPE_PIPE] = ACTIONS(702), - [anon_sym_QMARK_QMARK] = ACTIONS(702), - [anon_sym_DOT_DOT] = ACTIONS(700), - [anon_sym_DOT_DOT_EQ] = ACTIONS(702), - [anon_sym_QMARK] = ACTIONS(700), - [anon_sym_PIPE] = ACTIONS(700), - [anon_sym_match] = ACTIONS(700), - [anon_sym_EQ_GT] = ACTIONS(702), - [anon_sym_SEMI] = ACTIONS(702), - [anon_sym_if] = ACTIONS(700), - [anon_sym_spawn] = ACTIONS(700), - [anon_sym_chan] = ACTIONS(700), - [anon_sym_send] = ACTIONS(700), - [anon_sym_recv] = ACTIONS(700), - [anon_sym_select] = ACTIONS(700), - [anon_sym_POUND] = ACTIONS(702), - [anon_sym_use] = ACTIONS(700), - [anon_sym_export] = ACTIONS(700), - [anon_sym_macro_rules] = ACTIONS(700), - [anon_sym_let] = ACTIONS(700), - [anon_sym_while] = ACTIONS(700), - [anon_sym_for] = ACTIONS(700), - [anon_sym_fn] = ACTIONS(700), - [anon_sym_struct] = ACTIONS(700), - [anon_sym_type] = ACTIONS(700), - [anon_sym_trait] = ACTIONS(700), - [anon_sym_impl] = ACTIONS(700), - [anon_sym_return] = ACTIONS(700), - [anon_sym_break] = ACTIONS(700), - [anon_sym_continue] = ACTIONS(700), - [anon_sym_go] = ACTIONS(700), - [anon_sym_try] = ACTIONS(700), - }, - [STATE(66)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(704), @@ -15311,6 +16001,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(706), [anon_sym_LBRACE] = ACTIONS(706), [anon_sym_BANG] = ACTIONS(704), + [anon_sym_TILDE] = ACTIONS(706), [anon_sym_STAR] = ACTIONS(706), [anon_sym_SLASH] = ACTIONS(704), [anon_sym_PERCENT] = ACTIONS(706), @@ -15324,11 +16015,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(706), [anon_sym_AMP_AMP] = ACTIONS(706), [anon_sym_PIPE_PIPE] = ACTIONS(706), + [anon_sym_PIPE] = ACTIONS(704), + [anon_sym_CARET] = ACTIONS(706), + [anon_sym_AMP] = ACTIONS(704), + [anon_sym_LT_LT] = ACTIONS(706), + [anon_sym_GT_GT] = ACTIONS(706), [anon_sym_QMARK_QMARK] = ACTIONS(706), [anon_sym_DOT_DOT] = ACTIONS(704), [anon_sym_DOT_DOT_EQ] = ACTIONS(706), [anon_sym_QMARK] = ACTIONS(704), - [anon_sym_PIPE] = ACTIONS(704), [anon_sym_match] = ACTIONS(704), [anon_sym_EQ_GT] = ACTIONS(706), [anon_sym_SEMI] = ACTIONS(706), @@ -15356,7 +16051,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(704), [anon_sym_try] = ACTIONS(704), }, - [STATE(67)] = { + [STATE(62)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(708), @@ -15380,6 +16075,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(710), [anon_sym_LBRACE] = ACTIONS(710), [anon_sym_BANG] = ACTIONS(708), + [anon_sym_TILDE] = ACTIONS(710), [anon_sym_STAR] = ACTIONS(710), [anon_sym_SLASH] = ACTIONS(708), [anon_sym_PERCENT] = ACTIONS(710), @@ -15393,11 +16089,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(710), [anon_sym_AMP_AMP] = ACTIONS(710), [anon_sym_PIPE_PIPE] = ACTIONS(710), + [anon_sym_PIPE] = ACTIONS(708), + [anon_sym_CARET] = ACTIONS(710), + [anon_sym_AMP] = ACTIONS(708), + [anon_sym_LT_LT] = ACTIONS(710), + [anon_sym_GT_GT] = ACTIONS(710), [anon_sym_QMARK_QMARK] = ACTIONS(710), [anon_sym_DOT_DOT] = ACTIONS(708), [anon_sym_DOT_DOT_EQ] = ACTIONS(710), [anon_sym_QMARK] = ACTIONS(708), - [anon_sym_PIPE] = ACTIONS(708), [anon_sym_match] = ACTIONS(708), [anon_sym_EQ_GT] = ACTIONS(710), [anon_sym_SEMI] = ACTIONS(710), @@ -15425,7 +16125,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(708), [anon_sym_try] = ACTIONS(708), }, - [STATE(68)] = { + [STATE(63)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(712), @@ -15449,6 +16149,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(714), [anon_sym_LBRACE] = ACTIONS(714), [anon_sym_BANG] = ACTIONS(712), + [anon_sym_TILDE] = ACTIONS(714), [anon_sym_STAR] = ACTIONS(714), [anon_sym_SLASH] = ACTIONS(712), [anon_sym_PERCENT] = ACTIONS(714), @@ -15462,11 +16163,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(714), [anon_sym_AMP_AMP] = ACTIONS(714), [anon_sym_PIPE_PIPE] = ACTIONS(714), + [anon_sym_PIPE] = ACTIONS(712), + [anon_sym_CARET] = ACTIONS(714), + [anon_sym_AMP] = ACTIONS(712), + [anon_sym_LT_LT] = ACTIONS(714), + [anon_sym_GT_GT] = ACTIONS(714), [anon_sym_QMARK_QMARK] = ACTIONS(714), [anon_sym_DOT_DOT] = ACTIONS(712), [anon_sym_DOT_DOT_EQ] = ACTIONS(714), [anon_sym_QMARK] = ACTIONS(712), - [anon_sym_PIPE] = ACTIONS(712), [anon_sym_match] = ACTIONS(712), [anon_sym_EQ_GT] = ACTIONS(714), [anon_sym_SEMI] = ACTIONS(714), @@ -15494,7 +16199,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(712), [anon_sym_try] = ACTIONS(712), }, - [STATE(69)] = { + [STATE(64)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(716), @@ -15518,6 +16223,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(718), [anon_sym_LBRACE] = ACTIONS(718), [anon_sym_BANG] = ACTIONS(716), + [anon_sym_TILDE] = ACTIONS(718), [anon_sym_STAR] = ACTIONS(718), [anon_sym_SLASH] = ACTIONS(716), [anon_sym_PERCENT] = ACTIONS(718), @@ -15531,11 +16237,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(718), [anon_sym_AMP_AMP] = ACTIONS(718), [anon_sym_PIPE_PIPE] = ACTIONS(718), + [anon_sym_PIPE] = ACTIONS(716), + [anon_sym_CARET] = ACTIONS(718), + [anon_sym_AMP] = ACTIONS(716), + [anon_sym_LT_LT] = ACTIONS(718), + [anon_sym_GT_GT] = ACTIONS(718), [anon_sym_QMARK_QMARK] = ACTIONS(718), [anon_sym_DOT_DOT] = ACTIONS(716), [anon_sym_DOT_DOT_EQ] = ACTIONS(718), [anon_sym_QMARK] = ACTIONS(716), - [anon_sym_PIPE] = ACTIONS(716), [anon_sym_match] = ACTIONS(716), [anon_sym_EQ_GT] = ACTIONS(718), [anon_sym_SEMI] = ACTIONS(718), @@ -15563,7 +16273,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(716), [anon_sym_try] = ACTIONS(716), }, - [STATE(70)] = { + [STATE(65)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(720), @@ -15587,6 +16297,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(722), [anon_sym_LBRACE] = ACTIONS(722), [anon_sym_BANG] = ACTIONS(720), + [anon_sym_TILDE] = ACTIONS(722), [anon_sym_STAR] = ACTIONS(722), [anon_sym_SLASH] = ACTIONS(720), [anon_sym_PERCENT] = ACTIONS(722), @@ -15600,11 +16311,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(722), [anon_sym_AMP_AMP] = ACTIONS(722), [anon_sym_PIPE_PIPE] = ACTIONS(722), + [anon_sym_PIPE] = ACTIONS(720), + [anon_sym_CARET] = ACTIONS(722), + [anon_sym_AMP] = ACTIONS(720), + [anon_sym_LT_LT] = ACTIONS(722), + [anon_sym_GT_GT] = ACTIONS(722), [anon_sym_QMARK_QMARK] = ACTIONS(722), [anon_sym_DOT_DOT] = ACTIONS(720), [anon_sym_DOT_DOT_EQ] = ACTIONS(722), [anon_sym_QMARK] = ACTIONS(720), - [anon_sym_PIPE] = ACTIONS(720), [anon_sym_match] = ACTIONS(720), [anon_sym_EQ_GT] = ACTIONS(722), [anon_sym_SEMI] = ACTIONS(722), @@ -15632,7 +16347,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(720), [anon_sym_try] = ACTIONS(720), }, - [STATE(71)] = { + [STATE(66)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(724), @@ -15656,6 +16371,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(726), [anon_sym_LBRACE] = ACTIONS(726), [anon_sym_BANG] = ACTIONS(724), + [anon_sym_TILDE] = ACTIONS(726), [anon_sym_STAR] = ACTIONS(726), [anon_sym_SLASH] = ACTIONS(724), [anon_sym_PERCENT] = ACTIONS(726), @@ -15669,11 +16385,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(726), [anon_sym_AMP_AMP] = ACTIONS(726), [anon_sym_PIPE_PIPE] = ACTIONS(726), + [anon_sym_PIPE] = ACTIONS(724), + [anon_sym_CARET] = ACTIONS(726), + [anon_sym_AMP] = ACTIONS(724), + [anon_sym_LT_LT] = ACTIONS(726), + [anon_sym_GT_GT] = ACTIONS(726), [anon_sym_QMARK_QMARK] = ACTIONS(726), [anon_sym_DOT_DOT] = ACTIONS(724), [anon_sym_DOT_DOT_EQ] = ACTIONS(726), [anon_sym_QMARK] = ACTIONS(724), - [anon_sym_PIPE] = ACTIONS(724), [anon_sym_match] = ACTIONS(724), [anon_sym_EQ_GT] = ACTIONS(726), [anon_sym_SEMI] = ACTIONS(726), @@ -15701,7 +16421,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(724), [anon_sym_try] = ACTIONS(724), }, - [STATE(72)] = { + [STATE(67)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(728), @@ -15725,6 +16445,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(730), [anon_sym_LBRACE] = ACTIONS(730), [anon_sym_BANG] = ACTIONS(728), + [anon_sym_TILDE] = ACTIONS(730), [anon_sym_STAR] = ACTIONS(730), [anon_sym_SLASH] = ACTIONS(728), [anon_sym_PERCENT] = ACTIONS(730), @@ -15738,11 +16459,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(730), [anon_sym_AMP_AMP] = ACTIONS(730), [anon_sym_PIPE_PIPE] = ACTIONS(730), + [anon_sym_PIPE] = ACTIONS(728), + [anon_sym_CARET] = ACTIONS(730), + [anon_sym_AMP] = ACTIONS(728), + [anon_sym_LT_LT] = ACTIONS(730), + [anon_sym_GT_GT] = ACTIONS(730), [anon_sym_QMARK_QMARK] = ACTIONS(730), [anon_sym_DOT_DOT] = ACTIONS(728), [anon_sym_DOT_DOT_EQ] = ACTIONS(730), [anon_sym_QMARK] = ACTIONS(728), - [anon_sym_PIPE] = ACTIONS(728), [anon_sym_match] = ACTIONS(728), [anon_sym_EQ_GT] = ACTIONS(730), [anon_sym_SEMI] = ACTIONS(730), @@ -15770,7 +16495,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(728), [anon_sym_try] = ACTIONS(728), }, - [STATE(73)] = { + [STATE(68)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(732), @@ -15794,6 +16519,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(734), [anon_sym_LBRACE] = ACTIONS(734), [anon_sym_BANG] = ACTIONS(732), + [anon_sym_TILDE] = ACTIONS(734), [anon_sym_STAR] = ACTIONS(734), [anon_sym_SLASH] = ACTIONS(732), [anon_sym_PERCENT] = ACTIONS(734), @@ -15807,11 +16533,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(734), [anon_sym_AMP_AMP] = ACTIONS(734), [anon_sym_PIPE_PIPE] = ACTIONS(734), + [anon_sym_PIPE] = ACTIONS(732), + [anon_sym_CARET] = ACTIONS(734), + [anon_sym_AMP] = ACTIONS(732), + [anon_sym_LT_LT] = ACTIONS(734), + [anon_sym_GT_GT] = ACTIONS(734), [anon_sym_QMARK_QMARK] = ACTIONS(734), [anon_sym_DOT_DOT] = ACTIONS(732), [anon_sym_DOT_DOT_EQ] = ACTIONS(734), [anon_sym_QMARK] = ACTIONS(732), - [anon_sym_PIPE] = ACTIONS(732), [anon_sym_match] = ACTIONS(732), [anon_sym_EQ_GT] = ACTIONS(734), [anon_sym_SEMI] = ACTIONS(734), @@ -15839,7 +16569,81 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(732), [anon_sym_try] = ACTIONS(732), }, - [STATE(74)] = { + [STATE(69)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(108), + [sym_integer_literal] = ACTIONS(108), + [sym_float_literal] = ACTIONS(110), + [anon_sym_true] = ACTIONS(108), + [anon_sym_false] = ACTIONS(108), + [anon_sym_nil] = ACTIONS(108), + [anon_sym_DQUOTE] = ACTIONS(110), + [anon_sym_SQUOTE] = ACTIONS(110), + [sym_raw_string] = ACTIONS(110), + [anon_sym_RBRACE] = ACTIONS(110), + [anon_sym_LPAREN] = ACTIONS(110), + [anon_sym_RPAREN] = ACTIONS(110), + [anon_sym_COMMA] = ACTIONS(110), + [anon_sym_COLON] = ACTIONS(110), + [anon_sym_DOT] = ACTIONS(108), + [anon_sym_QMARK_DOT] = ACTIONS(110), + [anon_sym_LBRACK] = ACTIONS(110), + [anon_sym_RBRACK] = ACTIONS(110), + [anon_sym_QMARK_LBRACK] = ACTIONS(110), + [anon_sym_LBRACE] = ACTIONS(110), + [anon_sym_BANG] = ACTIONS(108), + [anon_sym_TILDE] = ACTIONS(110), + [anon_sym_STAR] = ACTIONS(110), + [anon_sym_SLASH] = ACTIONS(108), + [anon_sym_PERCENT] = ACTIONS(110), + [anon_sym_PLUS] = ACTIONS(110), + [anon_sym_DASH] = ACTIONS(110), + [anon_sym_EQ_EQ] = ACTIONS(110), + [anon_sym_BANG_EQ] = ACTIONS(110), + [anon_sym_LT] = ACTIONS(108), + [anon_sym_GT] = ACTIONS(108), + [anon_sym_LT_EQ] = ACTIONS(110), + [anon_sym_GT_EQ] = ACTIONS(110), + [anon_sym_AMP_AMP] = ACTIONS(110), + [anon_sym_PIPE_PIPE] = ACTIONS(110), + [anon_sym_PIPE] = ACTIONS(108), + [anon_sym_CARET] = ACTIONS(110), + [anon_sym_AMP] = ACTIONS(108), + [anon_sym_LT_LT] = ACTIONS(110), + [anon_sym_GT_GT] = ACTIONS(110), + [anon_sym_QMARK_QMARK] = ACTIONS(110), + [anon_sym_DOT_DOT] = ACTIONS(108), + [anon_sym_DOT_DOT_EQ] = ACTIONS(110), + [anon_sym_QMARK] = ACTIONS(108), + [anon_sym_match] = ACTIONS(108), + [anon_sym_EQ_GT] = ACTIONS(110), + [anon_sym_SEMI] = ACTIONS(110), + [anon_sym_if] = ACTIONS(108), + [anon_sym_spawn] = ACTIONS(108), + [anon_sym_chan] = ACTIONS(108), + [anon_sym_send] = ACTIONS(108), + [anon_sym_recv] = ACTIONS(108), + [anon_sym_select] = ACTIONS(108), + [anon_sym_POUND] = ACTIONS(110), + [anon_sym_use] = ACTIONS(108), + [anon_sym_export] = ACTIONS(108), + [anon_sym_macro_rules] = ACTIONS(108), + [anon_sym_let] = ACTIONS(108), + [anon_sym_while] = ACTIONS(108), + [anon_sym_for] = ACTIONS(108), + [anon_sym_fn] = ACTIONS(108), + [anon_sym_struct] = ACTIONS(108), + [anon_sym_type] = ACTIONS(108), + [anon_sym_trait] = ACTIONS(108), + [anon_sym_impl] = ACTIONS(108), + [anon_sym_return] = ACTIONS(108), + [anon_sym_break] = ACTIONS(108), + [anon_sym_continue] = ACTIONS(108), + [anon_sym_go] = ACTIONS(108), + [anon_sym_try] = ACTIONS(108), + }, + [STATE(70)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(736), @@ -15863,6 +16667,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(738), [anon_sym_LBRACE] = ACTIONS(738), [anon_sym_BANG] = ACTIONS(736), + [anon_sym_TILDE] = ACTIONS(738), [anon_sym_STAR] = ACTIONS(738), [anon_sym_SLASH] = ACTIONS(736), [anon_sym_PERCENT] = ACTIONS(738), @@ -15876,11 +16681,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(738), [anon_sym_AMP_AMP] = ACTIONS(738), [anon_sym_PIPE_PIPE] = ACTIONS(738), + [anon_sym_PIPE] = ACTIONS(736), + [anon_sym_CARET] = ACTIONS(738), + [anon_sym_AMP] = ACTIONS(736), + [anon_sym_LT_LT] = ACTIONS(738), + [anon_sym_GT_GT] = ACTIONS(738), [anon_sym_QMARK_QMARK] = ACTIONS(738), [anon_sym_DOT_DOT] = ACTIONS(736), [anon_sym_DOT_DOT_EQ] = ACTIONS(738), [anon_sym_QMARK] = ACTIONS(736), - [anon_sym_PIPE] = ACTIONS(736), [anon_sym_match] = ACTIONS(736), [anon_sym_EQ_GT] = ACTIONS(738), [anon_sym_SEMI] = ACTIONS(738), @@ -15908,7 +16717,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(736), [anon_sym_try] = ACTIONS(736), }, - [STATE(75)] = { + [STATE(71)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(740), @@ -15932,6 +16741,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(742), [anon_sym_LBRACE] = ACTIONS(742), [anon_sym_BANG] = ACTIONS(740), + [anon_sym_TILDE] = ACTIONS(742), [anon_sym_STAR] = ACTIONS(742), [anon_sym_SLASH] = ACTIONS(740), [anon_sym_PERCENT] = ACTIONS(742), @@ -15945,11 +16755,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(742), [anon_sym_AMP_AMP] = ACTIONS(742), [anon_sym_PIPE_PIPE] = ACTIONS(742), + [anon_sym_PIPE] = ACTIONS(740), + [anon_sym_CARET] = ACTIONS(742), + [anon_sym_AMP] = ACTIONS(740), + [anon_sym_LT_LT] = ACTIONS(742), + [anon_sym_GT_GT] = ACTIONS(742), [anon_sym_QMARK_QMARK] = ACTIONS(742), [anon_sym_DOT_DOT] = ACTIONS(740), [anon_sym_DOT_DOT_EQ] = ACTIONS(742), [anon_sym_QMARK] = ACTIONS(740), - [anon_sym_PIPE] = ACTIONS(740), [anon_sym_match] = ACTIONS(740), [anon_sym_EQ_GT] = ACTIONS(742), [anon_sym_SEMI] = ACTIONS(742), @@ -15977,7 +16791,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(740), [anon_sym_try] = ACTIONS(740), }, - [STATE(76)] = { + [STATE(72)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(744), @@ -16001,6 +16815,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(746), [anon_sym_LBRACE] = ACTIONS(746), [anon_sym_BANG] = ACTIONS(744), + [anon_sym_TILDE] = ACTIONS(746), [anon_sym_STAR] = ACTIONS(746), [anon_sym_SLASH] = ACTIONS(744), [anon_sym_PERCENT] = ACTIONS(746), @@ -16014,11 +16829,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(746), [anon_sym_AMP_AMP] = ACTIONS(746), [anon_sym_PIPE_PIPE] = ACTIONS(746), + [anon_sym_PIPE] = ACTIONS(744), + [anon_sym_CARET] = ACTIONS(746), + [anon_sym_AMP] = ACTIONS(744), + [anon_sym_LT_LT] = ACTIONS(746), + [anon_sym_GT_GT] = ACTIONS(746), [anon_sym_QMARK_QMARK] = ACTIONS(746), [anon_sym_DOT_DOT] = ACTIONS(744), [anon_sym_DOT_DOT_EQ] = ACTIONS(746), [anon_sym_QMARK] = ACTIONS(744), - [anon_sym_PIPE] = ACTIONS(744), [anon_sym_match] = ACTIONS(744), [anon_sym_EQ_GT] = ACTIONS(746), [anon_sym_SEMI] = ACTIONS(746), @@ -16046,7 +16865,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(744), [anon_sym_try] = ACTIONS(744), }, - [STATE(77)] = { + [STATE(73)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(748), @@ -16070,6 +16889,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(750), [anon_sym_LBRACE] = ACTIONS(750), [anon_sym_BANG] = ACTIONS(748), + [anon_sym_TILDE] = ACTIONS(750), [anon_sym_STAR] = ACTIONS(750), [anon_sym_SLASH] = ACTIONS(748), [anon_sym_PERCENT] = ACTIONS(750), @@ -16083,11 +16903,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(750), [anon_sym_AMP_AMP] = ACTIONS(750), [anon_sym_PIPE_PIPE] = ACTIONS(750), + [anon_sym_PIPE] = ACTIONS(748), + [anon_sym_CARET] = ACTIONS(750), + [anon_sym_AMP] = ACTIONS(748), + [anon_sym_LT_LT] = ACTIONS(750), + [anon_sym_GT_GT] = ACTIONS(750), [anon_sym_QMARK_QMARK] = ACTIONS(750), [anon_sym_DOT_DOT] = ACTIONS(748), [anon_sym_DOT_DOT_EQ] = ACTIONS(750), [anon_sym_QMARK] = ACTIONS(748), - [anon_sym_PIPE] = ACTIONS(748), [anon_sym_match] = ACTIONS(748), [anon_sym_EQ_GT] = ACTIONS(750), [anon_sym_SEMI] = ACTIONS(750), @@ -16115,7 +16939,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(748), [anon_sym_try] = ACTIONS(748), }, - [STATE(78)] = { + [STATE(74)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(752), @@ -16139,6 +16963,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(754), [anon_sym_LBRACE] = ACTIONS(754), [anon_sym_BANG] = ACTIONS(752), + [anon_sym_TILDE] = ACTIONS(754), [anon_sym_STAR] = ACTIONS(754), [anon_sym_SLASH] = ACTIONS(752), [anon_sym_PERCENT] = ACTIONS(754), @@ -16152,11 +16977,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(754), [anon_sym_AMP_AMP] = ACTIONS(754), [anon_sym_PIPE_PIPE] = ACTIONS(754), + [anon_sym_PIPE] = ACTIONS(752), + [anon_sym_CARET] = ACTIONS(754), + [anon_sym_AMP] = ACTIONS(752), + [anon_sym_LT_LT] = ACTIONS(754), + [anon_sym_GT_GT] = ACTIONS(754), [anon_sym_QMARK_QMARK] = ACTIONS(754), [anon_sym_DOT_DOT] = ACTIONS(752), [anon_sym_DOT_DOT_EQ] = ACTIONS(754), [anon_sym_QMARK] = ACTIONS(752), - [anon_sym_PIPE] = ACTIONS(752), [anon_sym_match] = ACTIONS(752), [anon_sym_EQ_GT] = ACTIONS(754), [anon_sym_SEMI] = ACTIONS(754), @@ -16184,7 +17013,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(752), [anon_sym_try] = ACTIONS(752), }, - [STATE(79)] = { + [STATE(75)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(756), @@ -16208,6 +17037,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(758), [anon_sym_LBRACE] = ACTIONS(758), [anon_sym_BANG] = ACTIONS(756), + [anon_sym_TILDE] = ACTIONS(758), [anon_sym_STAR] = ACTIONS(758), [anon_sym_SLASH] = ACTIONS(756), [anon_sym_PERCENT] = ACTIONS(758), @@ -16221,11 +17051,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(758), [anon_sym_AMP_AMP] = ACTIONS(758), [anon_sym_PIPE_PIPE] = ACTIONS(758), + [anon_sym_PIPE] = ACTIONS(756), + [anon_sym_CARET] = ACTIONS(758), + [anon_sym_AMP] = ACTIONS(756), + [anon_sym_LT_LT] = ACTIONS(758), + [anon_sym_GT_GT] = ACTIONS(758), [anon_sym_QMARK_QMARK] = ACTIONS(758), [anon_sym_DOT_DOT] = ACTIONS(756), [anon_sym_DOT_DOT_EQ] = ACTIONS(758), [anon_sym_QMARK] = ACTIONS(756), - [anon_sym_PIPE] = ACTIONS(756), [anon_sym_match] = ACTIONS(756), [anon_sym_EQ_GT] = ACTIONS(758), [anon_sym_SEMI] = ACTIONS(758), @@ -16253,7 +17087,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(756), [anon_sym_try] = ACTIONS(756), }, - [STATE(80)] = { + [STATE(76)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(760), @@ -16277,6 +17111,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(762), [anon_sym_LBRACE] = ACTIONS(762), [anon_sym_BANG] = ACTIONS(760), + [anon_sym_TILDE] = ACTIONS(762), [anon_sym_STAR] = ACTIONS(762), [anon_sym_SLASH] = ACTIONS(760), [anon_sym_PERCENT] = ACTIONS(762), @@ -16290,11 +17125,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(762), [anon_sym_AMP_AMP] = ACTIONS(762), [anon_sym_PIPE_PIPE] = ACTIONS(762), + [anon_sym_PIPE] = ACTIONS(760), + [anon_sym_CARET] = ACTIONS(762), + [anon_sym_AMP] = ACTIONS(760), + [anon_sym_LT_LT] = ACTIONS(762), + [anon_sym_GT_GT] = ACTIONS(762), [anon_sym_QMARK_QMARK] = ACTIONS(762), [anon_sym_DOT_DOT] = ACTIONS(760), [anon_sym_DOT_DOT_EQ] = ACTIONS(762), [anon_sym_QMARK] = ACTIONS(760), - [anon_sym_PIPE] = ACTIONS(760), [anon_sym_match] = ACTIONS(760), [anon_sym_EQ_GT] = ACTIONS(762), [anon_sym_SEMI] = ACTIONS(762), @@ -16322,7 +17161,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(760), [anon_sym_try] = ACTIONS(760), }, - [STATE(81)] = { + [STATE(77)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(764), @@ -16346,6 +17185,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(766), [anon_sym_LBRACE] = ACTIONS(766), [anon_sym_BANG] = ACTIONS(764), + [anon_sym_TILDE] = ACTIONS(766), [anon_sym_STAR] = ACTIONS(766), [anon_sym_SLASH] = ACTIONS(764), [anon_sym_PERCENT] = ACTIONS(766), @@ -16359,11 +17199,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(766), [anon_sym_AMP_AMP] = ACTIONS(766), [anon_sym_PIPE_PIPE] = ACTIONS(766), + [anon_sym_PIPE] = ACTIONS(764), + [anon_sym_CARET] = ACTIONS(766), + [anon_sym_AMP] = ACTIONS(764), + [anon_sym_LT_LT] = ACTIONS(766), + [anon_sym_GT_GT] = ACTIONS(766), [anon_sym_QMARK_QMARK] = ACTIONS(766), [anon_sym_DOT_DOT] = ACTIONS(764), [anon_sym_DOT_DOT_EQ] = ACTIONS(766), [anon_sym_QMARK] = ACTIONS(764), - [anon_sym_PIPE] = ACTIONS(764), [anon_sym_match] = ACTIONS(764), [anon_sym_EQ_GT] = ACTIONS(766), [anon_sym_SEMI] = ACTIONS(766), @@ -16391,7 +17235,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(764), [anon_sym_try] = ACTIONS(764), }, - [STATE(82)] = { + [STATE(78)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(768), @@ -16415,6 +17259,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(770), [anon_sym_LBRACE] = ACTIONS(770), [anon_sym_BANG] = ACTIONS(768), + [anon_sym_TILDE] = ACTIONS(770), [anon_sym_STAR] = ACTIONS(770), [anon_sym_SLASH] = ACTIONS(768), [anon_sym_PERCENT] = ACTIONS(770), @@ -16428,11 +17273,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(770), [anon_sym_AMP_AMP] = ACTIONS(770), [anon_sym_PIPE_PIPE] = ACTIONS(770), + [anon_sym_PIPE] = ACTIONS(768), + [anon_sym_CARET] = ACTIONS(770), + [anon_sym_AMP] = ACTIONS(768), + [anon_sym_LT_LT] = ACTIONS(770), + [anon_sym_GT_GT] = ACTIONS(770), [anon_sym_QMARK_QMARK] = ACTIONS(770), [anon_sym_DOT_DOT] = ACTIONS(768), [anon_sym_DOT_DOT_EQ] = ACTIONS(770), [anon_sym_QMARK] = ACTIONS(768), - [anon_sym_PIPE] = ACTIONS(768), [anon_sym_match] = ACTIONS(768), [anon_sym_EQ_GT] = ACTIONS(770), [anon_sym_SEMI] = ACTIONS(770), @@ -16460,7 +17309,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(768), [anon_sym_try] = ACTIONS(768), }, - [STATE(83)] = { + [STATE(79)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(772), @@ -16484,6 +17333,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(774), [anon_sym_LBRACE] = ACTIONS(774), [anon_sym_BANG] = ACTIONS(772), + [anon_sym_TILDE] = ACTIONS(774), [anon_sym_STAR] = ACTIONS(774), [anon_sym_SLASH] = ACTIONS(772), [anon_sym_PERCENT] = ACTIONS(774), @@ -16497,11 +17347,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(774), [anon_sym_AMP_AMP] = ACTIONS(774), [anon_sym_PIPE_PIPE] = ACTIONS(774), + [anon_sym_PIPE] = ACTIONS(772), + [anon_sym_CARET] = ACTIONS(774), + [anon_sym_AMP] = ACTIONS(772), + [anon_sym_LT_LT] = ACTIONS(774), + [anon_sym_GT_GT] = ACTIONS(774), [anon_sym_QMARK_QMARK] = ACTIONS(774), [anon_sym_DOT_DOT] = ACTIONS(772), [anon_sym_DOT_DOT_EQ] = ACTIONS(774), [anon_sym_QMARK] = ACTIONS(772), - [anon_sym_PIPE] = ACTIONS(772), [anon_sym_match] = ACTIONS(772), [anon_sym_EQ_GT] = ACTIONS(774), [anon_sym_SEMI] = ACTIONS(774), @@ -16529,7 +17383,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(772), [anon_sym_try] = ACTIONS(772), }, - [STATE(84)] = { + [STATE(80)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(776), @@ -16553,6 +17407,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(778), [anon_sym_LBRACE] = ACTIONS(778), [anon_sym_BANG] = ACTIONS(776), + [anon_sym_TILDE] = ACTIONS(778), [anon_sym_STAR] = ACTIONS(778), [anon_sym_SLASH] = ACTIONS(776), [anon_sym_PERCENT] = ACTIONS(778), @@ -16566,11 +17421,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(778), [anon_sym_AMP_AMP] = ACTIONS(778), [anon_sym_PIPE_PIPE] = ACTIONS(778), + [anon_sym_PIPE] = ACTIONS(776), + [anon_sym_CARET] = ACTIONS(778), + [anon_sym_AMP] = ACTIONS(776), + [anon_sym_LT_LT] = ACTIONS(778), + [anon_sym_GT_GT] = ACTIONS(778), [anon_sym_QMARK_QMARK] = ACTIONS(778), [anon_sym_DOT_DOT] = ACTIONS(776), [anon_sym_DOT_DOT_EQ] = ACTIONS(778), [anon_sym_QMARK] = ACTIONS(776), - [anon_sym_PIPE] = ACTIONS(776), [anon_sym_match] = ACTIONS(776), [anon_sym_EQ_GT] = ACTIONS(778), [anon_sym_SEMI] = ACTIONS(778), @@ -16598,7 +17457,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(776), [anon_sym_try] = ACTIONS(776), }, - [STATE(85)] = { + [STATE(81)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(780), @@ -16622,6 +17481,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_QMARK_LBRACK] = ACTIONS(782), [anon_sym_LBRACE] = ACTIONS(782), [anon_sym_BANG] = ACTIONS(780), + [anon_sym_TILDE] = ACTIONS(782), [anon_sym_STAR] = ACTIONS(782), [anon_sym_SLASH] = ACTIONS(780), [anon_sym_PERCENT] = ACTIONS(782), @@ -16635,11 +17495,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(782), [anon_sym_AMP_AMP] = ACTIONS(782), [anon_sym_PIPE_PIPE] = ACTIONS(782), + [anon_sym_PIPE] = ACTIONS(780), + [anon_sym_CARET] = ACTIONS(782), + [anon_sym_AMP] = ACTIONS(780), + [anon_sym_LT_LT] = ACTIONS(782), + [anon_sym_GT_GT] = ACTIONS(782), [anon_sym_QMARK_QMARK] = ACTIONS(782), [anon_sym_DOT_DOT] = ACTIONS(780), [anon_sym_DOT_DOT_EQ] = ACTIONS(782), [anon_sym_QMARK] = ACTIONS(780), - [anon_sym_PIPE] = ACTIONS(780), [anon_sym_match] = ACTIONS(780), [anon_sym_EQ_GT] = ACTIONS(782), [anon_sym_SEMI] = ACTIONS(782), @@ -16667,7 +17531,7 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(780), [anon_sym_try] = ACTIONS(780), }, - [STATE(86)] = { + [STATE(82)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), [aux_sym_identifier_token1] = ACTIONS(784), @@ -16690,7 +17554,8 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_RBRACK] = ACTIONS(786), [anon_sym_QMARK_LBRACK] = ACTIONS(786), [anon_sym_LBRACE] = ACTIONS(786), - [anon_sym_BANG] = ACTIONS(788), + [anon_sym_BANG] = ACTIONS(784), + [anon_sym_TILDE] = ACTIONS(786), [anon_sym_STAR] = ACTIONS(786), [anon_sym_SLASH] = ACTIONS(784), [anon_sym_PERCENT] = ACTIONS(786), @@ -16704,11 +17569,15 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_GT_EQ] = ACTIONS(786), [anon_sym_AMP_AMP] = ACTIONS(786), [anon_sym_PIPE_PIPE] = ACTIONS(786), + [anon_sym_PIPE] = ACTIONS(784), + [anon_sym_CARET] = ACTIONS(786), + [anon_sym_AMP] = ACTIONS(784), + [anon_sym_LT_LT] = ACTIONS(786), + [anon_sym_GT_GT] = ACTIONS(786), [anon_sym_QMARK_QMARK] = ACTIONS(786), [anon_sym_DOT_DOT] = ACTIONS(784), [anon_sym_DOT_DOT_EQ] = ACTIONS(786), [anon_sym_QMARK] = ACTIONS(784), - [anon_sym_PIPE] = ACTIONS(784), [anon_sym_match] = ACTIONS(784), [anon_sym_EQ_GT] = ACTIONS(786), [anon_sym_SEMI] = ACTIONS(786), @@ -16736,2059 +17605,3088 @@ static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [anon_sym_go] = ACTIONS(784), [anon_sym_try] = ACTIONS(784), }, + [STATE(83)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(788), + [sym_integer_literal] = ACTIONS(788), + [sym_float_literal] = ACTIONS(790), + [anon_sym_true] = ACTIONS(788), + [anon_sym_false] = ACTIONS(788), + [anon_sym_nil] = ACTIONS(788), + [anon_sym_DQUOTE] = ACTIONS(790), + [anon_sym_SQUOTE] = ACTIONS(790), + [sym_raw_string] = ACTIONS(790), + [anon_sym_RBRACE] = ACTIONS(790), + [anon_sym_LPAREN] = ACTIONS(790), + [anon_sym_RPAREN] = ACTIONS(790), + [anon_sym_COMMA] = ACTIONS(790), + [anon_sym_COLON] = ACTIONS(790), + [anon_sym_DOT] = ACTIONS(788), + [anon_sym_QMARK_DOT] = ACTIONS(790), + [anon_sym_LBRACK] = ACTIONS(790), + [anon_sym_RBRACK] = ACTIONS(790), + [anon_sym_QMARK_LBRACK] = ACTIONS(790), + [anon_sym_LBRACE] = ACTIONS(790), + [anon_sym_BANG] = ACTIONS(788), + [anon_sym_TILDE] = ACTIONS(790), + [anon_sym_STAR] = ACTIONS(790), + [anon_sym_SLASH] = ACTIONS(788), + [anon_sym_PERCENT] = ACTIONS(790), + [anon_sym_PLUS] = ACTIONS(790), + [anon_sym_DASH] = ACTIONS(790), + [anon_sym_EQ_EQ] = ACTIONS(790), + [anon_sym_BANG_EQ] = ACTIONS(790), + [anon_sym_LT] = ACTIONS(788), + [anon_sym_GT] = ACTIONS(788), + [anon_sym_LT_EQ] = ACTIONS(790), + [anon_sym_GT_EQ] = ACTIONS(790), + [anon_sym_AMP_AMP] = ACTIONS(790), + [anon_sym_PIPE_PIPE] = ACTIONS(790), + [anon_sym_PIPE] = ACTIONS(788), + [anon_sym_CARET] = ACTIONS(790), + [anon_sym_AMP] = ACTIONS(788), + [anon_sym_LT_LT] = ACTIONS(790), + [anon_sym_GT_GT] = ACTIONS(790), + [anon_sym_QMARK_QMARK] = ACTIONS(790), + [anon_sym_DOT_DOT] = ACTIONS(788), + [anon_sym_DOT_DOT_EQ] = ACTIONS(790), + [anon_sym_QMARK] = ACTIONS(788), + [anon_sym_match] = ACTIONS(788), + [anon_sym_EQ_GT] = ACTIONS(790), + [anon_sym_SEMI] = ACTIONS(790), + [anon_sym_if] = ACTIONS(788), + [anon_sym_spawn] = ACTIONS(788), + [anon_sym_chan] = ACTIONS(788), + [anon_sym_send] = ACTIONS(788), + [anon_sym_recv] = ACTIONS(788), + [anon_sym_select] = ACTIONS(788), + [anon_sym_POUND] = ACTIONS(790), + [anon_sym_use] = ACTIONS(788), + [anon_sym_export] = ACTIONS(788), + [anon_sym_macro_rules] = ACTIONS(788), + [anon_sym_let] = ACTIONS(788), + [anon_sym_while] = ACTIONS(788), + [anon_sym_for] = ACTIONS(788), + [anon_sym_fn] = ACTIONS(788), + [anon_sym_struct] = ACTIONS(788), + [anon_sym_type] = ACTIONS(788), + [anon_sym_trait] = ACTIONS(788), + [anon_sym_impl] = ACTIONS(788), + [anon_sym_return] = ACTIONS(788), + [anon_sym_break] = ACTIONS(788), + [anon_sym_continue] = ACTIONS(788), + [anon_sym_go] = ACTIONS(788), + [anon_sym_try] = ACTIONS(788), + }, + [STATE(84)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(792), + [sym_integer_literal] = ACTIONS(792), + [sym_float_literal] = ACTIONS(794), + [anon_sym_true] = ACTIONS(792), + [anon_sym_false] = ACTIONS(792), + [anon_sym_nil] = ACTIONS(792), + [anon_sym_DQUOTE] = ACTIONS(794), + [anon_sym_SQUOTE] = ACTIONS(794), + [sym_raw_string] = ACTIONS(794), + [anon_sym_RBRACE] = ACTIONS(794), + [anon_sym_LPAREN] = ACTIONS(794), + [anon_sym_RPAREN] = ACTIONS(794), + [anon_sym_COMMA] = ACTIONS(794), + [anon_sym_COLON] = ACTIONS(794), + [anon_sym_DOT] = ACTIONS(792), + [anon_sym_QMARK_DOT] = ACTIONS(794), + [anon_sym_LBRACK] = ACTIONS(794), + [anon_sym_RBRACK] = ACTIONS(794), + [anon_sym_QMARK_LBRACK] = ACTIONS(794), + [anon_sym_LBRACE] = ACTIONS(794), + [anon_sym_BANG] = ACTIONS(792), + [anon_sym_TILDE] = ACTIONS(794), + [anon_sym_STAR] = ACTIONS(794), + [anon_sym_SLASH] = ACTIONS(792), + [anon_sym_PERCENT] = ACTIONS(794), + [anon_sym_PLUS] = ACTIONS(794), + [anon_sym_DASH] = ACTIONS(794), + [anon_sym_EQ_EQ] = ACTIONS(794), + [anon_sym_BANG_EQ] = ACTIONS(794), + [anon_sym_LT] = ACTIONS(792), + [anon_sym_GT] = ACTIONS(792), + [anon_sym_LT_EQ] = ACTIONS(794), + [anon_sym_GT_EQ] = ACTIONS(794), + [anon_sym_AMP_AMP] = ACTIONS(794), + [anon_sym_PIPE_PIPE] = ACTIONS(794), + [anon_sym_PIPE] = ACTIONS(792), + [anon_sym_CARET] = ACTIONS(794), + [anon_sym_AMP] = ACTIONS(792), + [anon_sym_LT_LT] = ACTIONS(794), + [anon_sym_GT_GT] = ACTIONS(794), + [anon_sym_QMARK_QMARK] = ACTIONS(794), + [anon_sym_DOT_DOT] = ACTIONS(792), + [anon_sym_DOT_DOT_EQ] = ACTIONS(794), + [anon_sym_QMARK] = ACTIONS(792), + [anon_sym_match] = ACTIONS(792), + [anon_sym_EQ_GT] = ACTIONS(794), + [anon_sym_SEMI] = ACTIONS(794), + [anon_sym_if] = ACTIONS(792), + [anon_sym_spawn] = ACTIONS(792), + [anon_sym_chan] = ACTIONS(792), + [anon_sym_send] = ACTIONS(792), + [anon_sym_recv] = ACTIONS(792), + [anon_sym_select] = ACTIONS(792), + [anon_sym_POUND] = ACTIONS(794), + [anon_sym_use] = ACTIONS(792), + [anon_sym_export] = ACTIONS(792), + [anon_sym_macro_rules] = ACTIONS(792), + [anon_sym_let] = ACTIONS(792), + [anon_sym_while] = ACTIONS(792), + [anon_sym_for] = ACTIONS(792), + [anon_sym_fn] = ACTIONS(792), + [anon_sym_struct] = ACTIONS(792), + [anon_sym_type] = ACTIONS(792), + [anon_sym_trait] = ACTIONS(792), + [anon_sym_impl] = ACTIONS(792), + [anon_sym_return] = ACTIONS(792), + [anon_sym_break] = ACTIONS(792), + [anon_sym_continue] = ACTIONS(792), + [anon_sym_go] = ACTIONS(792), + [anon_sym_try] = ACTIONS(792), + }, + [STATE(85)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(796), + [sym_integer_literal] = ACTIONS(796), + [sym_float_literal] = ACTIONS(798), + [anon_sym_true] = ACTIONS(796), + [anon_sym_false] = ACTIONS(796), + [anon_sym_nil] = ACTIONS(796), + [anon_sym_DQUOTE] = ACTIONS(798), + [anon_sym_SQUOTE] = ACTIONS(798), + [sym_raw_string] = ACTIONS(798), + [anon_sym_RBRACE] = ACTIONS(798), + [anon_sym_LPAREN] = ACTIONS(798), + [anon_sym_RPAREN] = ACTIONS(798), + [anon_sym_COMMA] = ACTIONS(798), + [anon_sym_COLON] = ACTIONS(798), + [anon_sym_DOT] = ACTIONS(796), + [anon_sym_QMARK_DOT] = ACTIONS(798), + [anon_sym_LBRACK] = ACTIONS(798), + [anon_sym_RBRACK] = ACTIONS(798), + [anon_sym_QMARK_LBRACK] = ACTIONS(798), + [anon_sym_LBRACE] = ACTIONS(798), + [anon_sym_BANG] = ACTIONS(796), + [anon_sym_TILDE] = ACTIONS(798), + [anon_sym_STAR] = ACTIONS(798), + [anon_sym_SLASH] = ACTIONS(796), + [anon_sym_PERCENT] = ACTIONS(798), + [anon_sym_PLUS] = ACTIONS(798), + [anon_sym_DASH] = ACTIONS(798), + [anon_sym_EQ_EQ] = ACTIONS(798), + [anon_sym_BANG_EQ] = ACTIONS(798), + [anon_sym_LT] = ACTIONS(796), + [anon_sym_GT] = ACTIONS(796), + [anon_sym_LT_EQ] = ACTIONS(798), + [anon_sym_GT_EQ] = ACTIONS(798), + [anon_sym_AMP_AMP] = ACTIONS(798), + [anon_sym_PIPE_PIPE] = ACTIONS(798), + [anon_sym_PIPE] = ACTIONS(796), + [anon_sym_CARET] = ACTIONS(798), + [anon_sym_AMP] = ACTIONS(796), + [anon_sym_LT_LT] = ACTIONS(798), + [anon_sym_GT_GT] = ACTIONS(798), + [anon_sym_QMARK_QMARK] = ACTIONS(798), + [anon_sym_DOT_DOT] = ACTIONS(796), + [anon_sym_DOT_DOT_EQ] = ACTIONS(798), + [anon_sym_QMARK] = ACTIONS(796), + [anon_sym_match] = ACTIONS(796), + [anon_sym_EQ_GT] = ACTIONS(798), + [anon_sym_SEMI] = ACTIONS(798), + [anon_sym_if] = ACTIONS(796), + [anon_sym_spawn] = ACTIONS(796), + [anon_sym_chan] = ACTIONS(796), + [anon_sym_send] = ACTIONS(796), + [anon_sym_recv] = ACTIONS(796), + [anon_sym_select] = ACTIONS(796), + [anon_sym_POUND] = ACTIONS(798), + [anon_sym_use] = ACTIONS(796), + [anon_sym_export] = ACTIONS(796), + [anon_sym_macro_rules] = ACTIONS(796), + [anon_sym_let] = ACTIONS(796), + [anon_sym_while] = ACTIONS(796), + [anon_sym_for] = ACTIONS(796), + [anon_sym_fn] = ACTIONS(796), + [anon_sym_struct] = ACTIONS(796), + [anon_sym_type] = ACTIONS(796), + [anon_sym_trait] = ACTIONS(796), + [anon_sym_impl] = ACTIONS(796), + [anon_sym_return] = ACTIONS(796), + [anon_sym_break] = ACTIONS(796), + [anon_sym_continue] = ACTIONS(796), + [anon_sym_go] = ACTIONS(796), + [anon_sym_try] = ACTIONS(796), + }, + [STATE(86)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(800), + [sym_integer_literal] = ACTIONS(800), + [sym_float_literal] = ACTIONS(802), + [anon_sym_true] = ACTIONS(800), + [anon_sym_false] = ACTIONS(800), + [anon_sym_nil] = ACTIONS(800), + [anon_sym_DQUOTE] = ACTIONS(802), + [anon_sym_SQUOTE] = ACTIONS(802), + [sym_raw_string] = ACTIONS(802), + [anon_sym_RBRACE] = ACTIONS(802), + [anon_sym_LPAREN] = ACTIONS(802), + [anon_sym_RPAREN] = ACTIONS(802), + [anon_sym_COMMA] = ACTIONS(802), + [anon_sym_COLON] = ACTIONS(802), + [anon_sym_DOT] = ACTIONS(800), + [anon_sym_QMARK_DOT] = ACTIONS(802), + [anon_sym_LBRACK] = ACTIONS(802), + [anon_sym_RBRACK] = ACTIONS(802), + [anon_sym_QMARK_LBRACK] = ACTIONS(802), + [anon_sym_LBRACE] = ACTIONS(802), + [anon_sym_BANG] = ACTIONS(800), + [anon_sym_TILDE] = ACTIONS(802), + [anon_sym_STAR] = ACTIONS(802), + [anon_sym_SLASH] = ACTIONS(800), + [anon_sym_PERCENT] = ACTIONS(802), + [anon_sym_PLUS] = ACTIONS(802), + [anon_sym_DASH] = ACTIONS(802), + [anon_sym_EQ_EQ] = ACTIONS(802), + [anon_sym_BANG_EQ] = ACTIONS(802), + [anon_sym_LT] = ACTIONS(800), + [anon_sym_GT] = ACTIONS(800), + [anon_sym_LT_EQ] = ACTIONS(802), + [anon_sym_GT_EQ] = ACTIONS(802), + [anon_sym_AMP_AMP] = ACTIONS(802), + [anon_sym_PIPE_PIPE] = ACTIONS(802), + [anon_sym_PIPE] = ACTIONS(800), + [anon_sym_CARET] = ACTIONS(802), + [anon_sym_AMP] = ACTIONS(800), + [anon_sym_LT_LT] = ACTIONS(802), + [anon_sym_GT_GT] = ACTIONS(802), + [anon_sym_QMARK_QMARK] = ACTIONS(802), + [anon_sym_DOT_DOT] = ACTIONS(800), + [anon_sym_DOT_DOT_EQ] = ACTIONS(802), + [anon_sym_QMARK] = ACTIONS(800), + [anon_sym_match] = ACTIONS(800), + [anon_sym_EQ_GT] = ACTIONS(802), + [anon_sym_SEMI] = ACTIONS(802), + [anon_sym_if] = ACTIONS(800), + [anon_sym_spawn] = ACTIONS(800), + [anon_sym_chan] = ACTIONS(800), + [anon_sym_send] = ACTIONS(800), + [anon_sym_recv] = ACTIONS(800), + [anon_sym_select] = ACTIONS(800), + [anon_sym_POUND] = ACTIONS(802), + [anon_sym_use] = ACTIONS(800), + [anon_sym_export] = ACTIONS(800), + [anon_sym_macro_rules] = ACTIONS(800), + [anon_sym_let] = ACTIONS(800), + [anon_sym_while] = ACTIONS(800), + [anon_sym_for] = ACTIONS(800), + [anon_sym_fn] = ACTIONS(800), + [anon_sym_struct] = ACTIONS(800), + [anon_sym_type] = ACTIONS(800), + [anon_sym_trait] = ACTIONS(800), + [anon_sym_impl] = ACTIONS(800), + [anon_sym_return] = ACTIONS(800), + [anon_sym_break] = ACTIONS(800), + [anon_sym_continue] = ACTIONS(800), + [anon_sym_go] = ACTIONS(800), + [anon_sym_try] = ACTIONS(800), + }, [STATE(87)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(791), - [sym_integer_literal] = ACTIONS(791), - [sym_float_literal] = ACTIONS(793), - [anon_sym_true] = ACTIONS(791), - [anon_sym_false] = ACTIONS(791), - [anon_sym_nil] = ACTIONS(791), - [anon_sym_DQUOTE] = ACTIONS(793), - [anon_sym_SQUOTE] = ACTIONS(793), - [sym_raw_string] = ACTIONS(793), - [anon_sym_RBRACE] = ACTIONS(793), - [anon_sym_LPAREN] = ACTIONS(793), - [anon_sym_RPAREN] = ACTIONS(793), - [anon_sym_COMMA] = ACTIONS(793), - [anon_sym_COLON] = ACTIONS(793), - [anon_sym_DOT] = ACTIONS(791), - [anon_sym_QMARK_DOT] = ACTIONS(793), - [anon_sym_LBRACK] = ACTIONS(793), - [anon_sym_RBRACK] = ACTIONS(793), - [anon_sym_QMARK_LBRACK] = ACTIONS(793), - [anon_sym_LBRACE] = ACTIONS(793), - [anon_sym_BANG] = ACTIONS(791), - [anon_sym_STAR] = ACTIONS(793), - [anon_sym_SLASH] = ACTIONS(791), - [anon_sym_PERCENT] = ACTIONS(793), - [anon_sym_PLUS] = ACTIONS(793), - [anon_sym_DASH] = ACTIONS(793), - [anon_sym_EQ_EQ] = ACTIONS(793), - [anon_sym_BANG_EQ] = ACTIONS(793), - [anon_sym_LT] = ACTIONS(791), - [anon_sym_GT] = ACTIONS(791), - [anon_sym_LT_EQ] = ACTIONS(793), - [anon_sym_GT_EQ] = ACTIONS(793), - [anon_sym_AMP_AMP] = ACTIONS(793), - [anon_sym_PIPE_PIPE] = ACTIONS(793), - [anon_sym_QMARK_QMARK] = ACTIONS(793), - [anon_sym_DOT_DOT] = ACTIONS(791), - [anon_sym_DOT_DOT_EQ] = ACTIONS(793), - [anon_sym_QMARK] = ACTIONS(791), - [anon_sym_PIPE] = ACTIONS(791), - [anon_sym_match] = ACTIONS(791), - [anon_sym_EQ_GT] = ACTIONS(793), - [anon_sym_SEMI] = ACTIONS(793), - [anon_sym_if] = ACTIONS(791), - [anon_sym_spawn] = ACTIONS(791), - [anon_sym_chan] = ACTIONS(791), - [anon_sym_send] = ACTIONS(791), - [anon_sym_recv] = ACTIONS(791), - [anon_sym_select] = ACTIONS(791), - [anon_sym_POUND] = ACTIONS(793), - [anon_sym_use] = ACTIONS(791), - [anon_sym_export] = ACTIONS(791), - [anon_sym_macro_rules] = ACTIONS(791), - [anon_sym_let] = ACTIONS(791), - [anon_sym_while] = ACTIONS(791), - [anon_sym_for] = ACTIONS(791), - [anon_sym_fn] = ACTIONS(791), - [anon_sym_struct] = ACTIONS(791), - [anon_sym_type] = ACTIONS(791), - [anon_sym_trait] = ACTIONS(791), - [anon_sym_impl] = ACTIONS(791), - [anon_sym_return] = ACTIONS(791), - [anon_sym_break] = ACTIONS(791), - [anon_sym_continue] = ACTIONS(791), - [anon_sym_go] = ACTIONS(791), - [anon_sym_try] = ACTIONS(791), + [aux_sym_identifier_token1] = ACTIONS(804), + [sym_integer_literal] = ACTIONS(804), + [sym_float_literal] = ACTIONS(806), + [anon_sym_true] = ACTIONS(804), + [anon_sym_false] = ACTIONS(804), + [anon_sym_nil] = ACTIONS(804), + [anon_sym_DQUOTE] = ACTIONS(806), + [anon_sym_SQUOTE] = ACTIONS(806), + [sym_raw_string] = ACTIONS(806), + [anon_sym_RBRACE] = ACTIONS(806), + [anon_sym_LPAREN] = ACTIONS(806), + [anon_sym_RPAREN] = ACTIONS(806), + [anon_sym_COMMA] = ACTIONS(806), + [anon_sym_COLON] = ACTIONS(806), + [anon_sym_DOT] = ACTIONS(804), + [anon_sym_QMARK_DOT] = ACTIONS(806), + [anon_sym_LBRACK] = ACTIONS(806), + [anon_sym_RBRACK] = ACTIONS(806), + [anon_sym_QMARK_LBRACK] = ACTIONS(806), + [anon_sym_LBRACE] = ACTIONS(806), + [anon_sym_BANG] = ACTIONS(804), + [anon_sym_TILDE] = ACTIONS(806), + [anon_sym_STAR] = ACTIONS(806), + [anon_sym_SLASH] = ACTIONS(804), + [anon_sym_PERCENT] = ACTIONS(806), + [anon_sym_PLUS] = ACTIONS(806), + [anon_sym_DASH] = ACTIONS(806), + [anon_sym_EQ_EQ] = ACTIONS(806), + [anon_sym_BANG_EQ] = ACTIONS(806), + [anon_sym_LT] = ACTIONS(804), + [anon_sym_GT] = ACTIONS(804), + [anon_sym_LT_EQ] = ACTIONS(806), + [anon_sym_GT_EQ] = ACTIONS(806), + [anon_sym_AMP_AMP] = ACTIONS(806), + [anon_sym_PIPE_PIPE] = ACTIONS(806), + [anon_sym_PIPE] = ACTIONS(804), + [anon_sym_CARET] = ACTIONS(806), + [anon_sym_AMP] = ACTIONS(804), + [anon_sym_LT_LT] = ACTIONS(806), + [anon_sym_GT_GT] = ACTIONS(806), + [anon_sym_QMARK_QMARK] = ACTIONS(806), + [anon_sym_DOT_DOT] = ACTIONS(804), + [anon_sym_DOT_DOT_EQ] = ACTIONS(806), + [anon_sym_QMARK] = ACTIONS(804), + [anon_sym_match] = ACTIONS(804), + [anon_sym_EQ_GT] = ACTIONS(806), + [anon_sym_SEMI] = ACTIONS(806), + [anon_sym_if] = ACTIONS(804), + [anon_sym_spawn] = ACTIONS(804), + [anon_sym_chan] = ACTIONS(804), + [anon_sym_send] = ACTIONS(804), + [anon_sym_recv] = ACTIONS(804), + [anon_sym_select] = ACTIONS(804), + [anon_sym_POUND] = ACTIONS(806), + [anon_sym_use] = ACTIONS(804), + [anon_sym_export] = ACTIONS(804), + [anon_sym_macro_rules] = ACTIONS(804), + [anon_sym_let] = ACTIONS(804), + [anon_sym_while] = ACTIONS(804), + [anon_sym_for] = ACTIONS(804), + [anon_sym_fn] = ACTIONS(804), + [anon_sym_struct] = ACTIONS(804), + [anon_sym_type] = ACTIONS(804), + [anon_sym_trait] = ACTIONS(804), + [anon_sym_impl] = ACTIONS(804), + [anon_sym_return] = ACTIONS(804), + [anon_sym_break] = ACTIONS(804), + [anon_sym_continue] = ACTIONS(804), + [anon_sym_go] = ACTIONS(804), + [anon_sym_try] = ACTIONS(804), }, [STATE(88)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(795), - [sym_integer_literal] = ACTIONS(795), - [sym_float_literal] = ACTIONS(797), - [anon_sym_true] = ACTIONS(795), - [anon_sym_false] = ACTIONS(795), - [anon_sym_nil] = ACTIONS(795), - [anon_sym_DQUOTE] = ACTIONS(797), - [anon_sym_SQUOTE] = ACTIONS(797), - [sym_raw_string] = ACTIONS(797), - [anon_sym_RBRACE] = ACTIONS(797), - [anon_sym_LPAREN] = ACTIONS(797), - [anon_sym_RPAREN] = ACTIONS(797), - [anon_sym_COMMA] = ACTIONS(797), - [anon_sym_COLON] = ACTIONS(797), - [anon_sym_DOT] = ACTIONS(795), - [anon_sym_QMARK_DOT] = ACTIONS(797), - [anon_sym_LBRACK] = ACTIONS(797), - [anon_sym_RBRACK] = ACTIONS(797), - [anon_sym_QMARK_LBRACK] = ACTIONS(797), - [anon_sym_LBRACE] = ACTIONS(797), - [anon_sym_BANG] = ACTIONS(795), - [anon_sym_STAR] = ACTIONS(797), - [anon_sym_SLASH] = ACTIONS(795), - [anon_sym_PERCENT] = ACTIONS(797), - [anon_sym_PLUS] = ACTIONS(797), - [anon_sym_DASH] = ACTIONS(797), - [anon_sym_EQ_EQ] = ACTIONS(797), - [anon_sym_BANG_EQ] = ACTIONS(797), - [anon_sym_LT] = ACTIONS(795), - [anon_sym_GT] = ACTIONS(795), - [anon_sym_LT_EQ] = ACTIONS(797), - [anon_sym_GT_EQ] = ACTIONS(797), - [anon_sym_AMP_AMP] = ACTIONS(797), - [anon_sym_PIPE_PIPE] = ACTIONS(797), - [anon_sym_QMARK_QMARK] = ACTIONS(797), - [anon_sym_DOT_DOT] = ACTIONS(795), - [anon_sym_DOT_DOT_EQ] = ACTIONS(797), - [anon_sym_QMARK] = ACTIONS(795), - [anon_sym_PIPE] = ACTIONS(795), - [anon_sym_match] = ACTIONS(795), - [anon_sym_EQ_GT] = ACTIONS(797), - [anon_sym_SEMI] = ACTIONS(797), - [anon_sym_if] = ACTIONS(795), - [anon_sym_spawn] = ACTIONS(795), - [anon_sym_chan] = ACTIONS(795), - [anon_sym_send] = ACTIONS(795), - [anon_sym_recv] = ACTIONS(795), - [anon_sym_select] = ACTIONS(795), - [anon_sym_POUND] = ACTIONS(797), - [anon_sym_use] = ACTIONS(795), - [anon_sym_export] = ACTIONS(795), - [anon_sym_macro_rules] = ACTIONS(795), - [anon_sym_let] = ACTIONS(795), - [anon_sym_while] = ACTIONS(795), - [anon_sym_for] = ACTIONS(795), - [anon_sym_fn] = ACTIONS(795), - [anon_sym_struct] = ACTIONS(795), - [anon_sym_type] = ACTIONS(795), - [anon_sym_trait] = ACTIONS(795), - [anon_sym_impl] = ACTIONS(795), - [anon_sym_return] = ACTIONS(795), - [anon_sym_break] = ACTIONS(795), - [anon_sym_continue] = ACTIONS(795), - [anon_sym_go] = ACTIONS(795), - [anon_sym_try] = ACTIONS(795), + [aux_sym_identifier_token1] = ACTIONS(808), + [sym_integer_literal] = ACTIONS(808), + [sym_float_literal] = ACTIONS(810), + [anon_sym_true] = ACTIONS(808), + [anon_sym_false] = ACTIONS(808), + [anon_sym_nil] = ACTIONS(808), + [anon_sym_DQUOTE] = ACTIONS(810), + [anon_sym_SQUOTE] = ACTIONS(810), + [sym_raw_string] = ACTIONS(810), + [anon_sym_RBRACE] = ACTIONS(810), + [anon_sym_LPAREN] = ACTIONS(810), + [anon_sym_RPAREN] = ACTIONS(810), + [anon_sym_COMMA] = ACTIONS(810), + [anon_sym_COLON] = ACTIONS(810), + [anon_sym_DOT] = ACTIONS(808), + [anon_sym_QMARK_DOT] = ACTIONS(810), + [anon_sym_LBRACK] = ACTIONS(810), + [anon_sym_RBRACK] = ACTIONS(810), + [anon_sym_QMARK_LBRACK] = ACTIONS(810), + [anon_sym_LBRACE] = ACTIONS(810), + [anon_sym_BANG] = ACTIONS(808), + [anon_sym_TILDE] = ACTIONS(810), + [anon_sym_STAR] = ACTIONS(810), + [anon_sym_SLASH] = ACTIONS(808), + [anon_sym_PERCENT] = ACTIONS(810), + [anon_sym_PLUS] = ACTIONS(810), + [anon_sym_DASH] = ACTIONS(810), + [anon_sym_EQ_EQ] = ACTIONS(810), + [anon_sym_BANG_EQ] = ACTIONS(810), + [anon_sym_LT] = ACTIONS(808), + [anon_sym_GT] = ACTIONS(808), + [anon_sym_LT_EQ] = ACTIONS(810), + [anon_sym_GT_EQ] = ACTIONS(810), + [anon_sym_AMP_AMP] = ACTIONS(810), + [anon_sym_PIPE_PIPE] = ACTIONS(810), + [anon_sym_PIPE] = ACTIONS(808), + [anon_sym_CARET] = ACTIONS(810), + [anon_sym_AMP] = ACTIONS(808), + [anon_sym_LT_LT] = ACTIONS(810), + [anon_sym_GT_GT] = ACTIONS(810), + [anon_sym_QMARK_QMARK] = ACTIONS(810), + [anon_sym_DOT_DOT] = ACTIONS(808), + [anon_sym_DOT_DOT_EQ] = ACTIONS(810), + [anon_sym_QMARK] = ACTIONS(808), + [anon_sym_match] = ACTIONS(808), + [anon_sym_EQ_GT] = ACTIONS(810), + [anon_sym_SEMI] = ACTIONS(810), + [anon_sym_if] = ACTIONS(808), + [anon_sym_spawn] = ACTIONS(808), + [anon_sym_chan] = ACTIONS(808), + [anon_sym_send] = ACTIONS(808), + [anon_sym_recv] = ACTIONS(808), + [anon_sym_select] = ACTIONS(808), + [anon_sym_POUND] = ACTIONS(810), + [anon_sym_use] = ACTIONS(808), + [anon_sym_export] = ACTIONS(808), + [anon_sym_macro_rules] = ACTIONS(808), + [anon_sym_let] = ACTIONS(808), + [anon_sym_while] = ACTIONS(808), + [anon_sym_for] = ACTIONS(808), + [anon_sym_fn] = ACTIONS(808), + [anon_sym_struct] = ACTIONS(808), + [anon_sym_type] = ACTIONS(808), + [anon_sym_trait] = ACTIONS(808), + [anon_sym_impl] = ACTIONS(808), + [anon_sym_return] = ACTIONS(808), + [anon_sym_break] = ACTIONS(808), + [anon_sym_continue] = ACTIONS(808), + [anon_sym_go] = ACTIONS(808), + [anon_sym_try] = ACTIONS(808), }, [STATE(89)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(799), - [sym_integer_literal] = ACTIONS(799), - [sym_float_literal] = ACTIONS(801), - [anon_sym_true] = ACTIONS(799), - [anon_sym_false] = ACTIONS(799), - [anon_sym_nil] = ACTIONS(799), - [anon_sym_DQUOTE] = ACTIONS(801), - [anon_sym_SQUOTE] = ACTIONS(801), - [sym_raw_string] = ACTIONS(801), - [anon_sym_RBRACE] = ACTIONS(801), - [anon_sym_LPAREN] = ACTIONS(801), - [anon_sym_RPAREN] = ACTIONS(801), - [anon_sym_COMMA] = ACTIONS(801), - [anon_sym_COLON] = ACTIONS(801), - [anon_sym_DOT] = ACTIONS(799), - [anon_sym_QMARK_DOT] = ACTIONS(801), - [anon_sym_LBRACK] = ACTIONS(801), - [anon_sym_RBRACK] = ACTIONS(801), - [anon_sym_QMARK_LBRACK] = ACTIONS(801), - [anon_sym_LBRACE] = ACTIONS(801), - [anon_sym_BANG] = ACTIONS(799), - [anon_sym_STAR] = ACTIONS(801), - [anon_sym_SLASH] = ACTIONS(799), - [anon_sym_PERCENT] = ACTIONS(801), - [anon_sym_PLUS] = ACTIONS(801), - [anon_sym_DASH] = ACTIONS(801), - [anon_sym_EQ_EQ] = ACTIONS(801), - [anon_sym_BANG_EQ] = ACTIONS(801), - [anon_sym_LT] = ACTIONS(799), - [anon_sym_GT] = ACTIONS(799), - [anon_sym_LT_EQ] = ACTIONS(801), - [anon_sym_GT_EQ] = ACTIONS(801), - [anon_sym_AMP_AMP] = ACTIONS(801), - [anon_sym_PIPE_PIPE] = ACTIONS(801), - [anon_sym_QMARK_QMARK] = ACTIONS(801), - [anon_sym_DOT_DOT] = ACTIONS(799), - [anon_sym_DOT_DOT_EQ] = ACTIONS(801), - [anon_sym_QMARK] = ACTIONS(799), - [anon_sym_PIPE] = ACTIONS(799), - [anon_sym_match] = ACTIONS(799), - [anon_sym_EQ_GT] = ACTIONS(801), - [anon_sym_SEMI] = ACTIONS(801), - [anon_sym_if] = ACTIONS(799), - [anon_sym_spawn] = ACTIONS(799), - [anon_sym_chan] = ACTIONS(799), - [anon_sym_send] = ACTIONS(799), - [anon_sym_recv] = ACTIONS(799), - [anon_sym_select] = ACTIONS(799), - [anon_sym_POUND] = ACTIONS(801), - [anon_sym_use] = ACTIONS(799), - [anon_sym_export] = ACTIONS(799), - [anon_sym_macro_rules] = ACTIONS(799), - [anon_sym_let] = ACTIONS(799), - [anon_sym_while] = ACTIONS(799), - [anon_sym_for] = ACTIONS(799), - [anon_sym_fn] = ACTIONS(799), - [anon_sym_struct] = ACTIONS(799), - [anon_sym_type] = ACTIONS(799), - [anon_sym_trait] = ACTIONS(799), - [anon_sym_impl] = ACTIONS(799), - [anon_sym_return] = ACTIONS(799), - [anon_sym_break] = ACTIONS(799), - [anon_sym_continue] = ACTIONS(799), - [anon_sym_go] = ACTIONS(799), - [anon_sym_try] = ACTIONS(799), + [aux_sym_identifier_token1] = ACTIONS(812), + [sym_integer_literal] = ACTIONS(812), + [sym_float_literal] = ACTIONS(814), + [anon_sym_true] = ACTIONS(812), + [anon_sym_false] = ACTIONS(812), + [anon_sym_nil] = ACTIONS(812), + [anon_sym_DQUOTE] = ACTIONS(814), + [anon_sym_SQUOTE] = ACTIONS(814), + [sym_raw_string] = ACTIONS(814), + [anon_sym_RBRACE] = ACTIONS(814), + [anon_sym_LPAREN] = ACTIONS(814), + [anon_sym_RPAREN] = ACTIONS(814), + [anon_sym_COMMA] = ACTIONS(814), + [anon_sym_COLON] = ACTIONS(814), + [anon_sym_DOT] = ACTIONS(812), + [anon_sym_QMARK_DOT] = ACTIONS(814), + [anon_sym_LBRACK] = ACTIONS(814), + [anon_sym_RBRACK] = ACTIONS(814), + [anon_sym_QMARK_LBRACK] = ACTIONS(814), + [anon_sym_LBRACE] = ACTIONS(814), + [anon_sym_BANG] = ACTIONS(812), + [anon_sym_TILDE] = ACTIONS(814), + [anon_sym_STAR] = ACTIONS(814), + [anon_sym_SLASH] = ACTIONS(812), + [anon_sym_PERCENT] = ACTIONS(814), + [anon_sym_PLUS] = ACTIONS(814), + [anon_sym_DASH] = ACTIONS(814), + [anon_sym_EQ_EQ] = ACTIONS(814), + [anon_sym_BANG_EQ] = ACTIONS(814), + [anon_sym_LT] = ACTIONS(812), + [anon_sym_GT] = ACTIONS(812), + [anon_sym_LT_EQ] = ACTIONS(814), + [anon_sym_GT_EQ] = ACTIONS(814), + [anon_sym_AMP_AMP] = ACTIONS(814), + [anon_sym_PIPE_PIPE] = ACTIONS(814), + [anon_sym_PIPE] = ACTIONS(812), + [anon_sym_CARET] = ACTIONS(814), + [anon_sym_AMP] = ACTIONS(812), + [anon_sym_LT_LT] = ACTIONS(814), + [anon_sym_GT_GT] = ACTIONS(814), + [anon_sym_QMARK_QMARK] = ACTIONS(814), + [anon_sym_DOT_DOT] = ACTIONS(812), + [anon_sym_DOT_DOT_EQ] = ACTIONS(814), + [anon_sym_QMARK] = ACTIONS(812), + [anon_sym_match] = ACTIONS(812), + [anon_sym_EQ_GT] = ACTIONS(814), + [anon_sym_SEMI] = ACTIONS(814), + [anon_sym_if] = ACTIONS(812), + [anon_sym_spawn] = ACTIONS(812), + [anon_sym_chan] = ACTIONS(812), + [anon_sym_send] = ACTIONS(812), + [anon_sym_recv] = ACTIONS(812), + [anon_sym_select] = ACTIONS(812), + [anon_sym_POUND] = ACTIONS(814), + [anon_sym_use] = ACTIONS(812), + [anon_sym_export] = ACTIONS(812), + [anon_sym_macro_rules] = ACTIONS(812), + [anon_sym_let] = ACTIONS(812), + [anon_sym_while] = ACTIONS(812), + [anon_sym_for] = ACTIONS(812), + [anon_sym_fn] = ACTIONS(812), + [anon_sym_struct] = ACTIONS(812), + [anon_sym_type] = ACTIONS(812), + [anon_sym_trait] = ACTIONS(812), + [anon_sym_impl] = ACTIONS(812), + [anon_sym_return] = ACTIONS(812), + [anon_sym_break] = ACTIONS(812), + [anon_sym_continue] = ACTIONS(812), + [anon_sym_go] = ACTIONS(812), + [anon_sym_try] = ACTIONS(812), }, [STATE(90)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(803), - [sym_integer_literal] = ACTIONS(803), - [sym_float_literal] = ACTIONS(805), - [anon_sym_true] = ACTIONS(803), - [anon_sym_false] = ACTIONS(803), - [anon_sym_nil] = ACTIONS(803), - [anon_sym_DQUOTE] = ACTIONS(805), - [anon_sym_SQUOTE] = ACTIONS(805), - [sym_raw_string] = ACTIONS(805), - [anon_sym_RBRACE] = ACTIONS(805), - [anon_sym_LPAREN] = ACTIONS(805), - [anon_sym_RPAREN] = ACTIONS(805), - [anon_sym_COMMA] = ACTIONS(805), - [anon_sym_COLON] = ACTIONS(805), - [anon_sym_DOT] = ACTIONS(803), - [anon_sym_QMARK_DOT] = ACTIONS(805), - [anon_sym_LBRACK] = ACTIONS(805), - [anon_sym_RBRACK] = ACTIONS(805), - [anon_sym_QMARK_LBRACK] = ACTIONS(805), - [anon_sym_LBRACE] = ACTIONS(805), - [anon_sym_BANG] = ACTIONS(803), - [anon_sym_STAR] = ACTIONS(805), - [anon_sym_SLASH] = ACTIONS(803), - [anon_sym_PERCENT] = ACTIONS(805), - [anon_sym_PLUS] = ACTIONS(805), - [anon_sym_DASH] = ACTIONS(805), - [anon_sym_EQ_EQ] = ACTIONS(805), - [anon_sym_BANG_EQ] = ACTIONS(805), - [anon_sym_LT] = ACTIONS(803), - [anon_sym_GT] = ACTIONS(803), - [anon_sym_LT_EQ] = ACTIONS(805), - [anon_sym_GT_EQ] = ACTIONS(805), - [anon_sym_AMP_AMP] = ACTIONS(805), - [anon_sym_PIPE_PIPE] = ACTIONS(805), - [anon_sym_QMARK_QMARK] = ACTIONS(805), - [anon_sym_DOT_DOT] = ACTIONS(803), - [anon_sym_DOT_DOT_EQ] = ACTIONS(805), - [anon_sym_QMARK] = ACTIONS(803), - [anon_sym_PIPE] = ACTIONS(803), - [anon_sym_match] = ACTIONS(803), - [anon_sym_EQ_GT] = ACTIONS(805), - [anon_sym_SEMI] = ACTIONS(805), - [anon_sym_if] = ACTIONS(803), - [anon_sym_spawn] = ACTIONS(803), - [anon_sym_chan] = ACTIONS(803), - [anon_sym_send] = ACTIONS(803), - [anon_sym_recv] = ACTIONS(803), - [anon_sym_select] = ACTIONS(803), - [anon_sym_POUND] = ACTIONS(805), - [anon_sym_use] = ACTIONS(803), - [anon_sym_export] = ACTIONS(803), - [anon_sym_macro_rules] = ACTIONS(803), - [anon_sym_let] = ACTIONS(803), - [anon_sym_while] = ACTIONS(803), - [anon_sym_for] = ACTIONS(803), - [anon_sym_fn] = ACTIONS(803), - [anon_sym_struct] = ACTIONS(803), - [anon_sym_type] = ACTIONS(803), - [anon_sym_trait] = ACTIONS(803), - [anon_sym_impl] = ACTIONS(803), - [anon_sym_return] = ACTIONS(803), - [anon_sym_break] = ACTIONS(803), - [anon_sym_continue] = ACTIONS(803), - [anon_sym_go] = ACTIONS(803), - [anon_sym_try] = ACTIONS(803), + [aux_sym_identifier_token1] = ACTIONS(816), + [sym_integer_literal] = ACTIONS(816), + [sym_float_literal] = ACTIONS(818), + [anon_sym_true] = ACTIONS(816), + [anon_sym_false] = ACTIONS(816), + [anon_sym_nil] = ACTIONS(816), + [anon_sym_DQUOTE] = ACTIONS(818), + [anon_sym_SQUOTE] = ACTIONS(818), + [sym_raw_string] = ACTIONS(818), + [anon_sym_RBRACE] = ACTIONS(818), + [anon_sym_LPAREN] = ACTIONS(818), + [anon_sym_RPAREN] = ACTIONS(818), + [anon_sym_COMMA] = ACTIONS(818), + [anon_sym_COLON] = ACTIONS(818), + [anon_sym_DOT] = ACTIONS(816), + [anon_sym_QMARK_DOT] = ACTIONS(818), + [anon_sym_LBRACK] = ACTIONS(818), + [anon_sym_RBRACK] = ACTIONS(818), + [anon_sym_QMARK_LBRACK] = ACTIONS(818), + [anon_sym_LBRACE] = ACTIONS(818), + [anon_sym_BANG] = ACTIONS(816), + [anon_sym_TILDE] = ACTIONS(818), + [anon_sym_STAR] = ACTIONS(818), + [anon_sym_SLASH] = ACTIONS(816), + [anon_sym_PERCENT] = ACTIONS(818), + [anon_sym_PLUS] = ACTIONS(818), + [anon_sym_DASH] = ACTIONS(818), + [anon_sym_EQ_EQ] = ACTIONS(818), + [anon_sym_BANG_EQ] = ACTIONS(818), + [anon_sym_LT] = ACTIONS(816), + [anon_sym_GT] = ACTIONS(816), + [anon_sym_LT_EQ] = ACTIONS(818), + [anon_sym_GT_EQ] = ACTIONS(818), + [anon_sym_AMP_AMP] = ACTIONS(818), + [anon_sym_PIPE_PIPE] = ACTIONS(818), + [anon_sym_PIPE] = ACTIONS(816), + [anon_sym_CARET] = ACTIONS(818), + [anon_sym_AMP] = ACTIONS(816), + [anon_sym_LT_LT] = ACTIONS(818), + [anon_sym_GT_GT] = ACTIONS(818), + [anon_sym_QMARK_QMARK] = ACTIONS(818), + [anon_sym_DOT_DOT] = ACTIONS(816), + [anon_sym_DOT_DOT_EQ] = ACTIONS(818), + [anon_sym_QMARK] = ACTIONS(816), + [anon_sym_match] = ACTIONS(816), + [anon_sym_EQ_GT] = ACTIONS(818), + [anon_sym_SEMI] = ACTIONS(818), + [anon_sym_if] = ACTIONS(816), + [anon_sym_spawn] = ACTIONS(816), + [anon_sym_chan] = ACTIONS(816), + [anon_sym_send] = ACTIONS(816), + [anon_sym_recv] = ACTIONS(816), + [anon_sym_select] = ACTIONS(816), + [anon_sym_POUND] = ACTIONS(818), + [anon_sym_use] = ACTIONS(816), + [anon_sym_export] = ACTIONS(816), + [anon_sym_macro_rules] = ACTIONS(816), + [anon_sym_let] = ACTIONS(816), + [anon_sym_while] = ACTIONS(816), + [anon_sym_for] = ACTIONS(816), + [anon_sym_fn] = ACTIONS(816), + [anon_sym_struct] = ACTIONS(816), + [anon_sym_type] = ACTIONS(816), + [anon_sym_trait] = ACTIONS(816), + [anon_sym_impl] = ACTIONS(816), + [anon_sym_return] = ACTIONS(816), + [anon_sym_break] = ACTIONS(816), + [anon_sym_continue] = ACTIONS(816), + [anon_sym_go] = ACTIONS(816), + [anon_sym_try] = ACTIONS(816), }, [STATE(91)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(807), - [sym_integer_literal] = ACTIONS(807), - [sym_float_literal] = ACTIONS(809), - [anon_sym_true] = ACTIONS(807), - [anon_sym_false] = ACTIONS(807), - [anon_sym_nil] = ACTIONS(807), - [anon_sym_DQUOTE] = ACTIONS(809), - [anon_sym_SQUOTE] = ACTIONS(809), - [sym_raw_string] = ACTIONS(809), - [anon_sym_RBRACE] = ACTIONS(809), - [anon_sym_LPAREN] = ACTIONS(809), - [anon_sym_RPAREN] = ACTIONS(809), - [anon_sym_COMMA] = ACTIONS(809), - [anon_sym_COLON] = ACTIONS(809), - [anon_sym_DOT] = ACTIONS(807), - [anon_sym_QMARK_DOT] = ACTIONS(809), - [anon_sym_LBRACK] = ACTIONS(809), - [anon_sym_RBRACK] = ACTIONS(809), - [anon_sym_QMARK_LBRACK] = ACTIONS(809), - [anon_sym_LBRACE] = ACTIONS(809), - [anon_sym_BANG] = ACTIONS(807), - [anon_sym_STAR] = ACTIONS(809), - [anon_sym_SLASH] = ACTIONS(807), - [anon_sym_PERCENT] = ACTIONS(809), - [anon_sym_PLUS] = ACTIONS(809), - [anon_sym_DASH] = ACTIONS(809), - [anon_sym_EQ_EQ] = ACTIONS(809), - [anon_sym_BANG_EQ] = ACTIONS(809), - [anon_sym_LT] = ACTIONS(807), - [anon_sym_GT] = ACTIONS(807), - [anon_sym_LT_EQ] = ACTIONS(809), - [anon_sym_GT_EQ] = ACTIONS(809), - [anon_sym_AMP_AMP] = ACTIONS(809), - [anon_sym_PIPE_PIPE] = ACTIONS(809), - [anon_sym_QMARK_QMARK] = ACTIONS(809), - [anon_sym_DOT_DOT] = ACTIONS(807), - [anon_sym_DOT_DOT_EQ] = ACTIONS(809), - [anon_sym_QMARK] = ACTIONS(807), - [anon_sym_PIPE] = ACTIONS(807), - [anon_sym_match] = ACTIONS(807), - [anon_sym_EQ_GT] = ACTIONS(809), - [anon_sym_SEMI] = ACTIONS(809), - [anon_sym_if] = ACTIONS(807), - [anon_sym_spawn] = ACTIONS(807), - [anon_sym_chan] = ACTIONS(807), - [anon_sym_send] = ACTIONS(807), - [anon_sym_recv] = ACTIONS(807), - [anon_sym_select] = ACTIONS(807), - [anon_sym_POUND] = ACTIONS(809), - [anon_sym_use] = ACTIONS(807), - [anon_sym_export] = ACTIONS(807), - [anon_sym_macro_rules] = ACTIONS(807), - [anon_sym_let] = ACTIONS(807), - [anon_sym_while] = ACTIONS(807), - [anon_sym_for] = ACTIONS(807), - [anon_sym_fn] = ACTIONS(807), - [anon_sym_struct] = ACTIONS(807), - [anon_sym_type] = ACTIONS(807), - [anon_sym_trait] = ACTIONS(807), - [anon_sym_impl] = ACTIONS(807), - [anon_sym_return] = ACTIONS(807), - [anon_sym_break] = ACTIONS(807), - [anon_sym_continue] = ACTIONS(807), - [anon_sym_go] = ACTIONS(807), - [anon_sym_try] = ACTIONS(807), + [aux_sym_identifier_token1] = ACTIONS(820), + [sym_integer_literal] = ACTIONS(820), + [sym_float_literal] = ACTIONS(822), + [anon_sym_true] = ACTIONS(820), + [anon_sym_false] = ACTIONS(820), + [anon_sym_nil] = ACTIONS(820), + [anon_sym_DQUOTE] = ACTIONS(822), + [anon_sym_SQUOTE] = ACTIONS(822), + [sym_raw_string] = ACTIONS(822), + [anon_sym_RBRACE] = ACTIONS(822), + [anon_sym_LPAREN] = ACTIONS(822), + [anon_sym_RPAREN] = ACTIONS(822), + [anon_sym_COMMA] = ACTIONS(822), + [anon_sym_COLON] = ACTIONS(822), + [anon_sym_DOT] = ACTIONS(820), + [anon_sym_QMARK_DOT] = ACTIONS(822), + [anon_sym_LBRACK] = ACTIONS(822), + [anon_sym_RBRACK] = ACTIONS(822), + [anon_sym_QMARK_LBRACK] = ACTIONS(822), + [anon_sym_LBRACE] = ACTIONS(822), + [anon_sym_BANG] = ACTIONS(820), + [anon_sym_TILDE] = ACTIONS(822), + [anon_sym_STAR] = ACTIONS(822), + [anon_sym_SLASH] = ACTIONS(820), + [anon_sym_PERCENT] = ACTIONS(822), + [anon_sym_PLUS] = ACTIONS(822), + [anon_sym_DASH] = ACTIONS(822), + [anon_sym_EQ_EQ] = ACTIONS(822), + [anon_sym_BANG_EQ] = ACTIONS(822), + [anon_sym_LT] = ACTIONS(820), + [anon_sym_GT] = ACTIONS(820), + [anon_sym_LT_EQ] = ACTIONS(822), + [anon_sym_GT_EQ] = ACTIONS(822), + [anon_sym_AMP_AMP] = ACTIONS(822), + [anon_sym_PIPE_PIPE] = ACTIONS(822), + [anon_sym_PIPE] = ACTIONS(820), + [anon_sym_CARET] = ACTIONS(822), + [anon_sym_AMP] = ACTIONS(820), + [anon_sym_LT_LT] = ACTIONS(822), + [anon_sym_GT_GT] = ACTIONS(822), + [anon_sym_QMARK_QMARK] = ACTIONS(822), + [anon_sym_DOT_DOT] = ACTIONS(820), + [anon_sym_DOT_DOT_EQ] = ACTIONS(822), + [anon_sym_QMARK] = ACTIONS(820), + [anon_sym_match] = ACTIONS(820), + [anon_sym_EQ_GT] = ACTIONS(822), + [anon_sym_SEMI] = ACTIONS(822), + [anon_sym_if] = ACTIONS(820), + [anon_sym_spawn] = ACTIONS(820), + [anon_sym_chan] = ACTIONS(820), + [anon_sym_send] = ACTIONS(820), + [anon_sym_recv] = ACTIONS(820), + [anon_sym_select] = ACTIONS(820), + [anon_sym_POUND] = ACTIONS(822), + [anon_sym_use] = ACTIONS(820), + [anon_sym_export] = ACTIONS(820), + [anon_sym_macro_rules] = ACTIONS(820), + [anon_sym_let] = ACTIONS(820), + [anon_sym_while] = ACTIONS(820), + [anon_sym_for] = ACTIONS(820), + [anon_sym_fn] = ACTIONS(820), + [anon_sym_struct] = ACTIONS(820), + [anon_sym_type] = ACTIONS(820), + [anon_sym_trait] = ACTIONS(820), + [anon_sym_impl] = ACTIONS(820), + [anon_sym_return] = ACTIONS(820), + [anon_sym_break] = ACTIONS(820), + [anon_sym_continue] = ACTIONS(820), + [anon_sym_go] = ACTIONS(820), + [anon_sym_try] = ACTIONS(820), }, [STATE(92)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(811), - [sym_integer_literal] = ACTIONS(811), - [sym_float_literal] = ACTIONS(813), - [anon_sym_true] = ACTIONS(811), - [anon_sym_false] = ACTIONS(811), - [anon_sym_nil] = ACTIONS(811), - [anon_sym_DQUOTE] = ACTIONS(813), - [anon_sym_SQUOTE] = ACTIONS(813), - [sym_raw_string] = ACTIONS(813), - [anon_sym_RBRACE] = ACTIONS(813), - [anon_sym_LPAREN] = ACTIONS(813), - [anon_sym_RPAREN] = ACTIONS(813), - [anon_sym_COMMA] = ACTIONS(813), - [anon_sym_COLON] = ACTIONS(813), - [anon_sym_DOT] = ACTIONS(811), - [anon_sym_QMARK_DOT] = ACTIONS(813), - [anon_sym_LBRACK] = ACTIONS(813), - [anon_sym_RBRACK] = ACTIONS(813), - [anon_sym_QMARK_LBRACK] = ACTIONS(813), - [anon_sym_LBRACE] = ACTIONS(813), - [anon_sym_BANG] = ACTIONS(811), - [anon_sym_STAR] = ACTIONS(813), - [anon_sym_SLASH] = ACTIONS(811), - [anon_sym_PERCENT] = ACTIONS(813), - [anon_sym_PLUS] = ACTIONS(813), - [anon_sym_DASH] = ACTIONS(813), - [anon_sym_EQ_EQ] = ACTIONS(813), - [anon_sym_BANG_EQ] = ACTIONS(813), - [anon_sym_LT] = ACTIONS(811), - [anon_sym_GT] = ACTIONS(811), - [anon_sym_LT_EQ] = ACTIONS(813), - [anon_sym_GT_EQ] = ACTIONS(813), - [anon_sym_AMP_AMP] = ACTIONS(813), - [anon_sym_PIPE_PIPE] = ACTIONS(813), - [anon_sym_QMARK_QMARK] = ACTIONS(813), - [anon_sym_DOT_DOT] = ACTIONS(811), - [anon_sym_DOT_DOT_EQ] = ACTIONS(813), - [anon_sym_QMARK] = ACTIONS(811), - [anon_sym_PIPE] = ACTIONS(811), - [anon_sym_match] = ACTIONS(811), - [anon_sym_EQ_GT] = ACTIONS(813), - [anon_sym_SEMI] = ACTIONS(813), - [anon_sym_if] = ACTIONS(811), - [anon_sym_spawn] = ACTIONS(811), - [anon_sym_chan] = ACTIONS(811), - [anon_sym_send] = ACTIONS(811), - [anon_sym_recv] = ACTIONS(811), - [anon_sym_select] = ACTIONS(811), - [anon_sym_POUND] = ACTIONS(813), - [anon_sym_use] = ACTIONS(811), - [anon_sym_export] = ACTIONS(811), - [anon_sym_macro_rules] = ACTIONS(811), - [anon_sym_let] = ACTIONS(811), - [anon_sym_while] = ACTIONS(811), - [anon_sym_for] = ACTIONS(811), - [anon_sym_fn] = ACTIONS(811), - [anon_sym_struct] = ACTIONS(811), - [anon_sym_type] = ACTIONS(811), - [anon_sym_trait] = ACTIONS(811), - [anon_sym_impl] = ACTIONS(811), - [anon_sym_return] = ACTIONS(811), - [anon_sym_break] = ACTIONS(811), - [anon_sym_continue] = ACTIONS(811), - [anon_sym_go] = ACTIONS(811), - [anon_sym_try] = ACTIONS(811), + [aux_sym_identifier_token1] = ACTIONS(824), + [sym_integer_literal] = ACTIONS(824), + [sym_float_literal] = ACTIONS(826), + [anon_sym_true] = ACTIONS(824), + [anon_sym_false] = ACTIONS(824), + [anon_sym_nil] = ACTIONS(824), + [anon_sym_DQUOTE] = ACTIONS(826), + [anon_sym_SQUOTE] = ACTIONS(826), + [sym_raw_string] = ACTIONS(826), + [anon_sym_RBRACE] = ACTIONS(826), + [anon_sym_LPAREN] = ACTIONS(826), + [anon_sym_RPAREN] = ACTIONS(826), + [anon_sym_COMMA] = ACTIONS(826), + [anon_sym_COLON] = ACTIONS(826), + [anon_sym_DOT] = ACTIONS(824), + [anon_sym_QMARK_DOT] = ACTIONS(826), + [anon_sym_LBRACK] = ACTIONS(826), + [anon_sym_RBRACK] = ACTIONS(826), + [anon_sym_QMARK_LBRACK] = ACTIONS(826), + [anon_sym_LBRACE] = ACTIONS(826), + [anon_sym_BANG] = ACTIONS(824), + [anon_sym_TILDE] = ACTIONS(826), + [anon_sym_STAR] = ACTIONS(826), + [anon_sym_SLASH] = ACTIONS(824), + [anon_sym_PERCENT] = ACTIONS(826), + [anon_sym_PLUS] = ACTIONS(826), + [anon_sym_DASH] = ACTIONS(826), + [anon_sym_EQ_EQ] = ACTIONS(826), + [anon_sym_BANG_EQ] = ACTIONS(826), + [anon_sym_LT] = ACTIONS(824), + [anon_sym_GT] = ACTIONS(824), + [anon_sym_LT_EQ] = ACTIONS(826), + [anon_sym_GT_EQ] = ACTIONS(826), + [anon_sym_AMP_AMP] = ACTIONS(826), + [anon_sym_PIPE_PIPE] = ACTIONS(826), + [anon_sym_PIPE] = ACTIONS(824), + [anon_sym_CARET] = ACTIONS(826), + [anon_sym_AMP] = ACTIONS(824), + [anon_sym_LT_LT] = ACTIONS(826), + [anon_sym_GT_GT] = ACTIONS(826), + [anon_sym_QMARK_QMARK] = ACTIONS(826), + [anon_sym_DOT_DOT] = ACTIONS(824), + [anon_sym_DOT_DOT_EQ] = ACTIONS(826), + [anon_sym_QMARK] = ACTIONS(824), + [anon_sym_match] = ACTIONS(824), + [anon_sym_EQ_GT] = ACTIONS(826), + [anon_sym_SEMI] = ACTIONS(826), + [anon_sym_if] = ACTIONS(824), + [anon_sym_spawn] = ACTIONS(824), + [anon_sym_chan] = ACTIONS(824), + [anon_sym_send] = ACTIONS(824), + [anon_sym_recv] = ACTIONS(824), + [anon_sym_select] = ACTIONS(824), + [anon_sym_POUND] = ACTIONS(826), + [anon_sym_use] = ACTIONS(824), + [anon_sym_export] = ACTIONS(824), + [anon_sym_macro_rules] = ACTIONS(824), + [anon_sym_let] = ACTIONS(824), + [anon_sym_while] = ACTIONS(824), + [anon_sym_for] = ACTIONS(824), + [anon_sym_fn] = ACTIONS(824), + [anon_sym_struct] = ACTIONS(824), + [anon_sym_type] = ACTIONS(824), + [anon_sym_trait] = ACTIONS(824), + [anon_sym_impl] = ACTIONS(824), + [anon_sym_return] = ACTIONS(824), + [anon_sym_break] = ACTIONS(824), + [anon_sym_continue] = ACTIONS(824), + [anon_sym_go] = ACTIONS(824), + [anon_sym_try] = ACTIONS(824), }, [STATE(93)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(815), - [sym_integer_literal] = ACTIONS(815), - [sym_float_literal] = ACTIONS(817), - [anon_sym_true] = ACTIONS(815), - [anon_sym_false] = ACTIONS(815), - [anon_sym_nil] = ACTIONS(815), - [anon_sym_DQUOTE] = ACTIONS(817), - [anon_sym_SQUOTE] = ACTIONS(817), - [sym_raw_string] = ACTIONS(817), - [anon_sym_RBRACE] = ACTIONS(817), - [anon_sym_LPAREN] = ACTIONS(817), - [anon_sym_RPAREN] = ACTIONS(817), - [anon_sym_COMMA] = ACTIONS(817), - [anon_sym_COLON] = ACTIONS(817), - [anon_sym_DOT] = ACTIONS(815), - [anon_sym_QMARK_DOT] = ACTIONS(817), - [anon_sym_LBRACK] = ACTIONS(817), - [anon_sym_RBRACK] = ACTIONS(817), - [anon_sym_QMARK_LBRACK] = ACTIONS(817), - [anon_sym_LBRACE] = ACTIONS(817), - [anon_sym_BANG] = ACTIONS(815), - [anon_sym_STAR] = ACTIONS(817), - [anon_sym_SLASH] = ACTIONS(815), - [anon_sym_PERCENT] = ACTIONS(817), - [anon_sym_PLUS] = ACTIONS(817), - [anon_sym_DASH] = ACTIONS(817), - [anon_sym_EQ_EQ] = ACTIONS(817), - [anon_sym_BANG_EQ] = ACTIONS(817), - [anon_sym_LT] = ACTIONS(815), - [anon_sym_GT] = ACTIONS(815), - [anon_sym_LT_EQ] = ACTIONS(817), - [anon_sym_GT_EQ] = ACTIONS(817), - [anon_sym_AMP_AMP] = ACTIONS(817), - [anon_sym_PIPE_PIPE] = ACTIONS(817), - [anon_sym_QMARK_QMARK] = ACTIONS(817), - [anon_sym_DOT_DOT] = ACTIONS(815), - [anon_sym_DOT_DOT_EQ] = ACTIONS(817), - [anon_sym_QMARK] = ACTIONS(815), - [anon_sym_PIPE] = ACTIONS(815), - [anon_sym_match] = ACTIONS(815), - [anon_sym_EQ_GT] = ACTIONS(817), - [anon_sym_SEMI] = ACTIONS(817), - [anon_sym_if] = ACTIONS(815), - [anon_sym_spawn] = ACTIONS(815), - [anon_sym_chan] = ACTIONS(815), - [anon_sym_send] = ACTIONS(815), - [anon_sym_recv] = ACTIONS(815), - [anon_sym_select] = ACTIONS(815), - [anon_sym_POUND] = ACTIONS(817), - [anon_sym_use] = ACTIONS(815), - [anon_sym_export] = ACTIONS(815), - [anon_sym_macro_rules] = ACTIONS(815), - [anon_sym_let] = ACTIONS(815), - [anon_sym_while] = ACTIONS(815), - [anon_sym_for] = ACTIONS(815), - [anon_sym_fn] = ACTIONS(815), - [anon_sym_struct] = ACTIONS(815), - [anon_sym_type] = ACTIONS(815), - [anon_sym_trait] = ACTIONS(815), - [anon_sym_impl] = ACTIONS(815), - [anon_sym_return] = ACTIONS(815), - [anon_sym_break] = ACTIONS(815), - [anon_sym_continue] = ACTIONS(815), - [anon_sym_go] = ACTIONS(815), - [anon_sym_try] = ACTIONS(815), + [aux_sym_identifier_token1] = ACTIONS(828), + [sym_integer_literal] = ACTIONS(828), + [sym_float_literal] = ACTIONS(830), + [anon_sym_true] = ACTIONS(828), + [anon_sym_false] = ACTIONS(828), + [anon_sym_nil] = ACTIONS(828), + [anon_sym_DQUOTE] = ACTIONS(830), + [anon_sym_SQUOTE] = ACTIONS(830), + [sym_raw_string] = ACTIONS(830), + [anon_sym_RBRACE] = ACTIONS(830), + [anon_sym_LPAREN] = ACTIONS(830), + [anon_sym_RPAREN] = ACTIONS(830), + [anon_sym_COMMA] = ACTIONS(830), + [anon_sym_COLON] = ACTIONS(830), + [anon_sym_DOT] = ACTIONS(828), + [anon_sym_QMARK_DOT] = ACTIONS(830), + [anon_sym_LBRACK] = ACTIONS(830), + [anon_sym_RBRACK] = ACTIONS(830), + [anon_sym_QMARK_LBRACK] = ACTIONS(830), + [anon_sym_LBRACE] = ACTIONS(830), + [anon_sym_BANG] = ACTIONS(828), + [anon_sym_TILDE] = ACTIONS(830), + [anon_sym_STAR] = ACTIONS(830), + [anon_sym_SLASH] = ACTIONS(828), + [anon_sym_PERCENT] = ACTIONS(830), + [anon_sym_PLUS] = ACTIONS(830), + [anon_sym_DASH] = ACTIONS(830), + [anon_sym_EQ_EQ] = ACTIONS(830), + [anon_sym_BANG_EQ] = ACTIONS(830), + [anon_sym_LT] = ACTIONS(828), + [anon_sym_GT] = ACTIONS(828), + [anon_sym_LT_EQ] = ACTIONS(830), + [anon_sym_GT_EQ] = ACTIONS(830), + [anon_sym_AMP_AMP] = ACTIONS(830), + [anon_sym_PIPE_PIPE] = ACTIONS(830), + [anon_sym_PIPE] = ACTIONS(828), + [anon_sym_CARET] = ACTIONS(830), + [anon_sym_AMP] = ACTIONS(828), + [anon_sym_LT_LT] = ACTIONS(830), + [anon_sym_GT_GT] = ACTIONS(830), + [anon_sym_QMARK_QMARK] = ACTIONS(830), + [anon_sym_DOT_DOT] = ACTIONS(828), + [anon_sym_DOT_DOT_EQ] = ACTIONS(830), + [anon_sym_QMARK] = ACTIONS(828), + [anon_sym_match] = ACTIONS(828), + [anon_sym_EQ_GT] = ACTIONS(830), + [anon_sym_SEMI] = ACTIONS(830), + [anon_sym_if] = ACTIONS(828), + [anon_sym_spawn] = ACTIONS(828), + [anon_sym_chan] = ACTIONS(828), + [anon_sym_send] = ACTIONS(828), + [anon_sym_recv] = ACTIONS(828), + [anon_sym_select] = ACTIONS(828), + [anon_sym_POUND] = ACTIONS(830), + [anon_sym_use] = ACTIONS(828), + [anon_sym_export] = ACTIONS(828), + [anon_sym_macro_rules] = ACTIONS(828), + [anon_sym_let] = ACTIONS(828), + [anon_sym_while] = ACTIONS(828), + [anon_sym_for] = ACTIONS(828), + [anon_sym_fn] = ACTIONS(828), + [anon_sym_struct] = ACTIONS(828), + [anon_sym_type] = ACTIONS(828), + [anon_sym_trait] = ACTIONS(828), + [anon_sym_impl] = ACTIONS(828), + [anon_sym_return] = ACTIONS(828), + [anon_sym_break] = ACTIONS(828), + [anon_sym_continue] = ACTIONS(828), + [anon_sym_go] = ACTIONS(828), + [anon_sym_try] = ACTIONS(828), }, [STATE(94)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(819), - [sym_integer_literal] = ACTIONS(819), - [sym_float_literal] = ACTIONS(821), - [anon_sym_true] = ACTIONS(819), - [anon_sym_false] = ACTIONS(819), - [anon_sym_nil] = ACTIONS(819), - [anon_sym_DQUOTE] = ACTIONS(821), - [anon_sym_SQUOTE] = ACTIONS(821), - [sym_raw_string] = ACTIONS(821), - [anon_sym_RBRACE] = ACTIONS(821), - [anon_sym_LPAREN] = ACTIONS(821), - [anon_sym_RPAREN] = ACTIONS(821), - [anon_sym_COMMA] = ACTIONS(821), - [anon_sym_COLON] = ACTIONS(821), - [anon_sym_DOT] = ACTIONS(819), - [anon_sym_QMARK_DOT] = ACTIONS(821), - [anon_sym_LBRACK] = ACTIONS(821), - [anon_sym_RBRACK] = ACTIONS(821), - [anon_sym_QMARK_LBRACK] = ACTIONS(821), - [anon_sym_LBRACE] = ACTIONS(821), - [anon_sym_BANG] = ACTIONS(819), - [anon_sym_STAR] = ACTIONS(821), - [anon_sym_SLASH] = ACTIONS(819), - [anon_sym_PERCENT] = ACTIONS(821), - [anon_sym_PLUS] = ACTIONS(821), - [anon_sym_DASH] = ACTIONS(821), - [anon_sym_EQ_EQ] = ACTIONS(821), - [anon_sym_BANG_EQ] = ACTIONS(821), - [anon_sym_LT] = ACTIONS(819), - [anon_sym_GT] = ACTIONS(819), - [anon_sym_LT_EQ] = ACTIONS(821), - [anon_sym_GT_EQ] = ACTIONS(821), - [anon_sym_AMP_AMP] = ACTIONS(821), - [anon_sym_PIPE_PIPE] = ACTIONS(821), - [anon_sym_QMARK_QMARK] = ACTIONS(821), - [anon_sym_DOT_DOT] = ACTIONS(819), - [anon_sym_DOT_DOT_EQ] = ACTIONS(821), - [anon_sym_QMARK] = ACTIONS(819), - [anon_sym_PIPE] = ACTIONS(819), - [anon_sym_match] = ACTIONS(819), - [anon_sym_EQ_GT] = ACTIONS(821), - [anon_sym_SEMI] = ACTIONS(821), - [anon_sym_if] = ACTIONS(819), - [anon_sym_spawn] = ACTIONS(819), - [anon_sym_chan] = ACTIONS(819), - [anon_sym_send] = ACTIONS(819), - [anon_sym_recv] = ACTIONS(819), - [anon_sym_select] = ACTIONS(819), - [anon_sym_POUND] = ACTIONS(821), - [anon_sym_use] = ACTIONS(819), - [anon_sym_export] = ACTIONS(819), - [anon_sym_macro_rules] = ACTIONS(819), - [anon_sym_let] = ACTIONS(819), - [anon_sym_while] = ACTIONS(819), - [anon_sym_for] = ACTIONS(819), - [anon_sym_fn] = ACTIONS(819), - [anon_sym_struct] = ACTIONS(819), - [anon_sym_type] = ACTIONS(819), - [anon_sym_trait] = ACTIONS(819), - [anon_sym_impl] = ACTIONS(819), - [anon_sym_return] = ACTIONS(819), - [anon_sym_break] = ACTIONS(819), - [anon_sym_continue] = ACTIONS(819), - [anon_sym_go] = ACTIONS(819), - [anon_sym_try] = ACTIONS(819), + [aux_sym_identifier_token1] = ACTIONS(832), + [sym_integer_literal] = ACTIONS(832), + [sym_float_literal] = ACTIONS(834), + [anon_sym_true] = ACTIONS(832), + [anon_sym_false] = ACTIONS(832), + [anon_sym_nil] = ACTIONS(832), + [anon_sym_DQUOTE] = ACTIONS(834), + [anon_sym_SQUOTE] = ACTIONS(834), + [sym_raw_string] = ACTIONS(834), + [anon_sym_RBRACE] = ACTIONS(834), + [anon_sym_LPAREN] = ACTIONS(834), + [anon_sym_RPAREN] = ACTIONS(834), + [anon_sym_COMMA] = ACTIONS(834), + [anon_sym_COLON] = ACTIONS(834), + [anon_sym_DOT] = ACTIONS(832), + [anon_sym_QMARK_DOT] = ACTIONS(834), + [anon_sym_LBRACK] = ACTIONS(834), + [anon_sym_RBRACK] = ACTIONS(834), + [anon_sym_QMARK_LBRACK] = ACTIONS(834), + [anon_sym_LBRACE] = ACTIONS(834), + [anon_sym_BANG] = ACTIONS(832), + [anon_sym_TILDE] = ACTIONS(834), + [anon_sym_STAR] = ACTIONS(834), + [anon_sym_SLASH] = ACTIONS(832), + [anon_sym_PERCENT] = ACTIONS(834), + [anon_sym_PLUS] = ACTIONS(834), + [anon_sym_DASH] = ACTIONS(834), + [anon_sym_EQ_EQ] = ACTIONS(834), + [anon_sym_BANG_EQ] = ACTIONS(834), + [anon_sym_LT] = ACTIONS(832), + [anon_sym_GT] = ACTIONS(832), + [anon_sym_LT_EQ] = ACTIONS(834), + [anon_sym_GT_EQ] = ACTIONS(834), + [anon_sym_AMP_AMP] = ACTIONS(834), + [anon_sym_PIPE_PIPE] = ACTIONS(834), + [anon_sym_PIPE] = ACTIONS(832), + [anon_sym_CARET] = ACTIONS(834), + [anon_sym_AMP] = ACTIONS(832), + [anon_sym_LT_LT] = ACTIONS(834), + [anon_sym_GT_GT] = ACTIONS(834), + [anon_sym_QMARK_QMARK] = ACTIONS(834), + [anon_sym_DOT_DOT] = ACTIONS(832), + [anon_sym_DOT_DOT_EQ] = ACTIONS(834), + [anon_sym_QMARK] = ACTIONS(832), + [anon_sym_match] = ACTIONS(832), + [anon_sym_EQ_GT] = ACTIONS(834), + [anon_sym_SEMI] = ACTIONS(834), + [anon_sym_if] = ACTIONS(832), + [anon_sym_spawn] = ACTIONS(832), + [anon_sym_chan] = ACTIONS(832), + [anon_sym_send] = ACTIONS(832), + [anon_sym_recv] = ACTIONS(832), + [anon_sym_select] = ACTIONS(832), + [anon_sym_POUND] = ACTIONS(834), + [anon_sym_use] = ACTIONS(832), + [anon_sym_export] = ACTIONS(832), + [anon_sym_macro_rules] = ACTIONS(832), + [anon_sym_let] = ACTIONS(832), + [anon_sym_while] = ACTIONS(832), + [anon_sym_for] = ACTIONS(832), + [anon_sym_fn] = ACTIONS(832), + [anon_sym_struct] = ACTIONS(832), + [anon_sym_type] = ACTIONS(832), + [anon_sym_trait] = ACTIONS(832), + [anon_sym_impl] = ACTIONS(832), + [anon_sym_return] = ACTIONS(832), + [anon_sym_break] = ACTIONS(832), + [anon_sym_continue] = ACTIONS(832), + [anon_sym_go] = ACTIONS(832), + [anon_sym_try] = ACTIONS(832), }, [STATE(95)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(823), - [sym_integer_literal] = ACTIONS(823), - [sym_float_literal] = ACTIONS(825), - [anon_sym_true] = ACTIONS(823), - [anon_sym_false] = ACTIONS(823), - [anon_sym_nil] = ACTIONS(823), - [anon_sym_DQUOTE] = ACTIONS(825), - [anon_sym_SQUOTE] = ACTIONS(825), - [sym_raw_string] = ACTIONS(825), - [anon_sym_RBRACE] = ACTIONS(825), - [anon_sym_LPAREN] = ACTIONS(825), - [anon_sym_RPAREN] = ACTIONS(825), - [anon_sym_COMMA] = ACTIONS(825), - [anon_sym_COLON] = ACTIONS(825), - [anon_sym_DOT] = ACTIONS(823), - [anon_sym_QMARK_DOT] = ACTIONS(825), - [anon_sym_LBRACK] = ACTIONS(825), - [anon_sym_RBRACK] = ACTIONS(825), - [anon_sym_QMARK_LBRACK] = ACTIONS(825), - [anon_sym_LBRACE] = ACTIONS(825), - [anon_sym_BANG] = ACTIONS(823), - [anon_sym_STAR] = ACTIONS(825), - [anon_sym_SLASH] = ACTIONS(823), - [anon_sym_PERCENT] = ACTIONS(825), - [anon_sym_PLUS] = ACTIONS(825), - [anon_sym_DASH] = ACTIONS(825), - [anon_sym_EQ_EQ] = ACTIONS(825), - [anon_sym_BANG_EQ] = ACTIONS(825), - [anon_sym_LT] = ACTIONS(823), - [anon_sym_GT] = ACTIONS(823), - [anon_sym_LT_EQ] = ACTIONS(825), - [anon_sym_GT_EQ] = ACTIONS(825), - [anon_sym_AMP_AMP] = ACTIONS(825), - [anon_sym_PIPE_PIPE] = ACTIONS(825), - [anon_sym_QMARK_QMARK] = ACTIONS(825), - [anon_sym_DOT_DOT] = ACTIONS(823), - [anon_sym_DOT_DOT_EQ] = ACTIONS(825), - [anon_sym_QMARK] = ACTIONS(823), - [anon_sym_PIPE] = ACTIONS(823), - [anon_sym_match] = ACTIONS(823), - [anon_sym_EQ_GT] = ACTIONS(825), - [anon_sym_SEMI] = ACTIONS(825), - [anon_sym_if] = ACTIONS(823), - [anon_sym_spawn] = ACTIONS(823), - [anon_sym_chan] = ACTIONS(823), - [anon_sym_send] = ACTIONS(823), - [anon_sym_recv] = ACTIONS(823), - [anon_sym_select] = ACTIONS(823), - [anon_sym_POUND] = ACTIONS(825), - [anon_sym_use] = ACTIONS(823), - [anon_sym_export] = ACTIONS(823), - [anon_sym_macro_rules] = ACTIONS(823), - [anon_sym_let] = ACTIONS(823), - [anon_sym_while] = ACTIONS(823), - [anon_sym_for] = ACTIONS(823), - [anon_sym_fn] = ACTIONS(823), - [anon_sym_struct] = ACTIONS(823), - [anon_sym_type] = ACTIONS(823), - [anon_sym_trait] = ACTIONS(823), - [anon_sym_impl] = ACTIONS(823), - [anon_sym_return] = ACTIONS(823), - [anon_sym_break] = ACTIONS(823), - [anon_sym_continue] = ACTIONS(823), - [anon_sym_go] = ACTIONS(823), - [anon_sym_try] = ACTIONS(823), + [aux_sym_identifier_token1] = ACTIONS(836), + [sym_integer_literal] = ACTIONS(836), + [sym_float_literal] = ACTIONS(838), + [anon_sym_true] = ACTIONS(836), + [anon_sym_false] = ACTIONS(836), + [anon_sym_nil] = ACTIONS(836), + [anon_sym_DQUOTE] = ACTIONS(838), + [anon_sym_SQUOTE] = ACTIONS(838), + [sym_raw_string] = ACTIONS(838), + [anon_sym_RBRACE] = ACTIONS(838), + [anon_sym_LPAREN] = ACTIONS(838), + [anon_sym_RPAREN] = ACTIONS(838), + [anon_sym_COMMA] = ACTIONS(838), + [anon_sym_COLON] = ACTIONS(838), + [anon_sym_DOT] = ACTIONS(836), + [anon_sym_QMARK_DOT] = ACTIONS(838), + [anon_sym_LBRACK] = ACTIONS(838), + [anon_sym_RBRACK] = ACTIONS(838), + [anon_sym_QMARK_LBRACK] = ACTIONS(838), + [anon_sym_LBRACE] = ACTIONS(838), + [anon_sym_BANG] = ACTIONS(836), + [anon_sym_TILDE] = ACTIONS(838), + [anon_sym_STAR] = ACTIONS(838), + [anon_sym_SLASH] = ACTIONS(836), + [anon_sym_PERCENT] = ACTIONS(838), + [anon_sym_PLUS] = ACTIONS(838), + [anon_sym_DASH] = ACTIONS(838), + [anon_sym_EQ_EQ] = ACTIONS(838), + [anon_sym_BANG_EQ] = ACTIONS(838), + [anon_sym_LT] = ACTIONS(836), + [anon_sym_GT] = ACTIONS(836), + [anon_sym_LT_EQ] = ACTIONS(838), + [anon_sym_GT_EQ] = ACTIONS(838), + [anon_sym_AMP_AMP] = ACTIONS(838), + [anon_sym_PIPE_PIPE] = ACTIONS(838), + [anon_sym_PIPE] = ACTIONS(836), + [anon_sym_CARET] = ACTIONS(838), + [anon_sym_AMP] = ACTIONS(836), + [anon_sym_LT_LT] = ACTIONS(838), + [anon_sym_GT_GT] = ACTIONS(838), + [anon_sym_QMARK_QMARK] = ACTIONS(838), + [anon_sym_DOT_DOT] = ACTIONS(836), + [anon_sym_DOT_DOT_EQ] = ACTIONS(838), + [anon_sym_QMARK] = ACTIONS(836), + [anon_sym_match] = ACTIONS(836), + [anon_sym_EQ_GT] = ACTIONS(838), + [anon_sym_SEMI] = ACTIONS(838), + [anon_sym_if] = ACTIONS(836), + [anon_sym_spawn] = ACTIONS(836), + [anon_sym_chan] = ACTIONS(836), + [anon_sym_send] = ACTIONS(836), + [anon_sym_recv] = ACTIONS(836), + [anon_sym_select] = ACTIONS(836), + [anon_sym_POUND] = ACTIONS(838), + [anon_sym_use] = ACTIONS(836), + [anon_sym_export] = ACTIONS(836), + [anon_sym_macro_rules] = ACTIONS(836), + [anon_sym_let] = ACTIONS(836), + [anon_sym_while] = ACTIONS(836), + [anon_sym_for] = ACTIONS(836), + [anon_sym_fn] = ACTIONS(836), + [anon_sym_struct] = ACTIONS(836), + [anon_sym_type] = ACTIONS(836), + [anon_sym_trait] = ACTIONS(836), + [anon_sym_impl] = ACTIONS(836), + [anon_sym_return] = ACTIONS(836), + [anon_sym_break] = ACTIONS(836), + [anon_sym_continue] = ACTIONS(836), + [anon_sym_go] = ACTIONS(836), + [anon_sym_try] = ACTIONS(836), }, [STATE(96)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(827), - [sym_integer_literal] = ACTIONS(827), - [sym_float_literal] = ACTIONS(829), - [anon_sym_true] = ACTIONS(827), - [anon_sym_false] = ACTIONS(827), - [anon_sym_nil] = ACTIONS(827), - [anon_sym_DQUOTE] = ACTIONS(829), - [anon_sym_SQUOTE] = ACTIONS(829), - [sym_raw_string] = ACTIONS(829), - [anon_sym_RBRACE] = ACTIONS(829), - [anon_sym_LPAREN] = ACTIONS(829), - [anon_sym_RPAREN] = ACTIONS(829), - [anon_sym_COMMA] = ACTIONS(829), - [anon_sym_COLON] = ACTIONS(829), - [anon_sym_DOT] = ACTIONS(827), - [anon_sym_QMARK_DOT] = ACTIONS(829), - [anon_sym_LBRACK] = ACTIONS(829), - [anon_sym_RBRACK] = ACTIONS(829), - [anon_sym_QMARK_LBRACK] = ACTIONS(829), - [anon_sym_LBRACE] = ACTIONS(829), - [anon_sym_BANG] = ACTIONS(827), - [anon_sym_STAR] = ACTIONS(829), - [anon_sym_SLASH] = ACTIONS(827), - [anon_sym_PERCENT] = ACTIONS(829), - [anon_sym_PLUS] = ACTIONS(829), - [anon_sym_DASH] = ACTIONS(829), - [anon_sym_EQ_EQ] = ACTIONS(829), - [anon_sym_BANG_EQ] = ACTIONS(829), - [anon_sym_LT] = ACTIONS(827), - [anon_sym_GT] = ACTIONS(827), - [anon_sym_LT_EQ] = ACTIONS(829), - [anon_sym_GT_EQ] = ACTIONS(829), - [anon_sym_AMP_AMP] = ACTIONS(829), - [anon_sym_PIPE_PIPE] = ACTIONS(829), - [anon_sym_QMARK_QMARK] = ACTIONS(829), - [anon_sym_DOT_DOT] = ACTIONS(827), - [anon_sym_DOT_DOT_EQ] = ACTIONS(829), - [anon_sym_QMARK] = ACTIONS(827), - [anon_sym_PIPE] = ACTIONS(827), - [anon_sym_match] = ACTIONS(827), - [anon_sym_EQ_GT] = ACTIONS(829), - [anon_sym_SEMI] = ACTIONS(829), - [anon_sym_if] = ACTIONS(827), - [anon_sym_spawn] = ACTIONS(827), - [anon_sym_chan] = ACTIONS(827), - [anon_sym_send] = ACTIONS(827), - [anon_sym_recv] = ACTIONS(827), - [anon_sym_select] = ACTIONS(827), - [anon_sym_POUND] = ACTIONS(829), - [anon_sym_use] = ACTIONS(827), - [anon_sym_export] = ACTIONS(827), - [anon_sym_macro_rules] = ACTIONS(827), - [anon_sym_let] = ACTIONS(827), - [anon_sym_while] = ACTIONS(827), - [anon_sym_for] = ACTIONS(827), - [anon_sym_fn] = ACTIONS(827), - [anon_sym_struct] = ACTIONS(827), - [anon_sym_type] = ACTIONS(827), - [anon_sym_trait] = ACTIONS(827), - [anon_sym_impl] = ACTIONS(827), - [anon_sym_return] = ACTIONS(827), - [anon_sym_break] = ACTIONS(827), - [anon_sym_continue] = ACTIONS(827), - [anon_sym_go] = ACTIONS(827), - [anon_sym_try] = ACTIONS(827), + [aux_sym_identifier_token1] = ACTIONS(840), + [sym_integer_literal] = ACTIONS(840), + [sym_float_literal] = ACTIONS(842), + [anon_sym_true] = ACTIONS(840), + [anon_sym_false] = ACTIONS(840), + [anon_sym_nil] = ACTIONS(840), + [anon_sym_DQUOTE] = ACTIONS(842), + [anon_sym_SQUOTE] = ACTIONS(842), + [sym_raw_string] = ACTIONS(842), + [anon_sym_RBRACE] = ACTIONS(842), + [anon_sym_LPAREN] = ACTIONS(842), + [anon_sym_RPAREN] = ACTIONS(842), + [anon_sym_COMMA] = ACTIONS(842), + [anon_sym_COLON] = ACTIONS(842), + [anon_sym_DOT] = ACTIONS(840), + [anon_sym_QMARK_DOT] = ACTIONS(842), + [anon_sym_LBRACK] = ACTIONS(842), + [anon_sym_RBRACK] = ACTIONS(842), + [anon_sym_QMARK_LBRACK] = ACTIONS(842), + [anon_sym_LBRACE] = ACTIONS(842), + [anon_sym_BANG] = ACTIONS(840), + [anon_sym_TILDE] = ACTIONS(842), + [anon_sym_STAR] = ACTIONS(842), + [anon_sym_SLASH] = ACTIONS(840), + [anon_sym_PERCENT] = ACTIONS(842), + [anon_sym_PLUS] = ACTIONS(842), + [anon_sym_DASH] = ACTIONS(842), + [anon_sym_EQ_EQ] = ACTIONS(842), + [anon_sym_BANG_EQ] = ACTIONS(842), + [anon_sym_LT] = ACTIONS(840), + [anon_sym_GT] = ACTIONS(840), + [anon_sym_LT_EQ] = ACTIONS(842), + [anon_sym_GT_EQ] = ACTIONS(842), + [anon_sym_AMP_AMP] = ACTIONS(842), + [anon_sym_PIPE_PIPE] = ACTIONS(842), + [anon_sym_PIPE] = ACTIONS(840), + [anon_sym_CARET] = ACTIONS(842), + [anon_sym_AMP] = ACTIONS(840), + [anon_sym_LT_LT] = ACTIONS(842), + [anon_sym_GT_GT] = ACTIONS(842), + [anon_sym_QMARK_QMARK] = ACTIONS(842), + [anon_sym_DOT_DOT] = ACTIONS(840), + [anon_sym_DOT_DOT_EQ] = ACTIONS(842), + [anon_sym_QMARK] = ACTIONS(840), + [anon_sym_match] = ACTIONS(840), + [anon_sym_EQ_GT] = ACTIONS(842), + [anon_sym_SEMI] = ACTIONS(842), + [anon_sym_if] = ACTIONS(840), + [anon_sym_spawn] = ACTIONS(840), + [anon_sym_chan] = ACTIONS(840), + [anon_sym_send] = ACTIONS(840), + [anon_sym_recv] = ACTIONS(840), + [anon_sym_select] = ACTIONS(840), + [anon_sym_POUND] = ACTIONS(842), + [anon_sym_use] = ACTIONS(840), + [anon_sym_export] = ACTIONS(840), + [anon_sym_macro_rules] = ACTIONS(840), + [anon_sym_let] = ACTIONS(840), + [anon_sym_while] = ACTIONS(840), + [anon_sym_for] = ACTIONS(840), + [anon_sym_fn] = ACTIONS(840), + [anon_sym_struct] = ACTIONS(840), + [anon_sym_type] = ACTIONS(840), + [anon_sym_trait] = ACTIONS(840), + [anon_sym_impl] = ACTIONS(840), + [anon_sym_return] = ACTIONS(840), + [anon_sym_break] = ACTIONS(840), + [anon_sym_continue] = ACTIONS(840), + [anon_sym_go] = ACTIONS(840), + [anon_sym_try] = ACTIONS(840), }, [STATE(97)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(784), - [sym_integer_literal] = ACTIONS(784), - [sym_float_literal] = ACTIONS(786), - [anon_sym_true] = ACTIONS(784), - [anon_sym_false] = ACTIONS(784), - [anon_sym_nil] = ACTIONS(784), - [anon_sym_DQUOTE] = ACTIONS(786), - [anon_sym_SQUOTE] = ACTIONS(786), - [sym_raw_string] = ACTIONS(786), - [anon_sym_RBRACE] = ACTIONS(786), - [anon_sym_LPAREN] = ACTIONS(786), - [anon_sym_RPAREN] = ACTIONS(786), - [anon_sym_COMMA] = ACTIONS(786), - [anon_sym_COLON] = ACTIONS(786), - [anon_sym_DOT] = ACTIONS(784), - [anon_sym_QMARK_DOT] = ACTIONS(786), - [anon_sym_LBRACK] = ACTIONS(786), - [anon_sym_RBRACK] = ACTIONS(786), - [anon_sym_QMARK_LBRACK] = ACTIONS(786), - [anon_sym_LBRACE] = ACTIONS(786), - [anon_sym_BANG] = ACTIONS(784), - [anon_sym_STAR] = ACTIONS(786), - [anon_sym_SLASH] = ACTIONS(784), - [anon_sym_PERCENT] = ACTIONS(786), - [anon_sym_PLUS] = ACTIONS(786), - [anon_sym_DASH] = ACTIONS(786), - [anon_sym_EQ_EQ] = ACTIONS(786), - [anon_sym_BANG_EQ] = ACTIONS(786), - [anon_sym_LT] = ACTIONS(784), - [anon_sym_GT] = ACTIONS(784), - [anon_sym_LT_EQ] = ACTIONS(786), - [anon_sym_GT_EQ] = ACTIONS(786), - [anon_sym_AMP_AMP] = ACTIONS(786), - [anon_sym_PIPE_PIPE] = ACTIONS(786), - [anon_sym_QMARK_QMARK] = ACTIONS(786), - [anon_sym_DOT_DOT] = ACTIONS(784), - [anon_sym_DOT_DOT_EQ] = ACTIONS(786), - [anon_sym_QMARK] = ACTIONS(784), - [anon_sym_PIPE] = ACTIONS(784), - [anon_sym_match] = ACTIONS(784), - [anon_sym_EQ_GT] = ACTIONS(786), - [anon_sym_SEMI] = ACTIONS(786), - [anon_sym_if] = ACTIONS(784), - [anon_sym_spawn] = ACTIONS(784), - [anon_sym_chan] = ACTIONS(784), - [anon_sym_send] = ACTIONS(784), - [anon_sym_recv] = ACTIONS(784), - [anon_sym_select] = ACTIONS(784), - [anon_sym_POUND] = ACTIONS(786), - [anon_sym_use] = ACTIONS(784), - [anon_sym_export] = ACTIONS(784), - [anon_sym_macro_rules] = ACTIONS(784), - [anon_sym_let] = ACTIONS(784), - [anon_sym_while] = ACTIONS(784), - [anon_sym_for] = ACTIONS(784), - [anon_sym_fn] = ACTIONS(784), - [anon_sym_struct] = ACTIONS(784), - [anon_sym_type] = ACTIONS(784), - [anon_sym_trait] = ACTIONS(784), - [anon_sym_impl] = ACTIONS(784), - [anon_sym_return] = ACTIONS(784), - [anon_sym_break] = ACTIONS(784), - [anon_sym_continue] = ACTIONS(784), - [anon_sym_go] = ACTIONS(784), - [anon_sym_try] = ACTIONS(784), + [aux_sym_identifier_token1] = ACTIONS(844), + [sym_integer_literal] = ACTIONS(844), + [sym_float_literal] = ACTIONS(846), + [anon_sym_true] = ACTIONS(844), + [anon_sym_false] = ACTIONS(844), + [anon_sym_nil] = ACTIONS(844), + [anon_sym_DQUOTE] = ACTIONS(846), + [anon_sym_SQUOTE] = ACTIONS(846), + [sym_raw_string] = ACTIONS(846), + [anon_sym_RBRACE] = ACTIONS(846), + [anon_sym_LPAREN] = ACTIONS(846), + [anon_sym_RPAREN] = ACTIONS(846), + [anon_sym_COMMA] = ACTIONS(846), + [anon_sym_COLON] = ACTIONS(846), + [anon_sym_DOT] = ACTIONS(844), + [anon_sym_QMARK_DOT] = ACTIONS(846), + [anon_sym_LBRACK] = ACTIONS(846), + [anon_sym_RBRACK] = ACTIONS(846), + [anon_sym_QMARK_LBRACK] = ACTIONS(846), + [anon_sym_LBRACE] = ACTIONS(846), + [anon_sym_BANG] = ACTIONS(844), + [anon_sym_TILDE] = ACTIONS(846), + [anon_sym_STAR] = ACTIONS(846), + [anon_sym_SLASH] = ACTIONS(844), + [anon_sym_PERCENT] = ACTIONS(846), + [anon_sym_PLUS] = ACTIONS(846), + [anon_sym_DASH] = ACTIONS(846), + [anon_sym_EQ_EQ] = ACTIONS(846), + [anon_sym_BANG_EQ] = ACTIONS(846), + [anon_sym_LT] = ACTIONS(844), + [anon_sym_GT] = ACTIONS(844), + [anon_sym_LT_EQ] = ACTIONS(846), + [anon_sym_GT_EQ] = ACTIONS(846), + [anon_sym_AMP_AMP] = ACTIONS(846), + [anon_sym_PIPE_PIPE] = ACTIONS(846), + [anon_sym_PIPE] = ACTIONS(844), + [anon_sym_CARET] = ACTIONS(846), + [anon_sym_AMP] = ACTIONS(844), + [anon_sym_LT_LT] = ACTIONS(846), + [anon_sym_GT_GT] = ACTIONS(846), + [anon_sym_QMARK_QMARK] = ACTIONS(846), + [anon_sym_DOT_DOT] = ACTIONS(844), + [anon_sym_DOT_DOT_EQ] = ACTIONS(846), + [anon_sym_QMARK] = ACTIONS(844), + [anon_sym_match] = ACTIONS(844), + [anon_sym_EQ_GT] = ACTIONS(846), + [anon_sym_SEMI] = ACTIONS(846), + [anon_sym_if] = ACTIONS(844), + [anon_sym_spawn] = ACTIONS(844), + [anon_sym_chan] = ACTIONS(844), + [anon_sym_send] = ACTIONS(844), + [anon_sym_recv] = ACTIONS(844), + [anon_sym_select] = ACTIONS(844), + [anon_sym_POUND] = ACTIONS(846), + [anon_sym_use] = ACTIONS(844), + [anon_sym_export] = ACTIONS(844), + [anon_sym_macro_rules] = ACTIONS(844), + [anon_sym_let] = ACTIONS(844), + [anon_sym_while] = ACTIONS(844), + [anon_sym_for] = ACTIONS(844), + [anon_sym_fn] = ACTIONS(844), + [anon_sym_struct] = ACTIONS(844), + [anon_sym_type] = ACTIONS(844), + [anon_sym_trait] = ACTIONS(844), + [anon_sym_impl] = ACTIONS(844), + [anon_sym_return] = ACTIONS(844), + [anon_sym_break] = ACTIONS(844), + [anon_sym_continue] = ACTIONS(844), + [anon_sym_go] = ACTIONS(844), + [anon_sym_try] = ACTIONS(844), }, [STATE(98)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(831), - [sym_integer_literal] = ACTIONS(831), - [sym_float_literal] = ACTIONS(833), - [anon_sym_true] = ACTIONS(831), - [anon_sym_false] = ACTIONS(831), - [anon_sym_nil] = ACTIONS(831), - [anon_sym_DQUOTE] = ACTIONS(833), - [anon_sym_SQUOTE] = ACTIONS(833), - [sym_raw_string] = ACTIONS(833), - [anon_sym_RBRACE] = ACTIONS(833), - [anon_sym_LPAREN] = ACTIONS(833), - [anon_sym_RPAREN] = ACTIONS(833), - [anon_sym_COMMA] = ACTIONS(833), - [anon_sym_COLON] = ACTIONS(833), - [anon_sym_DOT] = ACTIONS(831), - [anon_sym_QMARK_DOT] = ACTIONS(833), - [anon_sym_LBRACK] = ACTIONS(833), - [anon_sym_RBRACK] = ACTIONS(833), - [anon_sym_QMARK_LBRACK] = ACTIONS(833), - [anon_sym_LBRACE] = ACTIONS(833), - [anon_sym_BANG] = ACTIONS(831), - [anon_sym_STAR] = ACTIONS(833), - [anon_sym_SLASH] = ACTIONS(831), - [anon_sym_PERCENT] = ACTIONS(833), - [anon_sym_PLUS] = ACTIONS(833), - [anon_sym_DASH] = ACTIONS(833), - [anon_sym_EQ_EQ] = ACTIONS(833), - [anon_sym_BANG_EQ] = ACTIONS(833), - [anon_sym_LT] = ACTIONS(831), - [anon_sym_GT] = ACTIONS(831), - [anon_sym_LT_EQ] = ACTIONS(833), - [anon_sym_GT_EQ] = ACTIONS(833), - [anon_sym_AMP_AMP] = ACTIONS(833), - [anon_sym_PIPE_PIPE] = ACTIONS(833), - [anon_sym_QMARK_QMARK] = ACTIONS(833), - [anon_sym_DOT_DOT] = ACTIONS(831), - [anon_sym_DOT_DOT_EQ] = ACTIONS(833), - [anon_sym_QMARK] = ACTIONS(831), - [anon_sym_PIPE] = ACTIONS(831), - [anon_sym_match] = ACTIONS(831), - [anon_sym_EQ_GT] = ACTIONS(833), - [anon_sym_SEMI] = ACTIONS(833), - [anon_sym_if] = ACTIONS(831), - [anon_sym_spawn] = ACTIONS(831), - [anon_sym_chan] = ACTIONS(831), - [anon_sym_send] = ACTIONS(831), - [anon_sym_recv] = ACTIONS(831), - [anon_sym_select] = ACTIONS(831), - [anon_sym_POUND] = ACTIONS(833), - [anon_sym_use] = ACTIONS(831), - [anon_sym_export] = ACTIONS(831), - [anon_sym_macro_rules] = ACTIONS(831), - [anon_sym_let] = ACTIONS(831), - [anon_sym_while] = ACTIONS(831), - [anon_sym_for] = ACTIONS(831), - [anon_sym_fn] = ACTIONS(831), - [anon_sym_struct] = ACTIONS(831), - [anon_sym_type] = ACTIONS(831), - [anon_sym_trait] = ACTIONS(831), - [anon_sym_impl] = ACTIONS(831), - [anon_sym_return] = ACTIONS(831), - [anon_sym_break] = ACTIONS(831), - [anon_sym_continue] = ACTIONS(831), - [anon_sym_go] = ACTIONS(831), - [anon_sym_try] = ACTIONS(831), + [aux_sym_identifier_token1] = ACTIONS(848), + [sym_integer_literal] = ACTIONS(848), + [sym_float_literal] = ACTIONS(850), + [anon_sym_true] = ACTIONS(848), + [anon_sym_false] = ACTIONS(848), + [anon_sym_nil] = ACTIONS(848), + [anon_sym_DQUOTE] = ACTIONS(850), + [anon_sym_SQUOTE] = ACTIONS(850), + [sym_raw_string] = ACTIONS(850), + [anon_sym_RBRACE] = ACTIONS(850), + [anon_sym_LPAREN] = ACTIONS(850), + [anon_sym_RPAREN] = ACTIONS(850), + [anon_sym_COMMA] = ACTIONS(850), + [anon_sym_COLON] = ACTIONS(850), + [anon_sym_DOT] = ACTIONS(848), + [anon_sym_QMARK_DOT] = ACTIONS(850), + [anon_sym_LBRACK] = ACTIONS(850), + [anon_sym_RBRACK] = ACTIONS(850), + [anon_sym_QMARK_LBRACK] = ACTIONS(850), + [anon_sym_LBRACE] = ACTIONS(850), + [anon_sym_BANG] = ACTIONS(848), + [anon_sym_TILDE] = ACTIONS(850), + [anon_sym_STAR] = ACTIONS(850), + [anon_sym_SLASH] = ACTIONS(848), + [anon_sym_PERCENT] = ACTIONS(850), + [anon_sym_PLUS] = ACTIONS(850), + [anon_sym_DASH] = ACTIONS(850), + [anon_sym_EQ_EQ] = ACTIONS(850), + [anon_sym_BANG_EQ] = ACTIONS(850), + [anon_sym_LT] = ACTIONS(848), + [anon_sym_GT] = ACTIONS(848), + [anon_sym_LT_EQ] = ACTIONS(850), + [anon_sym_GT_EQ] = ACTIONS(850), + [anon_sym_AMP_AMP] = ACTIONS(850), + [anon_sym_PIPE_PIPE] = ACTIONS(850), + [anon_sym_PIPE] = ACTIONS(848), + [anon_sym_CARET] = ACTIONS(850), + [anon_sym_AMP] = ACTIONS(848), + [anon_sym_LT_LT] = ACTIONS(850), + [anon_sym_GT_GT] = ACTIONS(850), + [anon_sym_QMARK_QMARK] = ACTIONS(850), + [anon_sym_DOT_DOT] = ACTIONS(848), + [anon_sym_DOT_DOT_EQ] = ACTIONS(850), + [anon_sym_QMARK] = ACTIONS(848), + [anon_sym_match] = ACTIONS(848), + [anon_sym_EQ_GT] = ACTIONS(850), + [anon_sym_SEMI] = ACTIONS(850), + [anon_sym_if] = ACTIONS(848), + [anon_sym_spawn] = ACTIONS(848), + [anon_sym_chan] = ACTIONS(848), + [anon_sym_send] = ACTIONS(848), + [anon_sym_recv] = ACTIONS(848), + [anon_sym_select] = ACTIONS(848), + [anon_sym_POUND] = ACTIONS(850), + [anon_sym_use] = ACTIONS(848), + [anon_sym_export] = ACTIONS(848), + [anon_sym_macro_rules] = ACTIONS(848), + [anon_sym_let] = ACTIONS(848), + [anon_sym_while] = ACTIONS(848), + [anon_sym_for] = ACTIONS(848), + [anon_sym_fn] = ACTIONS(848), + [anon_sym_struct] = ACTIONS(848), + [anon_sym_type] = ACTIONS(848), + [anon_sym_trait] = ACTIONS(848), + [anon_sym_impl] = ACTIONS(848), + [anon_sym_return] = ACTIONS(848), + [anon_sym_break] = ACTIONS(848), + [anon_sym_continue] = ACTIONS(848), + [anon_sym_go] = ACTIONS(848), + [anon_sym_try] = ACTIONS(848), }, [STATE(99)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(835), - [sym_integer_literal] = ACTIONS(835), - [sym_float_literal] = ACTIONS(837), - [anon_sym_true] = ACTIONS(835), - [anon_sym_false] = ACTIONS(835), - [anon_sym_nil] = ACTIONS(835), - [anon_sym_DQUOTE] = ACTIONS(837), - [anon_sym_SQUOTE] = ACTIONS(837), - [sym_raw_string] = ACTIONS(837), - [anon_sym_RBRACE] = ACTIONS(837), - [anon_sym_LPAREN] = ACTIONS(837), - [anon_sym_RPAREN] = ACTIONS(837), - [anon_sym_COMMA] = ACTIONS(837), - [anon_sym_COLON] = ACTIONS(837), - [anon_sym_DOT] = ACTIONS(835), - [anon_sym_QMARK_DOT] = ACTIONS(837), - [anon_sym_LBRACK] = ACTIONS(837), - [anon_sym_RBRACK] = ACTIONS(837), - [anon_sym_QMARK_LBRACK] = ACTIONS(837), - [anon_sym_LBRACE] = ACTIONS(837), - [anon_sym_BANG] = ACTIONS(835), - [anon_sym_STAR] = ACTIONS(837), - [anon_sym_SLASH] = ACTIONS(835), - [anon_sym_PERCENT] = ACTIONS(837), - [anon_sym_PLUS] = ACTIONS(837), - [anon_sym_DASH] = ACTIONS(837), - [anon_sym_EQ_EQ] = ACTIONS(837), - [anon_sym_BANG_EQ] = ACTIONS(837), - [anon_sym_LT] = ACTIONS(835), - [anon_sym_GT] = ACTIONS(835), - [anon_sym_LT_EQ] = ACTIONS(837), - [anon_sym_GT_EQ] = ACTIONS(837), - [anon_sym_AMP_AMP] = ACTIONS(837), - [anon_sym_PIPE_PIPE] = ACTIONS(837), - [anon_sym_QMARK_QMARK] = ACTIONS(837), - [anon_sym_DOT_DOT] = ACTIONS(835), - [anon_sym_DOT_DOT_EQ] = ACTIONS(837), - [anon_sym_QMARK] = ACTIONS(835), - [anon_sym_PIPE] = ACTIONS(835), - [anon_sym_match] = ACTIONS(835), - [anon_sym_EQ_GT] = ACTIONS(837), - [anon_sym_SEMI] = ACTIONS(837), - [anon_sym_if] = ACTIONS(835), - [anon_sym_spawn] = ACTIONS(835), - [anon_sym_chan] = ACTIONS(835), - [anon_sym_send] = ACTIONS(835), - [anon_sym_recv] = ACTIONS(835), - [anon_sym_select] = ACTIONS(835), - [anon_sym_POUND] = ACTIONS(837), - [anon_sym_use] = ACTIONS(835), - [anon_sym_export] = ACTIONS(835), - [anon_sym_macro_rules] = ACTIONS(835), - [anon_sym_let] = ACTIONS(835), - [anon_sym_while] = ACTIONS(835), - [anon_sym_for] = ACTIONS(835), - [anon_sym_fn] = ACTIONS(835), - [anon_sym_struct] = ACTIONS(835), - [anon_sym_type] = ACTIONS(835), - [anon_sym_trait] = ACTIONS(835), - [anon_sym_impl] = ACTIONS(835), - [anon_sym_return] = ACTIONS(835), - [anon_sym_break] = ACTIONS(835), - [anon_sym_continue] = ACTIONS(835), - [anon_sym_go] = ACTIONS(835), - [anon_sym_try] = ACTIONS(835), + [aux_sym_identifier_token1] = ACTIONS(852), + [sym_integer_literal] = ACTIONS(852), + [sym_float_literal] = ACTIONS(854), + [anon_sym_true] = ACTIONS(852), + [anon_sym_false] = ACTIONS(852), + [anon_sym_nil] = ACTIONS(852), + [anon_sym_DQUOTE] = ACTIONS(854), + [anon_sym_SQUOTE] = ACTIONS(854), + [sym_raw_string] = ACTIONS(854), + [anon_sym_RBRACE] = ACTIONS(854), + [anon_sym_LPAREN] = ACTIONS(854), + [anon_sym_RPAREN] = ACTIONS(854), + [anon_sym_COMMA] = ACTIONS(854), + [anon_sym_COLON] = ACTIONS(854), + [anon_sym_DOT] = ACTIONS(852), + [anon_sym_QMARK_DOT] = ACTIONS(854), + [anon_sym_LBRACK] = ACTIONS(854), + [anon_sym_RBRACK] = ACTIONS(854), + [anon_sym_QMARK_LBRACK] = ACTIONS(854), + [anon_sym_LBRACE] = ACTIONS(854), + [anon_sym_BANG] = ACTIONS(852), + [anon_sym_TILDE] = ACTIONS(854), + [anon_sym_STAR] = ACTIONS(854), + [anon_sym_SLASH] = ACTIONS(852), + [anon_sym_PERCENT] = ACTIONS(854), + [anon_sym_PLUS] = ACTIONS(854), + [anon_sym_DASH] = ACTIONS(854), + [anon_sym_EQ_EQ] = ACTIONS(854), + [anon_sym_BANG_EQ] = ACTIONS(854), + [anon_sym_LT] = ACTIONS(852), + [anon_sym_GT] = ACTIONS(852), + [anon_sym_LT_EQ] = ACTIONS(854), + [anon_sym_GT_EQ] = ACTIONS(854), + [anon_sym_AMP_AMP] = ACTIONS(854), + [anon_sym_PIPE_PIPE] = ACTIONS(854), + [anon_sym_PIPE] = ACTIONS(852), + [anon_sym_CARET] = ACTIONS(854), + [anon_sym_AMP] = ACTIONS(852), + [anon_sym_LT_LT] = ACTIONS(854), + [anon_sym_GT_GT] = ACTIONS(854), + [anon_sym_QMARK_QMARK] = ACTIONS(854), + [anon_sym_DOT_DOT] = ACTIONS(852), + [anon_sym_DOT_DOT_EQ] = ACTIONS(854), + [anon_sym_QMARK] = ACTIONS(852), + [anon_sym_match] = ACTIONS(852), + [anon_sym_EQ_GT] = ACTIONS(854), + [anon_sym_SEMI] = ACTIONS(854), + [anon_sym_if] = ACTIONS(852), + [anon_sym_spawn] = ACTIONS(852), + [anon_sym_chan] = ACTIONS(852), + [anon_sym_send] = ACTIONS(852), + [anon_sym_recv] = ACTIONS(852), + [anon_sym_select] = ACTIONS(852), + [anon_sym_POUND] = ACTIONS(854), + [anon_sym_use] = ACTIONS(852), + [anon_sym_export] = ACTIONS(852), + [anon_sym_macro_rules] = ACTIONS(852), + [anon_sym_let] = ACTIONS(852), + [anon_sym_while] = ACTIONS(852), + [anon_sym_for] = ACTIONS(852), + [anon_sym_fn] = ACTIONS(852), + [anon_sym_struct] = ACTIONS(852), + [anon_sym_type] = ACTIONS(852), + [anon_sym_trait] = ACTIONS(852), + [anon_sym_impl] = ACTIONS(852), + [anon_sym_return] = ACTIONS(852), + [anon_sym_break] = ACTIONS(852), + [anon_sym_continue] = ACTIONS(852), + [anon_sym_go] = ACTIONS(852), + [anon_sym_try] = ACTIONS(852), }, [STATE(100)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(839), - [sym_integer_literal] = ACTIONS(839), - [sym_float_literal] = ACTIONS(841), - [anon_sym_true] = ACTIONS(839), - [anon_sym_false] = ACTIONS(839), - [anon_sym_nil] = ACTIONS(839), - [anon_sym_DQUOTE] = ACTIONS(841), - [anon_sym_SQUOTE] = ACTIONS(841), - [sym_raw_string] = ACTIONS(841), - [anon_sym_RBRACE] = ACTIONS(841), - [anon_sym_LPAREN] = ACTIONS(841), - [anon_sym_RPAREN] = ACTIONS(841), - [anon_sym_COMMA] = ACTIONS(841), - [anon_sym_COLON] = ACTIONS(841), - [anon_sym_DOT] = ACTIONS(839), - [anon_sym_QMARK_DOT] = ACTIONS(841), - [anon_sym_LBRACK] = ACTIONS(841), - [anon_sym_RBRACK] = ACTIONS(841), - [anon_sym_QMARK_LBRACK] = ACTIONS(841), - [anon_sym_LBRACE] = ACTIONS(841), - [anon_sym_BANG] = ACTIONS(839), - [anon_sym_STAR] = ACTIONS(841), - [anon_sym_SLASH] = ACTIONS(839), - [anon_sym_PERCENT] = ACTIONS(841), - [anon_sym_PLUS] = ACTIONS(841), - [anon_sym_DASH] = ACTIONS(841), - [anon_sym_EQ_EQ] = ACTIONS(841), - [anon_sym_BANG_EQ] = ACTIONS(841), - [anon_sym_LT] = ACTIONS(839), - [anon_sym_GT] = ACTIONS(839), - [anon_sym_LT_EQ] = ACTIONS(841), - [anon_sym_GT_EQ] = ACTIONS(841), - [anon_sym_AMP_AMP] = ACTIONS(841), - [anon_sym_PIPE_PIPE] = ACTIONS(841), - [anon_sym_QMARK_QMARK] = ACTIONS(841), - [anon_sym_DOT_DOT] = ACTIONS(839), - [anon_sym_DOT_DOT_EQ] = ACTIONS(841), - [anon_sym_QMARK] = ACTIONS(839), - [anon_sym_PIPE] = ACTIONS(839), - [anon_sym_match] = ACTIONS(839), - [anon_sym_EQ_GT] = ACTIONS(841), - [anon_sym_SEMI] = ACTIONS(841), - [anon_sym_if] = ACTIONS(839), - [anon_sym_spawn] = ACTIONS(839), - [anon_sym_chan] = ACTIONS(839), - [anon_sym_send] = ACTIONS(839), - [anon_sym_recv] = ACTIONS(839), - [anon_sym_select] = ACTIONS(839), - [anon_sym_POUND] = ACTIONS(841), - [anon_sym_use] = ACTIONS(839), - [anon_sym_export] = ACTIONS(839), - [anon_sym_macro_rules] = ACTIONS(839), - [anon_sym_let] = ACTIONS(839), - [anon_sym_while] = ACTIONS(839), - [anon_sym_for] = ACTIONS(839), - [anon_sym_fn] = ACTIONS(839), - [anon_sym_struct] = ACTIONS(839), - [anon_sym_type] = ACTIONS(839), - [anon_sym_trait] = ACTIONS(839), - [anon_sym_impl] = ACTIONS(839), - [anon_sym_return] = ACTIONS(839), - [anon_sym_break] = ACTIONS(839), - [anon_sym_continue] = ACTIONS(839), - [anon_sym_go] = ACTIONS(839), - [anon_sym_try] = ACTIONS(839), + [aux_sym_identifier_token1] = ACTIONS(856), + [sym_integer_literal] = ACTIONS(856), + [sym_float_literal] = ACTIONS(858), + [anon_sym_true] = ACTIONS(856), + [anon_sym_false] = ACTIONS(856), + [anon_sym_nil] = ACTIONS(856), + [anon_sym_DQUOTE] = ACTIONS(858), + [anon_sym_SQUOTE] = ACTIONS(858), + [sym_raw_string] = ACTIONS(858), + [anon_sym_RBRACE] = ACTIONS(858), + [anon_sym_LPAREN] = ACTIONS(858), + [anon_sym_RPAREN] = ACTIONS(858), + [anon_sym_COMMA] = ACTIONS(858), + [anon_sym_COLON] = ACTIONS(858), + [anon_sym_DOT] = ACTIONS(856), + [anon_sym_QMARK_DOT] = ACTIONS(858), + [anon_sym_LBRACK] = ACTIONS(858), + [anon_sym_RBRACK] = ACTIONS(858), + [anon_sym_QMARK_LBRACK] = ACTIONS(858), + [anon_sym_LBRACE] = ACTIONS(858), + [anon_sym_BANG] = ACTIONS(856), + [anon_sym_TILDE] = ACTIONS(858), + [anon_sym_STAR] = ACTIONS(858), + [anon_sym_SLASH] = ACTIONS(856), + [anon_sym_PERCENT] = ACTIONS(858), + [anon_sym_PLUS] = ACTIONS(858), + [anon_sym_DASH] = ACTIONS(858), + [anon_sym_EQ_EQ] = ACTIONS(858), + [anon_sym_BANG_EQ] = ACTIONS(858), + [anon_sym_LT] = ACTIONS(856), + [anon_sym_GT] = ACTIONS(856), + [anon_sym_LT_EQ] = ACTIONS(858), + [anon_sym_GT_EQ] = ACTIONS(858), + [anon_sym_AMP_AMP] = ACTIONS(858), + [anon_sym_PIPE_PIPE] = ACTIONS(858), + [anon_sym_PIPE] = ACTIONS(856), + [anon_sym_CARET] = ACTIONS(858), + [anon_sym_AMP] = ACTIONS(856), + [anon_sym_LT_LT] = ACTIONS(858), + [anon_sym_GT_GT] = ACTIONS(858), + [anon_sym_QMARK_QMARK] = ACTIONS(858), + [anon_sym_DOT_DOT] = ACTIONS(856), + [anon_sym_DOT_DOT_EQ] = ACTIONS(858), + [anon_sym_QMARK] = ACTIONS(856), + [anon_sym_match] = ACTIONS(856), + [anon_sym_EQ_GT] = ACTIONS(858), + [anon_sym_SEMI] = ACTIONS(858), + [anon_sym_if] = ACTIONS(856), + [anon_sym_spawn] = ACTIONS(856), + [anon_sym_chan] = ACTIONS(856), + [anon_sym_send] = ACTIONS(856), + [anon_sym_recv] = ACTIONS(856), + [anon_sym_select] = ACTIONS(856), + [anon_sym_POUND] = ACTIONS(858), + [anon_sym_use] = ACTIONS(856), + [anon_sym_export] = ACTIONS(856), + [anon_sym_macro_rules] = ACTIONS(856), + [anon_sym_let] = ACTIONS(856), + [anon_sym_while] = ACTIONS(856), + [anon_sym_for] = ACTIONS(856), + [anon_sym_fn] = ACTIONS(856), + [anon_sym_struct] = ACTIONS(856), + [anon_sym_type] = ACTIONS(856), + [anon_sym_trait] = ACTIONS(856), + [anon_sym_impl] = ACTIONS(856), + [anon_sym_return] = ACTIONS(856), + [anon_sym_break] = ACTIONS(856), + [anon_sym_continue] = ACTIONS(856), + [anon_sym_go] = ACTIONS(856), + [anon_sym_try] = ACTIONS(856), }, [STATE(101)] = { [sym_line_comment] = ACTIONS(3), [sym_block_comment] = ACTIONS(3), - [aux_sym_identifier_token1] = ACTIONS(843), - [sym_integer_literal] = ACTIONS(843), - [sym_float_literal] = ACTIONS(845), - [anon_sym_true] = ACTIONS(843), - [anon_sym_false] = ACTIONS(843), - [anon_sym_nil] = ACTIONS(843), - [anon_sym_DQUOTE] = ACTIONS(845), - [anon_sym_SQUOTE] = ACTIONS(845), - [sym_raw_string] = ACTIONS(845), - [anon_sym_RBRACE] = ACTIONS(845), - [anon_sym_LPAREN] = ACTIONS(845), - [anon_sym_RPAREN] = ACTIONS(845), - [anon_sym_COMMA] = ACTIONS(845), - [anon_sym_COLON] = ACTIONS(845), - [anon_sym_DOT] = ACTIONS(843), - [anon_sym_QMARK_DOT] = ACTIONS(845), - [anon_sym_LBRACK] = ACTIONS(845), - [anon_sym_RBRACK] = ACTIONS(845), - [anon_sym_QMARK_LBRACK] = ACTIONS(845), - [anon_sym_LBRACE] = ACTIONS(845), - [anon_sym_BANG] = ACTIONS(843), - [anon_sym_STAR] = ACTIONS(845), - [anon_sym_SLASH] = ACTIONS(843), - [anon_sym_PERCENT] = ACTIONS(845), - [anon_sym_PLUS] = ACTIONS(845), - [anon_sym_DASH] = ACTIONS(845), - [anon_sym_EQ_EQ] = ACTIONS(845), - [anon_sym_BANG_EQ] = ACTIONS(845), - [anon_sym_LT] = ACTIONS(843), - [anon_sym_GT] = ACTIONS(843), - [anon_sym_LT_EQ] = ACTIONS(845), - [anon_sym_GT_EQ] = ACTIONS(845), - [anon_sym_AMP_AMP] = ACTIONS(845), - [anon_sym_PIPE_PIPE] = ACTIONS(845), - [anon_sym_QMARK_QMARK] = ACTIONS(845), - [anon_sym_DOT_DOT] = ACTIONS(843), - [anon_sym_DOT_DOT_EQ] = ACTIONS(845), - [anon_sym_QMARK] = ACTIONS(843), - [anon_sym_PIPE] = ACTIONS(843), - [anon_sym_match] = ACTIONS(843), - [anon_sym_EQ_GT] = ACTIONS(845), - [anon_sym_SEMI] = ACTIONS(845), - [anon_sym_if] = ACTIONS(843), - [anon_sym_spawn] = ACTIONS(843), - [anon_sym_chan] = ACTIONS(843), - [anon_sym_send] = ACTIONS(843), - [anon_sym_recv] = ACTIONS(843), - [anon_sym_select] = ACTIONS(843), - [anon_sym_POUND] = ACTIONS(845), - [anon_sym_use] = ACTIONS(843), - [anon_sym_export] = ACTIONS(843), - [anon_sym_macro_rules] = ACTIONS(843), - [anon_sym_let] = ACTIONS(843), - [anon_sym_while] = ACTIONS(843), - [anon_sym_for] = ACTIONS(843), - [anon_sym_fn] = ACTIONS(843), - [anon_sym_struct] = ACTIONS(843), - [anon_sym_type] = ACTIONS(843), - [anon_sym_trait] = ACTIONS(843), - [anon_sym_impl] = ACTIONS(843), - [anon_sym_return] = ACTIONS(843), - [anon_sym_break] = ACTIONS(843), - [anon_sym_continue] = ACTIONS(843), - [anon_sym_go] = ACTIONS(843), - [anon_sym_try] = ACTIONS(843), + [aux_sym_identifier_token1] = ACTIONS(697), + [sym_integer_literal] = ACTIONS(697), + [sym_float_literal] = ACTIONS(699), + [anon_sym_true] = ACTIONS(697), + [anon_sym_false] = ACTIONS(697), + [anon_sym_nil] = ACTIONS(697), + [anon_sym_DQUOTE] = ACTIONS(699), + [anon_sym_SQUOTE] = ACTIONS(699), + [sym_raw_string] = ACTIONS(699), + [anon_sym_RBRACE] = ACTIONS(699), + [anon_sym_LPAREN] = ACTIONS(699), + [anon_sym_RPAREN] = ACTIONS(699), + [anon_sym_COMMA] = ACTIONS(699), + [anon_sym_COLON] = ACTIONS(699), + [anon_sym_DOT] = ACTIONS(697), + [anon_sym_QMARK_DOT] = ACTIONS(699), + [anon_sym_LBRACK] = ACTIONS(699), + [anon_sym_RBRACK] = ACTIONS(699), + [anon_sym_QMARK_LBRACK] = ACTIONS(699), + [anon_sym_LBRACE] = ACTIONS(699), + [anon_sym_BANG] = ACTIONS(697), + [anon_sym_TILDE] = ACTIONS(699), + [anon_sym_STAR] = ACTIONS(699), + [anon_sym_SLASH] = ACTIONS(697), + [anon_sym_PERCENT] = ACTIONS(699), + [anon_sym_PLUS] = ACTIONS(699), + [anon_sym_DASH] = ACTIONS(699), + [anon_sym_EQ_EQ] = ACTIONS(699), + [anon_sym_BANG_EQ] = ACTIONS(699), + [anon_sym_LT] = ACTIONS(697), + [anon_sym_GT] = ACTIONS(697), + [anon_sym_LT_EQ] = ACTIONS(699), + [anon_sym_GT_EQ] = ACTIONS(699), + [anon_sym_AMP_AMP] = ACTIONS(699), + [anon_sym_PIPE_PIPE] = ACTIONS(699), + [anon_sym_PIPE] = ACTIONS(697), + [anon_sym_CARET] = ACTIONS(699), + [anon_sym_AMP] = ACTIONS(697), + [anon_sym_LT_LT] = ACTIONS(699), + [anon_sym_GT_GT] = ACTIONS(699), + [anon_sym_QMARK_QMARK] = ACTIONS(699), + [anon_sym_DOT_DOT] = ACTIONS(697), + [anon_sym_DOT_DOT_EQ] = ACTIONS(699), + [anon_sym_QMARK] = ACTIONS(697), + [anon_sym_match] = ACTIONS(697), + [anon_sym_EQ_GT] = ACTIONS(699), + [anon_sym_SEMI] = ACTIONS(699), + [anon_sym_if] = ACTIONS(697), + [anon_sym_spawn] = ACTIONS(697), + [anon_sym_chan] = ACTIONS(697), + [anon_sym_send] = ACTIONS(697), + [anon_sym_recv] = ACTIONS(697), + [anon_sym_select] = ACTIONS(697), + [anon_sym_POUND] = ACTIONS(699), + [anon_sym_use] = ACTIONS(697), + [anon_sym_export] = ACTIONS(697), + [anon_sym_macro_rules] = ACTIONS(697), + [anon_sym_let] = ACTIONS(697), + [anon_sym_while] = ACTIONS(697), + [anon_sym_for] = ACTIONS(697), + [anon_sym_fn] = ACTIONS(697), + [anon_sym_struct] = ACTIONS(697), + [anon_sym_type] = ACTIONS(697), + [anon_sym_trait] = ACTIONS(697), + [anon_sym_impl] = ACTIONS(697), + [anon_sym_return] = ACTIONS(697), + [anon_sym_break] = ACTIONS(697), + [anon_sym_continue] = ACTIONS(697), + [anon_sym_go] = ACTIONS(697), + [anon_sym_try] = ACTIONS(697), + }, + [STATE(102)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(860), + [sym_integer_literal] = ACTIONS(860), + [sym_float_literal] = ACTIONS(862), + [anon_sym_true] = ACTIONS(860), + [anon_sym_false] = ACTIONS(860), + [anon_sym_nil] = ACTIONS(860), + [anon_sym_DQUOTE] = ACTIONS(862), + [anon_sym_SQUOTE] = ACTIONS(862), + [sym_raw_string] = ACTIONS(862), + [anon_sym_RBRACE] = ACTIONS(862), + [anon_sym_LPAREN] = ACTIONS(864), + [anon_sym_DOT] = ACTIONS(732), + [anon_sym_QMARK_DOT] = ACTIONS(734), + [anon_sym_LBRACK] = ACTIONS(864), + [anon_sym_QMARK_LBRACK] = ACTIONS(734), + [anon_sym_LBRACE] = ACTIONS(862), + [anon_sym_BANG] = ACTIONS(867), + [anon_sym_TILDE] = ACTIONS(862), + [anon_sym_STAR] = ACTIONS(734), + [anon_sym_SLASH] = ACTIONS(732), + [anon_sym_PERCENT] = ACTIONS(734), + [anon_sym_PLUS] = ACTIONS(734), + [anon_sym_DASH] = ACTIONS(734), + [anon_sym_EQ_EQ] = ACTIONS(734), + [anon_sym_BANG_EQ] = ACTIONS(734), + [anon_sym_LT] = ACTIONS(732), + [anon_sym_GT] = ACTIONS(732), + [anon_sym_LT_EQ] = ACTIONS(734), + [anon_sym_GT_EQ] = ACTIONS(734), + [anon_sym_AMP_AMP] = ACTIONS(734), + [anon_sym_PIPE_PIPE] = ACTIONS(734), + [anon_sym_PIPE] = ACTIONS(867), + [anon_sym_CARET] = ACTIONS(734), + [anon_sym_AMP] = ACTIONS(732), + [anon_sym_LT_LT] = ACTIONS(734), + [anon_sym_GT_GT] = ACTIONS(734), + [anon_sym_QMARK_QMARK] = ACTIONS(734), + [anon_sym_DOT_DOT] = ACTIONS(732), + [anon_sym_DOT_DOT_EQ] = ACTIONS(734), + [anon_sym_QMARK] = ACTIONS(732), + [anon_sym_match] = ACTIONS(860), + [anon_sym_SEMI] = ACTIONS(734), + [anon_sym_if] = ACTIONS(860), + [anon_sym_spawn] = ACTIONS(860), + [anon_sym_chan] = ACTIONS(860), + [anon_sym_send] = ACTIONS(860), + [anon_sym_recv] = ACTIONS(860), + [anon_sym_select] = ACTIONS(860), + [anon_sym_POUND] = ACTIONS(862), + [anon_sym_use] = ACTIONS(860), + [anon_sym_export] = ACTIONS(860), + [anon_sym_macro_rules] = ACTIONS(860), + [anon_sym_let] = ACTIONS(860), + [anon_sym_else] = ACTIONS(860), + [anon_sym_while] = ACTIONS(860), + [anon_sym_for] = ACTIONS(860), + [anon_sym_fn] = ACTIONS(860), + [anon_sym_struct] = ACTIONS(860), + [anon_sym_type] = ACTIONS(860), + [anon_sym_trait] = ACTIONS(860), + [anon_sym_impl] = ACTIONS(860), + [anon_sym_return] = ACTIONS(860), + [anon_sym_break] = ACTIONS(860), + [anon_sym_continue] = ACTIONS(860), + [anon_sym_go] = ACTIONS(860), + [anon_sym_try] = ACTIONS(860), + }, + [STATE(103)] = { + [ts_builtin_sym_end] = ACTIONS(862), + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(860), + [sym_integer_literal] = ACTIONS(860), + [sym_float_literal] = ACTIONS(862), + [anon_sym_true] = ACTIONS(860), + [anon_sym_false] = ACTIONS(860), + [anon_sym_nil] = ACTIONS(860), + [anon_sym_DQUOTE] = ACTIONS(862), + [anon_sym_SQUOTE] = ACTIONS(862), + [sym_raw_string] = ACTIONS(862), + [anon_sym_LPAREN] = ACTIONS(864), + [anon_sym_DOT] = ACTIONS(732), + [anon_sym_QMARK_DOT] = ACTIONS(734), + [anon_sym_LBRACK] = ACTIONS(864), + [anon_sym_QMARK_LBRACK] = ACTIONS(734), + [anon_sym_LBRACE] = ACTIONS(862), + [anon_sym_BANG] = ACTIONS(867), + [anon_sym_TILDE] = ACTIONS(862), + [anon_sym_STAR] = ACTIONS(734), + [anon_sym_SLASH] = ACTIONS(732), + [anon_sym_PERCENT] = ACTIONS(734), + [anon_sym_PLUS] = ACTIONS(734), + [anon_sym_DASH] = ACTIONS(734), + [anon_sym_EQ_EQ] = ACTIONS(734), + [anon_sym_BANG_EQ] = ACTIONS(734), + [anon_sym_LT] = ACTIONS(732), + [anon_sym_GT] = ACTIONS(732), + [anon_sym_LT_EQ] = ACTIONS(734), + [anon_sym_GT_EQ] = ACTIONS(734), + [anon_sym_AMP_AMP] = ACTIONS(734), + [anon_sym_PIPE_PIPE] = ACTIONS(734), + [anon_sym_PIPE] = ACTIONS(867), + [anon_sym_CARET] = ACTIONS(734), + [anon_sym_AMP] = ACTIONS(732), + [anon_sym_LT_LT] = ACTIONS(734), + [anon_sym_GT_GT] = ACTIONS(734), + [anon_sym_QMARK_QMARK] = ACTIONS(734), + [anon_sym_DOT_DOT] = ACTIONS(732), + [anon_sym_DOT_DOT_EQ] = ACTIONS(734), + [anon_sym_QMARK] = ACTIONS(732), + [anon_sym_match] = ACTIONS(860), + [anon_sym_SEMI] = ACTIONS(734), + [anon_sym_if] = ACTIONS(860), + [anon_sym_spawn] = ACTIONS(860), + [anon_sym_chan] = ACTIONS(860), + [anon_sym_send] = ACTIONS(860), + [anon_sym_recv] = ACTIONS(860), + [anon_sym_select] = ACTIONS(860), + [anon_sym_POUND] = ACTIONS(862), + [anon_sym_use] = ACTIONS(860), + [anon_sym_export] = ACTIONS(860), + [anon_sym_macro_rules] = ACTIONS(860), + [anon_sym_let] = ACTIONS(860), + [anon_sym_else] = ACTIONS(860), + [anon_sym_while] = ACTIONS(860), + [anon_sym_for] = ACTIONS(860), + [anon_sym_fn] = ACTIONS(860), + [anon_sym_struct] = ACTIONS(860), + [anon_sym_type] = ACTIONS(860), + [anon_sym_trait] = ACTIONS(860), + [anon_sym_impl] = ACTIONS(860), + [anon_sym_return] = ACTIONS(860), + [anon_sym_break] = ACTIONS(860), + [anon_sym_continue] = ACTIONS(860), + [anon_sym_go] = ACTIONS(860), + [anon_sym_try] = ACTIONS(860), + }, + [STATE(104)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(860), + [sym_integer_literal] = ACTIONS(860), + [sym_float_literal] = ACTIONS(862), + [anon_sym_true] = ACTIONS(860), + [anon_sym_false] = ACTIONS(860), + [anon_sym_nil] = ACTIONS(860), + [anon_sym_DQUOTE] = ACTIONS(862), + [anon_sym_SQUOTE] = ACTIONS(862), + [sym_raw_string] = ACTIONS(862), + [anon_sym_RBRACE] = ACTIONS(862), + [anon_sym_LPAREN] = ACTIONS(864), + [anon_sym_COLON] = ACTIONS(734), + [anon_sym_DOT] = ACTIONS(732), + [anon_sym_QMARK_DOT] = ACTIONS(734), + [anon_sym_LBRACK] = ACTIONS(864), + [anon_sym_QMARK_LBRACK] = ACTIONS(734), + [anon_sym_LBRACE] = ACTIONS(862), + [anon_sym_BANG] = ACTIONS(867), + [anon_sym_TILDE] = ACTIONS(862), + [anon_sym_STAR] = ACTIONS(734), + [anon_sym_SLASH] = ACTIONS(732), + [anon_sym_PERCENT] = ACTIONS(734), + [anon_sym_PLUS] = ACTIONS(734), + [anon_sym_DASH] = ACTIONS(734), + [anon_sym_EQ_EQ] = ACTIONS(734), + [anon_sym_BANG_EQ] = ACTIONS(734), + [anon_sym_LT] = ACTIONS(732), + [anon_sym_GT] = ACTIONS(732), + [anon_sym_LT_EQ] = ACTIONS(734), + [anon_sym_GT_EQ] = ACTIONS(734), + [anon_sym_AMP_AMP] = ACTIONS(734), + [anon_sym_PIPE_PIPE] = ACTIONS(734), + [anon_sym_PIPE] = ACTIONS(867), + [anon_sym_CARET] = ACTIONS(734), + [anon_sym_AMP] = ACTIONS(732), + [anon_sym_LT_LT] = ACTIONS(734), + [anon_sym_GT_GT] = ACTIONS(734), + [anon_sym_QMARK_QMARK] = ACTIONS(734), + [anon_sym_DOT_DOT] = ACTIONS(732), + [anon_sym_DOT_DOT_EQ] = ACTIONS(734), + [anon_sym_QMARK] = ACTIONS(732), + [anon_sym_match] = ACTIONS(860), + [anon_sym_SEMI] = ACTIONS(734), + [anon_sym_if] = ACTIONS(860), + [anon_sym_spawn] = ACTIONS(860), + [anon_sym_chan] = ACTIONS(860), + [anon_sym_send] = ACTIONS(860), + [anon_sym_recv] = ACTIONS(860), + [anon_sym_select] = ACTIONS(860), + [anon_sym_POUND] = ACTIONS(862), + [anon_sym_use] = ACTIONS(860), + [anon_sym_export] = ACTIONS(860), + [anon_sym_macro_rules] = ACTIONS(860), + [anon_sym_let] = ACTIONS(860), + [anon_sym_while] = ACTIONS(860), + [anon_sym_for] = ACTIONS(860), + [anon_sym_fn] = ACTIONS(860), + [anon_sym_struct] = ACTIONS(860), + [anon_sym_type] = ACTIONS(860), + [anon_sym_trait] = ACTIONS(860), + [anon_sym_impl] = ACTIONS(860), + [anon_sym_return] = ACTIONS(860), + [anon_sym_break] = ACTIONS(860), + [anon_sym_continue] = ACTIONS(860), + [anon_sym_go] = ACTIONS(860), + [anon_sym_try] = ACTIONS(860), + }, + [STATE(105)] = { + [ts_builtin_sym_end] = ACTIONS(862), + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(860), + [sym_integer_literal] = ACTIONS(860), + [sym_float_literal] = ACTIONS(862), + [anon_sym_true] = ACTIONS(860), + [anon_sym_false] = ACTIONS(860), + [anon_sym_nil] = ACTIONS(860), + [anon_sym_DQUOTE] = ACTIONS(862), + [anon_sym_SQUOTE] = ACTIONS(862), + [sym_raw_string] = ACTIONS(862), + [anon_sym_LPAREN] = ACTIONS(864), + [anon_sym_DOT] = ACTIONS(732), + [anon_sym_QMARK_DOT] = ACTIONS(734), + [anon_sym_LBRACK] = ACTIONS(864), + [anon_sym_QMARK_LBRACK] = ACTIONS(734), + [anon_sym_LBRACE] = ACTIONS(862), + [anon_sym_BANG] = ACTIONS(867), + [anon_sym_TILDE] = ACTIONS(862), + [anon_sym_STAR] = ACTIONS(734), + [anon_sym_SLASH] = ACTIONS(732), + [anon_sym_PERCENT] = ACTIONS(734), + [anon_sym_PLUS] = ACTIONS(734), + [anon_sym_DASH] = ACTIONS(734), + [anon_sym_EQ_EQ] = ACTIONS(734), + [anon_sym_BANG_EQ] = ACTIONS(734), + [anon_sym_LT] = ACTIONS(732), + [anon_sym_GT] = ACTIONS(732), + [anon_sym_LT_EQ] = ACTIONS(734), + [anon_sym_GT_EQ] = ACTIONS(734), + [anon_sym_AMP_AMP] = ACTIONS(734), + [anon_sym_PIPE_PIPE] = ACTIONS(734), + [anon_sym_PIPE] = ACTIONS(867), + [anon_sym_CARET] = ACTIONS(734), + [anon_sym_AMP] = ACTIONS(732), + [anon_sym_LT_LT] = ACTIONS(734), + [anon_sym_GT_GT] = ACTIONS(734), + [anon_sym_QMARK_QMARK] = ACTIONS(734), + [anon_sym_DOT_DOT] = ACTIONS(732), + [anon_sym_DOT_DOT_EQ] = ACTIONS(734), + [anon_sym_QMARK] = ACTIONS(732), + [anon_sym_match] = ACTIONS(860), + [anon_sym_SEMI] = ACTIONS(734), + [anon_sym_if] = ACTIONS(860), + [anon_sym_spawn] = ACTIONS(860), + [anon_sym_chan] = ACTIONS(860), + [anon_sym_send] = ACTIONS(860), + [anon_sym_recv] = ACTIONS(860), + [anon_sym_select] = ACTIONS(860), + [anon_sym_POUND] = ACTIONS(862), + [anon_sym_use] = ACTIONS(860), + [anon_sym_export] = ACTIONS(860), + [anon_sym_macro_rules] = ACTIONS(860), + [anon_sym_let] = ACTIONS(860), + [anon_sym_while] = ACTIONS(860), + [anon_sym_for] = ACTIONS(860), + [anon_sym_fn] = ACTIONS(860), + [anon_sym_struct] = ACTIONS(860), + [anon_sym_type] = ACTIONS(860), + [anon_sym_trait] = ACTIONS(860), + [anon_sym_impl] = ACTIONS(860), + [anon_sym_return] = ACTIONS(860), + [anon_sym_break] = ACTIONS(860), + [anon_sym_continue] = ACTIONS(860), + [anon_sym_go] = ACTIONS(860), + [anon_sym_try] = ACTIONS(860), + }, + [STATE(106)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(870), + [sym_integer_literal] = ACTIONS(870), + [sym_float_literal] = ACTIONS(872), + [anon_sym_true] = ACTIONS(870), + [anon_sym_false] = ACTIONS(870), + [anon_sym_nil] = ACTIONS(870), + [anon_sym_DQUOTE] = ACTIONS(872), + [anon_sym_SQUOTE] = ACTIONS(872), + [sym_raw_string] = ACTIONS(872), + [anon_sym_LPAREN] = ACTIONS(872), + [anon_sym_DOT] = ACTIONS(870), + [anon_sym_QMARK_DOT] = ACTIONS(872), + [anon_sym_LBRACK] = ACTIONS(872), + [anon_sym_QMARK_LBRACK] = ACTIONS(872), + [anon_sym_LBRACE] = ACTIONS(872), + [anon_sym_BANG] = ACTIONS(870), + [anon_sym_TILDE] = ACTIONS(872), + [anon_sym_STAR] = ACTIONS(874), + [anon_sym_SLASH] = ACTIONS(876), + [anon_sym_PERCENT] = ACTIONS(874), + [anon_sym_PLUS] = ACTIONS(878), + [anon_sym_DASH] = ACTIONS(878), + [anon_sym_EQ_EQ] = ACTIONS(880), + [anon_sym_BANG_EQ] = ACTIONS(880), + [anon_sym_LT] = ACTIONS(882), + [anon_sym_GT] = ACTIONS(882), + [anon_sym_LT_EQ] = ACTIONS(880), + [anon_sym_GT_EQ] = ACTIONS(880), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(894), + [anon_sym_GT_GT] = ACTIONS(894), + [anon_sym_QMARK_QMARK] = ACTIONS(872), + [anon_sym_DOT_DOT] = ACTIONS(896), + [anon_sym_DOT_DOT_EQ] = ACTIONS(898), + [anon_sym_QMARK] = ACTIONS(870), + [anon_sym_match] = ACTIONS(870), + [anon_sym_if] = ACTIONS(870), + [anon_sym_spawn] = ACTIONS(870), + [anon_sym_chan] = ACTIONS(870), + [anon_sym_send] = ACTIONS(870), + [anon_sym_recv] = ACTIONS(870), + [anon_sym_select] = ACTIONS(870), + [anon_sym_POUND] = ACTIONS(872), + [anon_sym_use] = ACTIONS(870), + [anon_sym_export] = ACTIONS(870), + [anon_sym_macro_rules] = ACTIONS(870), + [anon_sym_let] = ACTIONS(870), + [anon_sym_while] = ACTIONS(870), + [anon_sym_for] = ACTIONS(870), + [anon_sym_fn] = ACTIONS(870), + [anon_sym_struct] = ACTIONS(870), + [anon_sym_type] = ACTIONS(870), + [anon_sym_trait] = ACTIONS(870), + [anon_sym_impl] = ACTIONS(870), + [anon_sym_return] = ACTIONS(870), + [anon_sym_break] = ACTIONS(870), + [anon_sym_continue] = ACTIONS(870), + [anon_sym_go] = ACTIONS(870), + [anon_sym_try] = ACTIONS(870), + }, + [STATE(107)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(900), + [sym_integer_literal] = ACTIONS(900), + [sym_float_literal] = ACTIONS(902), + [anon_sym_true] = ACTIONS(900), + [anon_sym_false] = ACTIONS(900), + [anon_sym_nil] = ACTIONS(900), + [anon_sym_DQUOTE] = ACTIONS(902), + [anon_sym_SQUOTE] = ACTIONS(902), + [sym_raw_string] = ACTIONS(902), + [anon_sym_LPAREN] = ACTIONS(902), + [anon_sym_DOT] = ACTIONS(900), + [anon_sym_QMARK_DOT] = ACTIONS(902), + [anon_sym_LBRACK] = ACTIONS(902), + [anon_sym_QMARK_LBRACK] = ACTIONS(902), + [anon_sym_LBRACE] = ACTIONS(902), + [anon_sym_BANG] = ACTIONS(900), + [anon_sym_TILDE] = ACTIONS(902), + [anon_sym_STAR] = ACTIONS(902), + [anon_sym_SLASH] = ACTIONS(900), + [anon_sym_PERCENT] = ACTIONS(902), + [anon_sym_PLUS] = ACTIONS(902), + [anon_sym_DASH] = ACTIONS(902), + [anon_sym_EQ_EQ] = ACTIONS(880), + [anon_sym_BANG_EQ] = ACTIONS(880), + [anon_sym_LT] = ACTIONS(882), + [anon_sym_GT] = ACTIONS(882), + [anon_sym_LT_EQ] = ACTIONS(880), + [anon_sym_GT_EQ] = ACTIONS(880), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(894), + [anon_sym_GT_GT] = ACTIONS(894), + [anon_sym_QMARK_QMARK] = ACTIONS(902), + [anon_sym_DOT_DOT] = ACTIONS(896), + [anon_sym_DOT_DOT_EQ] = ACTIONS(898), + [anon_sym_QMARK] = ACTIONS(900), + [anon_sym_match] = ACTIONS(900), + [anon_sym_if] = ACTIONS(900), + [anon_sym_spawn] = ACTIONS(900), + [anon_sym_chan] = ACTIONS(900), + [anon_sym_send] = ACTIONS(900), + [anon_sym_recv] = ACTIONS(900), + [anon_sym_select] = ACTIONS(900), + [anon_sym_POUND] = ACTIONS(902), + [anon_sym_use] = ACTIONS(900), + [anon_sym_export] = ACTIONS(900), + [anon_sym_macro_rules] = ACTIONS(900), + [anon_sym_let] = ACTIONS(900), + [anon_sym_while] = ACTIONS(900), + [anon_sym_for] = ACTIONS(900), + [anon_sym_fn] = ACTIONS(900), + [anon_sym_struct] = ACTIONS(900), + [anon_sym_type] = ACTIONS(900), + [anon_sym_trait] = ACTIONS(900), + [anon_sym_impl] = ACTIONS(900), + [anon_sym_return] = ACTIONS(900), + [anon_sym_break] = ACTIONS(900), + [anon_sym_continue] = ACTIONS(900), + [anon_sym_go] = ACTIONS(900), + [anon_sym_try] = ACTIONS(900), + }, + [STATE(108)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(904), + [sym_integer_literal] = ACTIONS(904), + [sym_float_literal] = ACTIONS(906), + [anon_sym_true] = ACTIONS(904), + [anon_sym_false] = ACTIONS(904), + [anon_sym_nil] = ACTIONS(904), + [anon_sym_DQUOTE] = ACTIONS(906), + [anon_sym_SQUOTE] = ACTIONS(906), + [sym_raw_string] = ACTIONS(906), + [anon_sym_LPAREN] = ACTIONS(906), + [anon_sym_DOT] = ACTIONS(904), + [anon_sym_QMARK_DOT] = ACTIONS(906), + [anon_sym_LBRACK] = ACTIONS(906), + [anon_sym_QMARK_LBRACK] = ACTIONS(906), + [anon_sym_LBRACE] = ACTIONS(906), + [anon_sym_BANG] = ACTIONS(904), + [anon_sym_TILDE] = ACTIONS(906), + [anon_sym_STAR] = ACTIONS(874), + [anon_sym_SLASH] = ACTIONS(876), + [anon_sym_PERCENT] = ACTIONS(874), + [anon_sym_PLUS] = ACTIONS(878), + [anon_sym_DASH] = ACTIONS(878), + [anon_sym_EQ_EQ] = ACTIONS(880), + [anon_sym_BANG_EQ] = ACTIONS(880), + [anon_sym_LT] = ACTIONS(882), + [anon_sym_GT] = ACTIONS(882), + [anon_sym_LT_EQ] = ACTIONS(880), + [anon_sym_GT_EQ] = ACTIONS(880), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(894), + [anon_sym_GT_GT] = ACTIONS(894), + [anon_sym_QMARK_QMARK] = ACTIONS(906), + [anon_sym_DOT_DOT] = ACTIONS(896), + [anon_sym_DOT_DOT_EQ] = ACTIONS(898), + [anon_sym_QMARK] = ACTIONS(904), + [anon_sym_match] = ACTIONS(904), + [anon_sym_if] = ACTIONS(904), + [anon_sym_spawn] = ACTIONS(904), + [anon_sym_chan] = ACTIONS(904), + [anon_sym_send] = ACTIONS(904), + [anon_sym_recv] = ACTIONS(904), + [anon_sym_select] = ACTIONS(904), + [anon_sym_POUND] = ACTIONS(906), + [anon_sym_use] = ACTIONS(904), + [anon_sym_export] = ACTIONS(904), + [anon_sym_macro_rules] = ACTIONS(904), + [anon_sym_let] = ACTIONS(904), + [anon_sym_while] = ACTIONS(904), + [anon_sym_for] = ACTIONS(904), + [anon_sym_fn] = ACTIONS(904), + [anon_sym_struct] = ACTIONS(904), + [anon_sym_type] = ACTIONS(904), + [anon_sym_trait] = ACTIONS(904), + [anon_sym_impl] = ACTIONS(904), + [anon_sym_return] = ACTIONS(904), + [anon_sym_break] = ACTIONS(904), + [anon_sym_continue] = ACTIONS(904), + [anon_sym_go] = ACTIONS(904), + [anon_sym_try] = ACTIONS(904), + }, + [STATE(109)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(716), + [sym_integer_literal] = ACTIONS(716), + [sym_float_literal] = ACTIONS(718), + [anon_sym_true] = ACTIONS(716), + [anon_sym_false] = ACTIONS(716), + [anon_sym_nil] = ACTIONS(716), + [anon_sym_DQUOTE] = ACTIONS(718), + [anon_sym_SQUOTE] = ACTIONS(718), + [sym_raw_string] = ACTIONS(718), + [anon_sym_LPAREN] = ACTIONS(718), + [anon_sym_DOT] = ACTIONS(716), + [anon_sym_QMARK_DOT] = ACTIONS(718), + [anon_sym_LBRACK] = ACTIONS(718), + [anon_sym_QMARK_LBRACK] = ACTIONS(718), + [anon_sym_LBRACE] = ACTIONS(908), + [anon_sym_BANG] = ACTIONS(716), + [anon_sym_TILDE] = ACTIONS(718), + [anon_sym_STAR] = ACTIONS(718), + [anon_sym_SLASH] = ACTIONS(716), + [anon_sym_PERCENT] = ACTIONS(718), + [anon_sym_PLUS] = ACTIONS(718), + [anon_sym_DASH] = ACTIONS(718), + [anon_sym_EQ_EQ] = ACTIONS(718), + [anon_sym_BANG_EQ] = ACTIONS(718), + [anon_sym_LT] = ACTIONS(716), + [anon_sym_GT] = ACTIONS(716), + [anon_sym_LT_EQ] = ACTIONS(718), + [anon_sym_GT_EQ] = ACTIONS(718), + [anon_sym_AMP_AMP] = ACTIONS(718), + [anon_sym_PIPE_PIPE] = ACTIONS(718), + [anon_sym_PIPE] = ACTIONS(716), + [anon_sym_CARET] = ACTIONS(718), + [anon_sym_AMP] = ACTIONS(716), + [anon_sym_LT_LT] = ACTIONS(718), + [anon_sym_GT_GT] = ACTIONS(718), + [anon_sym_QMARK_QMARK] = ACTIONS(718), + [anon_sym_DOT_DOT] = ACTIONS(716), + [anon_sym_DOT_DOT_EQ] = ACTIONS(718), + [anon_sym_QMARK] = ACTIONS(716), + [anon_sym_match] = ACTIONS(716), + [anon_sym_if] = ACTIONS(716), + [anon_sym_spawn] = ACTIONS(716), + [anon_sym_chan] = ACTIONS(716), + [anon_sym_send] = ACTIONS(716), + [anon_sym_recv] = ACTIONS(716), + [anon_sym_select] = ACTIONS(716), + [anon_sym_POUND] = ACTIONS(718), + [anon_sym_use] = ACTIONS(716), + [anon_sym_export] = ACTIONS(716), + [anon_sym_macro_rules] = ACTIONS(716), + [anon_sym_let] = ACTIONS(716), + [anon_sym_while] = ACTIONS(716), + [anon_sym_for] = ACTIONS(716), + [anon_sym_fn] = ACTIONS(716), + [anon_sym_struct] = ACTIONS(716), + [anon_sym_type] = ACTIONS(716), + [anon_sym_trait] = ACTIONS(716), + [anon_sym_impl] = ACTIONS(716), + [anon_sym_return] = ACTIONS(716), + [anon_sym_break] = ACTIONS(716), + [anon_sym_continue] = ACTIONS(716), + [anon_sym_go] = ACTIONS(716), + [anon_sym_try] = ACTIONS(716), + }, + [STATE(110)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(911), + [sym_integer_literal] = ACTIONS(911), + [sym_float_literal] = ACTIONS(913), + [anon_sym_true] = ACTIONS(911), + [anon_sym_false] = ACTIONS(911), + [anon_sym_nil] = ACTIONS(911), + [anon_sym_DQUOTE] = ACTIONS(913), + [anon_sym_SQUOTE] = ACTIONS(913), + [sym_raw_string] = ACTIONS(913), + [anon_sym_LPAREN] = ACTIONS(915), + [anon_sym_DOT] = ACTIONS(918), + [anon_sym_QMARK_DOT] = ACTIONS(920), + [anon_sym_LBRACK] = ACTIONS(922), + [anon_sym_QMARK_LBRACK] = ACTIONS(924), + [anon_sym_LBRACE] = ACTIONS(913), + [anon_sym_BANG] = ACTIONS(926), + [anon_sym_TILDE] = ACTIONS(913), + [anon_sym_STAR] = ACTIONS(928), + [anon_sym_SLASH] = ACTIONS(931), + [anon_sym_PERCENT] = ACTIONS(928), + [anon_sym_PLUS] = ACTIONS(934), + [anon_sym_DASH] = ACTIONS(934), + [anon_sym_EQ_EQ] = ACTIONS(937), + [anon_sym_BANG_EQ] = ACTIONS(937), + [anon_sym_LT] = ACTIONS(940), + [anon_sym_GT] = ACTIONS(940), + [anon_sym_LT_EQ] = ACTIONS(937), + [anon_sym_GT_EQ] = ACTIONS(937), + [anon_sym_AMP_AMP] = ACTIONS(943), + [anon_sym_PIPE_PIPE] = ACTIONS(946), + [anon_sym_PIPE] = ACTIONS(949), + [anon_sym_CARET] = ACTIONS(952), + [anon_sym_AMP] = ACTIONS(955), + [anon_sym_LT_LT] = ACTIONS(958), + [anon_sym_GT_GT] = ACTIONS(958), + [anon_sym_QMARK_QMARK] = ACTIONS(961), + [anon_sym_DOT_DOT] = ACTIONS(964), + [anon_sym_DOT_DOT_EQ] = ACTIONS(967), + [anon_sym_QMARK] = ACTIONS(970), + [anon_sym_match] = ACTIONS(911), + [anon_sym_if] = ACTIONS(911), + [anon_sym_spawn] = ACTIONS(911), + [anon_sym_chan] = ACTIONS(911), + [anon_sym_send] = ACTIONS(911), + [anon_sym_recv] = ACTIONS(911), + [anon_sym_select] = ACTIONS(911), + [anon_sym_POUND] = ACTIONS(913), + [anon_sym_use] = ACTIONS(911), + [anon_sym_export] = ACTIONS(911), + [anon_sym_macro_rules] = ACTIONS(911), + [anon_sym_let] = ACTIONS(911), + [anon_sym_while] = ACTIONS(911), + [anon_sym_for] = ACTIONS(911), + [anon_sym_fn] = ACTIONS(911), + [anon_sym_struct] = ACTIONS(911), + [anon_sym_type] = ACTIONS(911), + [anon_sym_trait] = ACTIONS(911), + [anon_sym_impl] = ACTIONS(911), + [anon_sym_return] = ACTIONS(911), + [anon_sym_break] = ACTIONS(911), + [anon_sym_continue] = ACTIONS(911), + [anon_sym_go] = ACTIONS(911), + [anon_sym_try] = ACTIONS(911), + }, + [STATE(111)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(911), + [sym_integer_literal] = ACTIONS(911), + [sym_float_literal] = ACTIONS(913), + [anon_sym_true] = ACTIONS(911), + [anon_sym_false] = ACTIONS(911), + [anon_sym_nil] = ACTIONS(911), + [anon_sym_DQUOTE] = ACTIONS(913), + [anon_sym_SQUOTE] = ACTIONS(913), + [sym_raw_string] = ACTIONS(913), + [anon_sym_LPAREN] = ACTIONS(915), + [anon_sym_DOT] = ACTIONS(918), + [anon_sym_QMARK_DOT] = ACTIONS(920), + [anon_sym_LBRACK] = ACTIONS(922), + [anon_sym_QMARK_LBRACK] = ACTIONS(924), + [anon_sym_LBRACE] = ACTIONS(913), + [anon_sym_BANG] = ACTIONS(926), + [anon_sym_TILDE] = ACTIONS(913), + [anon_sym_STAR] = ACTIONS(874), + [anon_sym_SLASH] = ACTIONS(876), + [anon_sym_PERCENT] = ACTIONS(874), + [anon_sym_PLUS] = ACTIONS(878), + [anon_sym_DASH] = ACTIONS(878), + [anon_sym_EQ_EQ] = ACTIONS(880), + [anon_sym_BANG_EQ] = ACTIONS(880), + [anon_sym_LT] = ACTIONS(882), + [anon_sym_GT] = ACTIONS(882), + [anon_sym_LT_EQ] = ACTIONS(880), + [anon_sym_GT_EQ] = ACTIONS(880), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(949), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(894), + [anon_sym_GT_GT] = ACTIONS(894), + [anon_sym_QMARK_QMARK] = ACTIONS(973), + [anon_sym_DOT_DOT] = ACTIONS(896), + [anon_sym_DOT_DOT_EQ] = ACTIONS(898), + [anon_sym_QMARK] = ACTIONS(975), + [anon_sym_match] = ACTIONS(911), + [anon_sym_if] = ACTIONS(911), + [anon_sym_spawn] = ACTIONS(911), + [anon_sym_chan] = ACTIONS(911), + [anon_sym_send] = ACTIONS(911), + [anon_sym_recv] = ACTIONS(911), + [anon_sym_select] = ACTIONS(911), + [anon_sym_POUND] = ACTIONS(913), + [anon_sym_use] = ACTIONS(911), + [anon_sym_export] = ACTIONS(911), + [anon_sym_macro_rules] = ACTIONS(911), + [anon_sym_let] = ACTIONS(911), + [anon_sym_while] = ACTIONS(911), + [anon_sym_for] = ACTIONS(911), + [anon_sym_fn] = ACTIONS(911), + [anon_sym_struct] = ACTIONS(911), + [anon_sym_type] = ACTIONS(911), + [anon_sym_trait] = ACTIONS(911), + [anon_sym_impl] = ACTIONS(911), + [anon_sym_return] = ACTIONS(911), + [anon_sym_break] = ACTIONS(911), + [anon_sym_continue] = ACTIONS(911), + [anon_sym_go] = ACTIONS(911), + [anon_sym_try] = ACTIONS(911), + }, + [STATE(112)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(900), + [sym_integer_literal] = ACTIONS(900), + [sym_float_literal] = ACTIONS(902), + [anon_sym_true] = ACTIONS(900), + [anon_sym_false] = ACTIONS(900), + [anon_sym_nil] = ACTIONS(900), + [anon_sym_DQUOTE] = ACTIONS(902), + [anon_sym_SQUOTE] = ACTIONS(902), + [sym_raw_string] = ACTIONS(902), + [anon_sym_LPAREN] = ACTIONS(902), + [anon_sym_DOT] = ACTIONS(900), + [anon_sym_QMARK_DOT] = ACTIONS(902), + [anon_sym_LBRACK] = ACTIONS(902), + [anon_sym_QMARK_LBRACK] = ACTIONS(902), + [anon_sym_LBRACE] = ACTIONS(902), + [anon_sym_BANG] = ACTIONS(900), + [anon_sym_TILDE] = ACTIONS(902), + [anon_sym_STAR] = ACTIONS(902), + [anon_sym_SLASH] = ACTIONS(900), + [anon_sym_PERCENT] = ACTIONS(902), + [anon_sym_PLUS] = ACTIONS(902), + [anon_sym_DASH] = ACTIONS(902), + [anon_sym_EQ_EQ] = ACTIONS(902), + [anon_sym_BANG_EQ] = ACTIONS(902), + [anon_sym_LT] = ACTIONS(900), + [anon_sym_GT] = ACTIONS(900), + [anon_sym_LT_EQ] = ACTIONS(902), + [anon_sym_GT_EQ] = ACTIONS(902), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(902), + [anon_sym_GT_GT] = ACTIONS(902), + [anon_sym_QMARK_QMARK] = ACTIONS(902), + [anon_sym_DOT_DOT] = ACTIONS(900), + [anon_sym_DOT_DOT_EQ] = ACTIONS(902), + [anon_sym_QMARK] = ACTIONS(900), + [anon_sym_match] = ACTIONS(900), + [anon_sym_if] = ACTIONS(900), + [anon_sym_spawn] = ACTIONS(900), + [anon_sym_chan] = ACTIONS(900), + [anon_sym_send] = ACTIONS(900), + [anon_sym_recv] = ACTIONS(900), + [anon_sym_select] = ACTIONS(900), + [anon_sym_POUND] = ACTIONS(902), + [anon_sym_use] = ACTIONS(900), + [anon_sym_export] = ACTIONS(900), + [anon_sym_macro_rules] = ACTIONS(900), + [anon_sym_let] = ACTIONS(900), + [anon_sym_while] = ACTIONS(900), + [anon_sym_for] = ACTIONS(900), + [anon_sym_fn] = ACTIONS(900), + [anon_sym_struct] = ACTIONS(900), + [anon_sym_type] = ACTIONS(900), + [anon_sym_trait] = ACTIONS(900), + [anon_sym_impl] = ACTIONS(900), + [anon_sym_return] = ACTIONS(900), + [anon_sym_break] = ACTIONS(900), + [anon_sym_continue] = ACTIONS(900), + [anon_sym_go] = ACTIONS(900), + [anon_sym_try] = ACTIONS(900), + }, + [STATE(113)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(900), + [sym_integer_literal] = ACTIONS(900), + [sym_float_literal] = ACTIONS(902), + [anon_sym_true] = ACTIONS(900), + [anon_sym_false] = ACTIONS(900), + [anon_sym_nil] = ACTIONS(900), + [anon_sym_DQUOTE] = ACTIONS(902), + [anon_sym_SQUOTE] = ACTIONS(902), + [sym_raw_string] = ACTIONS(902), + [anon_sym_LPAREN] = ACTIONS(902), + [anon_sym_DOT] = ACTIONS(900), + [anon_sym_QMARK_DOT] = ACTIONS(902), + [anon_sym_LBRACK] = ACTIONS(902), + [anon_sym_QMARK_LBRACK] = ACTIONS(902), + [anon_sym_LBRACE] = ACTIONS(902), + [anon_sym_BANG] = ACTIONS(900), + [anon_sym_TILDE] = ACTIONS(902), + [anon_sym_STAR] = ACTIONS(902), + [anon_sym_SLASH] = ACTIONS(900), + [anon_sym_PERCENT] = ACTIONS(902), + [anon_sym_PLUS] = ACTIONS(902), + [anon_sym_DASH] = ACTIONS(902), + [anon_sym_EQ_EQ] = ACTIONS(902), + [anon_sym_BANG_EQ] = ACTIONS(902), + [anon_sym_LT] = ACTIONS(900), + [anon_sym_GT] = ACTIONS(900), + [anon_sym_LT_EQ] = ACTIONS(902), + [anon_sym_GT_EQ] = ACTIONS(902), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(900), + [anon_sym_CARET] = ACTIONS(902), + [anon_sym_AMP] = ACTIONS(900), + [anon_sym_LT_LT] = ACTIONS(902), + [anon_sym_GT_GT] = ACTIONS(902), + [anon_sym_QMARK_QMARK] = ACTIONS(902), + [anon_sym_DOT_DOT] = ACTIONS(900), + [anon_sym_DOT_DOT_EQ] = ACTIONS(902), + [anon_sym_QMARK] = ACTIONS(900), + [anon_sym_match] = ACTIONS(900), + [anon_sym_if] = ACTIONS(900), + [anon_sym_spawn] = ACTIONS(900), + [anon_sym_chan] = ACTIONS(900), + [anon_sym_send] = ACTIONS(900), + [anon_sym_recv] = ACTIONS(900), + [anon_sym_select] = ACTIONS(900), + [anon_sym_POUND] = ACTIONS(902), + [anon_sym_use] = ACTIONS(900), + [anon_sym_export] = ACTIONS(900), + [anon_sym_macro_rules] = ACTIONS(900), + [anon_sym_let] = ACTIONS(900), + [anon_sym_while] = ACTIONS(900), + [anon_sym_for] = ACTIONS(900), + [anon_sym_fn] = ACTIONS(900), + [anon_sym_struct] = ACTIONS(900), + [anon_sym_type] = ACTIONS(900), + [anon_sym_trait] = ACTIONS(900), + [anon_sym_impl] = ACTIONS(900), + [anon_sym_return] = ACTIONS(900), + [anon_sym_break] = ACTIONS(900), + [anon_sym_continue] = ACTIONS(900), + [anon_sym_go] = ACTIONS(900), + [anon_sym_try] = ACTIONS(900), + }, + [STATE(114)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(900), + [sym_integer_literal] = ACTIONS(900), + [sym_float_literal] = ACTIONS(902), + [anon_sym_true] = ACTIONS(900), + [anon_sym_false] = ACTIONS(900), + [anon_sym_nil] = ACTIONS(900), + [anon_sym_DQUOTE] = ACTIONS(902), + [anon_sym_SQUOTE] = ACTIONS(902), + [sym_raw_string] = ACTIONS(902), + [anon_sym_LPAREN] = ACTIONS(902), + [anon_sym_DOT] = ACTIONS(900), + [anon_sym_QMARK_DOT] = ACTIONS(902), + [anon_sym_LBRACK] = ACTIONS(902), + [anon_sym_QMARK_LBRACK] = ACTIONS(902), + [anon_sym_LBRACE] = ACTIONS(902), + [anon_sym_BANG] = ACTIONS(900), + [anon_sym_TILDE] = ACTIONS(902), + [anon_sym_STAR] = ACTIONS(902), + [anon_sym_SLASH] = ACTIONS(900), + [anon_sym_PERCENT] = ACTIONS(902), + [anon_sym_PLUS] = ACTIONS(902), + [anon_sym_DASH] = ACTIONS(902), + [anon_sym_EQ_EQ] = ACTIONS(902), + [anon_sym_BANG_EQ] = ACTIONS(902), + [anon_sym_LT] = ACTIONS(900), + [anon_sym_GT] = ACTIONS(900), + [anon_sym_LT_EQ] = ACTIONS(902), + [anon_sym_GT_EQ] = ACTIONS(902), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(902), + [anon_sym_AMP] = ACTIONS(900), + [anon_sym_LT_LT] = ACTIONS(902), + [anon_sym_GT_GT] = ACTIONS(902), + [anon_sym_QMARK_QMARK] = ACTIONS(902), + [anon_sym_DOT_DOT] = ACTIONS(900), + [anon_sym_DOT_DOT_EQ] = ACTIONS(902), + [anon_sym_QMARK] = ACTIONS(900), + [anon_sym_match] = ACTIONS(900), + [anon_sym_if] = ACTIONS(900), + [anon_sym_spawn] = ACTIONS(900), + [anon_sym_chan] = ACTIONS(900), + [anon_sym_send] = ACTIONS(900), + [anon_sym_recv] = ACTIONS(900), + [anon_sym_select] = ACTIONS(900), + [anon_sym_POUND] = ACTIONS(902), + [anon_sym_use] = ACTIONS(900), + [anon_sym_export] = ACTIONS(900), + [anon_sym_macro_rules] = ACTIONS(900), + [anon_sym_let] = ACTIONS(900), + [anon_sym_while] = ACTIONS(900), + [anon_sym_for] = ACTIONS(900), + [anon_sym_fn] = ACTIONS(900), + [anon_sym_struct] = ACTIONS(900), + [anon_sym_type] = ACTIONS(900), + [anon_sym_trait] = ACTIONS(900), + [anon_sym_impl] = ACTIONS(900), + [anon_sym_return] = ACTIONS(900), + [anon_sym_break] = ACTIONS(900), + [anon_sym_continue] = ACTIONS(900), + [anon_sym_go] = ACTIONS(900), + [anon_sym_try] = ACTIONS(900), + }, + [STATE(115)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(900), + [sym_integer_literal] = ACTIONS(900), + [sym_float_literal] = ACTIONS(902), + [anon_sym_true] = ACTIONS(900), + [anon_sym_false] = ACTIONS(900), + [anon_sym_nil] = ACTIONS(900), + [anon_sym_DQUOTE] = ACTIONS(902), + [anon_sym_SQUOTE] = ACTIONS(902), + [sym_raw_string] = ACTIONS(902), + [anon_sym_LPAREN] = ACTIONS(902), + [anon_sym_DOT] = ACTIONS(900), + [anon_sym_QMARK_DOT] = ACTIONS(902), + [anon_sym_LBRACK] = ACTIONS(902), + [anon_sym_QMARK_LBRACK] = ACTIONS(902), + [anon_sym_LBRACE] = ACTIONS(902), + [anon_sym_BANG] = ACTIONS(900), + [anon_sym_TILDE] = ACTIONS(902), + [anon_sym_STAR] = ACTIONS(902), + [anon_sym_SLASH] = ACTIONS(900), + [anon_sym_PERCENT] = ACTIONS(902), + [anon_sym_PLUS] = ACTIONS(902), + [anon_sym_DASH] = ACTIONS(902), + [anon_sym_EQ_EQ] = ACTIONS(902), + [anon_sym_BANG_EQ] = ACTIONS(902), + [anon_sym_LT] = ACTIONS(900), + [anon_sym_GT] = ACTIONS(900), + [anon_sym_LT_EQ] = ACTIONS(902), + [anon_sym_GT_EQ] = ACTIONS(902), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(900), + [anon_sym_LT_LT] = ACTIONS(902), + [anon_sym_GT_GT] = ACTIONS(902), + [anon_sym_QMARK_QMARK] = ACTIONS(902), + [anon_sym_DOT_DOT] = ACTIONS(900), + [anon_sym_DOT_DOT_EQ] = ACTIONS(902), + [anon_sym_QMARK] = ACTIONS(900), + [anon_sym_match] = ACTIONS(900), + [anon_sym_if] = ACTIONS(900), + [anon_sym_spawn] = ACTIONS(900), + [anon_sym_chan] = ACTIONS(900), + [anon_sym_send] = ACTIONS(900), + [anon_sym_recv] = ACTIONS(900), + [anon_sym_select] = ACTIONS(900), + [anon_sym_POUND] = ACTIONS(902), + [anon_sym_use] = ACTIONS(900), + [anon_sym_export] = ACTIONS(900), + [anon_sym_macro_rules] = ACTIONS(900), + [anon_sym_let] = ACTIONS(900), + [anon_sym_while] = ACTIONS(900), + [anon_sym_for] = ACTIONS(900), + [anon_sym_fn] = ACTIONS(900), + [anon_sym_struct] = ACTIONS(900), + [anon_sym_type] = ACTIONS(900), + [anon_sym_trait] = ACTIONS(900), + [anon_sym_impl] = ACTIONS(900), + [anon_sym_return] = ACTIONS(900), + [anon_sym_break] = ACTIONS(900), + [anon_sym_continue] = ACTIONS(900), + [anon_sym_go] = ACTIONS(900), + [anon_sym_try] = ACTIONS(900), + }, + [STATE(116)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(900), + [sym_integer_literal] = ACTIONS(900), + [sym_float_literal] = ACTIONS(902), + [anon_sym_true] = ACTIONS(900), + [anon_sym_false] = ACTIONS(900), + [anon_sym_nil] = ACTIONS(900), + [anon_sym_DQUOTE] = ACTIONS(902), + [anon_sym_SQUOTE] = ACTIONS(902), + [sym_raw_string] = ACTIONS(902), + [anon_sym_LPAREN] = ACTIONS(902), + [anon_sym_DOT] = ACTIONS(900), + [anon_sym_QMARK_DOT] = ACTIONS(902), + [anon_sym_LBRACK] = ACTIONS(902), + [anon_sym_QMARK_LBRACK] = ACTIONS(902), + [anon_sym_LBRACE] = ACTIONS(902), + [anon_sym_BANG] = ACTIONS(900), + [anon_sym_TILDE] = ACTIONS(902), + [anon_sym_STAR] = ACTIONS(902), + [anon_sym_SLASH] = ACTIONS(900), + [anon_sym_PERCENT] = ACTIONS(902), + [anon_sym_PLUS] = ACTIONS(902), + [anon_sym_DASH] = ACTIONS(902), + [anon_sym_EQ_EQ] = ACTIONS(880), + [anon_sym_BANG_EQ] = ACTIONS(880), + [anon_sym_LT] = ACTIONS(882), + [anon_sym_GT] = ACTIONS(882), + [anon_sym_LT_EQ] = ACTIONS(880), + [anon_sym_GT_EQ] = ACTIONS(880), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(902), + [anon_sym_GT_GT] = ACTIONS(902), + [anon_sym_QMARK_QMARK] = ACTIONS(902), + [anon_sym_DOT_DOT] = ACTIONS(900), + [anon_sym_DOT_DOT_EQ] = ACTIONS(902), + [anon_sym_QMARK] = ACTIONS(900), + [anon_sym_match] = ACTIONS(900), + [anon_sym_if] = ACTIONS(900), + [anon_sym_spawn] = ACTIONS(900), + [anon_sym_chan] = ACTIONS(900), + [anon_sym_send] = ACTIONS(900), + [anon_sym_recv] = ACTIONS(900), + [anon_sym_select] = ACTIONS(900), + [anon_sym_POUND] = ACTIONS(902), + [anon_sym_use] = ACTIONS(900), + [anon_sym_export] = ACTIONS(900), + [anon_sym_macro_rules] = ACTIONS(900), + [anon_sym_let] = ACTIONS(900), + [anon_sym_while] = ACTIONS(900), + [anon_sym_for] = ACTIONS(900), + [anon_sym_fn] = ACTIONS(900), + [anon_sym_struct] = ACTIONS(900), + [anon_sym_type] = ACTIONS(900), + [anon_sym_trait] = ACTIONS(900), + [anon_sym_impl] = ACTIONS(900), + [anon_sym_return] = ACTIONS(900), + [anon_sym_break] = ACTIONS(900), + [anon_sym_continue] = ACTIONS(900), + [anon_sym_go] = ACTIONS(900), + [anon_sym_try] = ACTIONS(900), + }, + [STATE(117)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(977), + [sym_integer_literal] = ACTIONS(977), + [sym_float_literal] = ACTIONS(979), + [anon_sym_true] = ACTIONS(977), + [anon_sym_false] = ACTIONS(977), + [anon_sym_nil] = ACTIONS(977), + [anon_sym_DQUOTE] = ACTIONS(979), + [anon_sym_SQUOTE] = ACTIONS(979), + [sym_raw_string] = ACTIONS(979), + [anon_sym_LPAREN] = ACTIONS(979), + [anon_sym_DOT] = ACTIONS(977), + [anon_sym_QMARK_DOT] = ACTIONS(979), + [anon_sym_LBRACK] = ACTIONS(979), + [anon_sym_QMARK_LBRACK] = ACTIONS(979), + [anon_sym_LBRACE] = ACTIONS(979), + [anon_sym_BANG] = ACTIONS(977), + [anon_sym_TILDE] = ACTIONS(979), + [anon_sym_STAR] = ACTIONS(979), + [anon_sym_SLASH] = ACTIONS(977), + [anon_sym_PERCENT] = ACTIONS(979), + [anon_sym_PLUS] = ACTIONS(979), + [anon_sym_DASH] = ACTIONS(979), + [anon_sym_EQ_EQ] = ACTIONS(880), + [anon_sym_BANG_EQ] = ACTIONS(880), + [anon_sym_LT] = ACTIONS(882), + [anon_sym_GT] = ACTIONS(882), + [anon_sym_LT_EQ] = ACTIONS(880), + [anon_sym_GT_EQ] = ACTIONS(880), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(894), + [anon_sym_GT_GT] = ACTIONS(894), + [anon_sym_QMARK_QMARK] = ACTIONS(979), + [anon_sym_DOT_DOT] = ACTIONS(977), + [anon_sym_DOT_DOT_EQ] = ACTIONS(979), + [anon_sym_QMARK] = ACTIONS(977), + [anon_sym_match] = ACTIONS(977), + [anon_sym_if] = ACTIONS(977), + [anon_sym_spawn] = ACTIONS(977), + [anon_sym_chan] = ACTIONS(977), + [anon_sym_send] = ACTIONS(977), + [anon_sym_recv] = ACTIONS(977), + [anon_sym_select] = ACTIONS(977), + [anon_sym_POUND] = ACTIONS(979), + [anon_sym_use] = ACTIONS(977), + [anon_sym_export] = ACTIONS(977), + [anon_sym_macro_rules] = ACTIONS(977), + [anon_sym_let] = ACTIONS(977), + [anon_sym_while] = ACTIONS(977), + [anon_sym_for] = ACTIONS(977), + [anon_sym_fn] = ACTIONS(977), + [anon_sym_struct] = ACTIONS(977), + [anon_sym_type] = ACTIONS(977), + [anon_sym_trait] = ACTIONS(977), + [anon_sym_impl] = ACTIONS(977), + [anon_sym_return] = ACTIONS(977), + [anon_sym_break] = ACTIONS(977), + [anon_sym_continue] = ACTIONS(977), + [anon_sym_go] = ACTIONS(977), + [anon_sym_try] = ACTIONS(977), + }, + [STATE(118)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(772), + [sym_integer_literal] = ACTIONS(772), + [sym_float_literal] = ACTIONS(774), + [anon_sym_true] = ACTIONS(772), + [anon_sym_false] = ACTIONS(772), + [anon_sym_nil] = ACTIONS(772), + [anon_sym_DQUOTE] = ACTIONS(774), + [anon_sym_SQUOTE] = ACTIONS(774), + [sym_raw_string] = ACTIONS(774), + [anon_sym_LPAREN] = ACTIONS(774), + [anon_sym_DOT] = ACTIONS(772), + [anon_sym_QMARK_DOT] = ACTIONS(774), + [anon_sym_LBRACK] = ACTIONS(774), + [anon_sym_QMARK_LBRACK] = ACTIONS(774), + [anon_sym_LBRACE] = ACTIONS(774), + [anon_sym_BANG] = ACTIONS(772), + [anon_sym_TILDE] = ACTIONS(774), + [anon_sym_STAR] = ACTIONS(774), + [anon_sym_SLASH] = ACTIONS(772), + [anon_sym_PERCENT] = ACTIONS(774), + [anon_sym_PLUS] = ACTIONS(774), + [anon_sym_DASH] = ACTIONS(774), + [anon_sym_EQ_EQ] = ACTIONS(774), + [anon_sym_BANG_EQ] = ACTIONS(774), + [anon_sym_LT] = ACTIONS(772), + [anon_sym_GT] = ACTIONS(772), + [anon_sym_LT_EQ] = ACTIONS(774), + [anon_sym_GT_EQ] = ACTIONS(774), + [anon_sym_AMP_AMP] = ACTIONS(774), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(772), + [anon_sym_CARET] = ACTIONS(774), + [anon_sym_AMP] = ACTIONS(772), + [anon_sym_LT_LT] = ACTIONS(774), + [anon_sym_GT_GT] = ACTIONS(774), + [anon_sym_QMARK_QMARK] = ACTIONS(774), + [anon_sym_DOT_DOT] = ACTIONS(772), + [anon_sym_DOT_DOT_EQ] = ACTIONS(774), + [anon_sym_QMARK] = ACTIONS(772), + [anon_sym_match] = ACTIONS(772), + [anon_sym_if] = ACTIONS(772), + [anon_sym_spawn] = ACTIONS(772), + [anon_sym_chan] = ACTIONS(772), + [anon_sym_send] = ACTIONS(772), + [anon_sym_recv] = ACTIONS(772), + [anon_sym_select] = ACTIONS(772), + [anon_sym_POUND] = ACTIONS(774), + [anon_sym_use] = ACTIONS(772), + [anon_sym_export] = ACTIONS(772), + [anon_sym_macro_rules] = ACTIONS(772), + [anon_sym_let] = ACTIONS(772), + [anon_sym_while] = ACTIONS(772), + [anon_sym_for] = ACTIONS(772), + [anon_sym_fn] = ACTIONS(772), + [anon_sym_struct] = ACTIONS(772), + [anon_sym_type] = ACTIONS(772), + [anon_sym_trait] = ACTIONS(772), + [anon_sym_impl] = ACTIONS(772), + [anon_sym_return] = ACTIONS(772), + [anon_sym_break] = ACTIONS(772), + [anon_sym_continue] = ACTIONS(772), + [anon_sym_go] = ACTIONS(772), + [anon_sym_try] = ACTIONS(772), + }, + [STATE(119)] = { + [sym_line_comment] = ACTIONS(3), + [sym_block_comment] = ACTIONS(3), + [aux_sym_identifier_token1] = ACTIONS(900), + [sym_integer_literal] = ACTIONS(900), + [sym_float_literal] = ACTIONS(902), + [anon_sym_true] = ACTIONS(900), + [anon_sym_false] = ACTIONS(900), + [anon_sym_nil] = ACTIONS(900), + [anon_sym_DQUOTE] = ACTIONS(902), + [anon_sym_SQUOTE] = ACTIONS(902), + [sym_raw_string] = ACTIONS(902), + [anon_sym_LPAREN] = ACTIONS(902), + [anon_sym_DOT] = ACTIONS(900), + [anon_sym_QMARK_DOT] = ACTIONS(902), + [anon_sym_LBRACK] = ACTIONS(902), + [anon_sym_QMARK_LBRACK] = ACTIONS(902), + [anon_sym_LBRACE] = ACTIONS(902), + [anon_sym_BANG] = ACTIONS(900), + [anon_sym_TILDE] = ACTIONS(902), + [anon_sym_STAR] = ACTIONS(902), + [anon_sym_SLASH] = ACTIONS(900), + [anon_sym_PERCENT] = ACTIONS(902), + [anon_sym_PLUS] = ACTIONS(878), + [anon_sym_DASH] = ACTIONS(878), + [anon_sym_EQ_EQ] = ACTIONS(880), + [anon_sym_BANG_EQ] = ACTIONS(880), + [anon_sym_LT] = ACTIONS(882), + [anon_sym_GT] = ACTIONS(882), + [anon_sym_LT_EQ] = ACTIONS(880), + [anon_sym_GT_EQ] = ACTIONS(880), + [anon_sym_AMP_AMP] = ACTIONS(884), + [anon_sym_PIPE_PIPE] = ACTIONS(886), + [anon_sym_PIPE] = ACTIONS(888), + [anon_sym_CARET] = ACTIONS(890), + [anon_sym_AMP] = ACTIONS(892), + [anon_sym_LT_LT] = ACTIONS(894), + [anon_sym_GT_GT] = ACTIONS(894), + [anon_sym_QMARK_QMARK] = ACTIONS(902), + [anon_sym_DOT_DOT] = ACTIONS(896), + [anon_sym_DOT_DOT_EQ] = ACTIONS(898), + [anon_sym_QMARK] = ACTIONS(900), + [anon_sym_match] = ACTIONS(900), + [anon_sym_if] = ACTIONS(900), + [anon_sym_spawn] = ACTIONS(900), + [anon_sym_chan] = ACTIONS(900), + [anon_sym_send] = ACTIONS(900), + [anon_sym_recv] = ACTIONS(900), + [anon_sym_select] = ACTIONS(900), + [anon_sym_POUND] = ACTIONS(902), + [anon_sym_use] = ACTIONS(900), + [anon_sym_export] = ACTIONS(900), + [anon_sym_macro_rules] = ACTIONS(900), + [anon_sym_let] = ACTIONS(900), + [anon_sym_while] = ACTIONS(900), + [anon_sym_for] = ACTIONS(900), + [anon_sym_fn] = ACTIONS(900), + [anon_sym_struct] = ACTIONS(900), + [anon_sym_type] = ACTIONS(900), + [anon_sym_trait] = ACTIONS(900), + [anon_sym_impl] = ACTIONS(900), + [anon_sym_return] = ACTIONS(900), + [anon_sym_break] = ACTIONS(900), + [anon_sym_continue] = ACTIONS(900), + [anon_sym_go] = ACTIONS(900), + [anon_sym_try] = ACTIONS(900), }, }; static const uint16_t ts_small_parse_table[] = { - [0] = 7, - ACTIONS(854), 1, - anon_sym_BANG, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(851), 2, - anon_sym_LPAREN, - anon_sym_LBRACK, - ACTIONS(696), 6, - anon_sym_DOT, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - ACTIONS(849), 7, + [0] = 29, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LBRACE, - anon_sym_POUND, - ACTIONS(698), 16, - anon_sym_COLON, - anon_sym_QMARK_DOT, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - ACTIONS(847), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [78] = 7, - ACTIONS(854), 1, - anon_sym_BANG, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(981), 1, + anon_sym_RPAREN, + ACTIONS(983), 1, + anon_sym_COMMA, + STATE(815), 1, + sym_identifier, + STATE(1230), 1, + sym_named_argument, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(851), 2, - anon_sym_LPAREN, - anon_sym_LBRACK, - ACTIONS(696), 6, - anon_sym_DOT, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - ACTIONS(849), 7, - ts_builtin_sym_end, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LBRACE, - anon_sym_POUND, - ACTIONS(698), 15, - anon_sym_QMARK_DOT, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - ACTIONS(847), 30, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [156] = 7, - ACTIONS(854), 1, + ACTIONS(29), 2, anon_sym_BANG, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(851), 2, - anon_sym_LPAREN, - anon_sym_LBRACK, - ACTIONS(696), 6, - anon_sym_DOT, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - ACTIONS(849), 7, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1302), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(731), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [118] = 29, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LBRACE, - anon_sym_POUND, - ACTIONS(698), 15, - anon_sym_QMARK_DOT, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - ACTIONS(847), 30, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [234] = 7, - ACTIONS(854), 1, - anon_sym_BANG, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(985), 1, + anon_sym_RPAREN, + ACTIONS(987), 1, + anon_sym_COMMA, + STATE(815), 1, + sym_identifier, + STATE(1263), 1, + sym_named_argument, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(851), 2, - anon_sym_LPAREN, - anon_sym_LBRACK, - ACTIONS(696), 6, - anon_sym_DOT, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - ACTIONS(849), 7, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1302), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(731), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [236] = 28, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_LBRACE, - anon_sym_POUND, - ACTIONS(698), 15, - anon_sym_QMARK_DOT, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - ACTIONS(847), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [311] = 4, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(989), 1, + anon_sym_RBRACE, + STATE(907), 1, + sym_identifier, + STATE(1366), 1, + sym_map_entry, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(742), 21, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1514), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(731), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [351] = 28, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, anon_sym_LPAREN, - anon_sym_QMARK_DOT, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_POUND, - ACTIONS(740), 36, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, + ACTIONS(31), 1, anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [380] = 9, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, - ACTIONS(867), 1, - anon_sym_AMP_AMP, - ACTIONS(869), 1, - anon_sym_DOT_DOT, - ACTIONS(871), 1, - anon_sym_DOT_DOT_EQ, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(991), 1, + anon_sym_RBRACE, + STATE(907), 1, + sym_identifier, + STATE(1406), 1, + sym_map_entry, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(865), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(863), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 15, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1514), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(731), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [466] = 28, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, anon_sym_LPAREN, - anon_sym_QMARK_DOT, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_POUND, - ACTIONS(859), 33, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_QMARK, + ACTIONS(31), 1, anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [459] = 12, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, - ACTIONS(867), 1, - anon_sym_AMP_AMP, - ACTIONS(869), 1, - anon_sym_DOT_DOT, - ACTIONS(871), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(879), 1, - anon_sym_SLASH, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(993), 1, + anon_sym_RBRACE, + STATE(907), 1, + sym_identifier, + STATE(1286), 1, + sym_map_entry, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(865), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(877), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(881), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(863), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(875), 11, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1514), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(731), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [581] = 28, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, anon_sym_LPAREN, - anon_sym_QMARK_DOT, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_QMARK_QMARK, - anon_sym_POUND, - ACTIONS(873), 32, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [544] = 12, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, - ACTIONS(867), 1, - anon_sym_AMP_AMP, - ACTIONS(869), 1, - anon_sym_DOT_DOT, - ACTIONS(871), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(879), 1, - anon_sym_SLASH, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(865), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(877), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(881), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(863), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(885), 11, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_QMARK_QMARK, - anon_sym_POUND, - ACTIONS(883), 32, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [629] = 20, - ACTIONS(891), 1, - anon_sym_LPAREN, - ACTIONS(894), 1, - anon_sym_DOT, - ACTIONS(896), 1, - anon_sym_QMARK_DOT, - ACTIONS(898), 1, - anon_sym_LBRACK, - ACTIONS(900), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(907), 1, - anon_sym_SLASH, - ACTIONS(919), 1, - anon_sym_AMP_AMP, - ACTIONS(922), 1, - anon_sym_PIPE_PIPE, - ACTIONS(925), 1, - anon_sym_QMARK_QMARK, - ACTIONS(928), 1, - anon_sym_DOT_DOT, - ACTIONS(931), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(934), 1, - anon_sym_QMARK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(904), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(910), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(916), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(913), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(889), 6, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LBRACE, - anon_sym_POUND, - ACTIONS(887), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [730] = 20, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, - ACTIONS(867), 1, - anon_sym_AMP_AMP, - ACTIONS(869), 1, - anon_sym_DOT_DOT, - ACTIONS(871), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(879), 1, - anon_sym_SLASH, - ACTIONS(891), 1, - anon_sym_LPAREN, - ACTIONS(894), 1, - anon_sym_DOT, - ACTIONS(896), 1, - anon_sym_QMARK_DOT, - ACTIONS(898), 1, - anon_sym_LBRACK, - ACTIONS(900), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(937), 1, - anon_sym_QMARK_QMARK, - ACTIONS(939), 1, - anon_sym_QMARK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(865), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(877), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(881), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(863), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(889), 6, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LBRACE, - anon_sym_POUND, - ACTIONS(887), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [831] = 4, - ACTIONS(941), 1, - anon_sym_LBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(686), 21, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_POUND, - ACTIONS(684), 36, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [900] = 5, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, - ACTIONS(867), 1, - anon_sym_AMP_AMP, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(861), 20, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_POUND, - ACTIONS(859), 36, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [971] = 7, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, - ACTIONS(867), 1, - anon_sym_AMP_AMP, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(865), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(863), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(946), 16, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_POUND, - ACTIONS(944), 34, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [1046] = 10, - ACTIONS(857), 1, - anon_sym_PIPE_PIPE, - ACTIONS(867), 1, - anon_sym_AMP_AMP, - ACTIONS(869), 1, - anon_sym_DOT_DOT, - ACTIONS(871), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(865), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(881), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(863), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 13, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_QMARK_QMARK, - anon_sym_POUND, - ACTIONS(859), 33, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [1127] = 29, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -18803,19 +20701,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(948), 1, + ACTIONS(995), 1, anon_sym_RPAREN, - ACTIONS(950), 1, + ACTIONS(997), 1, anon_sym_COMMA, - STATE(754), 1, + STATE(60), 1, sym_identifier, - STATE(1256), 1, - sym_named_argument, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -18823,13 +20719,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1408), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -18843,7 +20742,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -18858,7 +20757,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [1244] = 29, + [696] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -18875,8 +20774,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -18891,19 +20788,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(952), 1, + ACTIONS(999), 1, anon_sym_RPAREN, - ACTIONS(954), 1, - anon_sym_COMMA, - STATE(754), 1, + STATE(60), 1, sym_identifier, - STATE(1243), 1, - sym_named_argument, - STATE(1369), 1, + STATE(1509), 1, + sym__argument_list, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -18911,13 +20806,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1248), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -18931,7 +20829,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -18946,7 +20844,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [1361] = 28, + [811] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -18963,8 +20861,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -18979,31 +20875,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(956), 1, - anon_sym_RPAREN, - STATE(86), 1, + ACTIONS(1001), 1, + anon_sym_RBRACE, + STATE(907), 1, sym_identifier, - STATE(1369), 1, + STATE(1406), 1, + sym_map_entry, + STATE(1606), 1, sym_type_identifier, - STATE(1540), 1, - sym__argument_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1227), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19017,7 +20916,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19032,7 +20931,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [1475] = 28, + [926] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19049,8 +20948,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19065,17 +20962,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(958), 1, + ACTIONS(1003), 1, anon_sym_RBRACE, - STATE(812), 1, + STATE(907), 1, sym_identifier, - STATE(1320), 1, + STATE(1406), 1, sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -19083,13 +20980,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19103,7 +21003,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19118,7 +21018,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [1589] = 28, + [1041] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19135,8 +21035,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19151,17 +21049,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(960), 1, + ACTIONS(1005), 1, anon_sym_RBRACE, - STATE(812), 1, + STATE(907), 1, sym_identifier, - STATE(1320), 1, + STATE(1406), 1, sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -19169,13 +21067,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19189,7 +21090,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19204,7 +21105,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [1703] = 28, + [1156] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19221,8 +21122,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19237,203 +21136,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(962), 1, - anon_sym_RBRACE, - STATE(812), 1, - sym_identifier, - STATE(1320), 1, - sym_map_entry, - STATE(1369), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(13), 2, - anon_sym_true, - anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1434), 2, - sym__full_expression, - sym_ternary_expression, - STATE(97), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(678), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [1817] = 28, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(964), 1, + ACTIONS(1007), 1, anon_sym_RPAREN, - STATE(86), 1, + ACTIONS(1009), 1, + anon_sym_COMMA, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, - STATE(1391), 1, - sym__argument_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1227), 2, - sym__full_expression, - sym_ternary_expression, - STATE(97), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(678), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [1931] = 28, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, + ACTIONS(29), 2, anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(966), 1, - anon_sym_RBRACE, - STATE(812), 1, - sym_identifier, - STATE(1157), 1, - sym_map_entry, - STATE(1369), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(13), 2, - anon_sym_true, - anon_sym_false, - STATE(67), 2, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1399), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19447,7 +21177,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19462,7 +21192,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2045] = 28, + [1271] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19479,8 +21209,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19495,17 +21223,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(968), 1, + ACTIONS(1011), 1, anon_sym_RPAREN, - ACTIONS(970), 1, + ACTIONS(1013), 1, anon_sym_COMMA, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -19513,13 +21241,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1299), 2, + STATE(1401), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19533,7 +21264,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19548,7 +21279,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2159] = 28, + [1386] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19565,8 +21296,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19581,17 +21310,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(972), 1, + ACTIONS(1015), 1, anon_sym_RPAREN, - ACTIONS(974), 1, - anon_sym_COMMA, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1456), 1, + sym__argument_list, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -19599,13 +21328,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1344), 2, + STATE(1248), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19619,7 +21351,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19634,7 +21366,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2273] = 28, + [1501] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19651,8 +21383,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19667,31 +21397,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(976), 1, - anon_sym_RPAREN, - STATE(86), 1, + ACTIONS(1017), 1, + anon_sym_RBRACE, + STATE(907), 1, sym_identifier, - STATE(1369), 1, + STATE(1406), 1, + sym_map_entry, + STATE(1606), 1, sym_type_identifier, - STATE(1552), 1, - sym__argument_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1227), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19705,7 +21438,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19720,7 +21453,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2387] = 28, + [1616] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19737,8 +21470,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19753,17 +21484,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(978), 1, + ACTIONS(1019), 1, anon_sym_RBRACE, - STATE(812), 1, + STATE(907), 1, sym_identifier, - STATE(1320), 1, + STATE(1406), 1, sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -19771,13 +21502,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19791,7 +21525,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19806,7 +21540,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2501] = 28, + [1731] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19823,8 +21557,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19839,17 +21571,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(980), 1, + ACTIONS(1021), 1, anon_sym_RBRACE, - STATE(812), 1, + STATE(907), 1, sym_identifier, - STATE(1320), 1, + STATE(1288), 1, sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -19857,13 +21589,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19877,7 +21612,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19892,7 +21627,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2615] = 28, + [1846] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19909,8 +21644,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -19925,17 +21658,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(982), 1, - anon_sym_RBRACE, - STATE(812), 1, + ACTIONS(1023), 1, + anon_sym_RPAREN, + ACTIONS(1025), 1, + anon_sym_COMMA, + STATE(60), 1, sym_identifier, - STATE(1160), 1, - sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -19943,13 +21676,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, STATE(1434), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -19963,7 +21699,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -19978,7 +21714,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2729] = 28, + [1961] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -19995,8 +21731,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20011,17 +21745,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(984), 1, + ACTIONS(1027), 1, anon_sym_RPAREN, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, - STATE(1504), 1, + STATE(1649), 1, sym__argument_list, ACTIONS(3), 2, sym_line_comment, @@ -20029,13 +21763,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1227), 2, + STATE(1248), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20049,7 +21786,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20064,7 +21801,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2843] = 28, + [2076] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20081,8 +21818,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20097,17 +21832,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(986), 1, + ACTIONS(1029), 1, anon_sym_RBRACE, - STATE(812), 1, + STATE(907), 1, sym_identifier, - STATE(1261), 1, + STATE(1406), 1, sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20115,13 +21850,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20135,7 +21873,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20150,7 +21888,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [2957] = 28, + [2191] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20167,8 +21905,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20183,17 +21919,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(988), 1, - anon_sym_RPAREN, - ACTIONS(990), 1, - anon_sym_COMMA, - STATE(86), 1, + ACTIONS(1031), 1, + anon_sym_RBRACE, + STATE(907), 1, sym_identifier, - STATE(1369), 1, + STATE(1406), 1, + sym_map_entry, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20201,13 +21937,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1342), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20221,7 +21960,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20236,7 +21975,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3071] = 28, + [2306] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20253,8 +21992,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20269,17 +22006,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(992), 1, - anon_sym_RBRACE, - STATE(812), 1, + ACTIONS(1033), 1, + anon_sym_RPAREN, + STATE(60), 1, sym_identifier, - STATE(1320), 1, - sym_map_entry, - STATE(1369), 1, + STATE(1580), 1, + sym__argument_list, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20287,13 +22024,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1248), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20307,7 +22047,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20322,7 +22062,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3185] = 28, + [2421] = 28, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20339,8 +22079,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20355,17 +22093,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(994), 1, - anon_sym_RPAREN, - ACTIONS(996), 1, - anon_sym_COMMA, - STATE(86), 1, + ACTIONS(1035), 1, + anon_sym_RBRACE, + STATE(907), 1, sym_identifier, - STATE(1369), 1, + STATE(1306), 1, + sym_map_entry, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20373,13 +22111,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1359), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20393,7 +22134,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20408,7 +22149,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3299] = 28, + [2536] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20425,8 +22166,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20441,17 +22180,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(998), 1, - anon_sym_RBRACE, - STATE(812), 1, + ACTIONS(1037), 1, + anon_sym_SEMI, + STATE(60), 1, sym_identifier, - STATE(1320), 1, - sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20459,13 +22196,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1536), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20479,7 +22219,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20494,7 +22234,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3413] = 28, + [2648] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20511,8 +22251,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20527,17 +22265,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1000), 1, - anon_sym_RBRACE, - STATE(812), 1, + ACTIONS(1039), 1, + anon_sym_RBRACK, + STATE(60), 1, sym_identifier, - STATE(1320), 1, - sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20545,13 +22281,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20565,7 +22304,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20580,7 +22319,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3527] = 28, + [2760] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20597,8 +22336,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20613,17 +22350,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1002), 1, - anon_sym_RBRACE, - STATE(812), 1, + ACTIONS(1041), 1, + anon_sym_RBRACK, + STATE(60), 1, sym_identifier, - STATE(1137), 1, - sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20631,13 +22366,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20651,7 +22389,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20666,7 +22404,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3641] = 27, + [2872] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20683,8 +22421,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20699,15 +22435,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1004), 1, + ACTIONS(1043), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20715,13 +22451,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1267), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20735,7 +22474,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20750,7 +22489,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3752] = 27, + [2984] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20767,8 +22506,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20783,15 +22520,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(812), 1, + ACTIONS(1045), 1, + anon_sym_RBRACK, + STATE(60), 1, sym_identifier, - STATE(1320), 1, - sym_map_entry, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20799,13 +22536,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1434), 2, + STATE(1287), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20819,7 +22559,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20834,7 +22574,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3863] = 27, + [3096] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20851,8 +22591,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20867,15 +22605,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1006), 1, + ACTIONS(1047), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20883,13 +22621,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20903,7 +22644,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -20918,7 +22659,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [3974] = 27, + [3208] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -20935,8 +22676,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -20951,15 +22690,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1008), 1, - anon_sym_RBRACK, - STATE(86), 1, + ACTIONS(1049), 1, + anon_sym_SEMI, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -20967,13 +22706,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1607), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -20987,7 +22729,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21002,7 +22744,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4085] = 27, + [3320] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21019,8 +22761,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21035,15 +22775,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1010), 1, + ACTIONS(1051), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21051,13 +22791,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1260), 2, + STATE(1344), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21071,7 +22814,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21086,7 +22829,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4196] = 27, + [3432] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21103,8 +22846,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21119,15 +22860,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1012), 1, - anon_sym_SEMI, - STATE(86), 1, + ACTIONS(1053), 1, + anon_sym_RBRACK, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21135,13 +22876,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1415), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21155,7 +22899,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21170,7 +22914,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4307] = 27, + [3544] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21187,8 +22931,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21203,15 +22945,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1014), 1, - anon_sym_RBRACK, - STATE(86), 1, + STATE(907), 1, sym_identifier, - STATE(1369), 1, + STATE(1406), 1, + sym_map_entry, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21219,13 +22961,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1268), 2, + STATE(1514), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21239,7 +22984,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21254,7 +22999,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4418] = 27, + [3656] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21271,8 +23016,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21287,15 +23030,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1016), 1, + ACTIONS(1055), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21303,13 +23046,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21323,7 +23069,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21338,7 +23084,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4529] = 27, + [3768] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21355,8 +23101,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21371,15 +23115,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1018), 1, + ACTIONS(1057), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21387,13 +23131,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1156), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21407,7 +23154,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21422,7 +23169,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4640] = 27, + [3880] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21439,8 +23186,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21455,15 +23200,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1020), 1, + ACTIONS(1059), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21471,13 +23216,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1285), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21491,7 +23239,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21506,7 +23254,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4751] = 27, + [3992] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21523,8 +23271,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21539,15 +23285,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1022), 1, + ACTIONS(1061), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21555,13 +23301,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21575,7 +23324,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21590,7 +23339,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4862] = 27, + [4104] = 27, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21607,8 +23356,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21623,15 +23370,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1024), 1, + ACTIONS(1063), 1, anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21639,13 +23386,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1158), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21659,7 +23409,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21674,7 +23424,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [4973] = 27, + [4216] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21691,8 +23441,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21707,15 +23455,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1026), 1, - anon_sym_SEMI, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21723,13 +23469,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1372), 2, + STATE(1612), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21743,7 +23492,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21758,62 +23507,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5084] = 27, - ACTIONS(9), 1, + [4325] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(1028), 1, - anon_sym_RBRACK, - STATE(86), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1069), 1, + anon_sym_PIPE, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(897), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21827,7 +23575,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(824), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21842,7 +23590,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5195] = 27, + [4434] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21859,8 +23607,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21875,15 +23621,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1030), 1, - anon_sym_RBRACK, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21891,13 +23635,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(493), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21911,7 +23658,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -21926,7 +23673,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5306] = 26, + [4543] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -21943,8 +23690,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -21959,13 +23704,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -21973,13 +23718,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1390), 2, + STATE(1503), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -21993,7 +23741,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22008,7 +23756,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5414] = 26, + [4652] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -22021,6 +23769,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, + anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, ACTIONS(33), 1, @@ -22035,19 +23785,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1032), 1, - anon_sym_LPAREN, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -22055,13 +23801,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(27), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(493), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22090,7 +23839,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5522] = 26, + [4761] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -22107,8 +23856,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -22123,13 +23870,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -22137,13 +23884,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1317), 2, + STATE(1302), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22157,7 +23907,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22172,60 +23922,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5630] = 26, - ACTIONS(9), 1, + [4870] = 26, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1079), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1286), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(681), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22239,7 +23990,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(812), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22254,60 +24005,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5738] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [4979] = 26, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(1110), 2, + STATE(694), 2, sym__full_expression, sym_ternary_expression, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22321,7 +24073,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(713), 14, + STATE(812), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22336,60 +24088,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5846] = 26, - ACTIONS(9), 1, + [5088] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1081), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(1352), 2, + STATE(701), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22403,7 +24156,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(735), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22418,60 +24171,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [5954] = 26, - ACTIONS(9), 1, + [5197] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1291), 2, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(681), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22485,7 +24239,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(736), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22500,60 +24254,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6062] = 26, - ACTIONS(9), 1, + [5306] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(445), 2, + STATE(694), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22567,7 +24322,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(736), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22582,60 +24337,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6170] = 26, - ACTIONS(9), 1, + [5415] = 26, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, + aux_sym_identifier_token1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1107), 1, + anon_sym_PIPE, + STATE(609), 1, sym_identifier, - STATE(1369), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1467), 2, + STATE(517), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(527), 2, + sym_double_string, + sym_single_string, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22649,7 +24405,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(535), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22664,60 +24420,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6278] = 26, - ACTIONS(598), 1, + [5524] = 26, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(645), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(701), 2, + sym__full_expression, + sym_ternary_expression, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22731,7 +24488,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(712), 14, + STATE(812), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22746,60 +24503,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6386] = 26, - ACTIONS(598), 1, + [5633] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(653), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(817), 2, sym__full_expression, sym_ternary_expression, - STATE(658), 2, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22813,7 +24571,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(712), 14, + STATE(827), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22828,60 +24586,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6494] = 26, - ACTIONS(578), 1, + [5742] = 26, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(646), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(855), 2, sym__full_expression, sym_ternary_expression, - STATE(658), 2, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22895,7 +24654,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(711), 14, + STATE(827), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22910,60 +24669,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6602] = 26, - ACTIONS(578), 1, + [5851] = 26, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(645), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(897), 2, + sym__full_expression, + sym_ternary_expression, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -22977,7 +24737,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(690), 14, + STATE(827), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -22992,60 +24752,144 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6710] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [5960] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(72), 2, + sym__full_expression, + sym_ternary_expression, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(723), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [6069] = 26, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, + sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, + anon_sym_DQUOTE, + ACTIONS(19), 1, + anon_sym_SQUOTE, + ACTIONS(21), 1, + sym_raw_string, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(31), 1, anon_sym_PIPE, - STATE(640), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(653), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(82), 2, sym__full_expression, sym_ternary_expression, - STATE(658), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23059,7 +24903,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(690), 14, + STATE(723), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23074,60 +24918,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6818] = 26, - ACTIONS(586), 1, + [6178] = 26, + ACTIONS(617), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(619), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(621), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(623), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(625), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(627), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(1058), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(1062), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(1064), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(1066), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(1068), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(609), 1, sym_identifier, - STATE(1405), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(559), 2, - sym__full_expression, - sym_ternary_expression, - STATE(611), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(963), 2, + sym__full_expression, + sym_ternary_expression, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23141,7 +24986,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(588), 14, + STATE(536), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23156,60 +25001,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [6926] = 26, - ACTIONS(598), 1, + [6287] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, - anon_sym_PIPE, - STATE(640), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(646), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(1592), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23223,7 +25069,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(712), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23238,60 +25084,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7034] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [6396] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(644), 1, - anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1080), 1, - anon_sym_LPAREN, - ACTIONS(1082), 1, - anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(60), 1, sym_identifier, - STATE(1437), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, - sym_double_string, - sym_single_string, - STATE(768), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(72), 2, sym__full_expression, sym_ternary_expression, - STATE(747), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23305,7 +25152,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(765), 14, + STATE(110), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23320,60 +25167,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7142] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [6505] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(644), 1, - anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1080), 1, - anon_sym_LPAREN, - ACTIONS(1082), 1, - anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, - anon_sym_PIPE, - STATE(749), 1, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1437), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(782), 2, + STATE(1585), 2, sym__full_expression, sym_ternary_expression, - STATE(747), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23387,7 +25235,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(765), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23402,60 +25250,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7250] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [6614] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(644), 1, - anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1080), 1, - anon_sym_LPAREN, - ACTIONS(1082), 1, - anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, - anon_sym_PIPE, - STATE(749), 1, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1437), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(748), 2, - sym__full_expression, - sym_ternary_expression, - STATE(757), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(1529), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23469,7 +25318,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(765), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23484,7 +25333,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7358] = 26, + [6723] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -23501,8 +25350,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -23517,13 +25364,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -23531,13 +25378,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1375), 2, + STATE(1574), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23551,7 +25401,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23566,60 +25416,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7466] = 26, - ACTIONS(598), 1, + [6832] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, - anon_sym_PIPE, - STATE(640), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(645), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(1581), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23633,7 +25484,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(709), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23648,7 +25499,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7574] = 26, + [6941] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -23665,8 +25516,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -23681,13 +25530,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -23695,13 +25544,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1376), 2, + STATE(1513), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23715,7 +25567,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23730,60 +25582,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7682] = 26, - ACTIONS(598), 1, - sym_integer_literal, - ACTIONS(600), 1, - sym_float_literal, - ACTIONS(604), 1, - anon_sym_nil, - ACTIONS(606), 1, - anon_sym_DQUOTE, - ACTIONS(608), 1, - anon_sym_SQUOTE, - ACTIONS(610), 1, - sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + [7050] = 26, + ACTIONS(617), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(619), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(621), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(623), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(625), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(627), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1087), 1, + sym_integer_literal, + ACTIONS(1089), 1, + sym_float_literal, + ACTIONS(1093), 1, + anon_sym_nil, + ACTIONS(1095), 1, + anon_sym_DQUOTE, + ACTIONS(1097), 1, + anon_sym_SQUOTE, + ACTIONS(1099), 1, + sym_raw_string, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1105), 1, + anon_sym_LBRACE, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(609), 1, sym_identifier, - STATE(1470), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(653), 2, + STATE(517), 2, sym__full_expression, sym_ternary_expression, - STATE(658), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23797,7 +25650,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(709), 14, + STATE(536), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23812,7 +25665,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7790] = 26, + [7159] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -23829,10 +25682,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -23845,13 +25694,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -23859,13 +25710,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1537), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(82), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23879,7 +25733,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(110), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23894,7 +25748,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [7898] = 26, + [7268] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -23911,6 +25765,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -23923,17 +25779,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -23941,13 +25793,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(68), 2, + STATE(1490), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -23961,7 +25816,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(110), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -23976,60 +25831,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8006] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [7377] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, - anon_sym_PIPE, - STATE(640), 1, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(646), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(1438), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24043,7 +25899,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(713), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24058,60 +25914,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8114] = 26, - ACTIONS(9), 1, + [7486] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1081), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(1448), 2, + STATE(1190), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24125,7 +25982,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(738), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24140,7 +25997,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8222] = 26, + [7595] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -24157,8 +26014,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -24173,13 +26028,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -24187,13 +26042,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1377), 2, + STATE(1479), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24207,7 +26065,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24222,60 +26080,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8330] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [7704] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, - anon_sym_PIPE, - STATE(640), 1, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1062), 2, + STATE(1424), 2, sym__full_expression, sym_ternary_expression, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24289,7 +26148,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(713), 14, + STATE(928), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24304,7 +26163,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8438] = 26, + [7813] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -24321,8 +26180,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -24337,13 +26194,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -24351,13 +26208,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(78), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(63), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24371,7 +26231,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(664), 14, + STATE(723), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24386,76 +26246,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8546] = 13, - ACTIONS(1088), 1, - aux_sym_identifier_token1, - ACTIONS(1097), 1, - anon_sym_DQUOTE, - ACTIONS(1100), 1, - anon_sym_SQUOTE, - ACTIONS(1103), 1, - sym_raw_string, - ACTIONS(1108), 1, - anon_sym_LPAREN, - ACTIONS(1111), 1, - anon_sym_LBRACK, - ACTIONS(1114), 1, - anon_sym_LBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - ACTIONS(1106), 3, - anon_sym_RBRACE, - anon_sym_RPAREN, - anon_sym_RBRACK, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1091), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1094), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [8628] = 26, + [7922] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -24472,6 +26263,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -24484,17 +26277,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -24502,13 +26291,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(78), 2, + STATE(1601), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24522,7 +26314,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(110), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24537,60 +26329,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8736] = 26, - ACTIONS(578), 1, + [8031] = 26, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1083), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(645), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(701), 2, + sym__full_expression, + sym_ternary_expression, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24604,7 +26397,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(711), 14, + STATE(736), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24619,60 +26412,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8844] = 26, - ACTIONS(9), 1, + [8140] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1516), 2, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(681), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24686,7 +26480,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(737), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24701,60 +26495,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [8952] = 26, - ACTIONS(578), 1, + [8249] = 26, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1083), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(653), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(694), 2, + sym__full_expression, + sym_ternary_expression, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24768,7 +26563,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(711), 14, + STATE(737), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24783,7 +26578,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9060] = 26, + [8358] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -24800,10 +26595,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -24816,13 +26607,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -24830,13 +26623,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1499), 2, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(72), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24850,7 +26646,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(899), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24865,7 +26661,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9168] = 26, + [8467] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -24882,10 +26678,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -24898,13 +26690,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -24912,13 +26706,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1318), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(42), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -24932,7 +26729,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -24947,60 +26744,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9276] = 26, - ACTIONS(9), 1, + [8576] = 26, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, + aux_sym_identifier_token1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1107), 1, + anon_sym_PIPE, + STATE(609), 1, sym_identifier, - STATE(1369), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(59), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(547), 2, + sym__full_expression, + sym_ternary_expression, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25014,7 +26812,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(664), 14, + STATE(535), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25029,7 +26827,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9384] = 26, + [8685] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -25046,10 +26844,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -25062,13 +26856,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -25076,13 +26872,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1340), 2, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(72), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25096,7 +26895,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(813), 14, + STATE(910), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25111,60 +26910,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9492] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [8794] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, - anon_sym_PIPE, - STATE(640), 1, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(646), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(1528), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25178,7 +26978,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(690), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25193,60 +26993,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9600] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [8903] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, - anon_sym_PIPE, - STATE(640), 1, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(645), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(1631), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25260,7 +27061,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(693), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25275,142 +27076,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9708] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [9012] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, - anon_sym_match, - ACTIONS(618), 1, - anon_sym_spawn, - ACTIONS(620), 1, - anon_sym_chan, - ACTIONS(622), 1, - anon_sym_send, - ACTIONS(624), 1, - anon_sym_recv, - ACTIONS(626), 1, - anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(31), 1, anon_sym_PIPE, - STATE(640), 1, - sym_identifier, - STATE(1470), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(602), 2, - anon_sym_true, - anon_sym_false, - STATE(653), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, - sym_double_string, - sym_single_string, - STATE(638), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(693), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [9816] = 26, - ACTIONS(586), 1, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, - sym_integer_literal, - ACTIONS(1058), 1, - sym_float_literal, - ACTIONS(1062), 1, - anon_sym_nil, - ACTIONS(1064), 1, - anon_sym_DQUOTE, - ACTIONS(1066), 1, - anon_sym_SQUOTE, - ACTIONS(1068), 1, - sym_raw_string, - ACTIONS(1070), 1, - anon_sym_LPAREN, - ACTIONS(1072), 1, - anon_sym_LBRACK, - ACTIONS(1074), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, - anon_sym_PIPE, - STATE(600), 1, + STATE(60), 1, sym_identifier, - STATE(1405), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(559), 2, - sym__full_expression, - sym_ternary_expression, - STATE(611), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(1405), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25424,7 +27144,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(589), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25439,7 +27159,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [9924] = 26, + [9121] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -25456,8 +27176,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -25472,13 +27190,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -25486,13 +27204,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1538), 2, + STATE(1622), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25506,7 +27227,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25521,60 +27242,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10032] = 26, - ACTIONS(586), 1, + [9230] = 26, + ACTIONS(617), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(619), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(621), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(623), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(625), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(627), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(1058), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(1062), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(1064), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(1066), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(1068), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(609), 1, sym_identifier, - STATE(1405), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(601), 2, - sym__full_expression, - sym_ternary_expression, - STATE(611), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(558), 2, + sym__full_expression, + sym_ternary_expression, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25588,7 +27310,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(588), 14, + STATE(535), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25603,7 +27325,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10140] = 26, + [9339] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -25632,17 +27354,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1109), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -25650,13 +27370,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(68), 2, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(82), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25670,7 +27393,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(810), 14, + STATE(910), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25685,142 +27408,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10248] = 26, - ACTIONS(9), 1, + [9448] = 26, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(580), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - STATE(86), 1, - sym_identifier, - STATE(1369), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(13), 2, - anon_sym_true, - anon_sym_false, - STATE(29), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(97), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(111), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [10356] = 26, - ACTIONS(586), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, - sym_integer_literal, - ACTIONS(1058), 1, - sym_float_literal, - ACTIONS(1062), 1, - anon_sym_nil, - ACTIONS(1064), 1, - anon_sym_DQUOTE, - ACTIONS(1066), 1, - anon_sym_SQUOTE, - ACTIONS(1068), 1, - sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(563), 2, - sym__full_expression, - sym_ternary_expression, - STATE(611), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(701), 2, + sym__full_expression, + sym_ternary_expression, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25834,7 +27476,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(588), 14, + STATE(807), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25849,60 +27491,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10464] = 26, - ACTIONS(598), 1, + [9557] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, - anon_sym_PIPE, - STATE(640), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(646), 2, - sym__full_expression, - sym_ternary_expression, - STATE(658), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(1468), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25916,7 +27559,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(709), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -25931,7 +27574,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10572] = 26, + [9666] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -25948,8 +27591,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -25964,13 +27605,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -25978,13 +27619,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1394), 2, + STATE(1466), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -25998,7 +27642,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26013,60 +27657,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10680] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [9775] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(644), 1, - anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1080), 1, - anon_sym_LPAREN, - ACTIONS(1082), 1, - anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(749), 1, + ACTIONS(1111), 1, + anon_sym_LPAREN, + STATE(60), 1, sym_identifier, - STATE(1437), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, - sym_double_string, - sym_single_string, - STATE(768), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(35), 2, sym__full_expression, sym_ternary_expression, - STATE(747), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26080,7 +27725,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(763), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26095,7 +27740,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10788] = 26, + [9884] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26112,6 +27757,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -26124,17 +27771,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -26142,13 +27785,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(78), 2, + STATE(1392), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26162,7 +27808,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(810), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26177,7 +27823,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [10896] = 26, + [9993] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26194,10 +27840,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -26210,13 +27852,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -26224,13 +27868,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1396), 2, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(63), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26244,7 +27891,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(899), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26259,60 +27906,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11004] = 26, - ACTIONS(578), 1, + [10102] = 26, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(782), 2, + STATE(1172), 2, sym__full_expression, sym_ternary_expression, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26326,7 +27974,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(763), 14, + STATE(738), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26341,60 +27989,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11112] = 26, - ACTIONS(578), 1, + [10211] = 26, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1083), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(748), 2, - sym__full_expression, - sym_ternary_expression, - STATE(757), 2, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(701), 2, + sym__full_expression, + sym_ternary_expression, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26408,7 +28057,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(763), 14, + STATE(737), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26423,7 +28072,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11220] = 26, + [10320] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26440,10 +28089,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -26456,13 +28101,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -26470,13 +28117,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(445), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(63), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26490,7 +28140,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(110), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26505,7 +28155,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11328] = 26, + [10429] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26522,8 +28172,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -26538,13 +28186,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -26552,13 +28200,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(68), 2, + STATE(493), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26572,7 +28223,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(664), 14, + STATE(928), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26587,7 +28238,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11436] = 26, + [10538] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26616,17 +28267,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1109), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -26634,13 +28283,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(59), 2, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(63), 2, sym__full_expression, sym_ternary_expression, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26654,7 +28306,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(814), 14, + STATE(910), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26669,7 +28321,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11544] = 26, + [10647] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26686,8 +28338,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -26702,13 +28352,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -26716,13 +28366,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1234), 2, + STATE(1549), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26736,7 +28389,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26751,60 +28404,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11652] = 26, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [10756] = 26, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(640), 1, + ACTIONS(1113), 1, + anon_sym_LPAREN, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(646), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(25), 2, sym__full_expression, sym_ternary_expression, - STATE(658), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26818,7 +28472,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(693), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26833,7 +28487,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11760] = 26, + [10865] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26846,8 +28500,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, ACTIONS(33), 1, @@ -26862,17 +28514,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + ACTIONS(1115), 1, + anon_sym_LPAREN, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -26880,13 +28532,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(59), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(26), 2, sym__full_expression, sym_ternary_expression, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -26900,7 +28555,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(110), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -26915,7 +28570,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [11868] = 26, + [10974] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -26928,14 +28583,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -26948,95 +28597,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, - sym_identifier, - STATE(1369), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(13), 2, - anon_sym_true, - anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(445), 2, - sym__full_expression, - sym_ternary_expression, - STATE(97), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(813), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [11976] = 26, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + ACTIONS(1117), 1, + anon_sym_LPAREN, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27044,13 +28615,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(59), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(28), 2, sym__full_expression, sym_ternary_expression, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27064,7 +28638,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(810), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27079,7 +28653,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12084] = 26, + [11083] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27096,8 +28670,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27112,13 +28684,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27126,13 +28698,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1544), 2, + STATE(1462), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27146,7 +28721,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27161,7 +28736,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12192] = 26, + [11192] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27174,8 +28749,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, + anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -27188,19 +28767,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - ACTIONS(1121), 1, - anon_sym_LPAREN, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27208,13 +28781,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(24), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(1449), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27228,7 +28804,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27243,7 +28819,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12300] = 26, + [11301] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27256,8 +28832,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, + anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -27270,19 +28850,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - ACTIONS(1123), 1, - anon_sym_LPAREN, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27290,13 +28864,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(25), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(1475), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27310,7 +28887,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27325,7 +28902,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12408] = 26, + [11410] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27342,8 +28919,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27358,13 +28933,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27372,13 +28947,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1382), 2, + STATE(1477), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27392,7 +28970,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27407,7 +28985,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12516] = 26, + [11519] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27424,8 +29002,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27440,13 +29016,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27454,13 +29030,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1480), 2, + STATE(1505), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27474,7 +29053,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27489,7 +29068,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12624] = 26, + [11628] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27506,8 +29085,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27522,13 +29099,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27536,13 +29113,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1536), 2, + STATE(1506), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27556,7 +29136,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27571,7 +29151,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12732] = 26, + [11737] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27588,8 +29168,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27604,13 +29182,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27618,13 +29196,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1506), 2, + STATE(1507), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27638,7 +29219,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27653,7 +29234,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12840] = 26, + [11846] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27670,8 +29251,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27686,13 +29265,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27700,13 +29279,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1509), 2, + STATE(1510), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27720,7 +29302,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27735,7 +29317,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [12948] = 26, + [11955] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27752,8 +29334,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27768,13 +29348,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27782,13 +29362,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1424), 2, + STATE(1511), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27802,7 +29385,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27817,7 +29400,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13056] = 26, + [12064] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27834,8 +29417,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27850,13 +29431,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27864,13 +29445,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1433), 2, + STATE(1519), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27884,7 +29468,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27899,7 +29483,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13164] = 26, + [12173] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27916,8 +29500,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -27932,13 +29514,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -27946,13 +29528,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1454), 2, + STATE(1542), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -27966,7 +29551,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -27981,7 +29566,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13272] = 26, + [12282] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -27998,8 +29583,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -28014,13 +29597,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28028,13 +29611,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1508), 2, + STATE(1583), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28048,7 +29634,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28063,60 +29649,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13380] = 26, - ACTIONS(9), 1, + [12391] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1081), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(1541), 2, + STATE(694), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28130,7 +29717,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(735), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28145,7 +29732,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13488] = 26, + [12500] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28162,6 +29749,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -28174,17 +29763,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28192,13 +29777,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(78), 2, + STATE(1375), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28212,7 +29800,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(814), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28227,7 +29815,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13596] = 26, + [12609] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28244,8 +29832,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -28260,13 +29846,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28274,13 +29860,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1500), 2, + STATE(1594), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28294,7 +29883,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28309,7 +29898,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13704] = 26, + [12718] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28326,8 +29915,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -28342,13 +29929,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28356,13 +29943,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1547), 2, + STATE(1595), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28376,7 +29966,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28391,7 +29981,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13812] = 26, + [12827] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28408,6 +29998,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -28420,17 +30012,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28438,13 +30026,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(30), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(1637), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28458,7 +30049,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28473,7 +30064,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [13920] = 26, + [12936] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28490,8 +30081,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -28506,13 +30095,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28520,13 +30109,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1417), 2, + STATE(1498), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28540,7 +30132,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28555,7 +30147,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14028] = 26, + [13045] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28572,8 +30164,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -28588,13 +30178,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28602,13 +30192,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1374), 2, + STATE(1628), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28622,7 +30215,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28637,60 +30230,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14136] = 26, - ACTIONS(9), 1, + [13154] = 26, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1079), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1378), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(681), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28704,7 +30298,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(807), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28719,7 +30313,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14244] = 26, + [13263] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28736,8 +30330,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -28752,13 +30344,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28766,13 +30358,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1411), 2, + STATE(1474), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28786,7 +30381,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28801,60 +30396,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14352] = 26, - ACTIONS(586), 1, + [13372] = 26, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, + anon_sym_DQUOTE, + ACTIONS(595), 1, + anon_sym_SQUOTE, + ACTIONS(597), 1, + sym_raw_string, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, - sym_integer_literal, - ACTIONS(1058), 1, - sym_float_literal, - ACTIONS(1062), 1, - anon_sym_nil, - ACTIONS(1064), 1, - anon_sym_DQUOTE, - ACTIONS(1066), 1, - anon_sym_SQUOTE, - ACTIONS(1068), 1, - sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(867), 2, + STATE(694), 2, sym__full_expression, sym_ternary_expression, - STATE(557), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28868,7 +30464,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(589), 14, + STATE(807), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28883,7 +30479,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14460] = 26, + [13481] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -28900,10 +30496,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -28916,13 +30508,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -28930,13 +30524,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1441), 2, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(82), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -28950,7 +30547,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(899), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -28965,60 +30562,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14568] = 26, - ACTIONS(9), 1, + [13590] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1081), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(1423), 2, + STATE(701), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29032,7 +30630,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(738), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29047,60 +30645,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14676] = 26, - ACTIONS(9), 1, + [13699] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(68), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(817), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(886), 2, + sym_double_string, + sym_single_string, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29114,7 +30713,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(814), 14, + STATE(824), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29129,7 +30728,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14784] = 26, + [13808] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29146,8 +30745,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -29162,13 +30759,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29176,13 +30773,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1543), 2, + STATE(1588), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29196,7 +30796,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29211,60 +30811,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [14892] = 26, - ACTIONS(578), 1, + [13917] = 26, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(1061), 2, + STATE(1187), 2, sym__full_expression, sym_ternary_expression, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29278,7 +30879,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(713), 14, + STATE(738), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29293,7 +30894,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15000] = 26, + [14026] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29310,8 +30911,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -29326,13 +30925,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29340,13 +30939,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1525), 2, + STATE(1600), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29360,7 +30962,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29375,7 +30977,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15108] = 26, + [14135] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29388,8 +30990,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, + anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -29402,19 +31008,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - ACTIONS(1125), 1, - anon_sym_LPAREN, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29422,13 +31022,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(37), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(1558), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29442,7 +31045,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29457,7 +31060,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15216] = 26, + [14244] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29470,14 +31073,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -29490,13 +31087,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + ACTIONS(1119), 1, + anon_sym_LPAREN, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29504,13 +31105,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1502), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(41), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29524,7 +31128,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29539,7 +31143,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15324] = 26, + [14353] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29566,19 +31170,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - ACTIONS(1127), 1, + ACTIONS(1121), 1, anon_sym_LPAREN, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29586,13 +31188,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(42), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(24), 2, sym__full_expression, sym_ternary_expression, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29621,7 +31226,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15432] = 26, + [14462] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29634,8 +31239,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, + anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -29648,19 +31257,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - ACTIONS(1129), 1, - anon_sym_LPAREN, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29668,13 +31271,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(33), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(1639), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29688,7 +31294,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29703,7 +31309,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15540] = 26, + [14571] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29720,8 +31326,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -29736,13 +31340,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29750,13 +31354,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1529), 2, + STATE(1459), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29770,7 +31377,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29785,7 +31392,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15648] = 26, + [14680] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29802,8 +31409,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -29818,13 +31423,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29832,13 +31437,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1492), 2, + STATE(1562), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29852,7 +31460,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29867,7 +31475,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15756] = 26, + [14789] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29884,8 +31492,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -29900,13 +31506,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29914,13 +31520,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1545), 2, + STATE(1563), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -29934,7 +31543,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -29949,7 +31558,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15864] = 26, + [14898] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -29966,8 +31575,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -29982,13 +31589,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -29996,13 +31603,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1365), 2, + STATE(1568), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30016,7 +31626,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30031,7 +31641,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [15972] = 26, + [15007] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30048,8 +31658,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -30064,13 +31672,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30078,13 +31686,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1388), 2, + STATE(1620), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30098,7 +31709,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30113,7 +31724,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16080] = 26, + [15116] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30130,10 +31741,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -30146,13 +31753,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30160,13 +31769,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1530), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(33), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30180,7 +31792,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30195,7 +31807,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16188] = 26, + [15225] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30212,6 +31824,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -30224,17 +31838,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30242,13 +31852,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(35), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(1641), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30262,7 +31875,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30277,7 +31890,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16296] = 26, + [15334] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30294,8 +31907,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -30310,13 +31921,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30324,13 +31935,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1463), 2, + STATE(1457), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30344,7 +31958,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30359,7 +31973,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16404] = 26, + [15443] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30376,8 +31990,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -30392,13 +32004,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30406,13 +32018,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1469), 2, + STATE(1460), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30426,7 +32041,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30441,7 +32056,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16512] = 26, + [15552] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30454,14 +32069,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -30474,13 +32083,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + ACTIONS(1123), 1, + anon_sym_LPAREN, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30488,13 +32101,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1503), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(32), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30508,7 +32124,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30523,7 +32139,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16620] = 26, + [15661] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30536,8 +32152,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, + ACTIONS(23), 1, + anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -30550,19 +32170,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - ACTIONS(1131), 1, - anon_sym_LPAREN, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30570,13 +32184,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(38), 2, - sym__full_expression, - sym_ternary_expression, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(1587), 2, + sym__full_expression, + sym_ternary_expression, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30590,7 +32207,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(111), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30605,7 +32222,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16728] = 26, + [15770] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30622,8 +32239,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -30638,13 +32253,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30652,13 +32267,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1475), 2, + STATE(1591), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30672,7 +32290,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30687,7 +32305,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16836] = 26, + [15879] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30704,8 +32322,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -30720,13 +32336,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30734,13 +32350,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1510), 2, + STATE(1564), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30754,7 +32373,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30769,7 +32388,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [16944] = 26, + [15988] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30786,8 +32405,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -30802,13 +32419,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30816,95 +32433,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1468), 2, - sym__full_expression, - sym_ternary_expression, - STATE(97), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(678), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [17052] = 26, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, + ACTIONS(29), 2, anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, - sym_identifier, - STATE(1369), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(13), 2, - anon_sym_true, - anon_sym_false, - STATE(67), 2, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1361), 2, + STATE(1478), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -30918,7 +32456,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -30933,7 +32471,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17160] = 26, + [16097] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -30950,8 +32488,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -30966,13 +32502,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -30980,13 +32516,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1362), 2, + STATE(1481), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31000,7 +32539,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31015,7 +32554,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17268] = 26, + [16206] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31032,8 +32571,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31048,13 +32585,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31062,13 +32599,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1368), 2, + STATE(1532), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31082,7 +32622,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31097,7 +32637,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17376] = 26, + [16315] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31126,17 +32666,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31144,13 +32682,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(41), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(39), 2, sym__full_expression, sym_ternary_expression, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31179,7 +32720,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17484] = 26, + [16424] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31196,8 +32737,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31212,13 +32751,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31226,13 +32765,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1389), 2, + STATE(1473), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31246,7 +32788,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31261,7 +32803,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17592] = 26, + [16533] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31278,8 +32820,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31294,13 +32834,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31308,13 +32848,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1528), 2, + STATE(1476), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31328,7 +32871,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31343,7 +32886,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17700] = 26, + [16642] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31360,8 +32903,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31376,13 +32917,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31390,13 +32931,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1460), 2, + STATE(1593), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31410,7 +32954,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31425,7 +32969,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17808] = 26, + [16751] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31442,8 +32986,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31458,13 +33000,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31472,13 +33014,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1494), 2, + STATE(1464), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31492,7 +33037,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31507,7 +33052,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [17916] = 26, + [16860] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31524,8 +33069,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31540,13 +33083,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31554,13 +33097,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1533), 2, + STATE(1500), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31574,7 +33120,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31589,7 +33135,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18024] = 26, + [16969] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31606,8 +33152,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31622,13 +33166,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31636,13 +33180,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1404), 2, + STATE(1531), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31656,7 +33203,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31671,7 +33218,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18132] = 26, + [17078] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31688,8 +33235,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31704,13 +33249,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31718,13 +33263,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1550), 2, + STATE(1540), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31738,7 +33286,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31753,7 +33301,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18240] = 26, + [17187] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31770,8 +33318,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31786,13 +33332,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31800,13 +33346,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1363), 2, + STATE(1550), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31820,7 +33369,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31835,7 +33384,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18348] = 26, + [17296] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31852,8 +33401,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -31868,13 +33415,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31882,13 +33429,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1367), 2, + STATE(1559), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31902,7 +33452,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -31917,7 +33467,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18456] = 26, + [17405] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -31944,19 +33494,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - ACTIONS(1133), 1, + ACTIONS(1125), 1, anon_sym_LPAREN, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -31964,13 +33512,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(39), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(38), 2, sym__full_expression, sym_ternary_expression, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -31999,7 +33550,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18564] = 26, + [17514] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32016,8 +33567,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32032,13 +33581,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32046,13 +33595,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1407), 2, + STATE(1618), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32066,7 +33618,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32081,7 +33633,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18672] = 26, + [17623] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32098,8 +33650,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32114,13 +33664,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32128,13 +33678,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1439), 2, + STATE(1635), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32148,7 +33701,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32163,7 +33716,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18780] = 26, + [17732] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32180,8 +33733,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32196,13 +33747,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32210,13 +33761,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1462), 2, + STATE(1642), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32230,7 +33784,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32245,142 +33799,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [18888] = 26, - ACTIONS(9), 1, + [17841] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, - sym_identifier, - STATE(1369), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(13), 2, - anon_sym_true, - anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1379), 2, - sym__full_expression, - sym_ternary_expression, - STATE(97), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(678), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [18996] = 26, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1484), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(855), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(886), 2, + sym_double_string, + sym_single_string, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32394,7 +33867,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(824), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32409,7 +33882,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19104] = 26, + [17950] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32426,8 +33899,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32442,13 +33913,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32456,13 +33927,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1532), 2, + STATE(1494), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32476,7 +33950,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32491,7 +33965,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19212] = 26, + [18059] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32508,8 +33982,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32524,13 +33996,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32538,13 +34010,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1414), 2, + STATE(1496), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32558,7 +34033,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32573,7 +34048,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19320] = 26, + [18168] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32590,8 +34065,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32606,13 +34079,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32620,13 +34093,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1416), 2, + STATE(1499), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32640,7 +34116,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32655,7 +34131,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19428] = 26, + [18277] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32672,8 +34148,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32688,13 +34162,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32702,13 +34176,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1419), 2, + STATE(1535), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32722,7 +34199,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32737,7 +34214,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19536] = 26, + [18386] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32754,8 +34231,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32770,13 +34245,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32784,13 +34259,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1447), 2, + STATE(1537), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32804,7 +34282,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32819,7 +34297,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19644] = 26, + [18495] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32836,8 +34314,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32852,13 +34328,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32866,13 +34342,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1449), 2, + STATE(1541), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32886,7 +34365,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32901,7 +34380,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19752] = 26, + [18604] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -32918,8 +34397,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -32934,13 +34411,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -32948,13 +34425,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1453), 2, + STATE(1543), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -32968,7 +34448,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -32983,7 +34463,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19860] = 26, + [18713] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -33000,8 +34480,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -33016,13 +34494,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -33030,13 +34508,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1455), 2, + STATE(1545), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33050,7 +34531,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33065,7 +34546,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [19968] = 26, + [18822] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -33082,8 +34563,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -33098,13 +34577,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -33112,13 +34591,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1457), 2, + STATE(1569), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33132,7 +34614,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33147,7 +34629,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20076] = 26, + [18931] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -33164,8 +34646,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -33180,13 +34660,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -33194,13 +34674,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1481), 2, + STATE(1570), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33214,7 +34697,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33229,60 +34712,61 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20184] = 26, - ACTIONS(9), 1, + [19040] = 26, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1081), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1482), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(681), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33296,7 +34780,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(735), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33311,7 +34795,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20292] = 26, + [19149] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -33328,8 +34812,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -33344,13 +34826,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -33358,13 +34840,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1520), 2, + STATE(1611), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33378,7 +34863,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33393,7 +34878,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20400] = 26, + [19258] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -33410,8 +34895,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -33426,13 +34909,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -33440,13 +34923,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1450), 2, + STATE(1619), 2, sym__full_expression, sym_ternary_expression, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33460,7 +34946,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(678), 14, + STATE(731), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33475,7 +34961,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20508] = 25, + [19367] = 26, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -33504,17 +34990,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -33522,10 +35006,16 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(27), 2, + sym__full_expression, + sym_ternary_expression, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33539,7 +35029,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(811), 14, + STATE(111), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33554,7 +35044,76 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20612] = 25, + [19476] = 13, + ACTIONS(1127), 1, + aux_sym_identifier_token1, + ACTIONS(1136), 1, + anon_sym_DQUOTE, + ACTIONS(1139), 1, + anon_sym_SQUOTE, + ACTIONS(1142), 1, + sym_raw_string, + ACTIONS(1147), 1, + anon_sym_LPAREN, + ACTIONS(1150), 1, + anon_sym_LBRACK, + ACTIONS(1153), 1, + anon_sym_LBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + ACTIONS(1145), 3, + anon_sym_RBRACE, + anon_sym_RPAREN, + anon_sym_RBRACK, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1130), 17, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_EQ, + ACTIONS(1133), 19, + sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [19558] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -33571,8 +35130,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -33587,13 +35144,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -33601,10 +35158,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33618,7 +35178,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(680), 14, + STATE(720), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33633,57 +35193,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20716] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [19663] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33697,7 +35258,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(719), 14, + STATE(932), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33712,191 +35273,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [20820] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1149), 1, - anon_sym_RPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + [19768] = 25, + ACTIONS(585), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(587), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [20900] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1149), 1, - anon_sym_RBRACK, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [20980] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, - sym_integer_literal, - ACTIONS(600), 1, - sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33910,7 +35338,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(703), 14, + STATE(887), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -33925,57 +35353,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21084] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [19873] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -33989,7 +35418,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(704), 14, + STATE(847), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34004,57 +35433,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21188] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [19978] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34068,7 +35498,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(699), 14, + STATE(848), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34083,57 +35513,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21292] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [20083] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34147,7 +35578,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(700), 14, + STATE(894), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34162,57 +35593,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21396] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [20188] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34226,7 +35658,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(625), 14, + STATE(896), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34241,57 +35673,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21500] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [20293] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34305,7 +35738,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(705), 14, + STATE(712), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34320,124 +35753,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21604] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + [20398] = 25, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1149), 1, - anon_sym_RBRACE, - ACTIONS(1151), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, + ACTIONS(1158), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(637), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [21684] = 25, - ACTIONS(9), 1, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(818), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [20503] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1158), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34451,7 +35898,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(75), 14, + STATE(819), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34466,124 +35913,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21788] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + [20608] = 25, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(1159), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(297), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1155), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1157), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [21868] = 25, - ACTIONS(586), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, - sym_integer_literal, - ACTIONS(1058), 1, - sym_float_literal, - ACTIONS(1062), 1, - anon_sym_nil, - ACTIONS(1064), 1, - anon_sym_DQUOTE, - ACTIONS(1066), 1, - anon_sym_SQUOTE, - ACTIONS(1068), 1, - sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34597,7 +35978,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(558), 14, + STATE(820), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34612,124 +35993,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [21972] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + [20713] = 25, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(1159), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(298), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1161), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1163), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [22052] = 25, - ACTIONS(586), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, - sym_integer_literal, - ACTIONS(1058), 1, - sym_float_literal, - ACTIONS(1062), 1, - anon_sym_nil, - ACTIONS(1064), 1, - anon_sym_DQUOTE, - ACTIONS(1066), 1, - anon_sym_SQUOTE, - ACTIONS(1068), 1, - sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34743,7 +36058,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(584), 14, + STATE(822), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34758,57 +36073,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22156] = 25, - ACTIONS(586), 1, + [20818] = 25, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, + anon_sym_DQUOTE, + ACTIONS(595), 1, + anon_sym_SQUOTE, + ACTIONS(597), 1, + sym_raw_string, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, - sym_integer_literal, - ACTIONS(1058), 1, - sym_float_literal, - ACTIONS(1062), 1, - anon_sym_nil, - ACTIONS(1064), 1, - anon_sym_DQUOTE, - ACTIONS(1066), 1, - anon_sym_SQUOTE, - ACTIONS(1068), 1, - sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34822,7 +36138,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(585), 14, + STATE(852), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34837,57 +36153,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22260] = 25, - ACTIONS(586), 1, + [20923] = 25, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, + anon_sym_DQUOTE, + ACTIONS(595), 1, + anon_sym_SQUOTE, + ACTIONS(597), 1, + sym_raw_string, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(588), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(590), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(592), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(594), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(596), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1054), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, - sym_integer_literal, - ACTIONS(1058), 1, - sym_float_literal, - ACTIONS(1062), 1, - anon_sym_nil, - ACTIONS(1064), 1, - anon_sym_DQUOTE, - ACTIONS(1066), 1, - anon_sym_SQUOTE, - ACTIONS(1068), 1, - sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34901,7 +36218,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(610), 14, + STATE(823), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34916,57 +36233,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22364] = 25, - ACTIONS(586), 1, - anon_sym_match, - ACTIONS(588), 1, - anon_sym_spawn, - ACTIONS(590), 1, - anon_sym_chan, - ACTIONS(592), 1, - anon_sym_send, - ACTIONS(594), 1, - anon_sym_recv, - ACTIONS(596), 1, - anon_sym_select, - ACTIONS(1054), 1, + [21028] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(1058), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(1062), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(1064), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(1066), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(1068), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -34980,7 +36298,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(578), 14, + STATE(739), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -34995,57 +36313,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22468] = 25, - ACTIONS(586), 1, - anon_sym_match, - ACTIONS(588), 1, - anon_sym_spawn, - ACTIONS(590), 1, - anon_sym_chan, - ACTIONS(592), 1, - anon_sym_send, - ACTIONS(594), 1, - anon_sym_recv, - ACTIONS(596), 1, - anon_sym_select, - ACTIONS(1054), 1, - aux_sym_identifier_token1, - ACTIONS(1056), 1, + [21133] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(1058), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(1062), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(1064), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1066), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1068), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(31), 1, anon_sym_PIPE, - STATE(600), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1405), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35059,7 +36378,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(555), 14, + STATE(721), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35074,57 +36393,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22572] = 25, - ACTIONS(586), 1, - anon_sym_match, - ACTIONS(588), 1, - anon_sym_spawn, - ACTIONS(590), 1, - anon_sym_chan, - ACTIONS(592), 1, - anon_sym_send, - ACTIONS(594), 1, - anon_sym_recv, - ACTIONS(596), 1, - anon_sym_select, - ACTIONS(1054), 1, - aux_sym_identifier_token1, - ACTIONS(1056), 1, + [21238] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(1058), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(1062), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(1064), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1066), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1068), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(31), 1, anon_sym_PIPE, - STATE(600), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1405), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35138,7 +36458,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(586), 14, + STATE(728), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35153,57 +36473,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22676] = 25, - ACTIONS(586), 1, - anon_sym_match, - ACTIONS(588), 1, - anon_sym_spawn, - ACTIONS(590), 1, - anon_sym_chan, - ACTIONS(592), 1, - anon_sym_send, - ACTIONS(594), 1, - anon_sym_recv, - ACTIONS(596), 1, - anon_sym_select, - ACTIONS(1054), 1, + [21343] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1056), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(1058), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(1062), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(1064), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(1066), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(1068), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(1070), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1072), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1074), 1, - anon_sym_LBRACE, - ACTIONS(1076), 1, - anon_sym_BANG, - ACTIONS(1078), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(600), 1, + STATE(677), 1, sym_identifier, - STATE(1405), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1060), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(611), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(557), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35217,7 +36538,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(556), 14, + STATE(759), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35232,57 +36553,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22780] = 25, - ACTIONS(578), 1, + [21448] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35296,7 +36618,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(808), 14, + STATE(760), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35311,124 +36633,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [22884] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1159), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(305), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1169), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1171), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [22964] = 25, - ACTIONS(578), 1, + [21553] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35442,7 +36698,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(803), 14, + STATE(740), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35457,57 +36713,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23068] = 25, - ACTIONS(578), 1, + [21658] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35521,7 +36778,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(805), 14, + STATE(741), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35536,57 +36793,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23172] = 25, - ACTIONS(578), 1, + [21763] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35600,7 +36858,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(809), 14, + STATE(712), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35615,57 +36873,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23276] = 25, - ACTIONS(578), 1, + [21868] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35679,7 +36938,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(802), 14, + STATE(742), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35694,57 +36953,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23380] = 25, - ACTIONS(578), 1, + [21973] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35758,7 +37018,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(778), 14, + STATE(743), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35773,57 +37033,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23484] = 25, - ACTIONS(578), 1, + [22078] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35837,7 +37098,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(804), 14, + STATE(744), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35852,124 +37113,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23588] = 13, - ACTIONS(1135), 1, + [22183] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1173), 1, - anon_sym_RBRACK, + ACTIONS(1081), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(601), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [23668] = 25, - ACTIONS(578), 1, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(745), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [22288] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -35983,7 +37258,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(806), 14, + STATE(734), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -35998,57 +37273,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23772] = 25, - ACTIONS(598), 1, + [22393] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(729), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [22498] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, + sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, + anon_sym_DQUOTE, + ACTIONS(595), 1, + anon_sym_SQUOTE, + ACTIONS(597), 1, + sym_raw_string, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1081), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(601), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36062,7 +37418,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(741), 14, + STATE(746), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36077,7 +37433,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23876] = 25, + [22603] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -36094,8 +37450,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -36110,13 +37464,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -36124,10 +37478,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36141,7 +37498,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(672), 14, + STATE(730), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36156,7 +37513,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [23980] = 25, + [22708] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -36173,8 +37530,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -36189,13 +37544,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -36203,10 +37558,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36220,7 +37578,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(667), 14, + STATE(79), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36235,7 +37593,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24084] = 25, + [22813] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -36252,6 +37610,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -36264,17 +37624,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -36282,10 +37638,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36299,7 +37658,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(831), 14, + STATE(732), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36314,7 +37673,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24188] = 25, + [22918] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -36331,8 +37690,6 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, ACTIONS(31), 1, anon_sym_PIPE, ACTIONS(33), 1, @@ -36347,13 +37704,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(578), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -36361,10 +37718,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36378,7 +37738,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(669), 14, + STATE(722), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36393,7 +37753,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24292] = 25, + [23023] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -36410,6 +37770,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -36422,17 +37784,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -36440,10 +37798,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36457,7 +37818,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(825), 14, + STATE(724), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36472,57 +37833,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24396] = 25, - ACTIONS(9), 1, + [23128] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, + aux_sym_identifier_token1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(580), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(609), 1, sym_identifier, - STATE(1369), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36536,7 +37898,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(826), 14, + STATE(611), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36551,7 +37913,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24500] = 25, + [23233] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -36568,6 +37930,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -36580,17 +37944,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -36598,10 +37958,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36615,7 +37978,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(822), 14, + STATE(725), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36630,7 +37993,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24604] = 25, + [23338] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -36647,6 +38010,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -36659,17 +38024,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -36677,10 +38038,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36694,7 +38058,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(823), 14, + STATE(726), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36709,57 +38073,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24708] = 25, - ACTIONS(9), 1, + [23443] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, + aux_sym_identifier_token1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(580), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(609), 1, sym_identifier, - STATE(1369), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36773,7 +38138,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(75), 14, + STATE(532), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36788,57 +38153,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24812] = 25, - ACTIONS(9), 1, + [23548] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, + aux_sym_identifier_token1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(1105), 1, + anon_sym_LBRACE, + ACTIONS(1107), 1, + anon_sym_PIPE, + STATE(609), 1, + sym_identifier, + STATE(1485), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, + anon_sym_true, + anon_sym_false, + STATE(527), 2, + sym_double_string, + sym_single_string, + STATE(606), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(533), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [23653] = 25, + ACTIONS(617), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(619), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(621), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(623), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(625), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(627), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, + ACTIONS(1087), 1, + sym_integer_literal, + ACTIONS(1089), 1, + sym_float_literal, + ACTIONS(1093), 1, + anon_sym_nil, + ACTIONS(1095), 1, + anon_sym_DQUOTE, + ACTIONS(1097), 1, + anon_sym_SQUOTE, + ACTIONS(1099), 1, + sym_raw_string, + ACTIONS(1101), 1, + anon_sym_LPAREN, + ACTIONS(1103), 1, + anon_sym_LBRACK, + ACTIONS(1105), 1, + anon_sym_LBRACE, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(609), 1, sym_identifier, - STATE(1369), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36852,7 +38298,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(829), 14, + STATE(508), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36867,57 +38313,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [24916] = 25, - ACTIONS(9), 1, + [23758] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, + aux_sym_identifier_token1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(580), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(609), 1, sym_identifier, - STATE(1369), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -36931,7 +38378,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(821), 14, + STATE(509), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -36946,258 +38393,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [25020] = 13, - ACTIONS(1135), 1, + [23863] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1187), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(343), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1183), 17, + ACTIONS(1087), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1185), 19, + ACTIONS(1089), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [25100] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(1093), 1, + anon_sym_nil, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - ACTIONS(1187), 1, - anon_sym_RBRACK, + ACTIONS(1107), 1, + anon_sym_PIPE, + STATE(609), 1, + sym_identifier, + STATE(1485), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(344), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1189), 17, - sym_integer_literal, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1191), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [25180] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1187), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(345), 5, - sym_identifier, + STATE(606), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1193), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1195), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [25260] = 25, - ACTIONS(578), 1, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(555), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [23968] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, - anon_sym_match, - ACTIONS(618), 1, - anon_sym_spawn, - ACTIONS(620), 1, - anon_sym_chan, - ACTIONS(622), 1, - anon_sym_send, - ACTIONS(624), 1, - anon_sym_recv, - ACTIONS(626), 1, - anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1105), 1, + anon_sym_LBRACE, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(609), 1, sym_identifier, - STATE(1470), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37211,7 +38538,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(731), 14, + STATE(510), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37226,258 +38553,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [25364] = 13, - ACTIONS(1135), 1, + [24073] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1201), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + ACTIONS(1087), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(1089), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [25444] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(1093), 1, + anon_sym_nil, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(1105), 1, anon_sym_LBRACE, - ACTIONS(1201), 1, - anon_sym_RBRACK, + ACTIONS(1107), 1, + anon_sym_PIPE, + STATE(609), 1, + sym_identifier, + STATE(1485), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [25524] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1201), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(183), 5, - sym_identifier, + STATE(606), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [25604] = 25, - ACTIONS(578), 1, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(512), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [24178] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, - anon_sym_match, - ACTIONS(618), 1, - anon_sym_spawn, - ACTIONS(620), 1, - anon_sym_chan, - ACTIONS(622), 1, - anon_sym_send, - ACTIONS(624), 1, - anon_sym_recv, - ACTIONS(626), 1, - anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1105), 1, + anon_sym_LBRACE, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(609), 1, sym_identifier, - STATE(1470), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37491,7 +38698,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(706), 14, + STATE(513), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37506,57 +38713,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [25708] = 25, - ACTIONS(578), 1, + [24283] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, - anon_sym_match, - ACTIONS(618), 1, - anon_sym_spawn, - ACTIONS(620), 1, - anon_sym_chan, - ACTIONS(622), 1, - anon_sym_send, - ACTIONS(624), 1, - anon_sym_recv, - ACTIONS(626), 1, - anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1105), 1, + anon_sym_LBRACE, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(609), 1, sym_identifier, - STATE(1470), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37570,7 +38778,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(707), 14, + STATE(534), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37585,57 +38793,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [25812] = 25, - ACTIONS(578), 1, + [24388] = 25, + ACTIONS(617), 1, + anon_sym_match, + ACTIONS(619), 1, + anon_sym_spawn, + ACTIONS(621), 1, + anon_sym_chan, + ACTIONS(623), 1, + anon_sym_send, + ACTIONS(625), 1, + anon_sym_recv, + ACTIONS(627), 1, + anon_sym_select, + ACTIONS(1085), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(1087), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(1089), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(1093), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(1095), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(1097), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(1099), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, - anon_sym_match, - ACTIONS(618), 1, - anon_sym_spawn, - ACTIONS(620), 1, - anon_sym_chan, - ACTIONS(622), 1, - anon_sym_send, - ACTIONS(624), 1, - anon_sym_recv, - ACTIONS(626), 1, - anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1101), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1103), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1105), 1, + anon_sym_LBRACE, + ACTIONS(1107), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(609), 1, sym_identifier, - STATE(1470), 1, + STATE(1485), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + STATE(527), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(606), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37649,7 +38858,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(708), 14, + STATE(514), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37664,7 +38873,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [25916] = 25, + [24493] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -37681,6 +38890,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LPAREN, ACTIONS(25), 1, anon_sym_LBRACK, + ACTIONS(31), 1, + anon_sym_PIPE, ACTIONS(33), 1, anon_sym_match, ACTIONS(37), 1, @@ -37693,17 +38904,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, - anon_sym_PIPE, - STATE(86), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -37711,10 +38918,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(29), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37728,7 +38938,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(827), 14, + STATE(727), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37743,57 +38953,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [26020] = 25, - ACTIONS(9), 1, + [24598] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37807,7 +39018,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(115), 14, + STATE(900), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37822,57 +39033,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [26124] = 25, - ACTIONS(9), 1, + [24703] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37886,7 +39098,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(107), 14, + STATE(911), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37901,57 +39113,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [26228] = 25, - ACTIONS(9), 1, + [24808] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -37965,7 +39178,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(108), 14, + STATE(923), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -37980,258 +39193,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [26332] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1207), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(357), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1203), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1205), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [26412] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1207), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(358), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1209), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1211), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [26492] = 13, - ACTIONS(1135), 1, + [24913] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1207), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(359), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1213), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1215), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [26572] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1165), 1, - anon_sym_BANG, - ACTIONS(1167), 1, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(829), 1, sym_identifier, - STATE(1437), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -38245,7 +39258,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(807), 14, + STATE(908), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -38260,325 +39273,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [26676] = 13, - ACTIONS(1135), 1, + [25018] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1217), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + ACTIONS(639), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(641), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [26756] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1217), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, + ACTIONS(645), 1, anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [26836] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, + anon_sym_match, + ACTIONS(659), 1, + anon_sym_spawn, + ACTIONS(661), 1, + anon_sym_chan, + ACTIONS(663), 1, + anon_sym_send, + ACTIONS(665), 1, + anon_sym_recv, + ACTIONS(667), 1, + anon_sym_select, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1217), 1, - anon_sym_RBRACE, + ACTIONS(1160), 1, + anon_sym_PIPE, + STATE(829), 1, + sym_identifier, + STATE(1525), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(669), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [26916] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1223), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(325), 5, - sym_identifier, + STATE(828), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1219), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1221), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [26996] = 25, - ACTIONS(598), 1, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(901), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [25123] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -38592,7 +39418,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(775), 14, + STATE(851), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -38607,258 +39433,218 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [27100] = 13, - ACTIONS(1135), 1, + [25228] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(639), 1, + sym_integer_literal, + ACTIONS(641), 1, + sym_float_literal, + ACTIONS(645), 1, + anon_sym_nil, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, + anon_sym_match, + ACTIONS(659), 1, + anon_sym_spawn, + ACTIONS(661), 1, + anon_sym_chan, + ACTIONS(663), 1, + anon_sym_send, + ACTIONS(665), 1, + anon_sym_recv, + ACTIONS(667), 1, + anon_sym_select, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1229), 1, - anon_sym_RPAREN, + ACTIONS(1160), 1, + anon_sym_PIPE, + STATE(829), 1, + sym_identifier, + STATE(1525), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(366), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1225), 17, - sym_integer_literal, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(669), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1227), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [27180] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1229), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(367), 5, - sym_identifier, + STATE(828), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1231), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(909), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [25333] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1233), 19, + ACTIONS(641), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [27260] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(645), 1, + anon_sym_nil, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, + anon_sym_match, + ACTIONS(659), 1, + anon_sym_spawn, + ACTIONS(661), 1, + anon_sym_chan, + ACTIONS(663), 1, + anon_sym_send, + ACTIONS(665), 1, + anon_sym_recv, + ACTIONS(667), 1, + anon_sym_select, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1229), 1, - anon_sym_RBRACE, + ACTIONS(1160), 1, + anon_sym_PIPE, + STATE(829), 1, + sym_identifier, + STATE(1525), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(368), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1235), 17, - sym_integer_literal, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(669), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1237), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [27340] = 25, - ACTIONS(598), 1, + anon_sym_TILDE, + STATE(886), 2, + sym_double_string, + sym_single_string, + STATE(828), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(906), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [25438] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -38872,7 +39658,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(776), 14, + STATE(898), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -38887,258 +39673,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [27444] = 13, - ACTIONS(1135), 1, + [25543] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1239), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + ACTIONS(639), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(641), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [27524] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(645), 1, + anon_sym_nil, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, + anon_sym_match, + ACTIONS(659), 1, + anon_sym_spawn, + ACTIONS(661), 1, + anon_sym_chan, + ACTIONS(663), 1, + anon_sym_send, + ACTIONS(665), 1, + anon_sym_recv, + ACTIONS(667), 1, + anon_sym_select, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1239), 1, - anon_sym_RBRACK, + ACTIONS(1160), 1, + anon_sym_PIPE, + STATE(829), 1, + sym_identifier, + STATE(1525), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(669), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [27604] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1239), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(183), 5, - sym_identifier, + STATE(828), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [27684] = 25, - ACTIONS(598), 1, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(912), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [25648] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -39152,7 +39818,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(777), 14, + STATE(905), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -39167,57 +39833,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [27788] = 25, - ACTIONS(598), 1, + [25753] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -39231,7 +39898,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(791), 14, + STATE(921), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -39246,57 +39913,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [27892] = 25, - ACTIONS(598), 1, + [25858] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1156), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -39310,7 +39978,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(792), 14, + STATE(935), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -39325,57 +39993,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [27996] = 25, - ACTIONS(598), 1, + [25963] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1156), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -39389,7 +40058,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(625), 14, + STATE(930), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -39404,57 +40073,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [28100] = 25, - ACTIONS(598), 1, + [26068] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1156), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -39468,7 +40138,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(779), 14, + STATE(931), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -39483,861 +40153,698 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [28204] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + [26173] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, + sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1245), 1, - anon_sym_RPAREN, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(377), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1241), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(936), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [26278] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1243), 19, + ACTIONS(11), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28284] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1245), 1, - anon_sym_RBRACK, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(378), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1247), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(934), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [26383] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1249), 19, + ACTIONS(11), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28364] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1245), 1, - anon_sym_RBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(379), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1251), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(937), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [26488] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1253), 19, + ACTIONS(11), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28444] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1255), 1, - anon_sym_RPAREN, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(183), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(79), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [26593] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(11), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28524] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1255), 1, - anon_sym_RBRACK, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(183), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(938), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [26698] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(11), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28604] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1255), 1, - anon_sym_RBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(675), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28684] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1261), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(383), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1257), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(939), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [26803] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1259), 19, + ACTIONS(11), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28764] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1261), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(384), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1263), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, + ACTIONS(15), 1, anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1265), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28844] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1261), 1, - anon_sym_RBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(385), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1267), 17, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(675), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1269), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [28924] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1271), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(183), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(940), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [26908] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(11), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29004] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1271), 1, - anon_sym_RBRACK, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(675), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29084] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1271), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(183), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29164] = 25, - ACTIONS(598), 1, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(942), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [27013] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1175), 1, - anon_sym_BANG, - ACTIONS(1177), 1, + ACTIONS(1156), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -40351,7 +40858,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(793), 14, + STATE(945), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -40366,459 +40873,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [29268] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + [27118] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, + sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(1147), 1, + ACTIONS(23), 1, anon_sym_LPAREN, - ACTIONS(1151), 1, + ACTIONS(25), 1, anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(1277), 1, - anon_sym_RPAREN, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(449), 2, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(390), 5, - sym_identifier, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1273), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1275), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29348] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1277), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(391), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1279), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1281), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29428] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1277), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(392), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1283), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1285), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29508] = 13, - ACTIONS(1135), 1, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(943), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [27223] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1287), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, + ACTIONS(585), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, + ACTIONS(587), 1, sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29588] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, - anon_sym_DQUOTE, - ACTIONS(1143), 1, - anon_sym_SQUOTE, - ACTIONS(1145), 1, - sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, - anon_sym_LBRACE, - ACTIONS(1287), 1, - anon_sym_RBRACK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, + ACTIONS(591), 1, anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29668] = 13, - ACTIONS(1135), 1, - aux_sym_identifier_token1, - ACTIONS(1141), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(1143), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(1145), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(1147), 1, - anon_sym_LPAREN, - ACTIONS(1151), 1, - anon_sym_LBRACK, - ACTIONS(1153), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(1287), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(449), 2, - sym_double_string, - sym_single_string, - STATE(183), 5, - sym_identifier, - sym_string_literal, - sym_macro_group, - sym__macro_token, - aux_sym_attribute_repeat1, - ACTIONS(1137), 17, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(1139), 19, - sym_float_literal, - anon_sym_COMMA, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [29748] = 25, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, - anon_sym_PIPE, - ACTIONS(33), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1162), 1, + anon_sym_PIPE, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -40832,7 +41018,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(681), 14, + STATE(770), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -40847,57 +41033,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [29852] = 25, - ACTIONS(598), 1, + [27328] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -40911,7 +41098,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(733), 14, + STATE(810), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -40926,57 +41113,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [29956] = 25, - ACTIONS(598), 1, + [27433] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -40990,7 +41178,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(728), 14, + STATE(811), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41005,57 +41193,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30060] = 25, - ACTIONS(598), 1, + [27538] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41069,7 +41258,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(698), 14, + STATE(814), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41084,57 +41273,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30164] = 25, - ACTIONS(598), 1, + [27643] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41148,7 +41338,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(722), 14, + STATE(119), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41163,57 +41353,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30268] = 25, - ACTIONS(598), 1, + [27748] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41227,7 +41418,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(723), 14, + STATE(107), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41242,57 +41433,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30372] = 25, - ACTIONS(598), 1, + [27853] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41306,7 +41498,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(625), 14, + STATE(108), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41321,57 +41513,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30476] = 25, - ACTIONS(598), 1, + [27958] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(628), 1, - aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1160), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(669), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41385,7 +41578,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(729), 14, + STATE(904), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41400,57 +41593,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30580] = 25, - ACTIONS(598), 1, + [28063] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(628), 1, + ACTIONS(633), 1, aux_sym_identifier_token1, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1046), 1, - anon_sym_BANG, - ACTIONS(1048), 1, + ACTIONS(1158), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(637), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41464,7 +41658,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(724), 14, + STATE(833), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41479,57 +41673,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30684] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [28168] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41543,7 +41738,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(721), 14, + STATE(809), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41558,57 +41753,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30788] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [28273] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41622,7 +41818,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(725), 14, + STATE(791), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41637,57 +41833,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30892] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [28378] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41701,7 +41898,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(726), 14, + STATE(792), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41716,57 +41913,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [30996] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [28483] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41780,7 +41978,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(625), 14, + STATE(761), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41795,57 +41993,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31100] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [28588] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1197), 1, - anon_sym_BANG, - ACTIONS(1199), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41859,7 +42058,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(727), 14, + STATE(778), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41874,57 +42073,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31204] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [28693] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -41938,7 +42138,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(797), 14, + STATE(712), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -41953,57 +42153,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31308] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [28798] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42017,7 +42218,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(787), 14, + STATE(779), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42032,57 +42233,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31412] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [28903] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42096,7 +42298,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(788), 14, + STATE(780), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42111,57 +42313,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31516] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [29008] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42175,7 +42378,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(784), 14, + STATE(781), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42190,57 +42393,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31620] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [29113] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42254,7 +42458,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(785), 14, + STATE(782), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42269,57 +42473,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31724] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [29218] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42333,7 +42538,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(778), 14, + STATE(793), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42348,57 +42553,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31828] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(630), 1, + [29323] = 25, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(633), 1, + aux_sym_identifier_token1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1079), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(635), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42412,7 +42618,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(789), 14, + STATE(783), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42427,57 +42633,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [31932] = 25, - ACTIONS(578), 1, + [29428] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(630), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(632), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(636), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(638), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(640), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(642), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(644), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(646), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(648), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(650), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(652), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(654), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(656), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1080), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1082), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1084), 1, - anon_sym_BANG, - ACTIONS(1086), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(749), 1, + STATE(677), 1, sym_identifier, - STATE(1437), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(634), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(757), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(747), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42491,7 +42698,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(786), 14, + STATE(764), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42506,57 +42713,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32036] = 25, - ACTIONS(9), 1, + [29533] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42570,7 +42778,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(109), 14, + STATE(784), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42585,57 +42793,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32140] = 25, - ACTIONS(9), 1, + [29638] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42649,7 +42858,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(113), 14, + STATE(816), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42664,57 +42873,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32244] = 25, - ACTIONS(9), 1, + [29743] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42728,7 +42938,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(106), 14, + STATE(712), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42743,57 +42953,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32348] = 25, - ACTIONS(9), 1, + [29848] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42807,7 +43018,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(75), 14, + STATE(786), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42822,136 +43033,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32452] = 25, - ACTIONS(9), 1, + [29953] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1034), 1, - anon_sym_BANG, - ACTIONS(1036), 1, - anon_sym_PIPE, - STATE(86), 1, - sym_identifier, - STATE(1369), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(13), 2, - anon_sym_true, - anon_sym_false, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(97), 13, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - sym_parenthesized_expression, - sym_call_expression, - sym_field_access, - sym_optional_field_access, - sym_index_access, - sym_optional_index_access, - sym_list_expression, - sym_map_expression, - sym_struct_literal, - sym_unwrap_expression, - STATE(114), 14, - sym__expression, - sym_primary_expression, - sym_unary_expression, - sym_binary_expression, - sym_nullish_coalescing_expression, - sym_range_expression, - sym_closure, - sym_match_expression, - sym_spawn_expression, - sym_chan_expression, - sym_send_expression, - sym_recv_expression, - sym_select_expression, - sym_macro_invocation, - [32556] = 25, - ACTIONS(9), 1, - sym_integer_literal, - ACTIONS(11), 1, - sym_float_literal, - ACTIONS(15), 1, - anon_sym_nil, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(23), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - ACTIONS(33), 1, - anon_sym_match, - ACTIONS(37), 1, - anon_sym_spawn, - ACTIONS(39), 1, - anon_sym_chan, - ACTIONS(41), 1, - anon_sym_send, - ACTIONS(43), 1, - anon_sym_recv, - ACTIONS(45), 1, - anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + STATE(677), 1, sym_identifier, - STATE(1369), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -42965,7 +43098,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(677), 14, + STATE(787), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -42980,57 +43113,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32660] = 25, - ACTIONS(578), 1, + [30058] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43044,7 +43178,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(697), 14, + STATE(788), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43059,57 +43193,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32764] = 25, - ACTIONS(578), 1, + [30163] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43123,7 +43258,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(688), 14, + STATE(789), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43138,57 +43273,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32868] = 25, - ACTIONS(578), 1, + [30268] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(585), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(587), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(591), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(593), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(595), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(597), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(599), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(603), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(605), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(607), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(609), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(611), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(613), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1075), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1077), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1162), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(677), 1, sym_identifier, - STATE(1470), 1, + STATE(1578), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(631), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(717), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43202,7 +43338,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(686), 14, + STATE(790), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43217,57 +43353,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [32972] = 25, - ACTIONS(578), 1, + [30373] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43281,7 +43418,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(689), 14, + STATE(892), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43296,57 +43433,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33076] = 25, - ACTIONS(578), 1, + [30478] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43360,7 +43498,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(695), 14, + STATE(864), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43375,57 +43513,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33180] = 25, - ACTIONS(578), 1, + [30583] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43439,7 +43578,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(696), 14, + STATE(865), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43454,57 +43593,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33284] = 25, - ACTIONS(578), 1, + [30688] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43518,7 +43658,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(625), 14, + STATE(857), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43533,57 +43673,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33388] = 25, - ACTIONS(578), 1, + [30793] = 25, + ACTIONS(581), 1, aux_sym_identifier_token1, - ACTIONS(598), 1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(612), 1, + ACTIONS(653), 1, anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(1038), 1, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(1040), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(1050), 1, - anon_sym_BANG, - ACTIONS(1052), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(829), 1, sym_identifier, - STATE(1470), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43597,7 +43738,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(692), 14, + STATE(858), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43612,57 +43753,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33492] = 25, - ACTIONS(9), 1, + [30898] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43676,7 +43818,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(828), 14, + STATE(851), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43691,57 +43833,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33596] = 25, - ACTIONS(9), 1, + [31003] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, + anon_sym_match, + ACTIONS(659), 1, + anon_sym_spawn, + ACTIONS(661), 1, + anon_sym_chan, + ACTIONS(663), 1, + anon_sym_send, + ACTIONS(665), 1, + anon_sym_recv, + ACTIONS(667), 1, + anon_sym_select, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(29), 1, - anon_sym_BANG, - ACTIONS(31), 1, + ACTIONS(1069), 1, anon_sym_PIPE, - ACTIONS(33), 1, + STATE(829), 1, + sym_identifier, + STATE(1525), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(643), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, + sym_double_string, + sym_single_string, + STATE(828), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(859), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [31108] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, + sym_integer_literal, + ACTIONS(641), 1, + sym_float_literal, + ACTIONS(645), 1, + anon_sym_nil, + ACTIONS(647), 1, + anon_sym_DQUOTE, + ACTIONS(649), 1, + anon_sym_SQUOTE, + ACTIONS(651), 1, + sym_raw_string, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(580), 1, - anon_sym_LBRACE, - STATE(86), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1069), 1, + anon_sym_PIPE, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43755,7 +43978,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(673), 14, + STATE(860), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43770,57 +43993,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33700] = 25, - ACTIONS(9), 1, + [31213] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43834,7 +44058,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(819), 14, + STATE(861), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43849,57 +44073,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33804] = 25, - ACTIONS(9), 1, + [31318] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, - anon_sym_LPAREN, - ACTIONS(25), 1, - anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43913,7 +44138,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(815), 14, + STATE(862), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -43928,57 +44153,138 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [33908] = 25, - ACTIONS(9), 1, + [31423] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, sym_integer_literal, - ACTIONS(11), 1, + ACTIONS(641), 1, sym_float_literal, - ACTIONS(15), 1, + ACTIONS(645), 1, anon_sym_nil, - ACTIONS(17), 1, + ACTIONS(647), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(649), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(651), 1, sym_raw_string, - ACTIONS(23), 1, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, + anon_sym_match, + ACTIONS(659), 1, + anon_sym_spawn, + ACTIONS(661), 1, + anon_sym_chan, + ACTIONS(663), 1, + anon_sym_send, + ACTIONS(665), 1, + anon_sym_recv, + ACTIONS(667), 1, + anon_sym_select, + ACTIONS(1065), 1, anon_sym_LPAREN, - ACTIONS(25), 1, + ACTIONS(1067), 1, anon_sym_LBRACK, - ACTIONS(33), 1, + ACTIONS(1069), 1, + anon_sym_PIPE, + STATE(829), 1, + sym_identifier, + STATE(1525), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(643), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, + sym_double_string, + sym_single_string, + STATE(828), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(866), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [31528] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(639), 1, + sym_integer_literal, + ACTIONS(641), 1, + sym_float_literal, + ACTIONS(645), 1, + anon_sym_nil, + ACTIONS(647), 1, + anon_sym_DQUOTE, + ACTIONS(649), 1, + anon_sym_SQUOTE, + ACTIONS(651), 1, + sym_raw_string, + ACTIONS(653), 1, + anon_sym_LBRACE, + ACTIONS(657), 1, anon_sym_match, - ACTIONS(37), 1, + ACTIONS(659), 1, anon_sym_spawn, - ACTIONS(39), 1, + ACTIONS(661), 1, anon_sym_chan, - ACTIONS(41), 1, + ACTIONS(663), 1, anon_sym_send, - ACTIONS(43), 1, + ACTIONS(665), 1, anon_sym_recv, - ACTIONS(45), 1, + ACTIONS(667), 1, anon_sym_select, - ACTIONS(580), 1, - anon_sym_LBRACE, - ACTIONS(658), 1, - aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1065), 1, + anon_sym_LPAREN, + ACTIONS(1067), 1, + anon_sym_LBRACK, + ACTIONS(1069), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(829), 1, sym_identifier, - STATE(1369), 1, + STATE(1525), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(13), 2, + ACTIONS(643), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(655), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(886), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(828), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -43992,7 +44298,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(818), 14, + STATE(863), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44007,7 +44313,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34012] = 25, + [31633] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -44036,17 +44342,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -44054,10 +44358,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -44071,7 +44378,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(816), 14, + STATE(106), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44086,7 +44393,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34116] = 25, + [31738] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -44115,17 +44422,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -44133,10 +44438,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -44150,7 +44458,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(820), 14, + STATE(112), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44165,7 +44473,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34220] = 25, + [31843] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -44194,17 +44502,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -44212,10 +44518,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -44229,7 +44538,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(75), 14, + STATE(118), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44244,7 +44553,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34324] = 25, + [31948] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -44273,17 +44582,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1117), 1, - anon_sym_BANG, - ACTIONS(1119), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -44291,10 +44598,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -44308,7 +44618,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(817), 14, + STATE(79), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44323,7 +44633,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34428] = 25, + [32053] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -44352,17 +44662,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -44370,10 +44678,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -44387,7 +44698,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(824), 14, + STATE(113), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44402,7 +44713,7 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34532] = 25, + [32158] = 25, ACTIONS(9), 1, sym_integer_literal, ACTIONS(11), 1, @@ -44431,17 +44742,15 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_recv, ACTIONS(45), 1, anon_sym_select, - ACTIONS(580), 1, + ACTIONS(583), 1, anon_sym_LBRACE, - ACTIONS(658), 1, + ACTIONS(671), 1, aux_sym_identifier_token1, - ACTIONS(1179), 1, - anon_sym_BANG, - ACTIONS(1181), 1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(86), 1, + STATE(60), 1, sym_identifier, - STATE(1369), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, @@ -44449,10 +44758,13 @@ static const uint16_t ts_small_parse_table[] = { ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(67), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(97), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -44466,7 +44778,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(830), 14, + STATE(114), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44481,57 +44793,58 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34636] = 25, - ACTIONS(578), 1, - aux_sym_identifier_token1, - ACTIONS(598), 1, + [32263] = 25, + ACTIONS(9), 1, sym_integer_literal, - ACTIONS(600), 1, + ACTIONS(11), 1, sym_float_literal, - ACTIONS(604), 1, + ACTIONS(15), 1, anon_sym_nil, - ACTIONS(606), 1, + ACTIONS(17), 1, anon_sym_DQUOTE, - ACTIONS(608), 1, + ACTIONS(19), 1, anon_sym_SQUOTE, - ACTIONS(610), 1, + ACTIONS(21), 1, sym_raw_string, - ACTIONS(612), 1, - anon_sym_LBRACE, - ACTIONS(616), 1, + ACTIONS(23), 1, + anon_sym_LPAREN, + ACTIONS(25), 1, + anon_sym_LBRACK, + ACTIONS(33), 1, anon_sym_match, - ACTIONS(618), 1, + ACTIONS(37), 1, anon_sym_spawn, - ACTIONS(620), 1, + ACTIONS(39), 1, anon_sym_chan, - ACTIONS(622), 1, + ACTIONS(41), 1, anon_sym_send, - ACTIONS(624), 1, + ACTIONS(43), 1, anon_sym_recv, - ACTIONS(626), 1, + ACTIONS(45), 1, anon_sym_select, - ACTIONS(1038), 1, - anon_sym_LPAREN, - ACTIONS(1040), 1, - anon_sym_LBRACK, - ACTIONS(1042), 1, - anon_sym_BANG, - ACTIONS(1044), 1, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, anon_sym_PIPE, - STATE(640), 1, + STATE(60), 1, sym_identifier, - STATE(1470), 1, + STATE(1606), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(602), 2, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - STATE(658), 2, + ACTIONS(1071), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(638), 13, + STATE(101), 13, sym_boolean_literal, sym_nil_literal, sym_string_literal, @@ -44545,7 +44858,7 @@ static const uint16_t ts_small_parse_table[] = { sym_map_expression, sym_struct_literal, sym_unwrap_expression, - STATE(701), 14, + STATE(115), 14, sym__expression, sym_primary_expression, sym_unary_expression, @@ -44560,3545 +44873,5299 @@ static const uint16_t ts_small_parse_table[] = { sym_recv_expression, sym_select_expression, sym_macro_invocation, - [34740] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(672), 18, - aux_sym_identifier_token1, + [32368] = 25, + ACTIONS(9), 1, sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(674), 28, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_RBRACK, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [34795] = 3, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(688), 18, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(1071), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(690), 28, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(116), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [32473] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_RBRACK, + ACTIONS(33), 1, + anon_sym_match, + ACTIONS(37), 1, + anon_sym_spawn, + ACTIONS(39), 1, + anon_sym_chan, + ACTIONS(41), 1, + anon_sym_send, + ACTIONS(43), 1, + anon_sym_recv, + ACTIONS(45), 1, + anon_sym_select, + ACTIONS(583), 1, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [34850] = 3, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1073), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(692), 18, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(1071), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(694), 28, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(117), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [32578] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(1077), 1, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [34905] = 3, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(760), 18, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(629), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(762), 28, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(756), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [32683] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(1077), 1, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [34960] = 3, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(682), 18, + ACTIONS(589), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(757), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [32788] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_LBRACK, - anon_sym_RBRACK, + ACTIONS(599), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_LT_EQ, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_POUND, - ACTIONS(680), 28, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(603), 1, anon_sym_match, - anon_sym_if, + ACTIONS(605), 1, anon_sym_spawn, + ACTIONS(607), 1, anon_sym_chan, + ACTIONS(609), 1, anon_sym_send, + ACTIONS(611), 1, anon_sym_recv, + ACTIONS(613), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35015] = 3, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 18, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(629), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(686), 28, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(758), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [32893] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(1077), 1, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [35070] = 3, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(676), 18, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(629), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(678), 28, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(749), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [32998] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(1077), 1, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [35125] = 3, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(764), 18, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(629), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(766), 28, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(750), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33103] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(1077), 1, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [35180] = 3, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(708), 18, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(589), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_COLON, - anon_sym_DOT, + ACTIONS(629), 2, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_EQ, - anon_sym_AMP, - ACTIONS(710), 28, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(751), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33208] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(599), 1, + anon_sym_LBRACE, + ACTIONS(603), 1, + anon_sym_match, + ACTIONS(605), 1, + anon_sym_spawn, + ACTIONS(607), 1, + anon_sym_chan, + ACTIONS(609), 1, + anon_sym_send, + ACTIONS(611), 1, + anon_sym_recv, + ACTIONS(613), 1, + anon_sym_select, + ACTIONS(1075), 1, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, + ACTIONS(1077), 1, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_DOT_DOT_EQ, - anon_sym_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - anon_sym_DASH_GT, - anon_sym_POUND, - anon_sym_DOLLAR, - anon_sym_COLON_COLON, - [35235] = 4, - ACTIONS(1293), 1, - anon_sym_SEMI, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1289), 12, - ts_builtin_sym_end, + ACTIONS(589), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(712), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33313] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, + ACTIONS(599), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1291), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(603), 1, anon_sym_match, - anon_sym_if, + ACTIONS(605), 1, anon_sym_spawn, + ACTIONS(607), 1, anon_sym_chan, + ACTIONS(609), 1, anon_sym_send, + ACTIONS(611), 1, anon_sym_recv, + ACTIONS(613), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35288] = 4, - ACTIONS(1299), 1, - anon_sym_SEMI, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1295), 12, - ts_builtin_sym_end, + ACTIONS(589), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(752), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33418] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, + ACTIONS(599), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1297), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(603), 1, anon_sym_match, - anon_sym_if, + ACTIONS(605), 1, anon_sym_spawn, + ACTIONS(607), 1, anon_sym_chan, + ACTIONS(609), 1, anon_sym_send, + ACTIONS(611), 1, anon_sym_recv, + ACTIONS(613), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35341] = 4, - ACTIONS(1305), 1, - anon_sym_SEMI, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1301), 12, - ts_builtin_sym_end, + ACTIONS(589), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(753), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33523] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, + ACTIONS(599), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1303), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(603), 1, anon_sym_match, - anon_sym_if, + ACTIONS(605), 1, anon_sym_spawn, + ACTIONS(607), 1, anon_sym_chan, + ACTIONS(609), 1, anon_sym_send, + ACTIONS(611), 1, anon_sym_recv, + ACTIONS(613), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35394] = 3, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1307), 12, - ts_builtin_sym_end, + ACTIONS(589), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(754), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33628] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, + ACTIONS(599), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1309), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(603), 1, anon_sym_match, - anon_sym_if, + ACTIONS(605), 1, anon_sym_spawn, + ACTIONS(607), 1, anon_sym_chan, + ACTIONS(609), 1, anon_sym_send, + ACTIONS(611), 1, anon_sym_recv, + ACTIONS(613), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35444] = 3, - ACTIONS(3), 2, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, + ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1311), 12, - ts_builtin_sym_end, + ACTIONS(589), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(755), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33733] = 25, + ACTIONS(581), 1, + aux_sym_identifier_token1, + ACTIONS(585), 1, + sym_integer_literal, + ACTIONS(587), 1, sym_float_literal, + ACTIONS(591), 1, + anon_sym_nil, + ACTIONS(593), 1, anon_sym_DQUOTE, + ACTIONS(595), 1, anon_sym_SQUOTE, + ACTIONS(597), 1, sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, + ACTIONS(599), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1313), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(603), 1, anon_sym_match, - anon_sym_if, + ACTIONS(605), 1, anon_sym_spawn, + ACTIONS(607), 1, anon_sym_chan, + ACTIONS(609), 1, anon_sym_send, + ACTIONS(611), 1, anon_sym_recv, + ACTIONS(613), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35494] = 3, + ACTIONS(1075), 1, + anon_sym_LPAREN, + ACTIONS(1077), 1, + anon_sym_LBRACK, + ACTIONS(1083), 1, + anon_sym_PIPE, + STATE(677), 1, + sym_identifier, + STATE(1578), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1315), 12, - ts_builtin_sym_end, + ACTIONS(589), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(629), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(688), 2, + sym_double_string, + sym_single_string, + STATE(717), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(733), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33838] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1317), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35544] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1319), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(944), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [33943] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1321), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35594] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1323), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(927), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34048] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1325), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35644] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1327), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(924), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34153] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1329), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35694] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1331), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(925), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34258] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1333), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35744] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1335), 12, - ts_builtin_sym_end, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1337), 29, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35794] = 4, - ACTIONS(1339), 1, - anon_sym_SEMI, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1301), 12, - ts_builtin_sym_end, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(914), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34363] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1303), 28, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35846] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(849), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(915), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34468] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(847), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35896] = 4, - ACTIONS(1341), 1, - anon_sym_SEMI, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1289), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(79), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34573] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1291), 28, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [35948] = 4, - ACTIONS(1343), 1, - anon_sym_SEMI, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1295), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(916), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34678] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1297), 28, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36000] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1345), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(917), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34783] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1347), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36050] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1349), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(918), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34888] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1351), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36100] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1353), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(919), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [34993] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1355), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36150] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1357), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(926), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [35098] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1359), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36200] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1109), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1361), 12, - ts_builtin_sym_end, + ACTIONS(13), 2, + anon_sym_true, + anon_sym_false, + ACTIONS(673), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(920), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [35203] = 25, + ACTIONS(9), 1, + sym_integer_literal, + ACTIONS(11), 1, sym_float_literal, + ACTIONS(15), 1, + anon_sym_nil, + ACTIONS(17), 1, anon_sym_DQUOTE, + ACTIONS(19), 1, anon_sym_SQUOTE, + ACTIONS(21), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(23), 1, anon_sym_LPAREN, + ACTIONS(25), 1, anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1363), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, + ACTIONS(33), 1, anon_sym_match, - anon_sym_if, + ACTIONS(37), 1, anon_sym_spawn, + ACTIONS(39), 1, anon_sym_chan, + ACTIONS(41), 1, anon_sym_send, + ACTIONS(43), 1, anon_sym_recv, + ACTIONS(45), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36250] = 3, + ACTIONS(583), 1, + anon_sym_LBRACE, + ACTIONS(671), 1, + aux_sym_identifier_token1, + ACTIONS(1156), 1, + anon_sym_PIPE, + STATE(60), 1, + sym_identifier, + STATE(1606), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1365), 12, - ts_builtin_sym_end, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1367), 29, - aux_sym_identifier_token1, - sym_integer_literal, + ACTIONS(13), 2, anon_sym_true, anon_sym_false, - anon_sym_nil, + ACTIONS(675), 2, + anon_sym_BANG, + anon_sym_TILDE, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(101), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(941), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [35308] = 25, + ACTIONS(617), 1, anon_sym_match, - anon_sym_if, + ACTIONS(619), 1, anon_sym_spawn, + ACTIONS(621), 1, anon_sym_chan, + ACTIONS(623), 1, anon_sym_send, + ACTIONS(625), 1, anon_sym_recv, + ACTIONS(627), 1, anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36300] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1369), 12, - ts_builtin_sym_end, + ACTIONS(1085), 1, + aux_sym_identifier_token1, + ACTIONS(1087), 1, + sym_integer_literal, + ACTIONS(1089), 1, sym_float_literal, + ACTIONS(1093), 1, + anon_sym_nil, + ACTIONS(1095), 1, anon_sym_DQUOTE, + ACTIONS(1097), 1, anon_sym_SQUOTE, + ACTIONS(1099), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1101), 1, anon_sym_LPAREN, + ACTIONS(1103), 1, anon_sym_LBRACK, + ACTIONS(1105), 1, anon_sym_LBRACE, - anon_sym_BANG, + ACTIONS(1107), 1, anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1371), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36350] = 3, + STATE(609), 1, + sym_identifier, + STATE(1485), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1373), 12, - ts_builtin_sym_end, - sym_float_literal, + ACTIONS(615), 2, + anon_sym_BANG, + anon_sym_TILDE, + ACTIONS(1091), 2, + anon_sym_true, + anon_sym_false, + STATE(527), 2, + sym_double_string, + sym_single_string, + STATE(606), 13, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + sym_parenthesized_expression, + sym_call_expression, + sym_field_access, + sym_optional_field_access, + sym_index_access, + sym_optional_index_access, + sym_list_expression, + sym_map_expression, + sym_struct_literal, + sym_unwrap_expression, + STATE(511), 14, + sym__expression, + sym_primary_expression, + sym_unary_expression, + sym_binary_expression, + sym_nullish_coalescing_expression, + sym_range_expression, + sym_closure, + sym_match_expression, + sym_spawn_expression, + sym_chan_expression, + sym_send_expression, + sym_recv_expression, + sym_select_expression, + sym_macro_invocation, + [35413] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1178), 1, + anon_sym_RPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1375), 29, - aux_sym_identifier_token1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(474), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1166), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36400] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1377), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1168), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [35493] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1379), 29, - aux_sym_identifier_token1, + ACTIONS(1188), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36450] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(674), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [35573] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(672), 29, - aux_sym_identifier_token1, + ACTIONS(1194), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(454), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1190), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36500] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1381), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1192), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [35653] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1383), 29, - aux_sym_identifier_token1, + ACTIONS(1194), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(455), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1196), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36550] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1385), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1198), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [35733] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1387), 29, - aux_sym_identifier_token1, + ACTIONS(1194), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(456), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1200), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36600] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1295), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1202), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [35813] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1297), 29, - aux_sym_identifier_token1, + ACTIONS(1204), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36650] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1389), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [35893] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1391), 29, - aux_sym_identifier_token1, + ACTIONS(1204), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36700] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1393), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [35973] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1395), 29, - aux_sym_identifier_token1, + ACTIONS(1204), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36750] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1397), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36053] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1399), 29, - aux_sym_identifier_token1, + ACTIONS(1210), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(490), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1206), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36800] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1401), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1208), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36133] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1403), 29, - aux_sym_identifier_token1, + ACTIONS(1210), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(491), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1212), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36850] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1405), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1214), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36213] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1407), 29, - aux_sym_identifier_token1, + ACTIONS(1210), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(450), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1216), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36900] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1409), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1218), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36293] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1411), 29, - aux_sym_identifier_token1, + ACTIONS(1220), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [36950] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1413), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36373] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1415), 29, - aux_sym_identifier_token1, + ACTIONS(1226), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(464), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1222), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37000] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1417), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1224), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36453] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1419), 29, - aux_sym_identifier_token1, + ACTIONS(1226), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(465), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1228), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37050] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1421), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1230), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36533] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1423), 29, - aux_sym_identifier_token1, + ACTIONS(1226), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(466), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1232), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37100] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1425), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1234), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36613] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1427), 29, - aux_sym_identifier_token1, + ACTIONS(1236), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37150] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1301), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36693] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1303), 29, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37200] = 3, + ACTIONS(1236), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(678), 12, - ts_builtin_sym_end, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(676), 29, - aux_sym_identifier_token1, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37250] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1429), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36773] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1431), 29, - aux_sym_identifier_token1, + ACTIONS(1236), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37300] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1433), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36853] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1435), 29, - aux_sym_identifier_token1, + ACTIONS(1242), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(492), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1238), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37350] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1437), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1240), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [36933] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1439), 29, - aux_sym_identifier_token1, + ACTIONS(1242), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(470), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1244), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37400] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1441), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1246), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37013] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1443), 29, - aux_sym_identifier_token1, + ACTIONS(1242), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(471), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1248), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37450] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1445), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1250), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37093] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1447), 29, - aux_sym_identifier_token1, + ACTIONS(1252), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37500] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1449), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37173] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1451), 29, - aux_sym_identifier_token1, + ACTIONS(1252), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37550] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1453), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37253] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1178), 1, + anon_sym_RBRACK, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1455), 29, - aux_sym_identifier_token1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(475), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1254), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37600] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1457), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1256), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37333] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1178), 1, + anon_sym_RBRACE, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1459), 29, - aux_sym_identifier_token1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(476), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1258), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37650] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1461), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1260), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37413] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1463), 29, - aux_sym_identifier_token1, + ACTIONS(1262), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37700] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1465), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37493] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1467), 29, - aux_sym_identifier_token1, + ACTIONS(1262), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37750] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1469), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37573] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1471), 29, - aux_sym_identifier_token1, + ACTIONS(1262), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_else, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37800] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1441), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37653] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1443), 28, - aux_sym_identifier_token1, + ACTIONS(1268), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(480), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1264), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37849] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1469), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1266), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37733] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1471), 28, - aux_sym_identifier_token1, + ACTIONS(1268), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(481), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1270), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37898] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1349), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1272), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37813] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1351), 28, - aux_sym_identifier_token1, + ACTIONS(1268), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(482), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1274), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37947] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1389), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1276), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37893] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1391), 28, - aux_sym_identifier_token1, + ACTIONS(1278), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [37996] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(849), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [37973] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(847), 28, - aux_sym_identifier_token1, + ACTIONS(1278), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38045] = 4, - ACTIONS(1477), 1, - anon_sym_else, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1475), 11, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38053] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1473), 28, - aux_sym_identifier_token1, + ACTIONS(1278), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38096] = 4, - ACTIONS(1479), 1, - anon_sym_else, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1381), 11, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38133] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1383), 28, - aux_sym_identifier_token1, + ACTIONS(1284), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(486), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1280), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38147] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1301), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1282), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38213] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1303), 28, - aux_sym_identifier_token1, + ACTIONS(1284), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(487), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1286), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38196] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1323), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1288), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38293] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1325), 28, - aux_sym_identifier_token1, + ACTIONS(1284), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(488), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1290), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38245] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1429), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1292), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38373] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1431), 28, - aux_sym_identifier_token1, + ACTIONS(1294), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38294] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1307), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38453] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1309), 28, - aux_sym_identifier_token1, + ACTIONS(1294), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38343] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1357), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38533] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1359), 28, - aux_sym_identifier_token1, + ACTIONS(1294), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38392] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1433), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38613] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, - anon_sym_RBRACE, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, + ACTIONS(1300), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(460), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1296), 17, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_COLON, + anon_sym_DOT, anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_EQ, + ACTIONS(1298), 19, + sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1435), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38693] = 13, + ACTIONS(1164), 1, aux_sym_identifier_token1, + ACTIONS(1170), 1, + anon_sym_DQUOTE, + ACTIONS(1172), 1, + anon_sym_SQUOTE, + ACTIONS(1174), 1, + sym_raw_string, + ACTIONS(1176), 1, + anon_sym_LPAREN, + ACTIONS(1180), 1, + anon_sym_LBRACK, + ACTIONS(1182), 1, + anon_sym_LBRACE, + ACTIONS(1188), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38441] = 4, - ACTIONS(1481), 1, - anon_sym_else, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1381), 11, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(1186), 19, sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38773] = 13, + ACTIONS(1164), 1, + aux_sym_identifier_token1, + ACTIONS(1170), 1, anon_sym_DQUOTE, + ACTIONS(1172), 1, anon_sym_SQUOTE, + ACTIONS(1174), 1, sym_raw_string, + ACTIONS(1176), 1, anon_sym_LPAREN, + ACTIONS(1180), 1, anon_sym_LBRACK, + ACTIONS(1182), 1, anon_sym_LBRACE, + ACTIONS(1188), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_COLON, + anon_sym_DOT, anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_EQ, + ACTIONS(1186), 19, + sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1383), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38853] = 13, + ACTIONS(1164), 1, aux_sym_identifier_token1, + ACTIONS(1170), 1, + anon_sym_DQUOTE, + ACTIONS(1172), 1, + anon_sym_SQUOTE, + ACTIONS(1174), 1, + sym_raw_string, + ACTIONS(1176), 1, + anon_sym_LPAREN, + ACTIONS(1180), 1, + anon_sym_LBRACK, + ACTIONS(1182), 1, + anon_sym_LBRACE, + ACTIONS(1252), 1, + anon_sym_RPAREN, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(500), 2, + sym_double_string, + sym_single_string, + STATE(297), 5, + sym_identifier, + sym_string_literal, + sym_macro_group, + sym__macro_token, + aux_sym_attribute_repeat1, + ACTIONS(1184), 17, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38492] = 3, + anon_sym_EQ, + ACTIONS(1186), 19, + sym_float_literal, + anon_sym_COMMA, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [38933] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1437), 12, - ts_builtin_sym_end, + ACTIONS(714), 19, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, + anon_sym_LT_EQ, anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_SEMI, anon_sym_POUND, - ACTIONS(1439), 28, + ACTIONS(712), 28, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -48127,337 +50194,429 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [38541] = 4, - ACTIONS(1484), 1, - anon_sym_else, + [38989] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1475), 11, - ts_builtin_sym_end, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_LPAREN, - anon_sym_LBRACK, - anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1473), 28, + ACTIONS(724), 18, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38592] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1425), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(726), 28, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1427), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39044] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(716), 18, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38641] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1327), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(718), 28, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1329), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39099] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(704), 18, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38690] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1393), 12, - ts_builtin_sym_end, + anon_sym_EQ, + ACTIONS(706), 28, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1395), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39154] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(820), 18, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38739] = 4, - ACTIONS(1486), 1, - anon_sym_else, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1475), 11, + anon_sym_EQ, + ACTIONS(822), 28, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1473), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39209] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(708), 18, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38790] = 4, - ACTIONS(1489), 1, - anon_sym_else, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1381), 11, + anon_sym_EQ, + ACTIONS(710), 28, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1383), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39264] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(728), 18, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38841] = 4, - ACTIONS(1492), 1, - anon_sym_else, + anon_sym_EQ, + ACTIONS(730), 28, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39319] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1475), 11, - ts_builtin_sym_end, + ACTIONS(792), 18, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_EQ, + ACTIONS(794), 28, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, anon_sym_POUND, - ACTIONS(1473), 28, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39374] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(816), 18, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, + anon_sym_COLON, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_export, anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [38892] = 3, + anon_sym_EQ, + ACTIONS(818), 28, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + anon_sym_DASH_GT, + anon_sym_POUND, + anon_sym_DOLLAR, + anon_sym_COLON_COLON, + [39429] = 4, + ACTIONS(1306), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1331), 12, + ACTIONS(1302), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -48468,9 +50627,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1333), 28, + ACTIONS(1304), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -48487,6 +50647,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -48499,11 +50660,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [38941] = 3, + [39483] = 4, + ACTIONS(1312), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1417), 12, + ACTIONS(1308), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -48514,9 +50677,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1419), 28, + ACTIONS(1310), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -48533,6 +50697,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -48545,11 +50710,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [38990] = 3, + [39537] = 4, + ACTIONS(1318), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1421), 12, + ACTIONS(1314), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -48560,9 +50727,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1423), 28, + ACTIONS(1316), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -48579,6 +50747,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -48591,11 +50760,59 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39039] = 3, + [39591] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(108), 15, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(110), 27, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [39642] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1449), 12, + ACTIONS(1320), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -48606,9 +50823,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1451), 28, + ACTIONS(1322), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -48625,6 +50843,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -48637,11 +50856,11 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39088] = 3, + [39693] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1335), 12, + ACTIONS(1324), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -48652,9 +50871,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1337), 28, + ACTIONS(1326), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -48671,6 +50891,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -48683,333 +50904,377 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39137] = 3, - ACTIONS(3), 2, - sym_line_comment, + [39744] = 8, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, + anon_sym_PIPE, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, sym_block_comment, - ACTIONS(1353), 12, - ts_builtin_sym_end, + ACTIONS(900), 13, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(902), 24, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1355), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [39805] = 4, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(772), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39186] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1361), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(774), 26, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1363), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [39858] = 5, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39235] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1295), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(902), 25, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [39913] = 6, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1297), 28, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 14, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39284] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1369), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(902), 25, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [39970] = 7, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1371), 28, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 14, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39333] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1397), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(902), 24, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40029] = 10, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1399), 28, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1340), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1338), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(900), 11, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39382] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1453), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(902), 20, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40094] = 11, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1455), 28, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1340), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1342), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1338), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(977), 11, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39431] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1345), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(979), 18, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1347), 28, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39480] = 3, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40161] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1365), 12, + ACTIONS(706), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49020,9 +51285,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1367), 28, + ACTIONS(704), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49039,6 +51305,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49051,11 +51318,11 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39529] = 3, + [40212] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(674), 12, + ACTIONS(710), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49066,9 +51333,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(672), 28, + ACTIONS(708), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49085,6 +51353,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49097,57 +51366,108 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39578] = 3, + [40263] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1401), 12, - ts_builtin_sym_end, + ACTIONS(712), 15, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(714), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1403), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40314] = 4, + ACTIONS(908), 1, + anon_sym_LBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(716), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39627] = 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(718), 26, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40367] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1385), 12, + ACTIONS(1344), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49158,9 +51478,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1387), 28, + ACTIONS(1346), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49177,6 +51498,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49189,11 +51511,11 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39676] = 3, + [40418] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1405), 12, + ACTIONS(1348), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49204,9 +51526,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1407), 28, + ACTIONS(1350), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49223,6 +51546,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49235,11 +51559,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39725] = 3, + [40469] = 4, + ACTIONS(1352), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1457), 12, + ACTIONS(1302), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49250,9 +51576,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1459), 28, + ACTIONS(1304), 28, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49281,399 +51608,827 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [39774] = 3, + [40522] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(678), 12, - ts_builtin_sym_end, + ACTIONS(693), 15, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(695), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(676), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40573] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(796), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39823] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1409), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(798), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1411), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40624] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(716), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39872] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1461), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(718), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1463), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40675] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(704), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39921] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1413), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(706), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1415), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40726] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(708), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [39970] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1373), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(710), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1375), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40777] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(792), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [40019] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1315), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(794), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1317), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40828] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(816), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [40068] = 4, - ACTIONS(1495), 1, - anon_sym_else, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1381), 11, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(818), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1383), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40879] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(820), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [40119] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1465), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(822), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1467), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40930] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(724), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, - anon_sym_if, - anon_sym_spawn, - anon_sym_chan, - anon_sym_send, - anon_sym_recv, - anon_sym_select, - anon_sym_use, - anon_sym_export, - anon_sym_macro_rules, - anon_sym_let, - anon_sym_while, - anon_sym_for, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - anon_sym_return, - anon_sym_break, - anon_sym_continue, - anon_sym_go, - anon_sym_try, - [40168] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1311), 12, - ts_builtin_sym_end, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(726), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_POUND, - ACTIONS(1313), 28, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [40981] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(728), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_match, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym__, + ACTIONS(730), 27, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [41032] = 14, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, + anon_sym_PIPE, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(1356), 1, + anon_sym_DOT_DOT, + ACTIONS(1358), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1340), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1342), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1354), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1338), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(900), 10, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_QMARK, + anon_sym__, + ACTIONS(902), 15, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_QMARK_QMARK, + anon_sym_SEMI, + [41105] = 13, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, + anon_sym_PIPE, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(1356), 1, + anon_sym_DOT_DOT, + ACTIONS(1358), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1340), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1342), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1338), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(900), 10, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_QMARK, + anon_sym__, + ACTIONS(902), 17, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_SEMI, + [41176] = 16, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, + anon_sym_PIPE, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(1356), 1, + anon_sym_DOT_DOT, + ACTIONS(1358), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1362), 1, + anon_sym_SLASH, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1340), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1342), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1354), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1360), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1338), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(904), 9, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + anon_sym__, + ACTIONS(906), 13, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_QMARK_QMARK, + anon_sym_SEMI, + [41253] = 24, + ACTIONS(1364), 1, + anon_sym_LPAREN, + ACTIONS(1367), 1, + anon_sym_DOT, + ACTIONS(1369), 1, + anon_sym_QMARK_DOT, + ACTIONS(1371), 1, + anon_sym_LBRACK, + ACTIONS(1373), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(1375), 1, + anon_sym_BANG, + ACTIONS(1380), 1, + anon_sym_SLASH, + ACTIONS(1392), 1, + anon_sym_AMP_AMP, + ACTIONS(1395), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1398), 1, + anon_sym_PIPE, + ACTIONS(1401), 1, + anon_sym_CARET, + ACTIONS(1404), 1, + anon_sym_AMP, + ACTIONS(1410), 1, + anon_sym_QMARK_QMARK, + ACTIONS(1413), 1, + anon_sym_DOT_DOT, + ACTIONS(1416), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1419), 1, + anon_sym_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1377), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1383), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1389), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1407), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1386), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(911), 6, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym__, + ACTIONS(913), 8, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_LBRACE, + anon_sym_SEMI, + [41346] = 24, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, + anon_sym_PIPE, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(1356), 1, + anon_sym_DOT_DOT, + ACTIONS(1358), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1362), 1, + anon_sym_SLASH, + ACTIONS(1367), 1, + anon_sym_DOT, + ACTIONS(1369), 1, + anon_sym_QMARK_DOT, + ACTIONS(1371), 1, + anon_sym_LBRACK, + ACTIONS(1373), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(1375), 1, + anon_sym_BANG, + ACTIONS(1422), 1, + anon_sym_LPAREN, + ACTIONS(1424), 1, + anon_sym_QMARK_QMARK, + ACTIONS(1426), 1, + anon_sym_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1340), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1342), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1354), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1360), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1338), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(911), 6, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym__, + ACTIONS(913), 8, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_LBRACE, + anon_sym_SEMI, + [41439] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1428), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1430), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, anon_sym_spawn, anon_sym_chan, @@ -49684,6 +52439,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49696,11 +52452,11 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [40217] = 3, + [41490] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1319), 12, + ACTIONS(1432), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49711,9 +52467,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1321), 28, + ACTIONS(1434), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49730,6 +52487,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49742,11 +52500,11 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [40266] = 3, + [41541] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1381), 12, + ACTIONS(1436), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49757,9 +52515,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1383), 28, + ACTIONS(1438), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49776,6 +52535,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49788,11 +52548,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [40315] = 3, + [41592] = 4, + ACTIONS(1440), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1377), 12, + ACTIONS(1314), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49803,9 +52565,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1379), 28, + ACTIONS(1316), 28, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49834,11 +52597,11 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [40364] = 3, + [41645] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1445), 12, + ACTIONS(1442), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, @@ -49849,9 +52612,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1447), 28, + ACTIONS(1444), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49868,6 +52632,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49880,23 +52645,25 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [40413] = 3, + [41696] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1497), 11, + ACTIONS(1446), 13, ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_LBRACK, anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, anon_sym_PIPE, anon_sym_POUND, - ACTIONS(1500), 28, + ACTIONS(1448), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49913,6 +52680,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_export, anon_sym_macro_rules, anon_sym_let, + anon_sym_else, anon_sym_while, anon_sym_for, anon_sym_fn, @@ -49925,11 +52693,11 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_continue, anon_sym_go, anon_sym_try, - [40461] = 3, + [41747] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 13, + ACTIONS(732), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49940,10 +52708,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(742), 24, + ACTIONS(734), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -49965,26 +52735,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40507] = 7, - ACTIONS(1507), 1, - anon_sym_AMP_AMP, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, + [41798] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1505), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1503), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(944), 11, + ACTIONS(736), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -49993,10 +52754,14 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(946), 18, + ACTIONS(738), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50012,14 +52777,23 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40561] = 3, + [41849] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 13, + ACTIONS(740), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50030,10 +52804,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(786), 24, + ACTIONS(742), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50055,66 +52831,65 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40607] = 12, - ACTIONS(1507), 1, - anon_sym_AMP_AMP, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1513), 1, - anon_sym_SLASH, - ACTIONS(1517), 1, - anon_sym_DOT_DOT, - ACTIONS(1519), 1, - anon_sym_DOT_DOT_EQ, + [41900] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1505), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1511), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1515), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1503), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(883), 9, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - anon_sym__, - ACTIONS(885), 13, + ACTIONS(1450), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_QMARK_QMARK, - anon_sym_SEMI, - [40671] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1452), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [41951] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(680), 13, + ACTIONS(744), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50125,10 +52900,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(682), 24, + ACTIONS(746), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50150,16 +52927,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40717] = 4, - ACTIONS(941), 1, - anon_sym_LBRACE, + [42002] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 13, + ACTIONS(748), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50170,10 +52948,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(686), 23, + ACTIONS(750), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50184,6 +52964,7 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, @@ -50194,14 +52975,65 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40765] = 3, + [42053] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(862), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(860), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [42104] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(843), 13, + ACTIONS(752), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50212,10 +53044,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(845), 24, + ACTIONS(754), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50237,14 +53071,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40811] = 3, + [42155] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(748), 13, + ACTIONS(756), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50255,10 +53092,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(750), 24, + ACTIONS(758), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50280,14 +53119,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40857] = 3, + [42206] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(752), 13, + ACTIONS(760), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50298,10 +53140,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(754), 24, + ACTIONS(762), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50323,14 +53167,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40903] = 3, + [42257] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(756), 13, + ACTIONS(764), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50341,10 +53188,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(758), 24, + ACTIONS(766), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50366,14 +53215,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40949] = 3, + [42308] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(768), 13, + ACTIONS(768), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50384,10 +53236,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(770), 24, + ACTIONS(770), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50409,14 +53263,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [40995] = 3, + [42359] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(772), 13, + ACTIONS(772), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50427,10 +53284,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(774), 24, + ACTIONS(774), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50452,14 +53311,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41041] = 3, + [42410] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(776), 13, + ACTIONS(776), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50470,10 +53332,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(778), 24, + ACTIONS(778), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50495,14 +53359,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41087] = 3, + [42461] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(780), 13, + ACTIONS(780), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50513,10 +53380,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(782), 24, + ACTIONS(782), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50538,14 +53407,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41133] = 3, + [42512] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(795), 13, + ACTIONS(784), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50556,10 +53428,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(797), 24, + ACTIONS(786), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50581,14 +53455,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41179] = 3, + [42563] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(799), 13, + ACTIONS(788), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50599,10 +53476,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(801), 24, + ACTIONS(790), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50624,14 +53503,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41225] = 3, + [42614] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(803), 13, + ACTIONS(800), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50642,10 +53524,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(805), 24, + ACTIONS(802), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50667,14 +53551,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41271] = 3, + [42665] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(668), 13, + ACTIONS(681), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50685,10 +53572,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(670), 24, + ACTIONS(683), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50710,14 +53599,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41317] = 3, + [42716] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(744), 13, + ACTIONS(808), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50728,10 +53620,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(746), 24, + ACTIONS(810), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50753,14 +53647,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41363] = 3, + [42767] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 13, + ACTIONS(812), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50771,10 +53668,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(686), 24, + ACTIONS(814), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50796,14 +53695,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41409] = 3, + [42818] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(807), 13, + ACTIONS(824), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50814,10 +53716,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(809), 24, + ACTIONS(826), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50839,14 +53743,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41455] = 3, + [42869] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(672), 13, + ACTIONS(828), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50857,10 +53764,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(674), 24, + ACTIONS(830), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50882,14 +53791,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41501] = 3, + [42920] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(676), 13, + ACTIONS(832), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50900,10 +53812,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(678), 24, + ACTIONS(834), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50925,16 +53839,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41547] = 4, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, + [42971] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 13, + ACTIONS(836), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50945,10 +53860,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(742), 23, + ACTIONS(838), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -50969,14 +53886,18 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41595] = 3, + [43022] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(760), 13, + ACTIONS(840), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -50987,10 +53908,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(762), 24, + ACTIONS(842), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -51012,337 +53935,449 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [41641] = 3, + [43073] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(764), 13, + ACTIONS(1454), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1456), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(766), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43124] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1458), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [41687] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(696), 13, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1460), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(698), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43175] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1462), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [41733] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(688), 13, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1464), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(690), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43226] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1466), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [41779] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(692), 13, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1468), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(694), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43277] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1470), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [41825] = 10, - ACTIONS(1507), 1, - anon_sym_AMP_AMP, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1517), 1, - anon_sym_DOT_DOT, - ACTIONS(1519), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1505), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1515), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1503), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(859), 10, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1472), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_QMARK, - anon_sym__, - ACTIONS(861), 15, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43328] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1474), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_QMARK_QMARK, - anon_sym_SEMI, - [41885] = 9, - ACTIONS(1507), 1, - anon_sym_AMP_AMP, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1517), 1, - anon_sym_DOT_DOT, - ACTIONS(1519), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1505), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1503), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(859), 10, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1476), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_QMARK, - anon_sym__, - ACTIONS(861), 17, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43379] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1478), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_SEMI, - [41943] = 12, - ACTIONS(1507), 1, - anon_sym_AMP_AMP, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1513), 1, - anon_sym_SLASH, - ACTIONS(1517), 1, - anon_sym_DOT_DOT, - ACTIONS(1519), 1, - anon_sym_DOT_DOT_EQ, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1480), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43430] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1505), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1511), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1515), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1503), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(873), 9, + ACTIONS(1482), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1484), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - anon_sym__, - ACTIONS(875), 13, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43481] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1302), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_QMARK_QMARK, - anon_sym_SEMI, - [42007] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1304), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43532] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(811), 13, + ACTIONS(844), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -51353,10 +54388,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(813), 24, + ACTIONS(846), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -51378,306 +54415,497 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42053] = 20, - ACTIONS(1521), 1, - anon_sym_LPAREN, - ACTIONS(1524), 1, - anon_sym_DOT, - ACTIONS(1526), 1, - anon_sym_QMARK_DOT, - ACTIONS(1528), 1, - anon_sym_LBRACK, - ACTIONS(1530), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(1532), 1, - anon_sym_BANG, - ACTIONS(1537), 1, - anon_sym_SLASH, - ACTIONS(1549), 1, - anon_sym_AMP_AMP, - ACTIONS(1552), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1555), 1, - anon_sym_QMARK_QMARK, - ACTIONS(1558), 1, - anon_sym_DOT_DOT, - ACTIONS(1561), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1564), 1, - anon_sym_QMARK, + [43583] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1534), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1540), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1546), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1543), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(887), 6, + ACTIONS(1486), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1488), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym__, - ACTIONS(889), 8, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43634] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1490), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_LBRACE, - anon_sym_SEMI, - [42133] = 20, - ACTIONS(1507), 1, - anon_sym_AMP_AMP, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1513), 1, - anon_sym_SLASH, - ACTIONS(1517), 1, - anon_sym_DOT_DOT, - ACTIONS(1519), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1524), 1, - anon_sym_DOT, - ACTIONS(1526), 1, - anon_sym_QMARK_DOT, - ACTIONS(1528), 1, + anon_sym_LPAREN, anon_sym_LBRACK, - ACTIONS(1530), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(1532), 1, + anon_sym_LBRACE, anon_sym_BANG, - ACTIONS(1567), 1, - anon_sym_LPAREN, - ACTIONS(1569), 1, - anon_sym_QMARK_QMARK, - ACTIONS(1571), 1, - anon_sym_QMARK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1505), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1511), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1515), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1503), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(887), 6, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1492), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym__, - ACTIONS(889), 8, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43685] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1494), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, - anon_sym_COMMA, + anon_sym_LPAREN, + anon_sym_LBRACK, anon_sym_LBRACE, - anon_sym_SEMI, - [42213] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(815), 13, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1496), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(817), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43736] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1498), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42259] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1500), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43787] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(819), 13, + ACTIONS(1502), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1504), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(821), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43838] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1506), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42305] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1508), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43889] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(823), 13, + ACTIONS(1510), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1512), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(825), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43940] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1514), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42351] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1516), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [43991] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(108), 13, + ACTIONS(1518), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1520), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(110), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44042] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1314), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42397] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1316), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44093] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(827), 13, + ACTIONS(848), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -51688,10 +54916,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(829), 24, + ACTIONS(850), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -51713,143 +54943,545 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42443] = 3, + [44144] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(835), 13, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(837), 24, + ACTIONS(1522), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42489] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1524), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44195] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(839), 13, + ACTIONS(1526), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1528), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44246] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1530), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(841), 24, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1532), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44297] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1534), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42535] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1536), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44348] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(664), 13, + ACTIONS(1538), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1540), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44399] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1542), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(666), 24, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1544), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44450] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1546), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42581] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1548), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44501] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1550), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1552), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44552] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1554), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1556), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44603] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1558), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1560), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44654] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1562), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1564), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44705] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(700), 13, + ACTIONS(852), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -51860,10 +55492,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(702), 24, + ACTIONS(854), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -51885,14 +55519,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42627] = 3, + [44756] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(704), 13, + ACTIONS(856), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -51903,10 +55540,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(706), 24, + ACTIONS(858), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -51928,29 +55567,33 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42673] = 4, - ACTIONS(1573), 1, - anon_sym_BANG, + [44807] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 12, + ACTIONS(685), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, anon_sym_DOT, + anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(786), 24, + ACTIONS(687), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -51972,57 +55615,113 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42721] = 3, + [44858] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(712), 13, + ACTIONS(1566), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1568), 29, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(714), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44909] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1570), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [42767] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1572), 29, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_else, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [44960] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(791), 13, + ACTIONS(697), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -52033,10 +55732,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(793), 24, + ACTIONS(699), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -52058,14 +55759,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42813] = 3, + [45011] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(716), 13, + ACTIONS(689), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -52076,10 +55780,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(718), 24, + ACTIONS(691), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -52101,14 +55807,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42859] = 3, + [45062] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(831), 13, + ACTIONS(677), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -52119,10 +55828,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(833), 24, + ACTIONS(679), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -52144,28 +55855,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42905] = 3, + [45113] = 4, + ACTIONS(1574), 1, + anon_sym_BANG, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(720), 13, + ACTIONS(697), 14, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, anon_sym_DOT, - anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(722), 24, + ACTIONS(699), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -52187,14 +55904,17 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42951] = 3, + [45166] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(724), 13, + ACTIONS(720), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -52205,10 +55925,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(726), 24, + ACTIONS(722), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -52230,14 +55952,50 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [42997] = 3, + [45217] = 16, + ACTIONS(1328), 1, + anon_sym_AMP_AMP, + ACTIONS(1330), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1332), 1, + anon_sym_PIPE, + ACTIONS(1334), 1, + anon_sym_CARET, + ACTIONS(1336), 1, + anon_sym_AMP, + ACTIONS(1356), 1, + anon_sym_DOT_DOT, + ACTIONS(1358), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1362), 1, + anon_sym_SLASH, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(728), 13, + ACTIONS(1340), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1342), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1354), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1360), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1338), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(870), 9, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -52245,13 +56003,9 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_nil, anon_sym_DOT, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(730), 24, + ACTIONS(872), 13, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -52263,67 +56017,62 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [43043] = 3, + [45294] = 4, + ACTIONS(1577), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(732), 13, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(734), 24, + ACTIONS(1308), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [43089] = 3, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1310), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45347] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(736), 13, + ACTIONS(804), 15, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, @@ -52334,10 +56083,12 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, anon_sym__, - ACTIONS(738), 24, + ACTIONS(806), 27, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -52359,1718 +56110,2577 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [43135] = 5, - ACTIONS(1507), 1, - anon_sym_AMP_AMP, - ACTIONS(1509), 1, - anon_sym_PIPE_PIPE, + [45398] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 13, + ACTIONS(1510), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1512), 28, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(861), 22, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45448] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1462), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [43185] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(708), 13, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1464), 28, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym__, - ACTIONS(710), 24, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45498] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1442), 13, + ts_builtin_sym_end, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - [43231] = 4, - ACTIONS(1576), 1, anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1444), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45548] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(686), 26, + ACTIONS(1446), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1448), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43277] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45598] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(768), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(770), 24, + ACTIONS(1534), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1536), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43319] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45648] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(744), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(746), 24, + ACTIONS(1538), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1540), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43361] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45698] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(764), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(766), 24, + ACTIONS(1454), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1456), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43403] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45748] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(776), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(778), 24, + ACTIONS(1542), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1544), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43445] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45798] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(780), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(782), 24, + ACTIONS(1506), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1508), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43487] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45848] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(692), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(694), 24, + ACTIONS(1558), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1560), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43529] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45898] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(720), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(722), 24, + ACTIONS(1562), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - anon_sym_if, - anon_sym_case, - anon_sym_default, - [43571] = 4, - ACTIONS(1576), 1, anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1564), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45948] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 13, - anon_sym_COLON, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_STAR, - anon_sym_SLASH, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_EQ, - ACTIONS(686), 19, + ACTIONS(1486), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - anon_sym_COLON_EQ, - anon_sym_PLUS_EQ, - anon_sym_DASH_EQ, - anon_sym_STAR_EQ, - anon_sym_SLASH_EQ, - anon_sym_PERCENT_EQ, - [43615] = 3, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1488), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [45998] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(724), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(726), 24, + ACTIONS(706), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(704), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43657] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46048] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(728), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(730), 24, + ACTIONS(1566), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - anon_sym_if, - anon_sym_case, - anon_sym_default, - [43699] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(732), 9, - anon_sym_DOT, + anon_sym_LBRACE, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, + anon_sym_TILDE, anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(734), 24, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_POUND, + ACTIONS(1568), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43741] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46098] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(736), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(738), 24, + ACTIONS(1546), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1548), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43783] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46148] = 4, + ACTIONS(1583), 1, + anon_sym_else, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(742), 24, + ACTIONS(1581), 12, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1579), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43825] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46200] = 4, + ACTIONS(1585), 1, + anon_sym_else, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(843), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(845), 24, + ACTIONS(1478), 12, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1480), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43867] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46252] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(686), 24, + ACTIONS(1428), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1430), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43909] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46302] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(795), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(797), 24, + ACTIONS(1570), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1572), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43951] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46352] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(799), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(801), 24, + ACTIONS(1490), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1492), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [43993] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46402] = 4, + ACTIONS(1587), 1, + anon_sym_else, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(803), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(805), 24, + ACTIONS(1581), 12, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1579), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44035] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46454] = 4, + ACTIONS(1590), 1, + anon_sym_else, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(807), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(809), 24, + ACTIONS(1478), 12, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1480), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44077] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46506] = 4, + ACTIONS(1593), 1, + anon_sym_else, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(811), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(813), 24, - anon_sym_RBRACE, + ACTIONS(1478), 12, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1480), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44119] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46558] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(748), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(750), 24, + ACTIONS(1348), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1350), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44161] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46608] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(696), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(698), 24, + ACTIONS(1436), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1438), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44203] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46658] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(772), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(774), 24, + ACTIONS(1466), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1468), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44245] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46708] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(815), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(817), 24, + ACTIONS(1470), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1472), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44287] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46758] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(819), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(821), 24, + ACTIONS(1450), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1452), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44329] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46808] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 9, - anon_sym_DOT, + ACTIONS(1324), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, + anon_sym_TILDE, anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(786), 24, - anon_sym_RBRACE, - anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_POUND, + ACTIONS(1326), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44371] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46858] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(823), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(825), 24, + ACTIONS(1314), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - anon_sym_if, - anon_sym_case, - anon_sym_default, - [44413] = 4, - ACTIONS(1578), 1, + anon_sym_LBRACE, anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1316), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46908] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 8, - anon_sym_DOT, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(786), 24, + ACTIONS(1474), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1476), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44457] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [46958] = 4, + ACTIONS(1596), 1, + anon_sym_else, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(831), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(833), 24, - anon_sym_RBRACE, + ACTIONS(1478), 12, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1480), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44499] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47010] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(108), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(110), 24, + ACTIONS(1522), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1524), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44541] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47060] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(700), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(702), 24, + ACTIONS(1526), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1528), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44583] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47110] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(704), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(706), 24, + ACTIONS(1530), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1532), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44625] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47160] = 4, + ACTIONS(1598), 1, + anon_sym_else, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(712), 9, - anon_sym_DOT, + ACTIONS(1581), 12, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, + anon_sym_TILDE, anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(714), 24, + anon_sym_POUND, + ACTIONS(1579), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47212] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1478), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1480), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44667] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47262] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(680), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(682), 24, + ACTIONS(862), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(860), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44709] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47312] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(827), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(829), 24, + ACTIONS(1514), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1516), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44751] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47362] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(791), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(793), 24, + ACTIONS(1518), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1520), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44793] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47412] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(835), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(837), 24, + ACTIONS(1320), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1322), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44835] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47462] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(839), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(841), 24, + ACTIONS(1458), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1460), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44877] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47512] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(716), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(718), 24, + ACTIONS(1432), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1434), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44919] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47562] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(672), 9, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(674), 24, + ACTIONS(710), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(708), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, anon_sym_if, - anon_sym_case, - anon_sym_default, - [44961] = 3, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47612] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(752), 9, - anon_sym_DOT, + ACTIONS(1554), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1556), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47662] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1482), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1484), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47712] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1302), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1304), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47762] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1494), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1496), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47812] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1550), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1552), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47862] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1344), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1346), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47912] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1498), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1500), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [47962] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1502), 13, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1504), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [48012] = 4, + ACTIONS(1600), 1, + anon_sym_else, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1581), 12, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1579), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [48064] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1603), 12, + ts_builtin_sym_end, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_LPAREN, + anon_sym_LBRACK, + anon_sym_LBRACE, + anon_sym_BANG, + anon_sym_TILDE, + anon_sym_PIPE, + anon_sym_POUND, + ACTIONS(1606), 28, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym_match, + anon_sym_if, + anon_sym_spawn, + anon_sym_chan, + anon_sym_send, + anon_sym_recv, + anon_sym_select, + anon_sym_use, + anon_sym_export, + anon_sym_macro_rules, + anon_sym_let, + anon_sym_while, + anon_sym_for, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + anon_sym_return, + anon_sym_break, + anon_sym_continue, + anon_sym_go, + anon_sym_try, + [48113] = 4, + ACTIONS(1609), 1, + anon_sym_LBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(716), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(754), 24, + ACTIONS(718), 29, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -54087,66 +58697,77 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, anon_sym_SEMI, anon_sym_if, anon_sym_case, anon_sym_default, - [45003] = 3, + [48163] = 4, + ACTIONS(1609), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(756), 9, + ACTIONS(716), 15, + anon_sym_COLON, anon_sym_DOT, anon_sym_BANG, + anon_sym_STAR, anon_sym_SLASH, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(758), 24, - anon_sym_RBRACE, + ACTIONS(718), 22, anon_sym_LPAREN, - anon_sym_COMMA, - anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, - anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - anon_sym_if, - anon_sym_case, - anon_sym_default, - [45045] = 3, + anon_sym_COLON_EQ, + anon_sym_PLUS_EQ, + anon_sym_DASH_EQ, + anon_sym_STAR_EQ, + anon_sym_SLASH_EQ, + anon_sym_PERCENT_EQ, + [48212] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(664), 9, + ACTIONS(760), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(666), 24, + ACTIONS(762), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, @@ -54165,27 +58786,31 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_case, anon_sym_default, - [45087] = 3, + [48258] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(676), 9, + ACTIONS(828), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(678), 24, + ACTIONS(830), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, @@ -54204,27 +58829,31 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_case, anon_sym_default, - [45129] = 3, + [48304] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(668), 9, + ACTIONS(832), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(670), 24, + ACTIONS(834), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, @@ -54243,27 +58872,31 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_case, anon_sym_default, - [45171] = 3, + [48350] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(708), 9, + ACTIONS(836), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(710), 24, + ACTIONS(838), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, @@ -54282,27 +58915,31 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_case, anon_sym_default, - [45213] = 3, + [48396] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(760), 9, + ACTIONS(840), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(762), 24, + ACTIONS(842), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, @@ -54321,27 +58958,31 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_case, anon_sym_default, - [45255] = 3, + [48442] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(688), 9, + ACTIONS(844), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(690), 24, + ACTIONS(846), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, @@ -54360,419 +59001,335 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_case, anon_sym_default, - [45297] = 8, - ACTIONS(788), 1, - anon_sym_BANG, - ACTIONS(1581), 1, - anon_sym_COLON, - ACTIONS(1584), 1, - anon_sym_EQ, - ACTIONS(1586), 1, - anon_sym_COLON_EQ, + [48488] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1588), 5, - anon_sym_PLUS_EQ, - anon_sym_DASH_EQ, - anon_sym_STAR_EQ, - anon_sym_SLASH_EQ, - anon_sym_PERCENT_EQ, - ACTIONS(784), 10, + ACTIONS(848), 10, anon_sym_DOT, - anon_sym_STAR, + anon_sym_BANG, anon_sym_SLASH, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(786), 13, + anon_sym_EQ, + ACTIONS(850), 27, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [45348] = 7, - ACTIONS(788), 1, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48534] = 4, + ACTIONS(1611), 1, anon_sym_BANG, - ACTIONS(1584), 1, - anon_sym_EQ, - ACTIONS(1586), 1, - anon_sym_COLON_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1588), 5, - anon_sym_PLUS_EQ, - anon_sym_DASH_EQ, - anon_sym_STAR_EQ, - anon_sym_SLASH_EQ, - anon_sym_PERCENT_EQ, - ACTIONS(784), 10, + ACTIONS(697), 9, anon_sym_DOT, - anon_sym_STAR, anon_sym_SLASH, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(786), 13, + anon_sym_EQ, + ACTIONS(699), 27, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [45396] = 7, - ACTIONS(788), 1, - anon_sym_BANG, - ACTIONS(1590), 1, - anon_sym_EQ, - ACTIONS(1592), 1, - anon_sym_COLON_EQ, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48582] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1594), 5, - anon_sym_PLUS_EQ, - anon_sym_DASH_EQ, - anon_sym_STAR_EQ, - anon_sym_SLASH_EQ, - anon_sym_PERCENT_EQ, - ACTIONS(784), 10, + ACTIONS(736), 10, anon_sym_DOT, - anon_sym_STAR, + anon_sym_BANG, anon_sym_SLASH, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(786), 13, + anon_sym_EQ, + ACTIONS(738), 27, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [45444] = 19, - ACTIONS(891), 1, - anon_sym_LPAREN, - ACTIONS(894), 1, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48628] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(852), 10, anon_sym_DOT, - ACTIONS(896), 1, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(854), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, - ACTIONS(898), 1, anon_sym_LBRACK, - ACTIONS(900), 1, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(1599), 1, - anon_sym_SLASH, - ACTIONS(1611), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1614), 1, anon_sym_PIPE_PIPE, - ACTIONS(1617), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - ACTIONS(1620), 1, - anon_sym_DOT_DOT, - ACTIONS(1623), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(1626), 1, - anon_sym_QMARK, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48674] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1596), 2, + ACTIONS(740), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(742), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1602), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1608), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1605), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(889), 7, - anon_sym_RBRACE, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, - anon_sym_EQ_GT, - anon_sym_SEMI, - [45515] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1645), 1, - anon_sym_RBRACE, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(675), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [45588] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - ACTIONS(1653), 1, - anon_sym_RBRACE, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(674), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [45661] = 12, - ACTIONS(1657), 1, - anon_sym_SLASH, - ACTIONS(1665), 1, anon_sym_AMP_AMP, - ACTIONS(1667), 1, anon_sym_PIPE_PIPE, - ACTIONS(1669), 1, - anon_sym_DOT_DOT, - ACTIONS(1671), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48720] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1655), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1659), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1663), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(883), 3, + ACTIONS(744), 10, anon_sym_DOT, anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(1661), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(885), 12, + anon_sym_EQ, + ACTIONS(746), 27, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - [45718] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - ACTIONS(1673), 1, - anon_sym_RBRACE, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(670), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [45791] = 7, - ACTIONS(1665), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1667), 1, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48766] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1663), 2, + ACTIONS(720), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(1661), 4, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(722), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(944), 5, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48812] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(748), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(946), 17, + anon_sym_EQ, + ACTIONS(750), 27, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -54783,181 +59340,168 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_SEMI, - [45838] = 20, - ACTIONS(1675), 1, - aux_sym_identifier_token1, - ACTIONS(1678), 1, - sym_integer_literal, - ACTIONS(1681), 1, - sym_float_literal, - ACTIONS(1687), 1, - anon_sym_nil, - ACTIONS(1690), 1, - anon_sym_DQUOTE, - ACTIONS(1693), 1, - anon_sym_SQUOTE, - ACTIONS(1696), 1, - sym_raw_string, - ACTIONS(1699), 1, - anon_sym_RBRACE, - ACTIONS(1701), 1, - anon_sym_LBRACK, - ACTIONS(1704), 1, - anon_sym_LBRACE, - ACTIONS(1707), 1, - anon_sym__, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1684), 2, - anon_sym_true, - anon_sym_false, - STATE(670), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [45911] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - ACTIONS(1710), 1, - anon_sym_RBRACE, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48858] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(668), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [45984] = 12, - ACTIONS(1657), 1, + ACTIONS(820), 10, + anon_sym_DOT, + anon_sym_BANG, anon_sym_SLASH, - ACTIONS(1665), 1, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(822), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1667), 1, anon_sym_PIPE_PIPE, - ACTIONS(1669), 1, - anon_sym_DOT_DOT, - ACTIONS(1671), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48904] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1655), 2, + ACTIONS(856), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(858), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1659), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1663), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(873), 3, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - ACTIONS(1661), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(875), 12, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48950] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(685), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(687), 27, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, - anon_sym_SEMI, - [46041] = 5, - ACTIONS(1665), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1667), 1, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [48996] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 7, + ACTIONS(689), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(861), 21, + anon_sym_EQ, + ACTIONS(691), 27, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -54972,198 +59516,121 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_SEMI, - [46084] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - ACTIONS(1712), 1, - anon_sym_RBRACE, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49042] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(670), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46157] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - ACTIONS(1714), 1, + ACTIONS(792), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(794), 27, anon_sym_RBRACE, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(670), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46230] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - ACTIONS(1716), 1, - anon_sym_RBRACE, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(670), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46303] = 9, - ACTIONS(1665), 1, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1667), 1, anon_sym_PIPE_PIPE, - ACTIONS(1669), 1, - anon_sym_DOT_DOT, - ACTIONS(1671), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49088] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1663), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(859), 4, + ACTIONS(677), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(1661), 4, + anon_sym_EQ, + ACTIONS(679), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(861), 16, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49134] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(776), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(778), 27, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -55174,132 +59641,216 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [46354] = 19, - ACTIONS(894), 1, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49180] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(752), 10, anon_sym_DOT, - ACTIONS(896), 1, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(754), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, - ACTIONS(898), 1, anon_sym_LBRACK, - ACTIONS(900), 1, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(1657), 1, - anon_sym_SLASH, - ACTIONS(1665), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1667), 1, anon_sym_PIPE_PIPE, - ACTIONS(1669), 1, - anon_sym_DOT_DOT, - ACTIONS(1671), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1718), 1, - anon_sym_LPAREN, - ACTIONS(1720), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - ACTIONS(1722), 1, - anon_sym_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49226] = 8, + ACTIONS(701), 1, + anon_sym_BANG, + ACTIONS(1614), 1, + anon_sym_COLON, + ACTIONS(1617), 1, + anon_sym_EQ, + ACTIONS(1619), 1, + anon_sym_COLON_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1655), 2, + ACTIONS(1621), 5, + anon_sym_PLUS_EQ, + anon_sym_DASH_EQ, + anon_sym_STAR_EQ, + anon_sym_SLASH_EQ, + anon_sym_PERCENT_EQ, + ACTIONS(697), 12, + anon_sym_DOT, anon_sym_STAR, + anon_sym_SLASH, anon_sym_PERCENT, - ACTIONS(1659), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1663), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1661), 4, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(699), 16, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(889), 7, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [49282] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(780), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(782), 27, anon_sym_RBRACE, - anon_sym_RPAREN, + anon_sym_LPAREN, anon_sym_COMMA, anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, anon_sym_RBRACK, - anon_sym_EQ_GT, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [46425] = 20, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - ACTIONS(1724), 1, - anon_sym_RBRACE, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1252), 1, - sym_pattern, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49328] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(676), 2, - sym_match_arm, - aux_sym_match_expression_repeat1, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46498] = 4, - ACTIONS(1667), 1, + ACTIONS(784), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(786), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49374] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 7, + ACTIONS(788), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(742), 22, + anon_sym_EQ, + ACTIONS(790), 27, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -55315,42 +59866,120 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_SEMI, - [46539] = 10, - ACTIONS(1665), 1, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49420] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(800), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(802), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1667), 1, anon_sym_PIPE_PIPE, - ACTIONS(1669), 1, - anon_sym_DOT_DOT, - ACTIONS(1671), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49466] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1659), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1663), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(859), 4, + ACTIONS(804), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(1661), 4, + anon_sym_EQ, + ACTIONS(806), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(861), 14, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49512] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(756), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(758), 27, anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -55359,340 +59988,86 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, - [46592] = 20, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1744), 1, - anon_sym_RBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1748), 1, - anon_sym_DOT_DOT, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(1005), 1, - sym_pattern, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49558] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46664] = 20, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - ACTIONS(1752), 1, - anon_sym_RBRACK, - ACTIONS(1754), 1, - anon_sym_DOT_DOT, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(1005), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46736] = 20, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - ACTIONS(1756), 1, - anon_sym_RBRACK, - ACTIONS(1758), 1, - anon_sym_DOT_DOT, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(1005), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46808] = 20, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - ACTIONS(1760), 1, - anon_sym_RBRACK, - ACTIONS(1762), 1, - anon_sym_DOT_DOT, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(1005), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [46880] = 12, - ACTIONS(1766), 1, + ACTIONS(808), 10, + anon_sym_DOT, + anon_sym_BANG, anon_sym_SLASH, - ACTIONS(1774), 1, - anon_sym_AMP_AMP, - ACTIONS(1776), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1778), 1, - anon_sym_DOT_DOT, - ACTIONS(1780), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1764), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1768), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1772), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(873), 4, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, anon_sym_PIPE, - ACTIONS(1770), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(875), 9, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(810), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_if, - [46935] = 19, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - ACTIONS(1782), 1, - anon_sym_RBRACK, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(947), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [47004] = 9, - ACTIONS(1774), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1776), 1, anon_sym_PIPE_PIPE, - ACTIONS(1778), 1, - anon_sym_DOT_DOT, - ACTIONS(1780), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49604] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1772), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1770), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(859), 5, + ACTIONS(812), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_QMARK, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, - ACTIONS(861), 13, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(814), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, @@ -55701,179 +60076,84 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_if, - [47053] = 12, - ACTIONS(1766), 1, - anon_sym_SLASH, - ACTIONS(1774), 1, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1776), 1, anon_sym_PIPE_PIPE, - ACTIONS(1778), 1, - anon_sym_DOT_DOT, - ACTIONS(1780), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49650] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1764), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1768), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1772), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(883), 4, + ACTIONS(712), 10, anon_sym_DOT, anon_sym_BANG, - anon_sym_QMARK, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, - ACTIONS(1770), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(885), 9, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(714), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_if, - [47108] = 20, - ACTIONS(887), 1, - anon_sym_PIPE, - ACTIONS(1784), 1, - anon_sym_LPAREN, - ACTIONS(1787), 1, - anon_sym_DOT, - ACTIONS(1789), 1, - anon_sym_QMARK_DOT, - ACTIONS(1791), 1, - anon_sym_LBRACK, - ACTIONS(1793), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, - anon_sym_BANG, - ACTIONS(1800), 1, - anon_sym_SLASH, - ACTIONS(1812), 1, - anon_sym_AMP_AMP, - ACTIONS(1815), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1818), 1, - anon_sym_QMARK_QMARK, - ACTIONS(1821), 1, - anon_sym_DOT_DOT, - ACTIONS(1824), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1827), 1, - anon_sym_QMARK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1797), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1803), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1809), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(889), 4, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_if, - ACTIONS(1806), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [47179] = 19, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - ACTIONS(1829), 1, - anon_sym_RBRACK, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(957), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [47248] = 7, - ACTIONS(1774), 1, anon_sym_AMP_AMP, - ACTIONS(1776), 1, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49696] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1772), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1770), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(944), 6, + ACTIONS(764), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(946), 14, + anon_sym_EQ, + ACTIONS(766), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, @@ -55882,81 +60162,87 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_if, - [47293] = 20, - ACTIONS(887), 1, - anon_sym_PIPE, - ACTIONS(1784), 1, - anon_sym_LPAREN, - ACTIONS(1787), 1, - anon_sym_DOT, - ACTIONS(1789), 1, - anon_sym_QMARK_DOT, - ACTIONS(1791), 1, - anon_sym_LBRACK, - ACTIONS(1793), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, - anon_sym_BANG, - ACTIONS(1800), 1, - anon_sym_SLASH, - ACTIONS(1812), 1, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1815), 1, anon_sym_PIPE_PIPE, - ACTIONS(1818), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - ACTIONS(1821), 1, - anon_sym_DOT_DOT, - ACTIONS(1824), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(1831), 1, - anon_sym_QMARK, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49742] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1797), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1803), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1809), 2, + ACTIONS(716), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(889), 4, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(718), 27, anon_sym_RBRACE, + anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, anon_sym_RBRACK, - anon_sym_if, - ACTIONS(1806), 4, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [47364] = 4, - ACTIONS(1576), 1, - anon_sym_LBRACE, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49788] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 9, + ACTIONS(816), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_EQ, - ACTIONS(686), 18, + ACTIONS(818), 27, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -55968,30 +60254,35 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, anon_sym_if, - [47403] = 5, - ACTIONS(1774), 1, - anon_sym_AMP_AMP, - ACTIONS(1776), 1, - anon_sym_PIPE_PIPE, + anon_sym_case, + anon_sym_default, + [49834] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 8, + ACTIONS(724), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(861), 18, + anon_sym_EQ, + ACTIONS(726), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, @@ -56004,28 +60295,37 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, anon_sym_if, - [47444] = 4, - ACTIONS(1776), 1, - anon_sym_PIPE_PIPE, + anon_sym_case, + anon_sym_default, + [49880] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 8, + ACTIONS(768), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(742), 19, + anon_sym_EQ, + ACTIONS(770), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, @@ -56039,110 +60339,125 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, anon_sym_if, - [47483] = 10, - ACTIONS(1774), 1, - anon_sym_AMP_AMP, - ACTIONS(1776), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1778), 1, - anon_sym_DOT_DOT, - ACTIONS(1780), 1, - anon_sym_DOT_DOT_EQ, + anon_sym_case, + anon_sym_default, + [49926] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1768), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1772), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1770), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(859), 5, + ACTIONS(728), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_QMARK, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, - ACTIONS(861), 11, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(730), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - anon_sym_QMARK_QMARK, - anon_sym_if, - [47534] = 9, - ACTIONS(1838), 1, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1840), 1, anon_sym_PIPE_PIPE, - ACTIONS(1842), 1, - anon_sym_DOT_DOT, - ACTIONS(1844), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [49972] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1836), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1834), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(859), 6, + ACTIONS(704), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_QMARK, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, anon_sym_EQ, - ACTIONS(861), 11, + ACTIONS(706), 27, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_if, - [47582] = 5, - ACTIONS(1846), 1, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1848), 1, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [50018] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 7, + ACTIONS(693), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(861), 18, + anon_sym_EQ, + ACTIONS(695), 27, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -56152,30 +60467,40 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, + anon_sym_if, anon_sym_case, anon_sym_default, - [47622] = 4, - ACTIONS(1848), 1, - anon_sym_PIPE_PIPE, + [50064] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 7, + ACTIONS(796), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(742), 19, + anon_sym_EQ, + ACTIONS(798), 27, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -56186,286 +60511,294 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_SEMI, + anon_sym_if, anon_sym_case, anon_sym_default, - [47660] = 7, - ACTIONS(1846), 1, - anon_sym_AMP_AMP, - ACTIONS(1848), 1, - anon_sym_PIPE_PIPE, + [50110] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1852), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1850), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(944), 5, + ACTIONS(681), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(946), 14, + anon_sym_EQ, + ACTIONS(683), 27, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_SEMI, - anon_sym_case, - anon_sym_default, - [47704] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(963), 1, - sym_literal_pattern, - STATE(1027), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [47770] = 10, - ACTIONS(1846), 1, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1848), 1, anon_sym_PIPE_PIPE, - ACTIONS(1856), 1, - anon_sym_DOT_DOT, - ACTIONS(1858), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [50156] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1852), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1854), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(859), 4, + ACTIONS(772), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(1850), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 11, + anon_sym_EQ, + ACTIONS(774), 27, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, + anon_sym_if, anon_sym_case, anon_sym_default, - [47820] = 9, - ACTIONS(1846), 1, - anon_sym_AMP_AMP, - ACTIONS(1848), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1856), 1, - anon_sym_DOT_DOT, - ACTIONS(1858), 1, - anon_sym_DOT_DOT_EQ, + [50202] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1852), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(859), 4, + ACTIONS(732), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(1850), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 13, + anon_sym_EQ, + ACTIONS(734), 27, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, + anon_sym_if, anon_sym_case, anon_sym_default, - [47868] = 12, - ACTIONS(1846), 1, - anon_sym_AMP_AMP, - ACTIONS(1848), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1856), 1, - anon_sym_DOT_DOT, - ACTIONS(1858), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1862), 1, - anon_sym_SLASH, + [50248] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1852), 2, + ACTIONS(108), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(1854), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1860), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(110), 27, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(873), 3, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - ACTIONS(1850), 4, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(875), 9, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [50294] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(824), 10, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(826), 27, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, + anon_sym_if, anon_sym_case, anon_sym_default, - [47922] = 10, - ACTIONS(1870), 1, - anon_sym_AMP_AMP, - ACTIONS(1872), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1874), 1, - anon_sym_DOT_DOT, - ACTIONS(1876), 1, - anon_sym_DOT_DOT_EQ, + [50340] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1864), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1868), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(859), 4, + ACTIONS(708), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, - ACTIONS(1866), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 11, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(710), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - anon_sym_QMARK_QMARK, - anon_sym_if, - [47972] = 9, - ACTIONS(1870), 1, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1872), 1, anon_sym_PIPE_PIPE, - ACTIONS(1874), 1, - anon_sym_DOT_DOT, - ACTIONS(1876), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_case, + anon_sym_default, + [50386] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1868), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(859), 4, + ACTIONS(697), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, anon_sym_PIPE, - ACTIONS(1866), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 13, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(699), 27, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, @@ -56474,794 +60807,639 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, anon_sym_if, - [48020] = 12, - ACTIONS(1870), 1, + anon_sym_case, + anon_sym_default, + [50432] = 7, + ACTIONS(701), 1, + anon_sym_BANG, + ACTIONS(1617), 1, + anon_sym_EQ, + ACTIONS(1619), 1, + anon_sym_COLON_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1621), 5, + anon_sym_PLUS_EQ, + anon_sym_DASH_EQ, + anon_sym_STAR_EQ, + anon_sym_SLASH_EQ, + anon_sym_PERCENT_EQ, + ACTIONS(697), 12, + anon_sym_DOT, + anon_sym_STAR, + anon_sym_SLASH, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(699), 16, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(1872), 1, anon_sym_PIPE_PIPE, - ACTIONS(1874), 1, - anon_sym_DOT_DOT, - ACTIONS(1876), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - ACTIONS(1880), 1, - anon_sym_SLASH, + anon_sym_SEMI, + [50485] = 7, + ACTIONS(701), 1, + anon_sym_BANG, + ACTIONS(1623), 1, + anon_sym_EQ, + ACTIONS(1625), 1, + anon_sym_COLON_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1864), 2, + ACTIONS(1627), 5, + anon_sym_PLUS_EQ, + anon_sym_DASH_EQ, + anon_sym_STAR_EQ, + anon_sym_SLASH_EQ, + anon_sym_PERCENT_EQ, + ACTIONS(697), 12, + anon_sym_DOT, + anon_sym_STAR, + anon_sym_SLASH, + anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1868), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1878), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(699), 16, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + [50538] = 16, + ACTIONS(1631), 1, + anon_sym_SLASH, + ACTIONS(1639), 1, + anon_sym_AMP_AMP, + ACTIONS(1641), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, + ACTIONS(1651), 1, + anon_sym_DOT_DOT, + ACTIONS(1653), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1629), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(873), 3, + ACTIONS(1633), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1637), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1649), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(870), 3, anon_sym_DOT, anon_sym_BANG, - anon_sym_PIPE, - ACTIONS(1866), 4, + anon_sym_QMARK, + ACTIONS(1635), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(875), 9, + ACTIONS(872), 12, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_QMARK_QMARK, - anon_sym_if, - [48074] = 20, - ACTIONS(1784), 1, - anon_sym_LPAREN, - ACTIONS(1787), 1, - anon_sym_DOT, - ACTIONS(1789), 1, - anon_sym_QMARK_DOT, - ACTIONS(1791), 1, - anon_sym_LBRACK, - ACTIONS(1793), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, - anon_sym_BANG, - ACTIONS(1885), 1, - anon_sym_SLASH, - ACTIONS(1897), 1, + anon_sym_EQ_GT, + anon_sym_SEMI, + [50608] = 14, + ACTIONS(1639), 1, anon_sym_AMP_AMP, - ACTIONS(1900), 1, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, - ACTIONS(1903), 1, - anon_sym_QMARK_QMARK, - ACTIONS(1906), 1, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, + ACTIONS(1651), 1, anon_sym_DOT_DOT, - ACTIONS(1909), 1, + ACTIONS(1653), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(1912), 1, - anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(887), 2, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(889), 2, - anon_sym_COLON, - anon_sym_if, - ACTIONS(1882), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1888), 2, + ACTIONS(1633), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1894), 2, + ACTIONS(1637), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1891), 4, + ACTIONS(1649), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_QMARK, + ACTIONS(1635), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [48144] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(1032), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [48210] = 19, - ACTIONS(1784), 1, + ACTIONS(902), 14, + anon_sym_RBRACE, anon_sym_LPAREN, - ACTIONS(1787), 1, - anon_sym_DOT, - ACTIONS(1789), 1, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, - ACTIONS(1791), 1, anon_sym_LBRACK, - ACTIONS(1793), 1, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, - anon_sym_BANG, - ACTIONS(1917), 1, - anon_sym_SLASH, - ACTIONS(1929), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_SEMI, + [50674] = 6, + ACTIONS(1639), 1, anon_sym_AMP_AMP, - ACTIONS(1932), 1, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, - ACTIONS(1935), 1, - anon_sym_QMARK_QMARK, - ACTIONS(1938), 1, - anon_sym_DOT_DOT, - ACTIONS(1941), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1944), 1, - anon_sym_QMARK, + ACTIONS(1643), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1914), 2, + ACTIONS(900), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 24, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1920), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1926), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(889), 4, - anon_sym_RBRACE, - anon_sym_SEMI, - anon_sym_case, - anon_sym_default, - ACTIONS(1923), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [48278] = 20, - ACTIONS(1784), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_SEMI, + [50724] = 23, + ACTIONS(915), 1, anon_sym_LPAREN, - ACTIONS(1787), 1, + ACTIONS(918), 1, anon_sym_DOT, - ACTIONS(1789), 1, + ACTIONS(920), 1, anon_sym_QMARK_DOT, - ACTIONS(1791), 1, + ACTIONS(922), 1, anon_sym_LBRACK, - ACTIONS(1793), 1, + ACTIONS(924), 1, anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, + ACTIONS(926), 1, anon_sym_BANG, - ACTIONS(1885), 1, + ACTIONS(1658), 1, anon_sym_SLASH, - ACTIONS(1897), 1, + ACTIONS(1670), 1, anon_sym_AMP_AMP, - ACTIONS(1900), 1, + ACTIONS(1673), 1, anon_sym_PIPE_PIPE, - ACTIONS(1903), 1, + ACTIONS(1676), 1, + anon_sym_PIPE, + ACTIONS(1679), 1, + anon_sym_CARET, + ACTIONS(1682), 1, + anon_sym_AMP, + ACTIONS(1688), 1, anon_sym_QMARK_QMARK, - ACTIONS(1906), 1, + ACTIONS(1691), 1, anon_sym_DOT_DOT, - ACTIONS(1909), 1, + ACTIONS(1694), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(1947), 1, + ACTIONS(1697), 1, anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(887), 2, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(889), 2, - anon_sym_COLON, - anon_sym_if, - ACTIONS(1882), 2, + ACTIONS(1655), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1888), 2, + ACTIONS(1661), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1894), 2, + ACTIONS(1667), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1891), 4, + ACTIONS(1685), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1664), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [48348] = 19, - ACTIONS(1787), 1, + ACTIONS(913), 7, + anon_sym_RBRACE, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_RBRACK, + anon_sym_EQ_GT, + anon_sym_SEMI, + [50808] = 7, + ACTIONS(1639), 1, + anon_sym_AMP_AMP, + ACTIONS(1641), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 8, anon_sym_DOT, - ACTIONS(1789), 1, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 23, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, - ACTIONS(1791), 1, anon_sym_LBRACK, - ACTIONS(1793), 1, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, - anon_sym_BANG, - ACTIONS(1846), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_SEMI, + [50860] = 10, + ACTIONS(1639), 1, anon_sym_AMP_AMP, - ACTIONS(1848), 1, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, - ACTIONS(1856), 1, - anon_sym_DOT_DOT, - ACTIONS(1858), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1862), 1, - anon_sym_SLASH, - ACTIONS(1950), 1, - anon_sym_LPAREN, - ACTIONS(1952), 1, - anon_sym_QMARK_QMARK, - ACTIONS(1954), 1, - anon_sym_QMARK, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1852), 2, + ACTIONS(1637), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1854), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1860), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(889), 4, - anon_sym_RBRACE, - anon_sym_SEMI, - anon_sym_case, - anon_sym_default, - ACTIONS(1850), 4, + ACTIONS(1635), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [48416] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(963), 1, - sym_literal_pattern, - STATE(1103), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [48482] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(941), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [48548] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(963), 1, - sym_literal_pattern, - STATE(1069), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [48614] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(933), 1, - sym_pattern, - STATE(936), 1, - sym_literal_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [48680] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(975), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [48746] = 12, - ACTIONS(1846), 1, - anon_sym_AMP_AMP, - ACTIONS(1848), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1856), 1, - anon_sym_DOT_DOT, - ACTIONS(1858), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1862), 1, - anon_sym_SLASH, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1852), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1854), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1860), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(883), 3, + ACTIONS(900), 5, anon_sym_DOT, anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(1850), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(885), 9, + ACTIONS(902), 19, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, anon_sym_SEMI, - anon_sym_case, - anon_sym_default, - [48800] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, - ACTIONS(1734), 1, - anon_sym_nil, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, - ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(963), 1, - sym_literal_pattern, - STATE(1025), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [48866] = 12, - ACTIONS(1870), 1, + [50918] = 16, + ACTIONS(1631), 1, + anon_sym_SLASH, + ACTIONS(1639), 1, anon_sym_AMP_AMP, - ACTIONS(1872), 1, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, - ACTIONS(1874), 1, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, + ACTIONS(1651), 1, anon_sym_DOT_DOT, - ACTIONS(1876), 1, + ACTIONS(1653), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(1880), 1, - anon_sym_SLASH, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1864), 2, + ACTIONS(1629), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1633), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1868), 2, + ACTIONS(1637), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1878), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(883), 3, + ACTIONS(1649), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(904), 3, anon_sym_DOT, anon_sym_BANG, - anon_sym_PIPE, - ACTIONS(1866), 4, + anon_sym_QMARK, + ACTIONS(1635), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(885), 9, + ACTIONS(906), 12, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_QMARK_QMARK, - anon_sym_if, - [48920] = 5, - ACTIONS(1838), 1, + anon_sym_EQ_GT, + anon_sym_SEMI, + [50988] = 11, + ACTIONS(1639), 1, anon_sym_AMP_AMP, - ACTIONS(1840), 1, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 9, + ACTIONS(1637), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1649), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1635), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(977), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(861), 16, + ACTIONS(979), 17, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_if, - [48960] = 4, - ACTIONS(1840), 1, + anon_sym_EQ_GT, + anon_sym_SEMI, + [51048] = 13, + ACTIONS(1639), 1, + anon_sym_AMP_AMP, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, + ACTIONS(1651), 1, + anon_sym_DOT_DOT, + ACTIONS(1653), 1, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 9, + ACTIONS(1637), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1649), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(742), 17, + ACTIONS(1635), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 16, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_if, - [48998] = 7, - ACTIONS(1838), 1, + anon_sym_EQ_GT, + anon_sym_SEMI, + [51112] = 8, + ACTIONS(1639), 1, anon_sym_AMP_AMP, - ACTIONS(1840), 1, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1836), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1834), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(944), 7, + ACTIONS(900), 7, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(946), 12, + ACTIONS(902), 23, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_if, - [49042] = 5, - ACTIONS(1870), 1, - anon_sym_AMP_AMP, - ACTIONS(1872), 1, + anon_sym_EQ_GT, + anon_sym_SEMI, + [51166] = 4, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 7, + ACTIONS(772), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_PIPE, - ACTIONS(861), 18, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(774), 25, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, @@ -57274,27 +61452,99 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_if, - [49082] = 4, - ACTIONS(1872), 1, + anon_sym_EQ_GT, + anon_sym_SEMI, + [51212] = 23, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(1631), 1, + anon_sym_SLASH, + ACTIONS(1639), 1, + anon_sym_AMP_AMP, + ACTIONS(1641), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, + ACTIONS(1651), 1, + anon_sym_DOT_DOT, + ACTIONS(1653), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1700), 1, + anon_sym_LPAREN, + ACTIONS(1702), 1, + anon_sym_QMARK_QMARK, + ACTIONS(1704), 1, + anon_sym_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1629), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1633), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1637), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1649), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1635), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(913), 7, + anon_sym_RBRACE, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_RBRACK, + anon_sym_EQ_GT, + anon_sym_SEMI, + [51296] = 5, + ACTIONS(1639), 1, + anon_sym_AMP_AMP, + ACTIONS(1641), 1, anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 7, + ACTIONS(900), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_PIPE, - ACTIONS(742), 19, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 24, anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_RPAREN, anon_sym_COMMA, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_RBRACK, @@ -57307,33 +61557,45 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_if, - [49120] = 7, - ACTIONS(1870), 1, + anon_sym_EQ_GT, + anon_sym_SEMI, + [51344] = 11, + ACTIONS(1710), 1, anon_sym_AMP_AMP, - ACTIONS(1872), 1, + ACTIONS(1712), 1, anon_sym_PIPE_PIPE, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, + ACTIONS(1718), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1868), 2, + ACTIONS(1708), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1866), 4, + ACTIONS(1720), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1706), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(944), 5, + ACTIONS(977), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_DOT_DOT, - anon_sym_PIPE, - ACTIONS(946), 14, + anon_sym_QMARK, + ACTIONS(979), 14, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_COMMA, @@ -57348,456 +61610,355 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_if, - [49164] = 10, - ACTIONS(1838), 1, + [51401] = 16, + ACTIONS(1724), 1, + anon_sym_SLASH, + ACTIONS(1732), 1, anon_sym_AMP_AMP, - ACTIONS(1840), 1, + ACTIONS(1734), 1, anon_sym_PIPE_PIPE, - ACTIONS(1842), 1, + ACTIONS(1736), 1, + anon_sym_PIPE, + ACTIONS(1738), 1, + anon_sym_CARET, + ACTIONS(1740), 1, + anon_sym_AMP, + ACTIONS(1744), 1, anon_sym_DOT_DOT, - ACTIONS(1844), 1, + ACTIONS(1746), 1, anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1836), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1956), 2, + ACTIONS(1722), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1726), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1834), 4, + ACTIONS(1730), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1742), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(904), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + ACTIONS(1728), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(859), 6, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(861), 9, + ACTIONS(906), 9, + anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, anon_sym_QMARK_QMARK, - anon_sym_if, - [49214] = 12, - ACTIONS(1838), 1, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [51468] = 23, + ACTIONS(1748), 1, + anon_sym_LPAREN, + ACTIONS(1751), 1, + anon_sym_DOT, + ACTIONS(1753), 1, + anon_sym_QMARK_DOT, + ACTIONS(1755), 1, + anon_sym_LBRACK, + ACTIONS(1757), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(1759), 1, + anon_sym_BANG, + ACTIONS(1764), 1, + anon_sym_SLASH, + ACTIONS(1776), 1, anon_sym_AMP_AMP, - ACTIONS(1840), 1, + ACTIONS(1779), 1, anon_sym_PIPE_PIPE, - ACTIONS(1842), 1, + ACTIONS(1782), 1, + anon_sym_PIPE, + ACTIONS(1785), 1, + anon_sym_CARET, + ACTIONS(1788), 1, + anon_sym_AMP, + ACTIONS(1794), 1, + anon_sym_QMARK_QMARK, + ACTIONS(1797), 1, anon_sym_DOT_DOT, - ACTIONS(1844), 1, + ACTIONS(1800), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(1960), 1, - anon_sym_SLASH, + ACTIONS(1803), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1836), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1956), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1958), 2, + ACTIONS(1761), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1834), 4, + ACTIONS(1767), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1773), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1791), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(913), 4, + anon_sym_RBRACE, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + ACTIONS(1770), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(873), 5, + [51549] = 23, + ACTIONS(1748), 1, + anon_sym_LPAREN, + ACTIONS(1751), 1, anon_sym_DOT, + ACTIONS(1753), 1, + anon_sym_QMARK_DOT, + ACTIONS(1755), 1, + anon_sym_LBRACK, + ACTIONS(1757), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(1759), 1, anon_sym_BANG, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(875), 7, - anon_sym_LPAREN, - anon_sym_COLON, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_if, - [49268] = 18, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, - anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - STATE(1000), 1, - sym_literal_pattern, - STATE(1100), 1, - sym_pattern, - STATE(1213), 1, - sym_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [49334] = 19, - ACTIONS(1787), 1, - anon_sym_DOT, - ACTIONS(1789), 1, - anon_sym_QMARK_DOT, - ACTIONS(1791), 1, - anon_sym_LBRACK, - ACTIONS(1793), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, - anon_sym_BANG, - ACTIONS(1870), 1, + ACTIONS(1809), 1, + anon_sym_SLASH, + ACTIONS(1821), 1, anon_sym_AMP_AMP, - ACTIONS(1872), 1, + ACTIONS(1824), 1, anon_sym_PIPE_PIPE, - ACTIONS(1876), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1880), 1, - anon_sym_SLASH, - ACTIONS(1950), 1, - anon_sym_LPAREN, - ACTIONS(1964), 1, + ACTIONS(1827), 1, + anon_sym_PIPE, + ACTIONS(1830), 1, + anon_sym_CARET, + ACTIONS(1833), 1, + anon_sym_AMP, + ACTIONS(1839), 1, anon_sym_QMARK_QMARK, - ACTIONS(1966), 1, + ACTIONS(1842), 1, anon_sym_DOT_DOT, - ACTIONS(1969), 1, - anon_sym_PIPE, + ACTIONS(1845), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1848), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1864), 2, + ACTIONS(1806), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1812), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1868), 2, + ACTIONS(1818), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1878), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1866), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(1962), 4, + ACTIONS(1836), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(913), 4, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_if, - [49402] = 18, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, + ACTIONS(1815), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [51630] = 23, + ACTIONS(1748), 1, + anon_sym_LPAREN, + ACTIONS(1751), 1, + anon_sym_DOT, + ACTIONS(1753), 1, + anon_sym_QMARK_DOT, + ACTIONS(1755), 1, anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - STATE(1000), 1, - sym_literal_pattern, - STATE(1213), 1, - sym_identifier, - STATE(1263), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [49468] = 12, - ACTIONS(1838), 1, + ACTIONS(1757), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(1759), 1, + anon_sym_BANG, + ACTIONS(1809), 1, + anon_sym_SLASH, + ACTIONS(1821), 1, anon_sym_AMP_AMP, - ACTIONS(1840), 1, + ACTIONS(1824), 1, anon_sym_PIPE_PIPE, + ACTIONS(1827), 1, + anon_sym_PIPE, + ACTIONS(1830), 1, + anon_sym_CARET, + ACTIONS(1833), 1, + anon_sym_AMP, + ACTIONS(1839), 1, + anon_sym_QMARK_QMARK, ACTIONS(1842), 1, anon_sym_DOT_DOT, - ACTIONS(1844), 1, + ACTIONS(1845), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(1960), 1, - anon_sym_SLASH, + ACTIONS(1850), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1836), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1956), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1958), 2, + ACTIONS(1806), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1834), 4, + ACTIONS(1812), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1818), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1836), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(913), 4, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_if, + ACTIONS(1815), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(883), 5, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(885), 7, - anon_sym_LPAREN, - anon_sym_COLON, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_if, - [49522] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, + [51711] = 23, + ACTIONS(1724), 1, + anon_sym_SLASH, + ACTIONS(1732), 1, + anon_sym_AMP_AMP, ACTIONS(1734), 1, - anon_sym_nil, + anon_sym_PIPE_PIPE, ACTIONS(1736), 1, - anon_sym_DQUOTE, + anon_sym_PIPE, ACTIONS(1738), 1, - anon_sym_SQUOTE, + anon_sym_CARET, ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, + anon_sym_AMP, + ACTIONS(1744), 1, + anon_sym_DOT_DOT, ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(963), 1, - sym_literal_pattern, - STATE(1090), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [49588] = 18, - ACTIONS(1629), 1, - aux_sym_identifier_token1, - ACTIONS(1631), 1, - sym_integer_literal, - ACTIONS(1633), 1, - sym_float_literal, - ACTIONS(1637), 1, - anon_sym_nil, - ACTIONS(1639), 1, - anon_sym_DQUOTE, - ACTIONS(1641), 1, - anon_sym_SQUOTE, - ACTIONS(1643), 1, - sym_raw_string, - ACTIONS(1647), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1751), 1, + anon_sym_DOT, + ACTIONS(1753), 1, + anon_sym_QMARK_DOT, + ACTIONS(1755), 1, anon_sym_LBRACK, - ACTIONS(1649), 1, - anon_sym_LBRACE, - ACTIONS(1651), 1, - anon_sym__, - STATE(1000), 1, - sym_literal_pattern, - STATE(1107), 1, - sym_pattern, - STATE(1213), 1, - sym_identifier, + ACTIONS(1757), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(1759), 1, + anon_sym_BANG, + ACTIONS(1853), 1, + anon_sym_LPAREN, + ACTIONS(1855), 1, + anon_sym_QMARK_QMARK, + ACTIONS(1857), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1635), 2, - anon_sym_true, - anon_sym_false, - STATE(1031), 2, - sym_double_string, - sym_single_string, - STATE(1040), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(1278), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [49654] = 18, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1728), 1, - sym_integer_literal, - ACTIONS(1730), 1, - sym_float_literal, + ACTIONS(1722), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1726), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1730), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1742), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(913), 4, + anon_sym_RBRACE, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + ACTIONS(1728), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [51792] = 16, + ACTIONS(1724), 1, + anon_sym_SLASH, + ACTIONS(1732), 1, + anon_sym_AMP_AMP, ACTIONS(1734), 1, - anon_sym_nil, + anon_sym_PIPE_PIPE, ACTIONS(1736), 1, - anon_sym_DQUOTE, + anon_sym_PIPE, ACTIONS(1738), 1, - anon_sym_SQUOTE, + anon_sym_CARET, ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(1742), 1, - anon_sym_LBRACK, + anon_sym_AMP, + ACTIONS(1744), 1, + anon_sym_DOT_DOT, ACTIONS(1746), 1, - anon_sym_LBRACE, - ACTIONS(1750), 1, - anon_sym__, - STATE(929), 1, - sym_identifier, - STATE(936), 1, - sym_literal_pattern, - STATE(1005), 1, - sym_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1732), 2, - anon_sym_true, - anon_sym_false, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(904), 3, - sym_boolean_literal, - sym_nil_literal, - sym_string_literal, - STATE(926), 7, - sym_wildcard_pattern, - sym_identifier_pattern, - sym_list_pattern, - sym_map_pattern, - sym_or_pattern, - sym_guarded_pattern, - sym_range_pattern, - [49720] = 3, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(688), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(690), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + ACTIONS(1722), 2, anon_sym_STAR, anon_sym_PERCENT, + ACTIONS(1726), 2, anon_sym_PLUS, anon_sym_DASH, + ACTIONS(1730), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1742), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(870), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + ACTIONS(1728), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + ACTIONS(872), 9, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_QMARK_QMARK, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [51859] = 8, + ACTIONS(1732), 1, anon_sym_AMP_AMP, + ACTIONS(1734), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [49755] = 3, + ACTIONS(1736), 1, + anon_sym_PIPE, + ACTIONS(1738), 1, + anon_sym_CARET, + ACTIONS(1740), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(807), 8, + ACTIONS(900), 7, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, @@ -57805,8 +61966,8 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(809), 18, + ACTIONS(902), 20, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -57819,26 +61980,31 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [49790] = 3, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [51910] = 4, + ACTIONS(1734), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(811), 8, + ACTIONS(772), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(813), 18, + ACTIONS(774), 22, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -57852,25 +62018,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [49825] = 3, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [51953] = 5, + ACTIONS(1732), 1, + anon_sym_AMP_AMP, + ACTIONS(1734), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(815), 8, + ACTIONS(900), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(817), 18, + ACTIONS(902), 21, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -57883,74 +62058,35 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [49860] = 19, - ACTIONS(1787), 1, - anon_sym_DOT, - ACTIONS(1789), 1, - anon_sym_QMARK_DOT, - ACTIONS(1791), 1, - anon_sym_LBRACK, - ACTIONS(1793), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(1795), 1, - anon_sym_BANG, - ACTIONS(1950), 1, - anon_sym_LPAREN, - ACTIONS(1973), 1, - anon_sym_SLASH, - ACTIONS(1981), 1, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [51998] = 6, + ACTIONS(1732), 1, anon_sym_AMP_AMP, - ACTIONS(1983), 1, + ACTIONS(1734), 1, anon_sym_PIPE_PIPE, - ACTIONS(1985), 1, - anon_sym_QMARK_QMARK, - ACTIONS(1987), 1, - anon_sym_DOT_DOT, - ACTIONS(1989), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(1962), 2, - anon_sym_COLON, - anon_sym_if, - ACTIONS(1969), 2, + ACTIONS(1736), 1, anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(1971), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1975), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1979), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1977), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - [49927] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(823), 8, + ACTIONS(900), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(825), 18, + ACTIONS(902), 21, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -57963,26 +62099,37 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [49962] = 3, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [52045] = 7, + ACTIONS(1732), 1, + anon_sym_AMP_AMP, + ACTIONS(1734), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1736), 1, + anon_sym_PIPE, + ACTIONS(1738), 1, + anon_sym_CARET, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(827), 8, + ACTIONS(900), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(829), 18, + ACTIONS(902), 20, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -57995,58 +62142,43 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [49997] = 3, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [52094] = 10, + ACTIONS(1732), 1, + anon_sym_AMP_AMP, + ACTIONS(1734), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1736), 1, + anon_sym_PIPE, + ACTIONS(1738), 1, + anon_sym_CARET, + ACTIONS(1740), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(835), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, + ACTIONS(1730), 2, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(837), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, + ACTIONS(1728), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50032] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(839), 8, + ACTIONS(900), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(841), 18, + ACTIONS(902), 16, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -58055,30 +62187,46 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50067] = 3, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [52149] = 11, + ACTIONS(1732), 1, + anon_sym_AMP_AMP, + ACTIONS(1734), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1736), 1, + anon_sym_PIPE, + ACTIONS(1738), 1, + anon_sym_CARET, + ACTIONS(1740), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(664), 8, + ACTIONS(1730), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1742), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1728), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(977), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(666), 18, + ACTIONS(979), 14, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -58087,31 +62235,31 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50102] = 3, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [52206] = 4, + ACTIONS(1609), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 8, + ACTIONS(716), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(786), 18, + anon_sym_EQ, + ACTIONS(718), 21, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -58125,112 +62273,122 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50137] = 3, + [52249] = 9, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1863), 1, + anon_sym_RPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(680), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(682), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, + STATE(1136), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [52302] = 16, + ACTIONS(1710), 1, anon_sym_AMP_AMP, + ACTIONS(1712), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, + ACTIONS(1718), 1, + anon_sym_AMP, + ACTIONS(1873), 1, + anon_sym_SLASH, + ACTIONS(1877), 1, + anon_sym_DOT_DOT, + ACTIONS(1879), 1, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50172] = 4, - ACTIONS(1991), 1, - anon_sym_BANG, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 7, - anon_sym_DOT, - anon_sym_SLASH, + ACTIONS(1708), 2, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(786), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + ACTIONS(1720), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1871), 2, anon_sym_STAR, anon_sym_PERCENT, + ACTIONS(1875), 2, anon_sym_PLUS, anon_sym_DASH, + ACTIONS(870), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + ACTIONS(1706), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50209] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(831), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(833), 18, + ACTIONS(872), 9, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50244] = 3, + [52369] = 8, + ACTIONS(1710), 1, + anon_sym_AMP_AMP, + ACTIONS(1712), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, + ACTIONS(1718), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(668), 8, + ACTIONS(900), 7, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, @@ -58238,11 +62396,13 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(670), 18, + ACTIONS(902), 20, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -58252,29 +62412,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50279] = 3, + [52420] = 4, + ACTIONS(1712), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(744), 8, + ACTIONS(772), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(746), 18, + ACTIONS(774), 22, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -58285,28 +62450,37 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50314] = 3, + [52463] = 5, + ACTIONS(1710), 1, + anon_sym_AMP_AMP, + ACTIONS(1712), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(684), 8, + ACTIONS(900), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(686), 18, + ACTIONS(902), 21, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -58316,33 +62490,38 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50349] = 5, - ACTIONS(788), 1, - anon_sym_BANG, - ACTIONS(1994), 1, - anon_sym_COLON, + [52508] = 6, + ACTIONS(1710), 1, + anon_sym_AMP_AMP, + ACTIONS(1712), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1714), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 6, + ACTIONS(900), 8, anon_sym_DOT, + anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(786), 18, + ACTIONS(902), 21, + anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_RPAREN, anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -58352,27 +62531,40 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - [50388] = 3, + anon_sym_if, + [52555] = 7, + ACTIONS(1710), 1, + anon_sym_AMP_AMP, + ACTIONS(1712), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(672), 8, + ACTIONS(900), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(674), 18, + ACTIONS(902), 20, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -58382,444 +62574,289 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50423] = 3, + [52604] = 10, + ACTIONS(1710), 1, + anon_sym_AMP_AMP, + ACTIONS(1712), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, + ACTIONS(1718), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(676), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, + ACTIONS(1708), 2, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(678), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, + ACTIONS(1706), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50458] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(708), 8, + ACTIONS(900), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(710), 18, + ACTIONS(902), 16, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50493] = 3, + [52659] = 14, + ACTIONS(1710), 1, + anon_sym_AMP_AMP, + ACTIONS(1712), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, + ACTIONS(1718), 1, + anon_sym_AMP, + ACTIONS(1877), 1, + anon_sym_DOT_DOT, + ACTIONS(1879), 1, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(760), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, + ACTIONS(1708), 2, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(762), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, + ACTIONS(1720), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1875), 2, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50528] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(764), 8, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(766), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, + ACTIONS(1706), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50563] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(791), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(793), 18, + ACTIONS(902), 11, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, + anon_sym_QMARK_QMARK, + anon_sym_if, + [52722] = 13, + ACTIONS(1710), 1, anon_sym_AMP_AMP, + ACTIONS(1712), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, + ACTIONS(1718), 1, + anon_sym_AMP, + ACTIONS(1877), 1, + anon_sym_DOT_DOT, + ACTIONS(1879), 1, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50598] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(692), 8, + ACTIONS(1708), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1720), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(694), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, + ACTIONS(1706), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50633] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(696), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(698), 18, + ACTIONS(902), 13, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50668] = 20, - ACTIONS(887), 1, - anon_sym_PIPE, - ACTIONS(1996), 1, - anon_sym_LPAREN, - ACTIONS(1999), 1, - anon_sym_DOT, - ACTIONS(2001), 1, - anon_sym_QMARK_DOT, - ACTIONS(2003), 1, - anon_sym_LBRACK, - ACTIONS(2005), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(2007), 1, - anon_sym_BANG, - ACTIONS(2012), 1, - anon_sym_SLASH, - ACTIONS(2024), 1, + [52783] = 16, + ACTIONS(1710), 1, anon_sym_AMP_AMP, - ACTIONS(2027), 1, + ACTIONS(1712), 1, anon_sym_PIPE_PIPE, - ACTIONS(2030), 1, - anon_sym_QMARK_QMARK, - ACTIONS(2033), 1, + ACTIONS(1714), 1, + anon_sym_PIPE, + ACTIONS(1716), 1, + anon_sym_CARET, + ACTIONS(1718), 1, + anon_sym_AMP, + ACTIONS(1873), 1, + anon_sym_SLASH, + ACTIONS(1877), 1, anon_sym_DOT_DOT, - ACTIONS(2036), 1, + ACTIONS(1879), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(2039), 1, - anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(889), 2, - anon_sym_EQ_GT, - anon_sym_if, - ACTIONS(2009), 2, + ACTIONS(1708), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1720), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1871), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2015), 2, + ACTIONS(1875), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2021), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(2018), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - [50737] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(108), 8, + ACTIONS(904), 3, anon_sym_DOT, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(110), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, + ACTIONS(1706), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50772] = 20, - ACTIONS(887), 1, - anon_sym_PIPE, - ACTIONS(1996), 1, + ACTIONS(906), 9, + anon_sym_RBRACE, anon_sym_LPAREN, - ACTIONS(1999), 1, - anon_sym_DOT, - ACTIONS(2001), 1, + anon_sym_COMMA, anon_sym_QMARK_DOT, - ACTIONS(2003), 1, anon_sym_LBRACK, - ACTIONS(2005), 1, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, - ACTIONS(2007), 1, - anon_sym_BANG, - ACTIONS(2012), 1, - anon_sym_SLASH, - ACTIONS(2024), 1, + anon_sym_QMARK_QMARK, + anon_sym_if, + [52850] = 14, + ACTIONS(1732), 1, anon_sym_AMP_AMP, - ACTIONS(2027), 1, + ACTIONS(1734), 1, anon_sym_PIPE_PIPE, - ACTIONS(2030), 1, - anon_sym_QMARK_QMARK, - ACTIONS(2033), 1, + ACTIONS(1736), 1, + anon_sym_PIPE, + ACTIONS(1738), 1, + anon_sym_CARET, + ACTIONS(1740), 1, + anon_sym_AMP, + ACTIONS(1744), 1, anon_sym_DOT_DOT, - ACTIONS(2036), 1, + ACTIONS(1746), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(2041), 1, - anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(889), 2, - anon_sym_EQ_GT, - anon_sym_if, - ACTIONS(2009), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(2015), 2, + ACTIONS(1726), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2021), 2, + ACTIONS(1730), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2018), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - [50841] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(700), 8, + ACTIONS(1742), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(702), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, + ACTIONS(1728), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50876] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(704), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(706), 18, + ACTIONS(902), 11, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, + anon_sym_QMARK_QMARK, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [52913] = 13, + ACTIONS(1732), 1, anon_sym_AMP_AMP, + ACTIONS(1734), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, + ACTIONS(1736), 1, + anon_sym_PIPE, + ACTIONS(1738), 1, + anon_sym_CARET, + ACTIONS(1740), 1, + anon_sym_AMP, + ACTIONS(1744), 1, + anon_sym_DOT_DOT, + ACTIONS(1746), 1, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50911] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(712), 8, + ACTIONS(1730), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1742), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(714), 18, + ACTIONS(1728), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 13, + anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -58828,21 +62865,25 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, + anon_sym_QMARK_QMARK, + anon_sym_SEMI, + anon_sym_case, + anon_sym_default, + [52974] = 8, + ACTIONS(1881), 1, anon_sym_AMP_AMP, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [50946] = 3, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, + ACTIONS(1889), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(716), 8, + ACTIONS(900), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, @@ -58850,9 +62891,10 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(718), 18, + anon_sym_EQ, + ACTIONS(902), 18, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -58864,91 +62906,725 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [50981] = 3, + [53024] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(720), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, + STATE(1162), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53074] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1239), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53124] = 16, + ACTIONS(1893), 1, + anon_sym_SLASH, + ACTIONS(1901), 1, + anon_sym_AMP_AMP, + ACTIONS(1903), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1905), 1, anon_sym_PIPE, - ACTIONS(722), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, + ACTIONS(1913), 1, + anon_sym_DOT_DOT, + ACTIONS(1915), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(870), 2, + anon_sym_DOT, + anon_sym_BANG, + ACTIONS(1891), 2, anon_sym_STAR, anon_sym_PERCENT, + ACTIONS(1895), 2, anon_sym_PLUS, anon_sym_DASH, + ACTIONS(1899), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1911), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1897), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + ACTIONS(872), 9, + anon_sym_RBRACE, + anon_sym_LPAREN, + anon_sym_COMMA, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_RBRACK, + anon_sym_QMARK_LBRACK, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51016] = 3, + [53190] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1369), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53240] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1342), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53290] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(982), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53340] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(993), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53390] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(724), 8, + STATE(1368), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53440] = 22, + ACTIONS(1751), 1, anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(726), 18, - anon_sym_LPAREN, + ACTIONS(1753), 1, anon_sym_QMARK_DOT, + ACTIONS(1755), 1, anon_sym_LBRACK, + ACTIONS(1757), 1, anon_sym_QMARK_LBRACK, + ACTIONS(1759), 1, + anon_sym_BANG, + ACTIONS(1853), 1, + anon_sym_LPAREN, + ACTIONS(1893), 1, + anon_sym_SLASH, + ACTIONS(1901), 1, + anon_sym_AMP_AMP, + ACTIONS(1903), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, + ACTIONS(1915), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1919), 1, + anon_sym_PIPE, + ACTIONS(1922), 1, + anon_sym_QMARK_QMARK, + ACTIONS(1924), 1, + anon_sym_DOT_DOT, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1891), 2, anon_sym_STAR, anon_sym_PERCENT, + ACTIONS(1895), 2, anon_sym_PLUS, anon_sym_DASH, + ACTIONS(1899), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1911), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1897), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, + ACTIONS(1917), 4, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_RBRACK, anon_sym_if, - [51051] = 3, + [53518] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(983), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53568] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(994), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53618] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1349), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53668] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1227), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53718] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1138), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53768] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(996), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53818] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1340), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [53868] = 4, + ACTIONS(1883), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(728), 8, + ACTIONS(772), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(730), 18, + anon_sym_EQ, + ACTIONS(774), 20, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -58961,26 +63637,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51086] = 3, + [53910] = 5, + ACTIONS(1881), 1, + anon_sym_AMP_AMP, + ACTIONS(1883), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(732), 8, + ACTIONS(900), 10, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(734), 18, + anon_sym_EQ, + ACTIONS(902), 19, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -58992,27 +63676,35 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51121] = 3, + [53954] = 6, + ACTIONS(1881), 1, + anon_sym_AMP_AMP, + ACTIONS(1883), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1885), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(736), 8, + ACTIONS(900), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(738), 18, + anon_sym_EQ, + ACTIONS(902), 19, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -59024,83 +63716,83 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51156] = 12, - ACTIONS(1973), 1, - anon_sym_SLASH, - ACTIONS(1981), 1, + [54000] = 7, + ACTIONS(1881), 1, anon_sym_AMP_AMP, - ACTIONS(1983), 1, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - ACTIONS(1987), 1, - anon_sym_DOT_DOT, - ACTIONS(1989), 1, - anon_sym_DOT_DOT_EQ, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1971), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1975), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1979), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(883), 4, + ACTIONS(900), 9, anon_sym_DOT, anon_sym_BANG, - anon_sym_PIPE, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, anon_sym_EQ, - ACTIONS(1977), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(885), 7, + ACTIONS(902), 18, anon_sym_LPAREN, anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, anon_sym_if, - [51209] = 10, - ACTIONS(1981), 1, + [54048] = 10, + ACTIONS(1881), 1, anon_sym_AMP_AMP, - ACTIONS(1983), 1, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - ACTIONS(1987), 1, - anon_sym_DOT_DOT, - ACTIONS(1989), 1, - anon_sym_DOT_DOT_EQ, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, + ACTIONS(1889), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1975), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1979), 2, + ACTIONS(1929), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1977), 4, + ACTIONS(1927), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(859), 5, + ACTIONS(900), 6, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_QMARK, anon_sym_EQ, - ACTIONS(861), 9, + ACTIONS(902), 14, anon_sym_LPAREN, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -59108,35 +63800,46 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, anon_sym_if, - [51258] = 9, - ACTIONS(1981), 1, + [54102] = 11, + ACTIONS(1881), 1, anon_sym_AMP_AMP, - ACTIONS(1983), 1, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - ACTIONS(1987), 1, - anon_sym_DOT_DOT, - ACTIONS(1989), 1, - anon_sym_DOT_DOT_EQ, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, + ACTIONS(1889), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1979), 2, + ACTIONS(1929), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1977), 4, + ACTIONS(1931), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1927), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(859), 5, + ACTIONS(977), 6, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_QMARK, anon_sym_EQ, - ACTIONS(861), 11, + ACTIONS(979), 12, anon_sym_LPAREN, anon_sym_COLON, anon_sym_QMARK_DOT, @@ -59147,24 +63850,36 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PLUS, anon_sym_DASH, anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, anon_sym_if, - [51305] = 3, + [54158] = 8, + ACTIONS(1901), 1, + anon_sym_AMP_AMP, + ACTIONS(1903), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1905), 1, + anon_sym_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 8, + ACTIONS(900), 6, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(742), 18, + ACTIONS(902), 20, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -59174,70 +63889,77 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51340] = 12, - ACTIONS(1973), 1, - anon_sym_SLASH, - ACTIONS(1981), 1, - anon_sym_AMP_AMP, - ACTIONS(1983), 1, - anon_sym_PIPE_PIPE, - ACTIONS(1987), 1, - anon_sym_DOT_DOT, - ACTIONS(1989), 1, - anon_sym_DOT_DOT_EQ, + [54208] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1971), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(1975), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(1979), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(873), 4, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(1977), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(875), 7, - anon_sym_LPAREN, - anon_sym_COLON, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_if, - [51393] = 3, + STATE(1203), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [54258] = 5, + ACTIONS(1901), 1, + anon_sym_AMP_AMP, + ACTIONS(1903), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(843), 8, + ACTIONS(900), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, anon_sym_PIPE, - ACTIONS(845), 18, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(902), 21, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -59247,29 +63969,37 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51428] = 3, + [54302] = 6, + ACTIONS(1901), 1, + anon_sym_AMP_AMP, + ACTIONS(1903), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1905), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(748), 8, + ACTIONS(900), 7, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(750), 18, + ACTIONS(902), 21, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -59279,61 +64009,39 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51463] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(752), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(754), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, + [54348] = 7, + ACTIONS(1901), 1, anon_sym_AMP_AMP, + ACTIONS(1903), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [51498] = 3, + ACTIONS(1905), 1, + anon_sym_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(756), 8, + ACTIONS(900), 7, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(758), 18, + ACTIONS(902), 20, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -59343,106 +64051,92 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51533] = 5, - ACTIONS(2044), 1, + [54396] = 10, + ACTIONS(1901), 1, anon_sym_AMP_AMP, - ACTIONS(2046), 1, + ACTIONS(1903), 1, anon_sym_PIPE_PIPE, + ACTIONS(1905), 1, + anon_sym_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 8, + ACTIONS(1899), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(861), 16, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, + ACTIONS(1897), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [51572] = 4, - ACTIONS(2046), 1, - anon_sym_PIPE_PIPE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(740), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(742), 17, + ACTIONS(902), 16, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51609] = 7, - ACTIONS(2044), 1, + [54450] = 11, + ACTIONS(1901), 1, anon_sym_AMP_AMP, - ACTIONS(2046), 1, + ACTIONS(1903), 1, anon_sym_PIPE_PIPE, + ACTIONS(1905), 1, + anon_sym_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2050), 2, + ACTIONS(1899), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2048), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(944), 6, + ACTIONS(1911), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(977), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(946), 12, + ACTIONS(1897), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(979), 14, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -59450,75 +64144,93 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_DASH, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [51652] = 10, - ACTIONS(2044), 1, + [54506] = 14, + ACTIONS(1881), 1, anon_sym_AMP_AMP, - ACTIONS(2046), 1, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - ACTIONS(2054), 1, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, + ACTIONS(1889), 1, + anon_sym_AMP, + ACTIONS(1935), 1, anon_sym_DOT_DOT, - ACTIONS(2056), 1, + ACTIONS(1937), 1, anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2050), 2, + ACTIONS(1929), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2052), 2, + ACTIONS(1931), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1933), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2048), 4, + ACTIONS(1927), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(859), 5, + ACTIONS(900), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(861), 9, + anon_sym_EQ, + ACTIONS(902), 9, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, anon_sym_if, - [51701] = 9, - ACTIONS(2044), 1, + [54568] = 13, + ACTIONS(1881), 1, anon_sym_AMP_AMP, - ACTIONS(2046), 1, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - ACTIONS(2054), 1, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, + ACTIONS(1889), 1, + anon_sym_AMP, + ACTIONS(1935), 1, anon_sym_DOT_DOT, - ACTIONS(2056), 1, + ACTIONS(1937), 1, anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2050), 2, + ACTIONS(1929), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2048), 4, + ACTIONS(1931), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1927), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(859), 5, + ACTIONS(900), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(861), 11, + anon_sym_EQ, + ACTIONS(902), 11, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -59527,336 +64239,1019 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PLUS, anon_sym_DASH, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, anon_sym_if, - [51748] = 12, - ACTIONS(2044), 1, + [54628] = 16, + ACTIONS(1881), 1, anon_sym_AMP_AMP, - ACTIONS(2046), 1, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - ACTIONS(2054), 1, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, + ACTIONS(1889), 1, + anon_sym_AMP, + ACTIONS(1935), 1, anon_sym_DOT_DOT, - ACTIONS(2056), 1, + ACTIONS(1937), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(2060), 1, + ACTIONS(1941), 1, anon_sym_SLASH, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2050), 2, + ACTIONS(1929), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2052), 2, + ACTIONS(1931), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1933), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2058), 2, + ACTIONS(1939), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(873), 4, + ACTIONS(904), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(2048), 4, + anon_sym_EQ, + ACTIONS(1927), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(875), 7, + ACTIONS(906), 7, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, anon_sym_if, - [51801] = 3, + [54694] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1000), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [54744] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1133), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [54794] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1127), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [54844] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1278), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [54894] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1154), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [54944] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1155), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [54994] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1260), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55044] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1181), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55094] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1279), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55144] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1135), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55194] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1142), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55244] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1312), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55294] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(768), 8, + STATE(1212), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55344] = 24, + ACTIONS(911), 1, + anon_sym_EQ, + ACTIONS(1748), 1, + anon_sym_LPAREN, + ACTIONS(1751), 1, anon_sym_DOT, + ACTIONS(1753), 1, + anon_sym_QMARK_DOT, + ACTIONS(1755), 1, + anon_sym_LBRACK, + ACTIONS(1757), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(1759), 1, anon_sym_BANG, + ACTIONS(1946), 1, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, + ACTIONS(1958), 1, + anon_sym_AMP_AMP, + ACTIONS(1961), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1964), 1, + anon_sym_PIPE, + ACTIONS(1967), 1, + anon_sym_CARET, + ACTIONS(1970), 1, + anon_sym_AMP, + ACTIONS(1976), 1, + anon_sym_QMARK_QMARK, + ACTIONS(1979), 1, anon_sym_DOT_DOT, + ACTIONS(1982), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1985), 1, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(770), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(913), 2, + anon_sym_COLON, + anon_sym_if, + ACTIONS(1943), 2, anon_sym_STAR, anon_sym_PERCENT, + ACTIONS(1949), 2, anon_sym_PLUS, anon_sym_DASH, + ACTIONS(1955), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1973), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1952), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + [55426] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1261), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55476] = 16, + ACTIONS(1881), 1, anon_sym_AMP_AMP, + ACTIONS(1883), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, + ACTIONS(1885), 1, + anon_sym_PIPE, + ACTIONS(1887), 1, + anon_sym_CARET, + ACTIONS(1889), 1, + anon_sym_AMP, + ACTIONS(1935), 1, + anon_sym_DOT_DOT, + ACTIONS(1937), 1, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [51836] = 5, - ACTIONS(1981), 1, - anon_sym_AMP_AMP, - ACTIONS(1983), 1, - anon_sym_PIPE_PIPE, + ACTIONS(1941), 1, + anon_sym_SLASH, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, + ACTIONS(1929), 2, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(861), 16, - anon_sym_LPAREN, - anon_sym_COLON, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, + ACTIONS(1931), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1933), 2, anon_sym_PLUS, anon_sym_DASH, + ACTIONS(1939), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(870), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + anon_sym_EQ, + ACTIONS(1927), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + ACTIONS(872), 7, + anon_sym_LPAREN, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, anon_sym_if, - [51875] = 4, - ACTIONS(1983), 1, + [55542] = 14, + ACTIONS(1901), 1, + anon_sym_AMP_AMP, + ACTIONS(1903), 1, anon_sym_PIPE_PIPE, + ACTIONS(1905), 1, + anon_sym_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, + ACTIONS(1913), 1, + anon_sym_DOT_DOT, + ACTIONS(1915), 1, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 8, + ACTIONS(1895), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1899), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1911), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 3, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(742), 17, + ACTIONS(1897), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 11, + anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COLON, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, anon_sym_if, - [51912] = 7, - ACTIONS(1981), 1, + [55604] = 13, + ACTIONS(1901), 1, anon_sym_AMP_AMP, - ACTIONS(1983), 1, + ACTIONS(1903), 1, anon_sym_PIPE_PIPE, + ACTIONS(1905), 1, + anon_sym_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, + ACTIONS(1913), 1, + anon_sym_DOT_DOT, + ACTIONS(1915), 1, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1979), 2, + ACTIONS(1899), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(1977), 4, + ACTIONS(1911), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + ACTIONS(1897), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(944), 6, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_EQ, - ACTIONS(946), 12, + ACTIONS(902), 13, + anon_sym_RBRACE, anon_sym_LPAREN, - anon_sym_COLON, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, anon_sym_if, - [51955] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(772), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(774), 18, + [55664] = 24, + ACTIONS(911), 1, + anon_sym_EQ, + ACTIONS(1748), 1, anon_sym_LPAREN, + ACTIONS(1751), 1, + anon_sym_DOT, + ACTIONS(1753), 1, anon_sym_QMARK_DOT, + ACTIONS(1755), 1, anon_sym_LBRACK, + ACTIONS(1757), 1, anon_sym_QMARK_LBRACK, - anon_sym_STAR, - anon_sym_PERCENT, - anon_sym_PLUS, - anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, + ACTIONS(1759), 1, + anon_sym_BANG, + ACTIONS(1946), 1, + anon_sym_SLASH, + ACTIONS(1958), 1, anon_sym_AMP_AMP, + ACTIONS(1961), 1, anon_sym_PIPE_PIPE, + ACTIONS(1964), 1, + anon_sym_PIPE, + ACTIONS(1967), 1, + anon_sym_CARET, + ACTIONS(1970), 1, + anon_sym_AMP, + ACTIONS(1976), 1, anon_sym_QMARK_QMARK, + ACTIONS(1979), 1, + anon_sym_DOT_DOT, + ACTIONS(1982), 1, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [51990] = 3, + ACTIONS(1987), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(776), 8, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(778), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + ACTIONS(913), 2, + anon_sym_COLON, + anon_sym_if, + ACTIONS(1943), 2, anon_sym_STAR, anon_sym_PERCENT, + ACTIONS(1949), 2, anon_sym_PLUS, anon_sym_DASH, + ACTIONS(1955), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1973), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1952), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + [55746] = 8, + ACTIONS(1859), 1, + aux_sym_identifier_token1, + ACTIONS(1861), 1, + anon_sym_LPAREN, + ACTIONS(1867), 1, + anon_sym_List, + ACTIONS(1869), 1, + anon_sym_Map, + STATE(985), 1, + sym_type_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1270), 8, + sym__type, + sym_primitive_type, + sym_list_type, + sym_map_type, + sym_function_type, + sym_optional_type, + sym_union_type, + sym_named_type, + ACTIONS(1865), 18, + anon_sym_Int, + anon_sym_Float, + anon_sym_String, + anon_sym_Bool, + anon_sym_Nil, + anon_sym_Any, + anon_sym_Number, + anon_sym_f64, + anon_sym_i8, + anon_sym_i16, + anon_sym_i32, + anon_sym_i64, + anon_sym_u8, + anon_sym_u16, + anon_sym_u32, + anon_sym_u64, + anon_sym_isize, + anon_sym_usize, + [55796] = 16, + ACTIONS(1893), 1, + anon_sym_SLASH, + ACTIONS(1901), 1, anon_sym_AMP_AMP, + ACTIONS(1903), 1, anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, + ACTIONS(1905), 1, + anon_sym_PIPE, + ACTIONS(1907), 1, + anon_sym_CARET, + ACTIONS(1909), 1, + anon_sym_AMP, + ACTIONS(1913), 1, + anon_sym_DOT_DOT, + ACTIONS(1915), 1, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [52025] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(780), 8, + ACTIONS(904), 2, anon_sym_DOT, anon_sym_BANG, - anon_sym_SLASH, - anon_sym_LT, - anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(782), 18, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + ACTIONS(1891), 2, anon_sym_STAR, anon_sym_PERCENT, + ACTIONS(1895), 2, anon_sym_PLUS, anon_sym_DASH, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [52060] = 12, - ACTIONS(2044), 1, - anon_sym_AMP_AMP, - ACTIONS(2046), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2054), 1, - anon_sym_DOT_DOT, - ACTIONS(2056), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(2060), 1, - anon_sym_SLASH, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2050), 2, + ACTIONS(1899), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2052), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(2058), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(883), 4, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(2048), 4, + ACTIONS(1911), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1897), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(885), 7, + ACTIONS(906), 9, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, anon_sym_if, - [52113] = 3, + [55862] = 5, + ACTIONS(701), 1, + anon_sym_BANG, + ACTIONS(1990), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(795), 8, + ACTIONS(697), 8, anon_sym_DOT, - anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(797), 18, + ACTIONS(699), 21, anon_sym_LPAREN, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -59870,27 +65265,33 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, - anon_sym_if, - [52148] = 3, + [55906] = 4, + ACTIONS(1903), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(799), 8, + ACTIONS(772), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, anon_sym_PIPE, - ACTIONS(801), 18, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(774), 22, + anon_sym_RBRACE, anon_sym_LPAREN, + anon_sym_COMMA, anon_sym_QMARK_DOT, anon_sym_LBRACK, + anon_sym_RBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, @@ -59901,25 +65302,27 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [52183] = 3, + [55948] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(803), 8, + ACTIONS(744), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - anon_sym_PIPE, - ACTIONS(805), 18, + ACTIONS(746), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -59934,25 +65337,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_EQ_GT, anon_sym_if, - [52218] = 3, + [55987] = 5, + ACTIONS(1992), 1, + anon_sym_AMP_AMP, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(819), 8, + ACTIONS(900), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, - anon_sym_QMARK, anon_sym_PIPE, - ACTIONS(821), 18, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_EQ, + ACTIONS(902), 19, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -59964,28 +65376,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, - anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [52253] = 4, - ACTIONS(2062), 1, + [56030] = 6, + ACTIONS(1992), 1, + anon_sym_AMP_AMP, + ACTIONS(1994), 1, anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 7, + ACTIONS(900), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_AMP, anon_sym_DOT_DOT, - anon_sym_PIPE, - ACTIONS(742), 17, + anon_sym_EQ, + ACTIONS(902), 19, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -59997,116 +65415,136 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [52289] = 10, - ACTIONS(2062), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2070), 1, + [56075] = 7, + ACTIONS(1992), 1, anon_sym_AMP_AMP, - ACTIONS(2072), 1, - anon_sym_DOT_DOT, - ACTIONS(2074), 1, - anon_sym_DOT_DOT_EQ, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2064), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(2068), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(859), 4, + ACTIONS(900), 8, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - anon_sym_PIPE, - ACTIONS(2066), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 9, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_EQ, + ACTIONS(902), 18, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, - anon_sym_if, - [52337] = 12, - ACTIONS(2062), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2070), 1, - anon_sym_AMP_AMP, - ACTIONS(2072), 1, - anon_sym_DOT_DOT, - ACTIONS(2074), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(2078), 1, - anon_sym_SLASH, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2064), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2068), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(2076), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(873), 3, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_PIPE, - ACTIONS(2066), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(875), 7, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, + anon_sym_DOT_DOT_EQ, anon_sym_if, - [52389] = 9, - ACTIONS(2062), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2070), 1, + [56122] = 20, + ACTIONS(2000), 1, + aux_sym_identifier_token1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2016), 1, + anon_sym_RBRACE, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(825), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [56195] = 10, + ACTIONS(1992), 1, anon_sym_AMP_AMP, - ACTIONS(2072), 1, - anon_sym_DOT_DOT, - ACTIONS(2074), 1, - anon_sym_DOT_DOT_EQ, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2068), 2, + ACTIONS(2026), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(859), 4, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_SLASH, - anon_sym_PIPE, - ACTIONS(2066), 4, + ACTIONS(2024), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(861), 11, + ACTIONS(900), 5, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + anon_sym_EQ, + ACTIONS(902), 14, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -60114,33 +65552,45 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, + anon_sym_DOT_DOT_EQ, anon_sym_if, - [52435] = 7, - ACTIONS(2062), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2070), 1, + [56248] = 11, + ACTIONS(1992), 1, anon_sym_AMP_AMP, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2068), 2, + ACTIONS(2026), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2066), 4, + ACTIONS(2030), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2024), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(944), 5, + ACTIONS(977), 5, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_DOT_DOT, - anon_sym_PIPE, - ACTIONS(946), 12, + anon_sym_EQ, + ACTIONS(979), 12, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, @@ -60150,112 +65600,240 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_DASH, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - anon_sym_EQ_GT, anon_sym_if, - [52477] = 19, - ACTIONS(1969), 1, - anon_sym_PIPE, - ACTIONS(1999), 1, + [56303] = 23, + ACTIONS(2032), 1, + anon_sym_LPAREN, + ACTIONS(2035), 1, anon_sym_DOT, - ACTIONS(2001), 1, + ACTIONS(2037), 1, anon_sym_QMARK_DOT, - ACTIONS(2003), 1, + ACTIONS(2039), 1, anon_sym_LBRACK, - ACTIONS(2005), 1, + ACTIONS(2041), 1, anon_sym_QMARK_LBRACK, - ACTIONS(2007), 1, + ACTIONS(2043), 1, anon_sym_BANG, - ACTIONS(2062), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2070), 1, + ACTIONS(2048), 1, + anon_sym_SLASH, + ACTIONS(2060), 1, anon_sym_AMP_AMP, + ACTIONS(2063), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2066), 1, + anon_sym_PIPE, + ACTIONS(2069), 1, + anon_sym_CARET, ACTIONS(2072), 1, - anon_sym_DOT_DOT, - ACTIONS(2074), 1, - anon_sym_DOT_DOT_EQ, + anon_sym_AMP, ACTIONS(2078), 1, - anon_sym_SLASH, - ACTIONS(2080), 1, - anon_sym_LPAREN, - ACTIONS(2082), 1, anon_sym_QMARK_QMARK, + ACTIONS(2081), 1, + anon_sym_DOT_DOT, + ACTIONS(2084), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2087), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1962), 2, + ACTIONS(913), 2, anon_sym_EQ_GT, anon_sym_if, - ACTIONS(2064), 2, + ACTIONS(2045), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2051), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2068), 2, + ACTIONS(2057), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2076), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(2066), 4, + ACTIONS(2075), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2054), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [52543] = 12, - ACTIONS(2062), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2070), 1, + [56382] = 20, + ACTIONS(2000), 1, + aux_sym_identifier_token1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + ACTIONS(2089), 1, + anon_sym_RBRACE, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(826), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [56455] = 20, + ACTIONS(2091), 1, + aux_sym_identifier_token1, + ACTIONS(2094), 1, + sym_integer_literal, + ACTIONS(2097), 1, + sym_float_literal, + ACTIONS(2103), 1, + anon_sym_nil, + ACTIONS(2106), 1, + anon_sym_DQUOTE, + ACTIONS(2109), 1, + anon_sym_SQUOTE, + ACTIONS(2112), 1, + sym_raw_string, + ACTIONS(2115), 1, + anon_sym_RBRACE, + ACTIONS(2117), 1, + anon_sym_LBRACK, + ACTIONS(2120), 1, + anon_sym_LBRACE, + ACTIONS(2123), 1, + anon_sym__, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2100), 2, + anon_sym_true, + anon_sym_false, + STATE(826), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [56528] = 23, + ACTIONS(2032), 1, + anon_sym_LPAREN, + ACTIONS(2035), 1, + anon_sym_DOT, + ACTIONS(2037), 1, + anon_sym_QMARK_DOT, + ACTIONS(2039), 1, + anon_sym_LBRACK, + ACTIONS(2041), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(2043), 1, + anon_sym_BANG, + ACTIONS(2048), 1, + anon_sym_SLASH, + ACTIONS(2060), 1, anon_sym_AMP_AMP, + ACTIONS(2063), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2066), 1, + anon_sym_PIPE, + ACTIONS(2069), 1, + anon_sym_CARET, ACTIONS(2072), 1, + anon_sym_AMP, + ACTIONS(2078), 1, + anon_sym_QMARK_QMARK, + ACTIONS(2081), 1, anon_sym_DOT_DOT, - ACTIONS(2074), 1, + ACTIONS(2084), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(2078), 1, - anon_sym_SLASH, + ACTIONS(2126), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2064), 2, + ACTIONS(913), 2, + anon_sym_EQ_GT, + anon_sym_if, + ACTIONS(2045), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2051), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2068), 2, + ACTIONS(2057), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2076), 2, - anon_sym_STAR, - anon_sym_PERCENT, - ACTIONS(883), 3, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_PIPE, - ACTIONS(2066), 4, + ACTIONS(2075), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2054), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(885), 7, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_QMARK_QMARK, - anon_sym_EQ_GT, - anon_sym_if, - [52595] = 5, - ACTIONS(2062), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2070), 1, - anon_sym_AMP_AMP, + [56607] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 7, + ACTIONS(697), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - anon_sym_DOT_DOT, anon_sym_PIPE, - ACTIONS(861), 16, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(699), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -60268,106 +65846,139 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, anon_sym_EQ_GT, anon_sym_if, - [52633] = 19, - ACTIONS(889), 1, - anon_sym_LBRACE, - ACTIONS(891), 1, - anon_sym_LPAREN, - ACTIONS(894), 1, + [56646] = 4, + ACTIONS(2129), 1, + anon_sym_BANG, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(697), 8, anon_sym_DOT, - ACTIONS(896), 1, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(699), 21, + anon_sym_LPAREN, anon_sym_QMARK_DOT, - ACTIONS(898), 1, anon_sym_LBRACK, - ACTIONS(900), 1, anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(2087), 1, - anon_sym_SLASH, - ACTIONS(2099), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(2102), 1, anon_sym_PIPE_PIPE, - ACTIONS(2105), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - ACTIONS(2108), 1, - anon_sym_DOT_DOT, - ACTIONS(2111), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(2114), 1, - anon_sym_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [56687] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2084), 2, + ACTIONS(720), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(722), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2090), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2096), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(2093), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [52698] = 7, - ACTIONS(2121), 1, anon_sym_AMP_AMP, - ACTIONS(2123), 1, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [56726] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2119), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(2117), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(944), 5, + ACTIONS(681), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(946), 11, + ACTIONS(683), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - [52739] = 5, - ACTIONS(788), 1, - anon_sym_BANG, - ACTIONS(2125), 1, - anon_sym_COLON, + anon_sym_EQ_GT, + anon_sym_if, + [56765] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(784), 6, + ACTIONS(693), 9, anon_sym_DOT, + anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(786), 16, + ACTIONS(695), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, @@ -60382,160 +65993,160 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT_EQ, anon_sym_AMP_AMP, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - [52776] = 20, - ACTIONS(889), 1, anon_sym_EQ_GT, - ACTIONS(894), 1, + anon_sym_if, + [56804] = 23, + ACTIONS(1751), 1, anon_sym_DOT, - ACTIONS(896), 1, + ACTIONS(1753), 1, anon_sym_QMARK_DOT, - ACTIONS(898), 1, + ACTIONS(1755), 1, anon_sym_LBRACK, - ACTIONS(900), 1, + ACTIONS(1757), 1, anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, + ACTIONS(1759), 1, anon_sym_BANG, - ACTIONS(1605), 1, - anon_sym_LT_EQ, - ACTIONS(1657), 1, - anon_sym_SLASH, - ACTIONS(1665), 1, + ACTIONS(1853), 1, + anon_sym_LPAREN, + ACTIONS(1992), 1, anon_sym_AMP_AMP, - ACTIONS(1667), 1, + ACTIONS(1994), 1, anon_sym_PIPE_PIPE, - ACTIONS(1669), 1, - anon_sym_DOT_DOT, - ACTIONS(1671), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(1718), 1, - anon_sym_LPAREN, - ACTIONS(1720), 1, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, + ACTIONS(2134), 1, + anon_sym_SLASH, + ACTIONS(2138), 1, + anon_sym_PIPE, + ACTIONS(2141), 1, anon_sym_QMARK_QMARK, - ACTIONS(2128), 1, - anon_sym_QMARK, + ACTIONS(2143), 1, + anon_sym_DOT_DOT, + ACTIONS(2145), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2147), 1, + anon_sym_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1655), 2, + ACTIONS(1917), 2, + anon_sym_COLON, + anon_sym_if, + ACTIONS(2026), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2030), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2132), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(1659), 2, + ACTIONS(2136), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(1663), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(1661), 3, + ACTIONS(2024), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, + anon_sym_LT_EQ, anon_sym_GT_EQ, - [52843] = 19, - ACTIONS(889), 1, - anon_sym_LBRACE, - ACTIONS(891), 1, - anon_sym_LPAREN, - ACTIONS(894), 1, + [56883] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(796), 9, anon_sym_DOT, - ACTIONS(896), 1, - anon_sym_QMARK_DOT, - ACTIONS(898), 1, - anon_sym_LBRACK, - ACTIONS(900), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, anon_sym_BANG, - ACTIONS(2087), 1, anon_sym_SLASH, - ACTIONS(2099), 1, - anon_sym_AMP_AMP, - ACTIONS(2102), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2105), 1, - anon_sym_QMARK_QMARK, - ACTIONS(2108), 1, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, - ACTIONS(2111), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(2130), 1, anon_sym_QMARK, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2084), 2, + ACTIONS(798), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2090), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2096), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(2093), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [52908] = 10, - ACTIONS(2121), 1, anon_sym_AMP_AMP, - ACTIONS(2123), 1, anon_sym_PIPE_PIPE, - ACTIONS(2134), 1, - anon_sym_DOT_DOT, - ACTIONS(2136), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [56922] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2119), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(2132), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(859), 4, + ACTIONS(732), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(2117), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 8, + ACTIONS(734), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, - anon_sym_QMARK_QMARK, - [52955] = 5, - ACTIONS(2121), 1, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(2123), 1, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [56961] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 7, + ACTIONS(108), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(861), 15, + ACTIONS(110), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, @@ -60544,142 +66155,195 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - [52992] = 12, - ACTIONS(2121), 1, anon_sym_AMP_AMP, - ACTIONS(2123), 1, anon_sym_PIPE_PIPE, - ACTIONS(2134), 1, - anon_sym_DOT_DOT, - ACTIONS(2136), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - ACTIONS(2140), 1, - anon_sym_SLASH, + anon_sym_EQ_GT, + anon_sym_if, + [57000] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2119), 2, + ACTIONS(736), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(2132), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(2138), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(738), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(873), 3, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - ACTIONS(2117), 4, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(875), 6, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, - anon_sym_QMARK_QMARK, - [53043] = 9, - ACTIONS(2121), 1, anon_sym_AMP_AMP, - ACTIONS(2123), 1, anon_sym_PIPE_PIPE, - ACTIONS(2134), 1, - anon_sym_DOT_DOT, - ACTIONS(2136), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57039] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2119), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(859), 4, + ACTIONS(740), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(2117), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(861), 10, + ACTIONS(742), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_QMARK_QMARK, - [53088] = 12, - ACTIONS(2121), 1, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(2123), 1, anon_sym_PIPE_PIPE, - ACTIONS(2134), 1, - anon_sym_DOT_DOT, - ACTIONS(2136), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - ACTIONS(2140), 1, - anon_sym_SLASH, + anon_sym_EQ_GT, + anon_sym_if, + [57078] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2119), 2, + ACTIONS(716), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(2132), 2, - anon_sym_PLUS, - anon_sym_DASH, - ACTIONS(2138), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(718), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(883), 3, - anon_sym_DOT, - anon_sym_BANG, - anon_sym_QMARK, - ACTIONS(2117), 4, + anon_sym_PLUS, + anon_sym_DASH, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(885), 6, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57117] = 20, + ACTIONS(2000), 1, + aux_sym_identifier_token1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, + ACTIONS(2020), 1, anon_sym_LBRACE, - anon_sym_QMARK_QMARK, - [53139] = 4, - ACTIONS(2123), 1, - anon_sym_PIPE_PIPE, + ACTIONS(2022), 1, + anon_sym__, + ACTIONS(2149), 1, + anon_sym_RBRACE, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(850), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [57190] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 7, + ACTIONS(748), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, anon_sym_QMARK, - ACTIONS(742), 16, + ACTIONS(750), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, @@ -60689,62 +66353,69 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - [53174] = 7, - ACTIONS(2146), 1, - anon_sym_AMP_AMP, - ACTIONS(2148), 1, - anon_sym_PIPE_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [57229] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2144), 2, - anon_sym_LT, - anon_sym_GT, - ACTIONS(944), 4, + ACTIONS(752), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, - ACTIONS(2142), 4, - anon_sym_EQ_EQ, - anon_sym_BANG_EQ, - anon_sym_LT_EQ, - anon_sym_GT_EQ, - ACTIONS(946), 11, + anon_sym_QMARK, + ACTIONS(754), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, - anon_sym_QMARK_QMARK, - anon_sym_DOT_DOT_EQ, - [53214] = 5, - ACTIONS(2146), 1, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(2148), 1, anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57268] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(859), 6, + ACTIONS(756), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, - ACTIONS(861), 15, + anon_sym_QMARK, + ACTIONS(758), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, @@ -60753,27 +66424,34 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - [53250] = 4, - ACTIONS(2148), 1, - anon_sym_PIPE_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [57307] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(740), 6, + ACTIONS(760), 9, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, anon_sym_LT, anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, anon_sym_DOT_DOT, - ACTIONS(742), 16, + anon_sym_QMARK, + ACTIONS(762), 21, anon_sym_LPAREN, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, @@ -60783,1367 +66461,5017 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LT_EQ, anon_sym_GT_EQ, anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, - [53284] = 18, - ACTIONS(894), 1, + anon_sym_EQ_GT, + anon_sym_if, + [57346] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(764), 9, anon_sym_DOT, - ACTIONS(896), 1, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(766), 21, + anon_sym_LPAREN, anon_sym_QMARK_DOT, - ACTIONS(898), 1, anon_sym_LBRACK, - ACTIONS(900), 1, anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(1718), 1, - anon_sym_LPAREN, - ACTIONS(2146), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(2148), 1, anon_sym_PIPE_PIPE, - ACTIONS(2150), 1, - anon_sym_LBRACE, - ACTIONS(2154), 1, - anon_sym_SLASH, - ACTIONS(2158), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - ACTIONS(2160), 1, - anon_sym_DOT_DOT, - ACTIONS(2162), 1, anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57385] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2144), 2, + ACTIONS(768), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(2152), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(770), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2156), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2142), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [53346] = 10, - ACTIONS(2146), 1, anon_sym_AMP_AMP, - ACTIONS(2148), 1, anon_sym_PIPE_PIPE, - ACTIONS(2160), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57424] = 14, + ACTIONS(1992), 1, + anon_sym_AMP_AMP, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, + ACTIONS(2143), 1, anon_sym_DOT_DOT, - ACTIONS(2162), 1, + ACTIONS(2145), 1, anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2144), 2, + ACTIONS(2026), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2156), 2, + ACTIONS(2030), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2136), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(859), 3, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - ACTIONS(2142), 4, + anon_sym_EQ, + ACTIONS(2024), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(861), 8, + ACTIONS(902), 9, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_QMARK_QMARK, - [53392] = 9, - ACTIONS(2146), 1, + anon_sym_if, + [57485] = 13, + ACTIONS(1992), 1, anon_sym_AMP_AMP, - ACTIONS(2148), 1, + ACTIONS(1994), 1, anon_sym_PIPE_PIPE, - ACTIONS(2160), 1, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, + ACTIONS(2143), 1, anon_sym_DOT_DOT, - ACTIONS(2162), 1, + ACTIONS(2145), 1, anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2144), 2, + ACTIONS(2026), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(859), 3, + ACTIONS(2030), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, anon_sym_DOT, anon_sym_BANG, anon_sym_SLASH, - ACTIONS(2142), 4, + anon_sym_EQ, + ACTIONS(2024), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(861), 10, + ACTIONS(902), 11, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_STAR, anon_sym_PERCENT, anon_sym_PLUS, anon_sym_DASH, anon_sym_QMARK_QMARK, - [53436] = 18, - ACTIONS(894), 1, - anon_sym_DOT, - ACTIONS(896), 1, - anon_sym_QMARK_DOT, - ACTIONS(898), 1, - anon_sym_LBRACK, - ACTIONS(900), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(1718), 1, - anon_sym_LPAREN, - ACTIONS(2146), 1, - anon_sym_AMP_AMP, - ACTIONS(2148), 1, - anon_sym_PIPE_PIPE, - ACTIONS(2154), 1, - anon_sym_SLASH, - ACTIONS(2158), 1, - anon_sym_QMARK_QMARK, - ACTIONS(2160), 1, - anon_sym_DOT_DOT, - ACTIONS(2162), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(2164), 1, - anon_sym_LBRACE, + anon_sym_if, + [57544] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2144), 2, + ACTIONS(704), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(2152), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(706), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2156), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2142), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [53498] = 18, - ACTIONS(894), 1, - anon_sym_DOT, - ACTIONS(896), 1, - anon_sym_QMARK_DOT, - ACTIONS(898), 1, - anon_sym_LBRACK, - ACTIONS(900), 1, - anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(1718), 1, - anon_sym_LPAREN, - ACTIONS(2146), 1, anon_sym_AMP_AMP, - ACTIONS(2148), 1, anon_sym_PIPE_PIPE, - ACTIONS(2154), 1, - anon_sym_SLASH, - ACTIONS(2158), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - ACTIONS(2160), 1, - anon_sym_DOT_DOT, - ACTIONS(2162), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(2166), 1, + anon_sym_EQ_GT, + anon_sym_if, + [57583] = 20, + ACTIONS(2000), 1, + aux_sym_identifier_token1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + ACTIONS(2151), 1, + anon_sym_RBRACE, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(826), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [57656] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2144), 2, + ACTIONS(772), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(2152), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(774), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2156), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2142), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [53560] = 12, - ACTIONS(2146), 1, anon_sym_AMP_AMP, - ACTIONS(2148), 1, anon_sym_PIPE_PIPE, - ACTIONS(2154), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57695] = 16, + ACTIONS(1992), 1, + anon_sym_AMP_AMP, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, + ACTIONS(2134), 1, anon_sym_SLASH, - ACTIONS(2160), 1, + ACTIONS(2143), 1, anon_sym_DOT_DOT, - ACTIONS(2162), 1, + ACTIONS(2145), 1, anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(873), 2, - anon_sym_DOT, - anon_sym_BANG, - ACTIONS(2144), 2, + ACTIONS(2026), 2, anon_sym_LT, anon_sym_GT, - ACTIONS(2152), 2, + ACTIONS(2030), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2132), 2, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2156), 2, + ACTIONS(2136), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2142), 4, + ACTIONS(904), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_EQ, + ACTIONS(2024), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(875), 6, + ACTIONS(906), 7, anon_sym_LPAREN, + anon_sym_COLON, anon_sym_QMARK_DOT, anon_sym_LBRACK, anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, anon_sym_QMARK_QMARK, - [53610] = 18, - ACTIONS(894), 1, + anon_sym_if, + [57760] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(776), 9, anon_sym_DOT, - ACTIONS(896), 1, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(778), 21, + anon_sym_LPAREN, anon_sym_QMARK_DOT, - ACTIONS(898), 1, anon_sym_LBRACK, - ACTIONS(900), 1, anon_sym_QMARK_LBRACK, - ACTIONS(902), 1, - anon_sym_BANG, - ACTIONS(1718), 1, - anon_sym_LPAREN, - ACTIONS(2146), 1, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, anon_sym_AMP_AMP, - ACTIONS(2148), 1, anon_sym_PIPE_PIPE, - ACTIONS(2154), 1, - anon_sym_SLASH, - ACTIONS(2158), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - ACTIONS(2160), 1, - anon_sym_DOT_DOT, - ACTIONS(2162), 1, anon_sym_DOT_DOT_EQ, - ACTIONS(2168), 1, - anon_sym_LBRACE, + anon_sym_EQ_GT, + anon_sym_if, + [57799] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2144), 2, + ACTIONS(780), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(2152), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(782), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2156), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2142), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - [53672] = 12, - ACTIONS(2146), 1, anon_sym_AMP_AMP, - ACTIONS(2148), 1, anon_sym_PIPE_PIPE, - ACTIONS(2154), 1, - anon_sym_SLASH, - ACTIONS(2160), 1, - anon_sym_DOT_DOT, - ACTIONS(2162), 1, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57838] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(883), 2, + ACTIONS(784), 9, anon_sym_DOT, anon_sym_BANG, - ACTIONS(2144), 2, + anon_sym_SLASH, anon_sym_LT, anon_sym_GT, - ACTIONS(2152), 2, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(786), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, anon_sym_STAR, anon_sym_PERCENT, - ACTIONS(2156), 2, anon_sym_PLUS, anon_sym_DASH, - ACTIONS(2142), 4, anon_sym_EQ_EQ, anon_sym_BANG_EQ, anon_sym_LT_EQ, anon_sym_GT_EQ, - ACTIONS(885), 6, - anon_sym_LPAREN, - anon_sym_QMARK_DOT, - anon_sym_LBRACK, - anon_sym_QMARK_LBRACK, - anon_sym_LBRACE, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, anon_sym_QMARK_QMARK, - [53722] = 9, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, - anon_sym_LPAREN, - ACTIONS(2174), 1, - anon_sym_RPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57877] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1033), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [53763] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(788), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(790), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57916] = 8, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, + ACTIONS(2161), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1202), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [53801] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(900), 7, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 18, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [57965] = 4, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1003), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [53839] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(772), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(774), 20, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58006] = 5, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1190), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [53877] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(900), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 19, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58049] = 6, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1084), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [53915] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(900), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 19, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58094] = 7, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1121), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [53953] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(900), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 18, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58141] = 10, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, + ACTIONS(2161), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1197), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [53991] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(2165), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2163), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(900), 5, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 14, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58194] = 11, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, + ACTIONS(2161), 1, + anon_sym_AMP, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1217), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54029] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(2165), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2167), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2163), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(977), 5, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(979), 12, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58249] = 14, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, + ACTIONS(2161), 1, + anon_sym_AMP, + ACTIONS(2171), 1, + anon_sym_DOT_DOT, + ACTIONS(2173), 1, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1081), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54067] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(2165), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2167), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2169), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(900), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_QMARK, + ACTIONS(2163), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 9, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [58310] = 13, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, + ACTIONS(2161), 1, + anon_sym_AMP, + ACTIONS(2171), 1, + anon_sym_DOT_DOT, + ACTIONS(2173), 1, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1111), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54105] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(2165), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2167), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_QMARK, + ACTIONS(2163), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 11, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [58369] = 16, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, + ACTIONS(2161), 1, + anon_sym_AMP, + ACTIONS(2171), 1, + anon_sym_DOT_DOT, + ACTIONS(2173), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2177), 1, + anon_sym_SLASH, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1015), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54143] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(2165), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2167), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2169), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2175), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(904), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + ACTIONS(2163), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(906), 7, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [58434] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1155), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54181] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(800), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(802), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1240), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54219] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, - anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58473] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1188), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54257] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(804), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(806), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58512] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1096), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54295] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(808), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(810), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58551] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1141), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54333] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(812), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(814), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58590] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(906), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54371] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(824), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(826), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58629] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1115), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54409] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(828), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(830), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58668] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1078), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54447] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(832), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(834), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58707] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1080), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54485] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(836), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(838), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58746] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(895), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54523] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(840), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(842), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58785] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1074), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54561] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(844), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(846), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58824] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1145), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54599] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(848), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(850), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1014), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54637] = 8, - ACTIONS(2170), 1, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58863] = 20, + ACTIONS(2000), 1, aux_sym_identifier_token1, - ACTIONS(2172), 1, - anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + ACTIONS(2179), 1, + anon_sym_RBRACE, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(905), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54675] = 8, - ACTIONS(2170), 1, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(880), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [58936] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(852), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(854), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [58975] = 20, + ACTIONS(2000), 1, aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + ACTIONS(2181), 1, + anon_sym_RBRACE, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(826), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [59048] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(856), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(858), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59087] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1169), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54713] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(685), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(687), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59126] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(892), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54751] = 8, - ACTIONS(2170), 1, - aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(689), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(691), 21, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59165] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1138), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54789] = 8, - ACTIONS(2170), 1, + ACTIONS(677), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(679), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59204] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(708), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(710), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59243] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(792), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(794), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59282] = 16, + ACTIONS(1992), 1, + anon_sym_AMP_AMP, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, + ACTIONS(2134), 1, + anon_sym_SLASH, + ACTIONS(2143), 1, + anon_sym_DOT_DOT, + ACTIONS(2145), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2026), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2030), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2132), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2136), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(870), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_EQ, + ACTIONS(2024), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(872), 7, + anon_sym_LPAREN, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_QMARK_QMARK, + anon_sym_if, + [59347] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(816), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(818), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59386] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(820), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(822), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59425] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(724), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(726), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59464] = 20, + ACTIONS(2000), 1, aux_sym_identifier_token1, - ACTIONS(2172), 1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + ACTIONS(2183), 1, + anon_sym_RBRACE, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(895), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [59537] = 16, + ACTIONS(2153), 1, + anon_sym_AMP_AMP, + ACTIONS(2155), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2157), 1, + anon_sym_PIPE, + ACTIONS(2159), 1, + anon_sym_CARET, + ACTIONS(2161), 1, + anon_sym_AMP, + ACTIONS(2171), 1, + anon_sym_DOT_DOT, + ACTIONS(2173), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2177), 1, + anon_sym_SLASH, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2165), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2167), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2169), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2175), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(870), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + ACTIONS(2163), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(872), 7, anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [59602] = 3, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(885), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54827] = 8, - ACTIONS(2170), 1, + ACTIONS(728), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(730), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59641] = 8, + ACTIONS(1992), 1, + anon_sym_AMP_AMP, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1996), 1, + anon_sym_PIPE, + ACTIONS(1998), 1, + anon_sym_CARET, + ACTIONS(2028), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 7, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_DOT_DOT, + anon_sym_EQ, + ACTIONS(902), 18, + anon_sym_LPAREN, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_if, + [59690] = 20, + ACTIONS(2000), 1, + aux_sym_identifier_token1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + ACTIONS(2185), 1, + anon_sym_RBRACE, + STATE(1109), 1, + sym_literal_pattern, + STATE(1282), 1, + sym_identifier, + STATE(1318), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(826), 2, + sym_match_arm, + aux_sym_match_expression_repeat1, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [59763] = 4, + ACTIONS(1994), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(772), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_EQ, + ACTIONS(774), 20, + anon_sym_LPAREN, + anon_sym_COLON, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_if, + [59804] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(712), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(714), 21, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59843] = 7, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 7, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(902), 18, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [59889] = 23, + ACTIONS(913), 1, + anon_sym_LBRACE, + ACTIONS(915), 1, + anon_sym_LPAREN, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(2198), 1, + anon_sym_SLASH, + ACTIONS(2210), 1, + anon_sym_AMP_AMP, + ACTIONS(2213), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2216), 1, + anon_sym_PIPE, + ACTIONS(2219), 1, + anon_sym_CARET, + ACTIONS(2222), 1, + anon_sym_AMP, + ACTIONS(2228), 1, + anon_sym_QMARK_QMARK, + ACTIONS(2231), 1, + anon_sym_DOT_DOT, + ACTIONS(2234), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2237), 1, + anon_sym_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2195), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2201), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2207), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2225), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2204), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [59967] = 16, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2241), 1, + anon_sym_SLASH, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(2253), 1, + anon_sym_DOT_DOT, + ACTIONS(2255), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(870), 2, + anon_sym_DOT, + anon_sym_BANG, + ACTIONS(2239), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2243), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2247), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2251), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2245), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(872), 7, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [60031] = 4, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(772), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(774), 20, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [60071] = 20, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2275), 1, + anon_sym_RBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2279), 1, + anon_sym_DOT_DOT, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1101), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [60143] = 20, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + ACTIONS(2283), 1, + anon_sym_RBRACK, + ACTIONS(2285), 1, + anon_sym_DOT_DOT, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1101), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [60215] = 22, + ACTIONS(2035), 1, + anon_sym_DOT, + ACTIONS(2037), 1, + anon_sym_QMARK_DOT, + ACTIONS(2039), 1, + anon_sym_LBRACK, + ACTIONS(2041), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(2043), 1, + anon_sym_BANG, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2241), 1, + anon_sym_SLASH, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(2253), 1, + anon_sym_DOT_DOT, + ACTIONS(2255), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2287), 1, + anon_sym_LPAREN, + ACTIONS(2289), 1, + anon_sym_PIPE, + ACTIONS(2292), 1, + anon_sym_QMARK_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1917), 2, + anon_sym_EQ_GT, + anon_sym_if, + ACTIONS(2239), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2243), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2247), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2251), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2245), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [60291] = 16, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2241), 1, + anon_sym_SLASH, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(2253), 1, + anon_sym_DOT_DOT, + ACTIONS(2255), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(904), 2, + anon_sym_DOT, + anon_sym_BANG, + ACTIONS(2239), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2243), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2247), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2251), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2245), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(906), 7, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [60355] = 6, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 7, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(902), 19, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [60399] = 5, + ACTIONS(701), 1, + anon_sym_BANG, + ACTIONS(2294), 1, + anon_sym_COLON, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(697), 8, + anon_sym_DOT, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(699), 19, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_PIPE_PIPE, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [60441] = 8, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 6, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_DOT_DOT, + ACTIONS(902), 18, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [60489] = 5, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(902), 19, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [60531] = 23, + ACTIONS(913), 1, + anon_sym_LBRACE, + ACTIONS(915), 1, + anon_sym_LPAREN, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(2198), 1, + anon_sym_SLASH, + ACTIONS(2210), 1, + anon_sym_AMP_AMP, + ACTIONS(2213), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2216), 1, + anon_sym_PIPE, + ACTIONS(2219), 1, + anon_sym_CARET, + ACTIONS(2222), 1, + anon_sym_AMP, + ACTIONS(2228), 1, + anon_sym_QMARK_QMARK, + ACTIONS(2231), 1, + anon_sym_DOT_DOT, + ACTIONS(2234), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2297), 1, + anon_sym_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2195), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2201), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2207), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2225), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2204), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [60609] = 14, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(2253), 1, + anon_sym_DOT_DOT, + ACTIONS(2255), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2243), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2247), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2251), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + ACTIONS(2245), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 9, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [60669] = 10, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2247), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(900), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + ACTIONS(2245), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 14, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [60721] = 20, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + ACTIONS(2300), 1, + anon_sym_RBRACK, + ACTIONS(2302), 1, + anon_sym_DOT_DOT, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1101), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [60793] = 8, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(2312), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 7, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 17, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [60841] = 4, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(772), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(774), 19, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [60881] = 5, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 9, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 18, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [60923] = 6, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 18, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [60967] = 7, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 17, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [61013] = 10, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(2312), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2316), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2314), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(900), 5, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(902), 13, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [61065] = 11, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(2312), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2316), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2318), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2314), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(977), 5, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + anon_sym_QMARK, + ACTIONS(979), 11, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [61119] = 11, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2247), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2251), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(977), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + ACTIONS(2245), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(979), 12, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [61173] = 20, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + ACTIONS(2320), 1, + anon_sym_RBRACK, + ACTIONS(2322), 1, + anon_sym_DOT_DOT, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1101), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [61245] = 13, + ACTIONS(2187), 1, + anon_sym_AMP_AMP, + ACTIONS(2189), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2191), 1, + anon_sym_PIPE, + ACTIONS(2193), 1, + anon_sym_CARET, + ACTIONS(2249), 1, + anon_sym_AMP, + ACTIONS(2253), 1, + anon_sym_DOT_DOT, + ACTIONS(2255), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2247), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2251), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + ACTIONS(2245), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 11, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_EQ_GT, + anon_sym_if, + [61303] = 14, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(2312), 1, + anon_sym_AMP, + ACTIONS(2326), 1, + anon_sym_DOT_DOT, + ACTIONS(2328), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2316), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2318), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2324), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(900), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_QMARK, + ACTIONS(2314), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 8, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_QMARK_QMARK, + [61363] = 13, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(2312), 1, + anon_sym_AMP, + ACTIONS(2326), 1, + anon_sym_DOT_DOT, + ACTIONS(2328), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2316), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2318), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_QMARK, + ACTIONS(2314), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 10, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + [61421] = 16, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(2312), 1, + anon_sym_AMP, + ACTIONS(2326), 1, + anon_sym_DOT_DOT, + ACTIONS(2328), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2332), 1, + anon_sym_SLASH, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2316), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2318), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2324), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2330), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(904), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + ACTIONS(2314), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(906), 6, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_QMARK_QMARK, + [61485] = 16, + ACTIONS(2304), 1, + anon_sym_AMP_AMP, + ACTIONS(2306), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2308), 1, + anon_sym_PIPE, + ACTIONS(2310), 1, + anon_sym_CARET, + ACTIONS(2312), 1, + anon_sym_AMP, + ACTIONS(2326), 1, + anon_sym_DOT_DOT, + ACTIONS(2328), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2332), 1, + anon_sym_SLASH, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2316), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2318), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2324), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2330), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(870), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_QMARK, + ACTIONS(2314), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(872), 6, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_QMARK_QMARK, + [61549] = 24, + ACTIONS(913), 1, + anon_sym_EQ_GT, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(1631), 1, + anon_sym_SLASH, + ACTIONS(1639), 1, + anon_sym_AMP_AMP, + ACTIONS(1641), 1, + anon_sym_PIPE_PIPE, + ACTIONS(1643), 1, + anon_sym_PIPE, + ACTIONS(1645), 1, + anon_sym_CARET, + ACTIONS(1647), 1, + anon_sym_AMP, + ACTIONS(1651), 1, + anon_sym_DOT_DOT, + ACTIONS(1653), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(1664), 1, + anon_sym_LT_EQ, + ACTIONS(1700), 1, + anon_sym_LPAREN, + ACTIONS(1702), 1, + anon_sym_QMARK_QMARK, + ACTIONS(2334), 1, + anon_sym_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1629), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(1633), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(1637), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(1649), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(1635), 3, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_GT_EQ, + [61629] = 19, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + ACTIONS(2336), 1, + anon_sym_RBRACK, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1094), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [61698] = 16, + ACTIONS(2340), 1, + anon_sym_SLASH, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(870), 2, + anon_sym_DOT, + anon_sym_BANG, + ACTIONS(2338), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2342), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(872), 6, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_QMARK_QMARK, + [61761] = 14, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2342), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 8, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_QMARK_QMARK, + [61820] = 22, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(1700), 1, + anon_sym_LPAREN, + ACTIONS(2340), 1, + anon_sym_SLASH, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2364), 1, + anon_sym_LBRACE, + ACTIONS(2366), 1, + anon_sym_QMARK_QMARK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2338), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2342), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [61895] = 19, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + ACTIONS(2368), 1, + anon_sym_RBRACK, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1059), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [61964] = 8, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 6, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_DOT_DOT, + ACTIONS(902), 17, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [62011] = 22, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(1700), 1, + anon_sym_LPAREN, + ACTIONS(2340), 1, + anon_sym_SLASH, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2366), 1, + anon_sym_QMARK_QMARK, + ACTIONS(2370), 1, + anon_sym_LBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2338), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2342), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [62086] = 13, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(900), 3, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 10, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + [62143] = 4, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(772), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(774), 19, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_AMP_AMP, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [62182] = 5, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 8, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(902), 18, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [62223] = 6, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 7, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(902), 18, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_CARET, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [62266] = 7, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(900), 7, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_LT, + anon_sym_GT, + anon_sym_AMP, + anon_sym_DOT_DOT, + ACTIONS(902), 17, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [62311] = 22, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(1700), 1, + anon_sym_LPAREN, + ACTIONS(2340), 1, + anon_sym_SLASH, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2366), 1, + anon_sym_QMARK_QMARK, + ACTIONS(2372), 1, + anon_sym_LBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2338), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2342), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [62386] = 10, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(900), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(902), 13, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_LT_LT, + anon_sym_GT_GT, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [62437] = 11, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(977), 4, + anon_sym_DOT, + anon_sym_BANG, + anon_sym_SLASH, + anon_sym_DOT_DOT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(979), 11, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_STAR, + anon_sym_PERCENT, + anon_sym_PLUS, + anon_sym_DASH, + anon_sym_QMARK_QMARK, + anon_sym_DOT_DOT_EQ, + [62490] = 22, + ACTIONS(918), 1, + anon_sym_DOT, + ACTIONS(920), 1, + anon_sym_QMARK_DOT, + ACTIONS(922), 1, + anon_sym_LBRACK, + ACTIONS(924), 1, + anon_sym_QMARK_LBRACK, + ACTIONS(926), 1, + anon_sym_BANG, + ACTIONS(1700), 1, + anon_sym_LPAREN, + ACTIONS(2340), 1, + anon_sym_SLASH, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(2366), 1, + anon_sym_QMARK_QMARK, + ACTIONS(2374), 1, + anon_sym_LBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2338), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2342), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + [62565] = 16, + ACTIONS(2340), 1, + anon_sym_SLASH, + ACTIONS(2348), 1, + anon_sym_AMP_AMP, + ACTIONS(2350), 1, + anon_sym_PIPE_PIPE, + ACTIONS(2352), 1, + anon_sym_PIPE, + ACTIONS(2354), 1, + anon_sym_CARET, + ACTIONS(2356), 1, + anon_sym_AMP, + ACTIONS(2360), 1, + anon_sym_DOT_DOT, + ACTIONS(2362), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(904), 2, + anon_sym_DOT, + anon_sym_BANG, + ACTIONS(2338), 2, + anon_sym_STAR, + anon_sym_PERCENT, + ACTIONS(2342), 2, + anon_sym_PLUS, + anon_sym_DASH, + ACTIONS(2346), 2, + anon_sym_LT, + anon_sym_GT, + ACTIONS(2358), 2, + anon_sym_LT_LT, + anon_sym_GT_GT, + ACTIONS(2344), 4, + anon_sym_EQ_EQ, + anon_sym_BANG_EQ, + anon_sym_LT_EQ, + anon_sym_GT_EQ, + ACTIONS(906), 6, + anon_sym_LPAREN, + anon_sym_QMARK_DOT, + anon_sym_LBRACK, + anon_sym_QMARK_LBRACK, + anon_sym_LBRACE, + anon_sym_QMARK_QMARK, + [62628] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1106), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [62694] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1101), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [62760] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1062), 1, + sym_literal_pattern, + STATE(1193), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [62826] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1062), 1, + sym_literal_pattern, + STATE(1128), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [62892] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1040), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [62958] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1062), 1, + sym_literal_pattern, + STATE(1107), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63024] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1030), 1, + sym_pattern, + STATE(1031), 1, + sym_literal_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63090] = 18, + ACTIONS(2000), 1, + aux_sym_identifier_token1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + STATE(1109), 1, + sym_literal_pattern, + STATE(1236), 1, + sym_pattern, + STATE(1282), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63156] = 18, + ACTIONS(2000), 1, + aux_sym_identifier_token1, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + STATE(1109), 1, + sym_literal_pattern, + STATE(1150), 1, + sym_pattern, + STATE(1282), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63222] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1062), 1, + sym_literal_pattern, + STATE(1208), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63288] = 18, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1031), 1, + sym_literal_pattern, + STATE(1053), 1, + sym_pattern, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63354] = 18, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2172), 1, - anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + ACTIONS(2259), 1, + sym_integer_literal, + ACTIONS(2261), 1, + sym_float_literal, + ACTIONS(2265), 1, + anon_sym_nil, + ACTIONS(2267), 1, + anon_sym_DQUOTE, + ACTIONS(2269), 1, + anon_sym_SQUOTE, + ACTIONS(2271), 1, + sym_raw_string, + ACTIONS(2273), 1, + anon_sym_LBRACK, + ACTIONS(2277), 1, + anon_sym_LBRACE, + ACTIONS(2281), 1, + anon_sym__, + STATE(1018), 1, + sym_identifier, + STATE(1062), 1, + sym_literal_pattern, + STATE(1156), 1, + sym_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(897), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54865] = 8, - ACTIONS(2170), 1, + ACTIONS(2263), 2, + anon_sym_true, + anon_sym_false, + STATE(979), 2, + sym_double_string, + sym_single_string, + STATE(991), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1011), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63420] = 18, + ACTIONS(2000), 1, aux_sym_identifier_token1, - ACTIONS(2172), 1, - anon_sym_LPAREN, - ACTIONS(2178), 1, - anon_sym_List, - ACTIONS(2180), 1, - anon_sym_Map, - STATE(884), 1, - sym_type_identifier, + ACTIONS(2002), 1, + sym_integer_literal, + ACTIONS(2004), 1, + sym_float_literal, + ACTIONS(2008), 1, + anon_sym_nil, + ACTIONS(2010), 1, + anon_sym_DQUOTE, + ACTIONS(2012), 1, + anon_sym_SQUOTE, + ACTIONS(2014), 1, + sym_raw_string, + ACTIONS(2018), 1, + anon_sym_LBRACK, + ACTIONS(2020), 1, + anon_sym_LBRACE, + ACTIONS(2022), 1, + anon_sym__, + STATE(1109), 1, + sym_literal_pattern, + STATE(1169), 1, + sym_pattern, + STATE(1282), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2176), 6, - anon_sym_Int, - anon_sym_Float, - anon_sym_String, - anon_sym_Bool, - anon_sym_Nil, - anon_sym_Any, - STATE(1250), 8, - sym__type, - sym_primitive_type, - sym_list_type, - sym_map_type, - sym_function_type, - sym_optional_type, - sym_union_type, - sym_named_type, - [54903] = 13, - ACTIONS(1629), 1, + ACTIONS(2006), 2, + anon_sym_true, + anon_sym_false, + STATE(1112), 2, + sym_double_string, + sym_single_string, + STATE(1097), 3, + sym_boolean_literal, + sym_nil_literal, + sym_string_literal, + STATE(1283), 7, + sym_wildcard_pattern, + sym_identifier_pattern, + sym_list_pattern, + sym_map_pattern, + sym_or_pattern, + sym_guarded_pattern, + sym_range_pattern, + [63486] = 13, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(1631), 1, + ACTIONS(2259), 1, sym_integer_literal, - ACTIONS(1633), 1, + ACTIONS(2261), 1, sym_float_literal, - ACTIONS(1637), 1, + ACTIONS(2265), 1, anon_sym_nil, - ACTIONS(1639), 1, + ACTIONS(2267), 1, anon_sym_DQUOTE, - ACTIONS(1641), 1, + ACTIONS(2269), 1, anon_sym_SQUOTE, - ACTIONS(1643), 1, + ACTIONS(2271), 1, sym_raw_string, - STATE(1213), 1, + STATE(1018), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1635), 2, + ACTIONS(2263), 2, anon_sym_true, anon_sym_false, - STATE(1031), 2, + STATE(979), 2, sym_double_string, sym_single_string, - STATE(1244), 2, + STATE(1007), 2, sym_literal_pattern, sym_identifier_pattern, - STATE(1040), 3, + STATE(991), 3, sym_boolean_literal, sym_nil_literal, sym_string_literal, - [54949] = 13, - ACTIONS(1726), 1, + [63532] = 13, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2182), 1, + ACTIONS(2376), 1, sym_integer_literal, - ACTIONS(2184), 1, + ACTIONS(2378), 1, sym_float_literal, - ACTIONS(2188), 1, + ACTIONS(2382), 1, anon_sym_nil, - ACTIONS(2190), 1, + ACTIONS(2384), 1, anon_sym_DQUOTE, - ACTIONS(2192), 1, + ACTIONS(2386), 1, anon_sym_SQUOTE, - ACTIONS(2194), 1, + ACTIONS(2388), 1, sym_raw_string, - STATE(929), 1, + STATE(1018), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2186), 2, + ACTIONS(2380), 2, anon_sym_true, anon_sym_false, - STATE(923), 2, + STATE(1007), 2, sym_literal_pattern, sym_identifier_pattern, - STATE(973), 2, + STATE(1066), 2, sym_double_string, sym_single_string, - STATE(976), 3, + STATE(1046), 3, sym_boolean_literal, sym_nil_literal, sym_string_literal, - [54995] = 13, - ACTIONS(1726), 1, + [63578] = 13, + ACTIONS(2000), 1, aux_sym_identifier_token1, - ACTIONS(1728), 1, + ACTIONS(2002), 1, sym_integer_literal, - ACTIONS(1730), 1, + ACTIONS(2004), 1, sym_float_literal, - ACTIONS(1734), 1, + ACTIONS(2008), 1, anon_sym_nil, - ACTIONS(1736), 1, + ACTIONS(2010), 1, anon_sym_DQUOTE, - ACTIONS(1738), 1, + ACTIONS(2012), 1, anon_sym_SQUOTE, - ACTIONS(1740), 1, + ACTIONS(2014), 1, sym_raw_string, - STATE(929), 1, + STATE(1282), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1732), 2, + ACTIONS(2006), 2, anon_sym_true, anon_sym_false, - STATE(876), 2, + STATE(1112), 2, sym_double_string, sym_single_string, - STATE(923), 2, + STATE(1232), 2, sym_literal_pattern, sym_identifier_pattern, - STATE(904), 3, + STATE(1097), 3, sym_boolean_literal, sym_nil_literal, sym_string_literal, - [55041] = 2, + [63624] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(686), 16, + ACTIONS(718), 16, anon_sym_RBRACE, anon_sym_LPAREN, anon_sym_RPAREN, @@ -62152,29 +71480,29 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_LBRACK, anon_sym_RBRACK, anon_sym_LBRACE, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_SEMI, anon_sym_if, anon_sym_as, anon_sym_from, anon_sym_EQ, anon_sym_in, - [55064] = 4, + [63647] = 4, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2200), 2, + ACTIONS(2394), 2, anon_sym_COMMA, anon_sym_SEMI, - ACTIONS(2196), 6, + ACTIONS(2390), 6, aux_sym_identifier_token1, sym_integer_literal, anon_sym_true, anon_sym_false, anon_sym_nil, anon_sym__, - ACTIONS(2198), 7, + ACTIONS(2392), 7, sym_float_literal, anon_sym_DQUOTE, anon_sym_SQUOTE, @@ -62182,383 +71510,424 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_RBRACE, anon_sym_LBRACK, anon_sym_LBRACE, - [55090] = 11, - ACTIONS(1728), 1, + [63673] = 11, + ACTIONS(2259), 1, sym_integer_literal, - ACTIONS(1730), 1, + ACTIONS(2261), 1, sym_float_literal, - ACTIONS(1736), 1, + ACTIONS(2267), 1, anon_sym_DQUOTE, - ACTIONS(1738), 1, + ACTIONS(2269), 1, anon_sym_SQUOTE, - ACTIONS(1740), 1, + ACTIONS(2271), 1, sym_raw_string, - ACTIONS(2204), 1, + ACTIONS(2398), 1, anon_sym_nil, - STATE(923), 1, + STATE(1007), 1, sym_literal_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2202), 2, + ACTIONS(2396), 2, anon_sym_true, anon_sym_false, - STATE(876), 2, + STATE(979), 2, sym_double_string, sym_single_string, - STATE(904), 3, + STATE(991), 3, sym_boolean_literal, sym_nil_literal, sym_string_literal, - [55129] = 11, - ACTIONS(2182), 1, + [63712] = 11, + ACTIONS(2376), 1, sym_integer_literal, - ACTIONS(2184), 1, + ACTIONS(2378), 1, sym_float_literal, - ACTIONS(2190), 1, + ACTIONS(2384), 1, anon_sym_DQUOTE, - ACTIONS(2192), 1, + ACTIONS(2386), 1, anon_sym_SQUOTE, - ACTIONS(2194), 1, + ACTIONS(2388), 1, sym_raw_string, - ACTIONS(2208), 1, + ACTIONS(2402), 1, anon_sym_nil, - STATE(923), 1, + STATE(1007), 1, sym_literal_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2206), 2, + ACTIONS(2400), 2, anon_sym_true, anon_sym_false, - STATE(973), 2, + STATE(1066), 2, sym_double_string, sym_single_string, - STATE(976), 3, + STATE(1046), 3, sym_boolean_literal, sym_nil_literal, sym_string_literal, - [55168] = 11, - ACTIONS(1631), 1, + [63751] = 11, + ACTIONS(2002), 1, sym_integer_literal, - ACTIONS(1633), 1, + ACTIONS(2004), 1, sym_float_literal, - ACTIONS(1639), 1, + ACTIONS(2010), 1, anon_sym_DQUOTE, - ACTIONS(1641), 1, + ACTIONS(2012), 1, anon_sym_SQUOTE, - ACTIONS(1643), 1, + ACTIONS(2014), 1, sym_raw_string, - ACTIONS(2212), 1, + ACTIONS(2406), 1, anon_sym_nil, - STATE(1244), 1, + STATE(1232), 1, sym_literal_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2210), 2, + ACTIONS(2404), 2, anon_sym_true, anon_sym_false, - STATE(1031), 2, + STATE(1112), 2, sym_double_string, sym_single_string, - STATE(1040), 3, + STATE(1097), 3, sym_boolean_literal, sym_nil_literal, sym_string_literal, - [55207] = 3, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2214), 6, - aux_sym_identifier_token1, - sym_integer_literal, - anon_sym_true, - anon_sym_false, - anon_sym_nil, - anon_sym__, - ACTIONS(2216), 7, - sym_float_literal, - anon_sym_DQUOTE, - anon_sym_SQUOTE, - sym_raw_string, - anon_sym_RBRACE, - anon_sym_LBRACK, - anon_sym_LBRACE, - [55229] = 9, + [63790] = 9, ACTIONS(47), 1, anon_sym_POUND, - ACTIONS(2218), 1, + ACTIONS(2408), 1, anon_sym_fn, - ACTIONS(2220), 1, + ACTIONS(2410), 1, anon_sym_struct, - ACTIONS(2222), 1, + ACTIONS(2412), 1, anon_sym_type, - ACTIONS(2224), 1, + ACTIONS(2414), 1, anon_sym_trait, - ACTIONS(2226), 1, + ACTIONS(2416), 1, anon_sym_impl, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(917), 2, + STATE(1022), 2, sym_attribute, aux_sym_attributed_item_repeat1, - STATE(455), 5, + STATE(537), 5, sym_function_definition, sym_struct_definition, sym_type_alias_definition, sym_trait_definition, sym_impl_definition, - [55263] = 9, + [63824] = 9, ACTIONS(47), 1, anon_sym_POUND, - ACTIONS(2228), 1, + ACTIONS(2418), 1, anon_sym_fn, - ACTIONS(2230), 1, + ACTIONS(2420), 1, anon_sym_struct, - ACTIONS(2232), 1, + ACTIONS(2422), 1, anon_sym_type, - ACTIONS(2234), 1, + ACTIONS(2424), 1, anon_sym_trait, - ACTIONS(2236), 1, + ACTIONS(2426), 1, anon_sym_impl, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(917), 2, + STATE(1022), 2, sym_attribute, aux_sym_attributed_item_repeat1, - STATE(546), 5, + STATE(631), 5, sym_function_definition, sym_struct_definition, sym_type_alias_definition, sym_trait_definition, sym_impl_definition, - [55297] = 3, - ACTIONS(692), 1, + [63858] = 3, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2428), 6, + aux_sym_identifier_token1, + sym_integer_literal, + anon_sym_true, + anon_sym_false, + anon_sym_nil, + anon_sym__, + ACTIONS(2430), 7, + sym_float_literal, + anon_sym_DQUOTE, + anon_sym_SQUOTE, + sym_raw_string, + anon_sym_RBRACE, + anon_sym_LBRACK, + anon_sym_LBRACE, + [63880] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1609), 11, + anon_sym_RBRACE, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_LBRACE, + anon_sym_LT, + anon_sym_GT, + anon_sym_PIPE, + anon_sym_QMARK, + anon_sym_SEMI, + anon_sym_EQ, + anon_sym_for, + [63898] = 3, + ACTIONS(724), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(694), 10, + ACTIONS(726), 10, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_as, anon_sym_EQ, - [55317] = 10, + [63918] = 10, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2238), 1, + ACTIONS(2432), 1, anon_sym_RBRACE, - ACTIONS(2240), 1, + ACTIONS(2434), 1, anon_sym_DOT_DOT, - STATE(1142), 1, + STATE(1256), 1, sym_map_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1498), 2, + STATE(1584), 2, + sym_identifier, + sym_string_literal, + [63952] = 10, + ACTIONS(17), 1, + anon_sym_DQUOTE, + ACTIONS(19), 1, + anon_sym_SQUOTE, + ACTIONS(21), 1, + sym_raw_string, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2436), 1, + anon_sym_RBRACE, + ACTIONS(2438), 1, + anon_sym_DOT_DOT, + STATE(1256), 1, + sym_map_pattern_entry, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1584), 2, sym_identifier, sym_string_literal, - [55351] = 3, - ACTIONS(708), 1, + [63986] = 3, + ACTIONS(728), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(710), 10, + ACTIONS(730), 10, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_as, anon_sym_EQ, - [55371] = 10, + [64006] = 10, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2242), 1, + ACTIONS(2440), 1, anon_sym_RBRACE, - ACTIONS(2244), 1, + ACTIONS(2442), 1, anon_sym_DOT_DOT, - STATE(1142), 1, + STATE(1256), 1, sym_map_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1498), 2, + STATE(1584), 2, sym_identifier, sym_string_literal, - [55405] = 10, + [64040] = 3, + ACTIONS(816), 1, + anon_sym_DOT_DOT, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(818), 10, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_SEMI, + anon_sym_if, + anon_sym_as, + anon_sym_EQ, + [64060] = 10, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2246), 1, + ACTIONS(2444), 1, anon_sym_RBRACE, - ACTIONS(2248), 1, + ACTIONS(2446), 1, anon_sym_DOT_DOT, - STATE(1142), 1, + STATE(1256), 1, sym_map_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1498), 2, + STATE(1584), 2, sym_identifier, sym_string_literal, - [55439] = 3, - ACTIONS(760), 1, + [64094] = 3, + ACTIONS(820), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(762), 10, + ACTIONS(822), 10, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_as, anon_sym_EQ, - [55459] = 3, - ACTIONS(764), 1, + [64114] = 3, + ACTIONS(792), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(766), 10, + ACTIONS(794), 10, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_SEMI, anon_sym_if, anon_sym_as, anon_sym_EQ, - [55479] = 10, + [64134] = 9, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2250), 1, + ACTIONS(2448), 1, anon_sym_RBRACE, - ACTIONS(2252), 1, - anon_sym_DOT_DOT, - STATE(1142), 1, + STATE(1200), 1, sym_map_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1498), 2, + STATE(1584), 2, sym_identifier, sym_string_literal, - [55513] = 3, - ACTIONS(688), 1, - anon_sym_DOT_DOT, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(690), 10, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, + [64165] = 4, + ACTIONS(2452), 1, anon_sym_PIPE, - anon_sym_SEMI, - anon_sym_if, - anon_sym_as, - anon_sym_EQ, - [55533] = 2, + STATE(981), 1, + aux_sym_union_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1576), 11, + ACTIONS(2450), 8, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, - anon_sym_LT, anon_sym_GT, anon_sym_QMARK, - anon_sym_PIPE, anon_sym_SEMI, anon_sym_EQ, - anon_sym_for, - [55551] = 3, - ACTIONS(2257), 1, - anon_sym_LT, + [64186] = 5, + ACTIONS(2457), 1, + anon_sym_PIPE, + ACTIONS(2460), 1, + anon_sym_QMARK, + STATE(987), 1, + aux_sym_union_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2254), 9, + ACTIONS(2455), 7, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, - anon_sym_QMARK, - anon_sym_PIPE, anon_sym_SEMI, anon_sym_EQ, - [55570] = 5, - ACTIONS(2261), 1, - anon_sym_QMARK, - ACTIONS(2264), 1, + [64209] = 5, + ACTIONS(2466), 1, anon_sym_PIPE, - STATE(889), 1, + ACTIONS(2470), 1, + anon_sym_QMARK, + STATE(987), 1, aux_sym_union_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2259), 7, + ACTIONS(2463), 7, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, @@ -62566,76 +71935,75 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT, anon_sym_SEMI, anon_sym_EQ, - [55593] = 9, + [64232] = 9, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2267), 1, + ACTIONS(2474), 1, anon_sym_RBRACE, - STATE(1105), 1, + STATE(1151), 1, sym_map_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1498), 2, + STATE(1584), 2, sym_identifier, sym_string_literal, - [55624] = 4, - ACTIONS(2271), 1, - anon_sym_PIPE, - STATE(887), 1, - aux_sym_union_type_repeat1, + [64263] = 3, + ACTIONS(2479), 1, + anon_sym_LT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2269), 8, + ACTIONS(2476), 9, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, + anon_sym_PIPE, anon_sym_QMARK, anon_sym_SEMI, anon_sym_EQ, - [55645] = 9, - ACTIONS(17), 1, + [64282] = 9, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(2267), 1, anon_sym_DQUOTE, - ACTIONS(19), 1, + ACTIONS(2269), 1, anon_sym_SQUOTE, - ACTIONS(21), 1, + ACTIONS(2271), 1, sym_raw_string, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(2274), 1, - anon_sym_RBRACE, - STATE(1068), 1, - sym_map_pattern_entry, + ACTIONS(2481), 1, + anon_sym_LBRACE, + ACTIONS(2483), 1, + anon_sym_STAR, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(979), 2, sym_double_string, sym_single_string, - STATE(1498), 2, + STATE(1423), 2, sym_identifier, sym_string_literal, - [55676] = 4, - ACTIONS(2278), 1, + [64313] = 4, + ACTIONS(2487), 1, anon_sym_PIPE, - STATE(887), 1, + STATE(981), 1, aux_sym_union_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2276), 8, + ACTIONS(2485), 8, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, @@ -62644,141 +72012,116 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_QMARK, anon_sym_SEMI, anon_sym_EQ, - [55697] = 9, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(1736), 1, - anon_sym_DQUOTE, - ACTIONS(1738), 1, - anon_sym_SQUOTE, - ACTIONS(1740), 1, - sym_raw_string, - ACTIONS(2281), 1, - anon_sym_LBRACE, - ACTIONS(2283), 1, - anon_sym_STAR, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(876), 2, - sym_double_string, - sym_single_string, - STATE(1305), 2, - sym_identifier, - sym_string_literal, - [55728] = 9, - ACTIONS(1726), 1, + [64334] = 9, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(1736), 1, + ACTIONS(2267), 1, anon_sym_DQUOTE, - ACTIONS(1738), 1, + ACTIONS(2269), 1, anon_sym_SQUOTE, - ACTIONS(1740), 1, + ACTIONS(2271), 1, sym_raw_string, - ACTIONS(2285), 1, + ACTIONS(2490), 1, anon_sym_LBRACE, - ACTIONS(2287), 1, + ACTIONS(2492), 1, anon_sym_STAR, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(876), 2, + STATE(979), 2, sym_double_string, sym_single_string, - STATE(1279), 2, + STATE(1398), 2, sym_identifier, sym_string_literal, - [55759] = 5, - ACTIONS(2292), 1, - anon_sym_QMARK, - ACTIONS(2296), 1, - anon_sym_PIPE, - STATE(889), 1, - aux_sym_union_type_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2289), 7, - anon_sym_RBRACE, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_LBRACE, - anon_sym_GT, - anon_sym_SEMI, - anon_sym_EQ, - [55782] = 2, + [64365] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2300), 9, + ACTIONS(2494), 9, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, - anon_sym_QMARK, anon_sym_PIPE, + anon_sym_QMARK, anon_sym_SEMI, anon_sym_EQ, - [55798] = 8, + [64381] = 8, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - STATE(1206), 1, + STATE(1293), 1, sym_for_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1521), 2, + STATE(1501), 2, sym_identifier, sym_string_literal, - [55826] = 4, - ACTIONS(2304), 1, - anon_sym_QMARK, - ACTIONS(2307), 1, + [64409] = 3, + ACTIONS(2498), 1, + anon_sym_DOT_DOT, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2496), 8, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_RBRACK, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_if, + anon_sym_EQ, + [64427] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2302), 7, + ACTIONS(2500), 9, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, + anon_sym_PIPE, + anon_sym_QMARK, anon_sym_SEMI, anon_sym_EQ, - [55846] = 2, + [64443] = 4, + ACTIONS(2504), 1, + anon_sym_PIPE, + ACTIONS(2507), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2310), 9, + ACTIONS(2502), 7, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, - anon_sym_QMARK, - anon_sym_PIPE, anon_sym_SEMI, anon_sym_EQ, - [55862] = 4, - ACTIONS(2314), 1, - anon_sym_QMARK, - ACTIONS(2317), 1, + [64463] = 4, + ACTIONS(2512), 1, anon_sym_PIPE, + ACTIONS(2515), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2312), 7, + ACTIONS(2510), 7, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, @@ -62786,7728 +72129,7791 @@ static const uint16_t ts_small_parse_table[] = { anon_sym_GT, anon_sym_SEMI, anon_sym_EQ, - [55882] = 2, + [64483] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2320), 9, + ACTIONS(2518), 9, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, - anon_sym_QMARK, anon_sym_PIPE, + anon_sym_QMARK, anon_sym_SEMI, anon_sym_EQ, - [55898] = 3, - ACTIONS(744), 1, - anon_sym_DOT_DOT, + [64499] = 4, + ACTIONS(2522), 1, + anon_sym_PIPE, + ACTIONS(2525), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(746), 8, + ACTIONS(2520), 7, anon_sym_RBRACE, + anon_sym_RPAREN, anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, - anon_sym_PIPE, - anon_sym_if, + anon_sym_LBRACE, + anon_sym_GT, + anon_sym_SEMI, anon_sym_EQ, - [55916] = 2, + [64519] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2322), 9, + ACTIONS(2528), 9, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, - anon_sym_QMARK, anon_sym_PIPE, + anon_sym_QMARK, anon_sym_SEMI, anon_sym_EQ, - [55932] = 3, - ACTIONS(668), 1, - anon_sym_DOT_DOT, + [64535] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(670), 8, + ACTIONS(2530), 9, anon_sym_RBRACE, + anon_sym_RPAREN, anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, + anon_sym_LBRACE, + anon_sym_GT, anon_sym_PIPE, - anon_sym_if, + anon_sym_QMARK, + anon_sym_SEMI, anon_sym_EQ, - [55950] = 8, + [64551] = 8, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - STATE(1142), 1, + STATE(1256), 1, sym_map_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1498), 2, + STATE(1584), 2, sym_identifier, sym_string_literal, - [55978] = 2, + [64579] = 4, + ACTIONS(2532), 1, + anon_sym_PIPE, + ACTIONS(2535), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2324), 9, + ACTIONS(2450), 7, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, - anon_sym_QMARK, - anon_sym_PIPE, anon_sym_SEMI, anon_sym_EQ, - [55994] = 3, - ACTIONS(2328), 1, - anon_sym_DOT_DOT, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2326), 8, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, - anon_sym_DOT_DOT_EQ, - anon_sym_PIPE, - anon_sym_if, - anon_sym_EQ, - [56012] = 4, - ACTIONS(2332), 1, - anon_sym_QMARK, - ACTIONS(2335), 1, - anon_sym_PIPE, + [64599] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2330), 7, + ACTIONS(2538), 9, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_LBRACE, anon_sym_GT, + anon_sym_PIPE, + anon_sym_QMARK, anon_sym_SEMI, anon_sym_EQ, - [56032] = 4, - ACTIONS(2338), 1, - anon_sym_QMARK, - ACTIONS(2341), 1, - anon_sym_PIPE, + [64615] = 3, + ACTIONS(693), 1, + anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2269), 7, + ACTIONS(695), 8, anon_sym_RBRACE, - anon_sym_RPAREN, anon_sym_COMMA, - anon_sym_LBRACE, - anon_sym_GT, - anon_sym_SEMI, + anon_sym_COLON, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_if, anon_sym_EQ, - [56052] = 2, + [64633] = 3, + ACTIONS(796), 1, + anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2344), 9, + ACTIONS(798), 8, anon_sym_RBRACE, - anon_sym_RPAREN, anon_sym_COMMA, - anon_sym_LBRACE, - anon_sym_GT, - anon_sym_QMARK, + anon_sym_COLON, + anon_sym_RBRACK, anon_sym_PIPE, - anon_sym_SEMI, + anon_sym_DOT_DOT_EQ, + anon_sym_if, anon_sym_EQ, - [56068] = 8, + [64651] = 8, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, - STATE(1281), 1, + STATE(1396), 1, sym_for_pattern_entry, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1501), 2, + sym_identifier, + sym_string_literal, + [64679] = 7, + ACTIONS(17), 1, + anon_sym_DQUOTE, + ACTIONS(19), 1, + anon_sym_SQUOTE, + ACTIONS(21), 1, + sym_raw_string, + ACTIONS(2257), 1, + aux_sym_identifier_token1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1521), 2, + STATE(1623), 2, sym_identifier, sym_string_literal, - [56096] = 2, + [64704] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2346), 8, + ACTIONS(2540), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56111] = 2, + [64719] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2348), 8, + ACTIONS(2542), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56126] = 7, + [64734] = 7, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1370), 2, + STATE(1645), 2, sym_identifier, sym_string_literal, - [56151] = 7, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(1726), 1, - aux_sym_identifier_token1, + [64759] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1408), 2, - sym_identifier, - sym_string_literal, - [56176] = 7, - ACTIONS(17), 1, - anon_sym_DQUOTE, - ACTIONS(19), 1, - anon_sym_SQUOTE, - ACTIONS(21), 1, - sym_raw_string, - ACTIONS(1726), 1, - aux_sym_identifier_token1, + ACTIONS(2544), 8, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + anon_sym_EQ, + [64774] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, - sym_double_string, - sym_single_string, - STATE(1429), 2, - sym_identifier, - sym_string_literal, - [56201] = 7, + ACTIONS(2546), 8, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + anon_sym_EQ, + [64789] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2548), 8, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_COLON, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + anon_sym_EQ, + [64804] = 7, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1436), 2, + STATE(1577), 2, sym_identifier, - sym_string_literal, - [56226] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2350), 8, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_if, - anon_sym_EQ, - [56241] = 2, + sym_string_literal, + [64829] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2352), 8, + ACTIONS(2550), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56256] = 4, - ACTIONS(2354), 1, - anon_sym_POUND, + [64844] = 7, + ACTIONS(17), 1, + anon_sym_DQUOTE, + ACTIONS(19), 1, + anon_sym_SQUOTE, + ACTIONS(21), 1, + sym_raw_string, + ACTIONS(2257), 1, + aux_sym_identifier_token1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(917), 2, - sym_attribute, - aux_sym_attributed_item_repeat1, - ACTIONS(2357), 5, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - [56275] = 2, + STATE(84), 2, + sym_double_string, + sym_single_string, + STATE(1551), 2, + sym_identifier, + sym_string_literal, + [64869] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2359), 8, + ACTIONS(2552), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, - anon_sym_if, - anon_sym_EQ, - [56290] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2361), 8, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, anon_sym_DOT_DOT, - anon_sym_PIPE, anon_sym_if, anon_sym_EQ, - [56305] = 7, + [64884] = 7, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1371), 2, + STATE(1602), 2, sym_identifier, sym_string_literal, - [56330] = 2, + [64909] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2363), 8, + ACTIONS(2554), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56345] = 2, + [64924] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2365), 8, + ACTIONS(2556), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56360] = 2, + [64939] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2367), 8, + ACTIONS(2558), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56375] = 2, + [64954] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2369), 8, + ACTIONS(2560), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56390] = 7, + [64969] = 7, ACTIONS(17), 1, anon_sym_DQUOTE, ACTIONS(19), 1, anon_sym_SQUOTE, ACTIONS(21), 1, sym_raw_string, - ACTIONS(1726), 1, + ACTIONS(2257), 1, aux_sym_identifier_token1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(67), 2, + STATE(84), 2, sym_double_string, sym_single_string, - STATE(1373), 2, + STATE(1469), 2, sym_identifier, sym_string_literal, - [56415] = 2, + [64994] = 4, + ACTIONS(2562), 1, + anon_sym_POUND, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2371), 8, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_COLON, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_if, - anon_sym_EQ, - [56430] = 2, + STATE(1022), 2, + sym_attribute, + aux_sym_attributed_item_repeat1, + ACTIONS(2565), 5, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + [65013] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2373), 8, + ACTIONS(2567), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56445] = 2, + [65028] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2375), 8, + ACTIONS(2569), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56460] = 2, + [65043] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2377), 8, + ACTIONS(2571), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56475] = 2, + [65058] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2379), 8, + ACTIONS(2573), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56490] = 2, + [65073] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2381), 8, + ACTIONS(2575), 8, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_COLON, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, anon_sym_EQ, - [56505] = 8, - ACTIONS(1726), 1, + [65088] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2383), 1, + ACTIONS(2577), 1, anon_sym_LPAREN, - ACTIONS(2385), 1, + ACTIONS(2579), 1, anon_sym_LBRACK, - ACTIONS(2387), 1, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2389), 1, + ACTIONS(2583), 1, anon_sym__, - STATE(956), 1, + STATE(1048), 1, sym_identifier, - STATE(1114), 1, + STATE(1617), 1, sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56531] = 5, - ACTIONS(2394), 1, + [65114] = 4, + ACTIONS(2587), 1, anon_sym_PIPE, - ACTIONS(2398), 1, - anon_sym_if, - STATE(943), 1, + STATE(1029), 1, aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2391), 4, + ACTIONS(2585), 5, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, - [56551] = 8, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2404), 1, - anon_sym_RPAREN, - ACTIONS(2406), 1, - anon_sym_COMMA, - ACTIONS(2408), 1, - anon_sym_LBRACE, - STATE(1191), 1, - sym_identifier, - STATE(1245), 1, - sym_parameter, - STATE(1505), 1, - sym_named_params_block, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [56577] = 4, - ACTIONS(2412), 1, + anon_sym_if, + [65132] = 5, + ACTIONS(2593), 1, anon_sym_PIPE, - STATE(935), 1, + ACTIONS(2597), 1, + anon_sym_if, + STATE(1037), 1, aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2410), 5, + ACTIONS(2590), 4, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, - anon_sym_if, - [56595] = 4, - ACTIONS(2415), 1, + [65152] = 4, + ACTIONS(2601), 1, anon_sym_DOT_DOT, - ACTIONS(2418), 1, + ACTIONS(2604), 1, anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2371), 5, + ACTIONS(2548), 5, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_PIPE, anon_sym_if, - [56613] = 8, - ACTIONS(2402), 1, + [65170] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2408), 1, + ACTIONS(2577), 1, + anon_sym_LPAREN, + ACTIONS(2579), 1, + anon_sym_LBRACK, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2420), 1, - anon_sym_RPAREN, - ACTIONS(2422), 1, - anon_sym_COMMA, - STATE(1191), 1, + ACTIONS(2583), 1, + anon_sym__, + STATE(1048), 1, sym_identifier, - STATE(1245), 1, - sym_parameter, - STATE(1443), 1, - sym_named_params_block, + STATE(1589), 1, + sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56639] = 8, - ACTIONS(1726), 1, + [65196] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2383), 1, + ACTIONS(2577), 1, anon_sym_LPAREN, - ACTIONS(2385), 1, + ACTIONS(2579), 1, anon_sym_LBRACK, - ACTIONS(2387), 1, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2389), 1, + ACTIONS(2583), 1, anon_sym__, - STATE(956), 1, + STATE(1048), 1, sym_identifier, - STATE(1526), 1, + STATE(1284), 1, sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56665] = 8, - ACTIONS(1726), 1, + [65222] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2383), 1, + ACTIONS(2577), 1, anon_sym_LPAREN, - ACTIONS(2385), 1, + ACTIONS(2579), 1, anon_sym_LBRACK, - ACTIONS(2387), 1, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2389), 1, + ACTIONS(2583), 1, anon_sym__, - STATE(956), 1, + STATE(1048), 1, sym_identifier, - STATE(1200), 1, + STATE(1386), 1, sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56691] = 8, - ACTIONS(1726), 1, + [65248] = 8, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2608), 1, + anon_sym_RPAREN, + ACTIONS(2610), 1, + anon_sym_COMMA, + ACTIONS(2612), 1, + anon_sym_LBRACE, + STATE(1249), 1, + sym_identifier, + STATE(1314), 1, + sym_parameter, + STATE(1544), 1, + sym_named_params_block, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [65274] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2383), 1, + ACTIONS(2577), 1, anon_sym_LPAREN, - ACTIONS(2385), 1, + ACTIONS(2579), 1, anon_sym_LBRACK, - ACTIONS(2387), 1, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2389), 1, + ACTIONS(2583), 1, anon_sym__, - STATE(956), 1, + STATE(1048), 1, sym_identifier, - STATE(1113), 1, + STATE(1653), 1, sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56717] = 5, - ACTIONS(2426), 1, + [65300] = 4, + ACTIONS(2616), 1, anon_sym_PIPE, - ACTIONS(2429), 1, - anon_sym_if, - STATE(943), 1, + STATE(1029), 1, aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2424), 4, + ACTIONS(2614), 5, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, - [56737] = 8, - ACTIONS(1726), 1, + anon_sym_if, + [65318] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2383), 1, + ACTIONS(2577), 1, anon_sym_LPAREN, - ACTIONS(2385), 1, + ACTIONS(2579), 1, anon_sym_LBRACK, - ACTIONS(2387), 1, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2389), 1, + ACTIONS(2583), 1, anon_sym__, - STATE(956), 1, + STATE(1048), 1, sym_identifier, - STATE(1280), 1, + STATE(1158), 1, sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56763] = 4, - ACTIONS(2434), 1, + [65344] = 8, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2612), 1, + anon_sym_LBRACE, + ACTIONS(2619), 1, + anon_sym_RPAREN, + ACTIONS(2621), 1, + anon_sym_COMMA, + STATE(1249), 1, + sym_identifier, + STATE(1314), 1, + sym_parameter, + STATE(1636), 1, + sym_named_params_block, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [65370] = 5, + ACTIONS(2625), 1, anon_sym_PIPE, - STATE(935), 1, + ACTIONS(2628), 1, + anon_sym_if, + STATE(1037), 1, aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2432), 5, + ACTIONS(2623), 4, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, - anon_sym_if, - [56781] = 8, - ACTIONS(1726), 1, - aux_sym_identifier_token1, - ACTIONS(2383), 1, - anon_sym_LPAREN, - ACTIONS(2385), 1, - anon_sym_LBRACK, - ACTIONS(2387), 1, - anon_sym_LBRACE, - ACTIONS(2389), 1, - anon_sym__, - STATE(956), 1, - sym_identifier, - STATE(1392), 1, - sym_for_pattern, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [56807] = 8, - ACTIONS(1726), 1, + [65390] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2383), 1, + ACTIONS(2577), 1, anon_sym_LPAREN, - ACTIONS(2385), 1, + ACTIONS(2579), 1, anon_sym_LBRACK, - ACTIONS(2387), 1, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2389), 1, + ACTIONS(2583), 1, anon_sym__, - STATE(956), 1, + STATE(1048), 1, sym_identifier, - STATE(1476), 1, + STATE(1608), 1, sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56833] = 8, - ACTIONS(1726), 1, + [65416] = 8, + ACTIONS(2257), 1, aux_sym_identifier_token1, - ACTIONS(2383), 1, + ACTIONS(2577), 1, anon_sym_LPAREN, - ACTIONS(2385), 1, + ACTIONS(2579), 1, anon_sym_LBRACK, - ACTIONS(2387), 1, + ACTIONS(2581), 1, anon_sym_LBRACE, - ACTIONS(2389), 1, + ACTIONS(2583), 1, anon_sym__, - STATE(956), 1, + STATE(1048), 1, sym_identifier, - STATE(1517), 1, + STATE(1149), 1, sym_for_pattern, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [56859] = 7, - ACTIONS(2437), 1, - anon_sym_COMMA, - ACTIONS(2439), 1, - anon_sym_RBRACK, - ACTIONS(2441), 1, - anon_sym_DOT_DOT, - ACTIONS(2443), 1, - anon_sym_PIPE, - ACTIONS(2445), 1, - anon_sym_if, - STATE(1106), 1, - aux_sym_list_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [56882] = 5, - ACTIONS(2449), 1, + [65442] = 5, + ACTIONS(2633), 1, anon_sym_SQUOTE, - ACTIONS(2453), 1, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2451), 2, + ACTIONS(2635), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(955), 2, + STATE(1089), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [56901] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(670), 6, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_if, - [56914] = 5, - ACTIONS(2455), 1, + [65461] = 5, + ACTIONS(2639), 1, anon_sym_DQUOTE, - ACTIONS(2460), 1, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2457), 2, + ACTIONS(2641), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1045), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [56933] = 5, - ACTIONS(2463), 1, + [65480] = 5, + ACTIONS(2645), 1, anon_sym_DQUOTE, - ACTIONS(2467), 1, + ACTIONS(2650), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, + ACTIONS(2647), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1045), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [56952] = 5, - ACTIONS(2467), 1, + [65499] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2496), 6, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + [65512] = 5, + ACTIONS(2637), 1, + anon_sym_DOLLAR_LBRACE, + ACTIONS(2653), 1, + anon_sym_SQUOTE, + ACTIONS(2631), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2655), 2, + aux_sym_single_string_token1, + sym_escape_sequence, + STATE(1079), 2, + sym_string_interpolation, + aux_sym_single_string_repeat1, + [65531] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2657), 6, + anon_sym_RBRACE, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_DOT_DOT, + anon_sym_in, + [65544] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2469), 1, + ACTIONS(2659), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2471), 2, + ACTIONS(2661), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(970), 2, + STATE(1051), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [56971] = 5, - ACTIONS(2453), 1, + [65563] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2473), 1, + ACTIONS(2663), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2475), 2, + ACTIONS(2665), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(999), 2, + STATE(1052), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [56990] = 5, - ACTIONS(2477), 1, - anon_sym_SQUOTE, - ACTIONS(2482), 1, + [65582] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2447), 2, + ACTIONS(2667), 1, + anon_sym_DQUOTE, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2479), 2, - aux_sym_single_string_token1, + ACTIONS(2641), 2, + aux_sym_double_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1045), 2, sym_string_interpolation, - aux_sym_single_string_repeat1, - [57009] = 5, - ACTIONS(2453), 1, + aux_sym_double_string_repeat1, + [65601] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2485), 1, + ACTIONS(2669), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2487), 2, + ACTIONS(2655), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1079), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57028] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2489), 6, - anon_sym_RBRACE, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - anon_sym_in, - [57041] = 7, - ACTIONS(2443), 1, - anon_sym_PIPE, - ACTIONS(2445), 1, - anon_sym_if, - ACTIONS(2491), 1, - anon_sym_COMMA, - ACTIONS(2493), 1, - anon_sym_RBRACK, - ACTIONS(2495), 1, - anon_sym_DOT_DOT, - STATE(1131), 1, - aux_sym_list_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [57064] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(690), 6, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_if, - [57077] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(746), 6, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, + [65620] = 4, + ACTIONS(2671), 1, anon_sym_PIPE, + ACTIONS(2674), 1, anon_sym_if, - [57090] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(694), 6, + ACTIONS(2585), 4, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_if, - [57103] = 5, - ACTIONS(2467), 1, + [65637] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2497), 1, - anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2677), 1, + anon_sym_SQUOTE, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, - aux_sym_double_string_token1, + ACTIONS(2655), 2, + aux_sym_single_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1079), 2, sym_string_interpolation, - aux_sym_double_string_repeat1, - [57122] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2499), 6, - anon_sym_RBRACE, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - anon_sym_in, - [57135] = 4, - ACTIONS(2501), 1, - anon_sym_DOT_DOT, - ACTIONS(2503), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2371), 4, - anon_sym_COLON, - anon_sym_PIPE, - anon_sym_if, - anon_sym_EQ, - [57152] = 2, + aux_sym_single_string_repeat1, + [65656] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2505), 6, + ACTIONS(2679), 6, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, anon_sym_in, - [57165] = 5, - ACTIONS(2467), 1, + [65669] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2507), 1, + ACTIONS(2681), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2509), 2, + ACTIONS(2683), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(985), 2, + STATE(1060), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57184] = 5, - ACTIONS(2467), 1, + [65688] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2511), 1, - anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2685), 1, + anon_sym_SQUOTE, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2513), 2, - aux_sym_double_string_token1, + ACTIONS(2687), 2, + aux_sym_single_string_token1, sym_escape_sequence, - STATE(968), 2, + STATE(1061), 2, sym_string_interpolation, - aux_sym_double_string_repeat1, - [57203] = 5, - ACTIONS(2453), 1, + aux_sym_single_string_repeat1, + [65707] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2515), 1, - anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2689), 1, + anon_sym_DQUOTE, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2517), 2, - aux_sym_single_string_token1, + ACTIONS(2641), 2, + aux_sym_double_string_token1, sym_escape_sequence, - STATE(969), 2, + STATE(1045), 2, sym_string_interpolation, - aux_sym_single_string_repeat1, - [57222] = 5, - ACTIONS(2467), 1, + aux_sym_double_string_repeat1, + [65726] = 7, + ACTIONS(2691), 1, + anon_sym_COMMA, + ACTIONS(2693), 1, + anon_sym_RBRACK, + ACTIONS(2695), 1, + anon_sym_PIPE, + ACTIONS(2697), 1, + anon_sym_DOT_DOT, + ACTIONS(2699), 1, + anon_sym_if, + STATE(1223), 1, + aux_sym_list_pattern_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [65749] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2519), 1, + ACTIONS(2701), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, + ACTIONS(2641), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1045), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57241] = 5, - ACTIONS(2453), 1, + [65768] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2521), 1, + ACTIONS(2703), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2487), 2, + ACTIONS(2655), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1079), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57260] = 5, - ACTIONS(2467), 1, + [65787] = 4, + ACTIONS(2705), 1, + anon_sym_DOT_DOT, + ACTIONS(2707), 1, + anon_sym_DOT_DOT_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2548), 4, + anon_sym_COLON, + anon_sym_PIPE, + anon_sym_if, + anon_sym_EQ, + [65804] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2523), 1, + ACTIONS(2709), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, + ACTIONS(2711), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1058), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57279] = 5, - ACTIONS(2467), 1, + [65823] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2525), 1, - anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2713), 1, + anon_sym_SQUOTE, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2527), 2, - aux_sym_double_string_token1, + ACTIONS(2715), 2, + aux_sym_single_string_token1, sym_escape_sequence, - STATE(961), 2, + STATE(1054), 2, sym_string_interpolation, - aux_sym_double_string_repeat1, - [57298] = 5, - ACTIONS(2453), 1, + aux_sym_single_string_repeat1, + [65842] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2529), 1, + ACTIONS(2717), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2487), 2, + ACTIONS(2719), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1047), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57317] = 2, + [65861] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(710), 6, + ACTIONS(794), 6, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, - anon_sym_if, - [57330] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(762), 6, - anon_sym_RBRACE, - anon_sym_COMMA, - anon_sym_RBRACK, anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_if, - [57343] = 4, - ACTIONS(2531), 1, - anon_sym_PIPE, - ACTIONS(2534), 1, anon_sym_if, + [65874] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2410), 4, + ACTIONS(818), 6, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, + anon_sym_PIPE, anon_sym_DOT_DOT, - [57360] = 2, + anon_sym_if, + [65887] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2326), 6, + ACTIONS(822), 6, anon_sym_RBRACE, anon_sym_COMMA, anon_sym_RBRACK, - anon_sym_DOT_DOT, anon_sym_PIPE, + anon_sym_DOT_DOT, anon_sym_if, - [57373] = 2, + [65900] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2537), 6, + ACTIONS(2721), 6, anon_sym_RBRACE, anon_sym_RPAREN, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, anon_sym_in, - [57386] = 5, - ACTIONS(2453), 1, - anon_sym_DOLLAR_LBRACE, - ACTIONS(2539), 1, - anon_sym_SQUOTE, - ACTIONS(2447), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2541), 2, - aux_sym_single_string_token1, - sym_escape_sequence, - STATE(972), 2, - sym_string_interpolation, - aux_sym_single_string_repeat1, - [57405] = 5, - ACTIONS(2467), 1, + [65913] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2543), 1, + ACTIONS(2723), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2545), 2, + ACTIONS(2725), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(995), 2, + STATE(1072), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57424] = 5, - ACTIONS(2453), 1, + [65932] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2547), 1, + ACTIONS(2727), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2549), 2, + ACTIONS(2729), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(997), 2, + STATE(1073), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57443] = 5, - ACTIONS(2467), 1, + [65951] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2551), 1, + ACTIONS(2731), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2553), 2, + ACTIONS(2641), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(983), 2, + STATE(1045), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57462] = 5, - ACTIONS(2453), 1, + [65970] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2555), 1, + ACTIONS(2733), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2557), 2, + ACTIONS(2655), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(984), 2, + STATE(1079), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57481] = 5, - ACTIONS(2467), 1, + [65989] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2735), 6, + anon_sym_RBRACE, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_DOT_DOT, + anon_sym_in, + [66002] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(695), 6, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + [66015] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(798), 6, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + [66028] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2559), 1, + ACTIONS(2737), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, + ACTIONS(2739), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1092), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57500] = 5, - ACTIONS(2453), 1, + [66047] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2561), 1, + ACTIONS(2741), 1, + anon_sym_SQUOTE, + ACTIONS(2631), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2743), 2, + aux_sym_single_string_token1, + sym_escape_sequence, + STATE(1093), 2, + sym_string_interpolation, + aux_sym_single_string_repeat1, + [66066] = 5, + ACTIONS(2745), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2750), 1, + anon_sym_DOLLAR_LBRACE, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2487), 2, + ACTIONS(2747), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1079), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57519] = 5, - ACTIONS(2467), 1, + [66085] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2563), 1, + ACTIONS(2753), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, + ACTIONS(2755), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1044), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57538] = 2, + [66104] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2565), 6, + ACTIONS(2757), 6, anon_sym_POUND, anon_sym_fn, anon_sym_struct, anon_sym_type, anon_sym_trait, anon_sym_impl, - [57551] = 5, - ACTIONS(2453), 1, - anon_sym_DOLLAR_LBRACE, - ACTIONS(2567), 1, - anon_sym_SQUOTE, - ACTIONS(2447), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2487), 2, - aux_sym_single_string_token1, - sym_escape_sequence, - STATE(954), 2, - sym_string_interpolation, - aux_sym_single_string_repeat1, - [57570] = 2, + [66117] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(766), 6, + ACTIONS(2759), 6, anon_sym_RBRACE, + anon_sym_RPAREN, anon_sym_COMMA, anon_sym_RBRACK, anon_sym_DOT_DOT, - anon_sym_PIPE, - anon_sym_if, - [57583] = 5, - ACTIONS(2467), 1, + anon_sym_in, + [66130] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2569), 1, + ACTIONS(2761), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2571), 2, + ACTIONS(2763), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(951), 2, + STATE(1090), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57602] = 5, - ACTIONS(2467), 1, + [66149] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2573), 1, + ACTIONS(2765), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2575), 2, + ACTIONS(2767), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(992), 2, + STATE(1088), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57621] = 5, - ACTIONS(2453), 1, + [66168] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2577), 1, + ACTIONS(2769), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2579), 2, + ACTIONS(2771), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(994), 2, + STATE(1091), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57640] = 5, - ACTIONS(2467), 1, + [66187] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(726), 6, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + [66200] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(730), 6, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_PIPE, + anon_sym_DOT_DOT, + anon_sym_if, + [66213] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2581), 1, + ACTIONS(2773), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, + ACTIONS(2641), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1045), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57659] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2583), 6, - anon_sym_POUND, - anon_sym_fn, - anon_sym_struct, - anon_sym_type, - anon_sym_trait, - anon_sym_impl, - [57672] = 5, - ACTIONS(2453), 1, + [66232] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2585), 1, + ACTIONS(2775), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2487), 2, + ACTIONS(2655), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1079), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57691] = 5, - ACTIONS(2467), 1, + [66251] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2587), 1, + ACTIONS(2777), 1, anon_sym_DQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2465), 2, + ACTIONS(2641), 2, aux_sym_double_string_token1, sym_escape_sequence, - STATE(950), 2, + STATE(1045), 2, sym_string_interpolation, aux_sym_double_string_repeat1, - [57710] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2589), 6, - anon_sym_RBRACE, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - anon_sym_in, - [57723] = 5, - ACTIONS(2453), 1, + [66270] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2591), 1, + ACTIONS(2779), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2487), 2, + ACTIONS(2655), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1079), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57742] = 5, - ACTIONS(2453), 1, + [66289] = 5, + ACTIONS(2643), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2593), 1, - anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2781), 1, + anon_sym_DQUOTE, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2595), 2, - aux_sym_single_string_token1, + ACTIONS(2641), 2, + aux_sym_double_string_token1, sym_escape_sequence, - STATE(987), 2, + STATE(1045), 2, sym_string_interpolation, - aux_sym_single_string_repeat1, - [57761] = 5, - ACTIONS(2453), 1, + aux_sym_double_string_repeat1, + [66308] = 5, + ACTIONS(2637), 1, anon_sym_DOLLAR_LBRACE, - ACTIONS(2597), 1, + ACTIONS(2783), 1, anon_sym_SQUOTE, - ACTIONS(2447), 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2487), 2, + ACTIONS(2655), 2, aux_sym_single_string_token1, sym_escape_sequence, - STATE(954), 2, + STATE(1079), 2, sym_string_interpolation, aux_sym_single_string_repeat1, - [57780] = 4, - ACTIONS(2599), 1, - anon_sym_DOT_DOT, - ACTIONS(2601), 1, - anon_sym_DOT_DOT_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2371), 3, + [66327] = 7, + ACTIONS(2695), 1, anon_sym_PIPE, - anon_sym_EQ_GT, + ACTIONS(2699), 1, anon_sym_if, - [57796] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2603), 1, - anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1490), 1, - sym__parameter_list, + ACTIONS(2785), 1, + anon_sym_COMMA, + ACTIONS(2787), 1, + anon_sym_RBRACK, + ACTIONS(2789), 1, + anon_sym_DOT_DOT, + STATE(1211), 1, + aux_sym_list_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [57816] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2605), 1, - anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1527), 1, - sym__parameter_list, + [66350] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [57836] = 5, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2613), 1, - anon_sym_EQ, + ACTIONS(2791), 6, + anon_sym_POUND, + anon_sym_fn, + anon_sym_struct, + anon_sym_type, + anon_sym_trait, + anon_sym_impl, + [66363] = 3, + ACTIONS(816), 1, + anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2607), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [57854] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2615), 1, + ACTIONS(818), 4, anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1471), 1, - sym__parameter_list, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [66377] = 3, + ACTIONS(2498), 1, + anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [57874] = 4, - ACTIONS(2443), 1, + ACTIONS(2496), 4, anon_sym_PIPE, - ACTIONS(2445), 1, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, anon_sym_if, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2617), 3, - anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - [57890] = 6, - ACTIONS(2402), 1, + [66391] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2619), 1, + ACTIONS(2793), 1, anon_sym_PIPE, - STATE(1201), 1, + STATE(1251), 1, sym_identifier, - STATE(1233), 1, + STATE(1300), 1, sym_parameter, - STATE(1507), 1, + STATE(1643), 1, sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [57910] = 6, - ACTIONS(2402), 1, + [66411] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2621), 1, + ACTIONS(2795), 1, anon_sym_PIPE, - STATE(1201), 1, + STATE(1251), 1, sym_identifier, - STATE(1233), 1, + STATE(1300), 1, sym_parameter, - STATE(1491), 1, + STATE(1486), 1, sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [57930] = 3, - ACTIONS(688), 1, - anon_sym_DOT_DOT, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(690), 4, - anon_sym_DOT_DOT_EQ, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [57944] = 6, - ACTIONS(2402), 1, + [66431] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2623), 1, + ACTIONS(2797), 1, anon_sym_RPAREN, - STATE(1191), 1, + STATE(1336), 1, sym_identifier, - STATE(1270), 1, - sym_parameter, - STATE(1472), 1, - sym_function_params, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [57964] = 3, - ACTIONS(692), 1, - anon_sym_DOT_DOT, + STATE(1338), 1, + sym_trait_method_param, + STATE(1534), 1, + sym_trait_method_params, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(694), 4, - anon_sym_DOT_DOT_EQ, + [66451] = 4, + ACTIONS(2695), 1, anon_sym_PIPE, - anon_sym_EQ_GT, + ACTIONS(2699), 1, anon_sym_if, - [57978] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2625), 1, - anon_sym_RPAREN, - STATE(1152), 1, - sym_identifier, - STATE(1153), 1, - sym_trait_method_param, - STATE(1444), 1, - sym_trait_method_params, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [57998] = 3, - ACTIONS(668), 1, + ACTIONS(2799), 3, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_DOT_DOT, + [66467] = 3, + ACTIONS(728), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(670), 4, - anon_sym_DOT_DOT_EQ, + ACTIONS(730), 4, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_EQ_GT, anon_sym_if, - [58012] = 3, - ACTIONS(744), 1, + [66481] = 3, + ACTIONS(796), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(746), 4, - anon_sym_DOT_DOT_EQ, + ACTIONS(798), 4, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_EQ_GT, anon_sym_if, - [58026] = 6, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2627), 1, - anon_sym_COMMA, - ACTIONS(2629), 1, - anon_sym_GT, - STATE(1267), 1, - aux_sym_function_type_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58046] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2631), 3, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_GT, - [58062] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2633), 1, - anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1519), 1, - sym__parameter_list, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58082] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2635), 1, - anon_sym_RPAREN, - STATE(1191), 1, - sym_identifier, - STATE(1270), 1, - sym_parameter, - STATE(1489), 1, - sym_function_params, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58102] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2637), 1, - anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1534), 1, - sym__parameter_list, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58122] = 5, - ACTIONS(2639), 1, - anon_sym_RBRACE, - ACTIONS(2641), 1, - anon_sym_case, - ACTIONS(2643), 1, - anon_sym_default, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(1036), 2, - sym_select_case, - aux_sym_select_expression_repeat1, - [58140] = 3, - ACTIONS(764), 1, + [66495] = 3, + ACTIONS(724), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(766), 4, - anon_sym_DOT_DOT_EQ, + ACTIONS(726), 4, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_EQ_GT, anon_sym_if, - [58154] = 5, - ACTIONS(2641), 1, - anon_sym_case, - ACTIONS(2643), 1, - anon_sym_default, - ACTIONS(2645), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(1036), 2, - sym_select_case, - aux_sym_select_expression_repeat1, - [58172] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2647), 1, - anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1406), 1, - sym__parameter_list, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58192] = 6, - ACTIONS(2402), 1, + [66509] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2649), 1, + ACTIONS(2801), 1, anon_sym_RPAREN, - STATE(1191), 1, + STATE(1249), 1, sym_identifier, - STATE(1270), 1, + STATE(1280), 1, sym_parameter, - STATE(1422), 1, + STATE(1502), 1, sym_function_params, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58212] = 5, - ACTIONS(2641), 1, - anon_sym_case, - ACTIONS(2643), 1, - anon_sym_default, - ACTIONS(2651), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(1036), 2, - sym_select_case, - aux_sym_select_expression_repeat1, - [58230] = 5, - ACTIONS(2653), 1, + [66529] = 4, + ACTIONS(2695), 1, anon_sym_PIPE, - ACTIONS(2656), 1, + ACTIONS(2699), 1, anon_sym_if, - STATE(1030), 1, - aux_sym_or_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2424), 2, - anon_sym_COLON, - anon_sym_EQ, - [58248] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2659), 1, - anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1428), 1, - sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58268] = 5, - ACTIONS(2661), 1, + ACTIONS(2803), 3, + anon_sym_RBRACE, + anon_sym_COMMA, + anon_sym_DOT_DOT, + [66545] = 5, + ACTIONS(2805), 1, anon_sym_PIPE, - ACTIONS(2665), 1, + ACTIONS(2809), 1, anon_sym_if, - STATE(1030), 1, + STATE(1110), 1, aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2391), 2, + ACTIONS(2590), 2, anon_sym_COLON, anon_sym_EQ, - [58286] = 6, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2669), 1, - anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1233), 1, - sym_parameter, - STATE(1438), 1, - sym__parameter_list, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58306] = 5, - ACTIONS(2641), 1, + [66563] = 5, + ACTIONS(2813), 1, + anon_sym_RBRACE, + ACTIONS(2815), 1, anon_sym_case, - ACTIONS(2643), 1, + ACTIONS(2817), 1, anon_sym_default, - ACTIONS(2671), 1, - anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1036), 2, + STATE(1119), 2, sym_select_case, aux_sym_select_expression_repeat1, - [58324] = 4, - ACTIONS(2673), 1, - anon_sym_PIPE, - STATE(1034), 1, - aux_sym_or_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2432), 3, - anon_sym_COLON, - anon_sym_if, - anon_sym_EQ, - [58340] = 3, - ACTIONS(708), 1, + [66581] = 4, + ACTIONS(2819), 1, anon_sym_DOT_DOT, + ACTIONS(2821), 1, + anon_sym_DOT_DOT_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(710), 4, - anon_sym_DOT_DOT_EQ, + ACTIONS(2548), 3, anon_sym_PIPE, anon_sym_EQ_GT, anon_sym_if, - [58354] = 4, - ACTIONS(2443), 1, + [66597] = 4, + ACTIONS(2823), 1, anon_sym_PIPE, - ACTIONS(2445), 1, - anon_sym_if, + STATE(1120), 1, + aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2676), 3, - anon_sym_RBRACE, - anon_sym_COMMA, + ACTIONS(2614), 3, + anon_sym_COLON, + anon_sym_if, + anon_sym_EQ, + [66613] = 3, + ACTIONS(693), 1, anon_sym_DOT_DOT, - [58370] = 6, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2627), 1, - anon_sym_COMMA, - ACTIONS(2678), 1, - anon_sym_RPAREN, - STATE(1140), 1, - aux_sym_function_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58390] = 4, - ACTIONS(2680), 1, + ACTIONS(695), 4, anon_sym_PIPE, - STATE(1034), 1, - aux_sym_or_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2410), 3, - anon_sym_COLON, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, anon_sym_if, - anon_sym_EQ, - [58406] = 3, - ACTIONS(760), 1, + [66627] = 3, + ACTIONS(792), 1, anon_sym_DOT_DOT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(762), 4, - anon_sym_DOT_DOT_EQ, + ACTIONS(794), 4, anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, anon_sym_EQ_GT, anon_sym_if, - [58420] = 5, - ACTIONS(2683), 1, - anon_sym_RBRACE, - ACTIONS(2685), 1, + [66641] = 5, + ACTIONS(2815), 1, anon_sym_case, - ACTIONS(2688), 1, + ACTIONS(2817), 1, anon_sym_default, + ACTIONS(2826), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1036), 2, + STATE(1119), 2, sym_select_case, aux_sym_select_expression_repeat1, - [58438] = 4, - ACTIONS(2693), 1, - anon_sym_COMMA, - STATE(1037), 1, - aux_sym_for_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2691), 3, - anon_sym_RPAREN, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - [58454] = 6, - ACTIONS(2402), 1, + [66659] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2696), 1, + ACTIONS(2828), 1, anon_sym_PIPE, - STATE(1201), 1, + STATE(1251), 1, sym_identifier, - STATE(1233), 1, + STATE(1300), 1, sym_parameter, - STATE(1446), 1, + STATE(1526), 1, sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58474] = 6, - ACTIONS(2402), 1, + [66679] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2698), 1, + ACTIONS(2830), 1, anon_sym_PIPE, - STATE(1201), 1, + STATE(1251), 1, sym_identifier, - STATE(1233), 1, + STATE(1300), 1, sym_parameter, - STATE(1531), 1, + STATE(1555), 1, sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58494] = 3, - ACTIONS(2328), 1, - anon_sym_DOT_DOT, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2326), 4, - anon_sym_DOT_DOT_EQ, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [58508] = 4, - ACTIONS(2702), 1, - anon_sym_COLON, - ACTIONS(2704), 1, - anon_sym_EQ, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2700), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [58523] = 4, - ACTIONS(2706), 1, - anon_sym_RBRACE, - ACTIONS(2708), 1, - anon_sym_fn, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(1052), 2, - sym_trait_method, - aux_sym_trait_definition_repeat1, - [58538] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2710), 1, - anon_sym_RBRACE, - STATE(1150), 1, - sym_identifier, - STATE(1310), 1, - sym_struct_field, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58555] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2712), 1, - anon_sym_RBRACE, - STATE(1150), 1, - sym_identifier, - STATE(1310), 1, - sym_struct_field, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58572] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2714), 1, - anon_sym_RBRACE, - STATE(1221), 1, - sym_identifier, - STATE(1302), 1, - sym_macro_export_item, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58589] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2716), 1, - anon_sym_RPAREN, - STATE(1152), 1, - sym_identifier, - STATE(1283), 1, - sym_trait_method_param, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58606] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2718), 1, - anon_sym_RBRACE, - STATE(1150), 1, - sym_identifier, - STATE(1154), 1, - sym_struct_field, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58623] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2720), 1, - anon_sym_RBRACE, - STATE(1223), 1, - sym_identifier, - STATE(1330), 1, - sym_struct_field_init, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58640] = 5, - ACTIONS(2402), 1, + [66699] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2722), 1, - anon_sym_RBRACE, - STATE(1221), 1, + ACTIONS(2832), 1, + anon_sym_PIPE, + STATE(1251), 1, sym_identifier, - STATE(1302), 1, - sym_macro_export_item, + STATE(1300), 1, + sym_parameter, + STATE(1492), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58657] = 4, - ACTIONS(2724), 1, + [66719] = 4, + ACTIONS(2836), 1, anon_sym_COMMA, - STATE(1050), 1, - aux_sym_function_type_repeat1, + STATE(1117), 1, + aux_sym_for_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2631), 2, + ACTIONS(2834), 3, anon_sym_RPAREN, - anon_sym_GT, - [58672] = 5, - ACTIONS(2402), 1, + anon_sym_RBRACK, + anon_sym_DOT_DOT, + [66735] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2727), 1, - anon_sym_RPAREN, - STATE(1288), 1, - sym_named_argument, - STATE(1461), 1, + ACTIONS(2839), 1, + anon_sym_PIPE, + STATE(1251), 1, sym_identifier, + STATE(1300), 1, + sym_parameter, + STATE(1530), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58689] = 4, - ACTIONS(2729), 1, - anon_sym_RBRACE, - ACTIONS(2731), 1, - anon_sym_fn, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(1052), 2, - sym_trait_method, - aux_sym_trait_definition_repeat1, - [58704] = 4, - ACTIONS(2708), 1, - anon_sym_fn, - ACTIONS(2734), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - STATE(1089), 2, - sym_trait_method, - aux_sym_trait_definition_repeat1, - [58719] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2736), 1, + [66755] = 5, + ACTIONS(2841), 1, anon_sym_RBRACE, - STATE(1223), 1, - sym_identifier, - STATE(1330), 1, - sym_struct_field_init, + ACTIONS(2843), 1, + anon_sym_case, + ACTIONS(2846), 1, + anon_sym_default, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58736] = 4, - ACTIONS(2738), 1, - anon_sym_RBRACE, - ACTIONS(2740), 1, - anon_sym_fn, + STATE(1119), 2, + sym_select_case, + aux_sym_select_expression_repeat1, + [66773] = 4, + ACTIONS(2849), 1, + anon_sym_PIPE, + STATE(1120), 1, + aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1120), 2, - sym_function_definition, - aux_sym_impl_definition_repeat1, - [58751] = 4, - ACTIONS(2641), 1, + ACTIONS(2585), 3, + anon_sym_COLON, + anon_sym_if, + anon_sym_EQ, + [66789] = 5, + ACTIONS(2815), 1, anon_sym_case, - ACTIONS(2643), 1, + ACTIONS(2817), 1, anon_sym_default, + ACTIONS(2852), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1019), 2, + STATE(1119), 2, sym_select_case, aux_sym_select_expression_repeat1, - [58766] = 5, - ACTIONS(2742), 1, - anon_sym_LPAREN, - ACTIONS(2744), 1, - anon_sym_LBRACK, - ACTIONS(2746), 1, - anon_sym_LBRACE, - STATE(605), 1, - sym_macro_group, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58783] = 5, - ACTIONS(2402), 1, + [66807] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2748), 1, - anon_sym_RBRACE, - STATE(1139), 1, - sym_struct_field_init, - STATE(1223), 1, + ACTIONS(2854), 1, + anon_sym_RPAREN, + STATE(1249), 1, sym_identifier, + STATE(1280), 1, + sym_parameter, + STATE(1640), 1, + sym_function_params, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58800] = 5, - ACTIONS(2750), 1, - anon_sym_COMMA, - ACTIONS(2752), 1, - anon_sym_RBRACK, - ACTIONS(2754), 1, - anon_sym_DOT_DOT, - STATE(1037), 1, - aux_sym_for_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58817] = 4, - ACTIONS(2756), 1, - anon_sym_COMMA, - STATE(1060), 1, - aux_sym_list_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2617), 2, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - [58832] = 3, - ACTIONS(2761), 1, - anon_sym_SEMI, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2759), 3, - anon_sym_RBRACE, - anon_sym_case, - anon_sym_default, - [58845] = 3, - ACTIONS(2765), 1, - anon_sym_SEMI, + [66827] = 6, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2856), 1, + anon_sym_PIPE, + STATE(1251), 1, + sym_identifier, + STATE(1300), 1, + sym_parameter, + STATE(1538), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2763), 3, - anon_sym_RBRACE, - anon_sym_case, - anon_sym_default, - [58858] = 5, - ACTIONS(2402), 1, + [66847] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2767), 1, + ACTIONS(2858), 1, anon_sym_PIPE, - STATE(1201), 1, + STATE(1251), 1, sym_identifier, - STATE(1245), 1, + STATE(1300), 1, sym_parameter, + STATE(1488), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58875] = 5, - ACTIONS(2769), 1, - anon_sym_LPAREN, - ACTIONS(2771), 1, - anon_sym_LBRACK, - ACTIONS(2773), 1, - anon_sym_LBRACE, - STATE(619), 1, - sym_macro_group, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58892] = 5, - ACTIONS(2402), 1, + [66867] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2775), 1, - anon_sym_RBRACE, - STATE(1041), 1, + ACTIONS(2860), 1, + anon_sym_PIPE, + STATE(1251), 1, sym_identifier, - STATE(1354), 1, - sym_named_param, + STATE(1300), 1, + sym_parameter, + STATE(1582), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58909] = 5, - ACTIONS(2402), 1, + [66887] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2777), 1, - anon_sym_RBRACE, - STATE(1192), 1, - sym_struct_field_init, - STATE(1223), 1, + ACTIONS(2862), 1, + anon_sym_PIPE, + STATE(1251), 1, sym_identifier, + STATE(1300), 1, + sym_parameter, + STATE(1497), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [58926] = 4, - ACTIONS(2779), 1, + [66907] = 4, + ACTIONS(2866), 1, anon_sym_PIPE, - STATE(1067), 1, - aux_sym_or_pattern_repeat1, + ACTIONS(2868), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2410), 2, - anon_sym_EQ_GT, - anon_sym_if, - [58941] = 5, - ACTIONS(2782), 1, - anon_sym_RBRACE, - ACTIONS(2784), 1, + ACTIONS(2864), 3, + anon_sym_RPAREN, anon_sym_COMMA, - ACTIONS(2786), 1, - anon_sym_DOT_DOT, - STATE(1083), 1, - aux_sym_map_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [58958] = 4, - ACTIONS(2788), 1, + anon_sym_GT, + [66923] = 5, + ACTIONS(2870), 1, anon_sym_PIPE, - ACTIONS(2791), 1, + ACTIONS(2873), 1, anon_sym_if, + STATE(1110), 1, + aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2410), 2, + ACTIONS(2623), 2, anon_sym_COLON, anon_sym_EQ, - [58973] = 4, - ACTIONS(2796), 1, - anon_sym_COMMA, - STATE(1070), 1, - aux_sym_map_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2794), 2, - anon_sym_RBRACE, - anon_sym_DOT_DOT, - [58988] = 5, - ACTIONS(2402), 1, + [66941] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2799), 1, - anon_sym_RBRACE, - STATE(1223), 1, + ACTIONS(2876), 1, + anon_sym_PIPE, + STATE(1251), 1, sym_identifier, - STATE(1330), 1, - sym_struct_field_init, + STATE(1300), 1, + sym_parameter, + STATE(1586), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59005] = 5, - ACTIONS(2242), 1, - anon_sym_RBRACE, - ACTIONS(2244), 1, + [66961] = 3, + ACTIONS(820), 1, anon_sym_DOT_DOT, - ACTIONS(2801), 1, - anon_sym_COMMA, - STATE(1070), 1, - aux_sym_map_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59022] = 5, - ACTIONS(2402), 1, + ACTIONS(822), 4, + anon_sym_PIPE, + anon_sym_DOT_DOT_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [66975] = 6, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2803), 1, - anon_sym_RBRACE, - STATE(1223), 1, + ACTIONS(2878), 1, + anon_sym_RPAREN, + STATE(1249), 1, sym_identifier, - STATE(1330), 1, - sym_struct_field_init, + STATE(1280), 1, + sym_parameter, + STATE(1458), 1, + sym_function_params, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59039] = 5, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [66995] = 6, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2880), 1, anon_sym_PIPE, - ACTIONS(2805), 1, - anon_sym_LBRACE, - STATE(549), 1, - sym_block, + STATE(1251), 1, + sym_identifier, + STATE(1300), 1, + sym_parameter, + STATE(1616), 1, + sym__parameter_list, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59056] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2807), 1, - anon_sym_RBRACE, - STATE(1221), 1, - sym_identifier, - STATE(1302), 1, - sym_macro_export_item, + [67015] = 5, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2884), 1, + anon_sym_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59073] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2809), 1, + ACTIONS(2882), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [67033] = 5, + ACTIONS(2815), 1, + anon_sym_case, + ACTIONS(2817), 1, + anon_sym_default, + ACTIONS(2886), 1, anon_sym_RBRACE, - STATE(1223), 1, - sym_identifier, - STATE(1330), 1, - sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59090] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2811), 1, - anon_sym_RBRACE, - STATE(1161), 1, - sym_identifier, - STATE(1304), 1, - sym_import_item, + STATE(1119), 2, + sym_select_case, + aux_sym_select_expression_repeat1, + [67051] = 6, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2888), 1, + anon_sym_COMMA, + ACTIONS(2890), 1, + anon_sym_GT, + STATE(1303), 1, + aux_sym_function_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59107] = 5, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [67071] = 6, + ACTIONS(2866), 1, anon_sym_PIPE, - ACTIONS(2813), 1, - anon_sym_LBRACE, - STATE(1325), 1, - sym_block, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2888), 1, + anon_sym_COMMA, + ACTIONS(2892), 1, + anon_sym_RPAREN, + STATE(1277), 1, + aux_sym_function_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59124] = 4, - ACTIONS(2641), 1, - anon_sym_case, - ACTIONS(2643), 1, - anon_sym_default, + [67091] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2894), 1, + anon_sym_RBRACE, + STATE(1341), 1, + sym_identifier, + STATE(1437), 1, + sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1021), 2, - sym_select_case, - aux_sym_select_expression_repeat1, - [59139] = 5, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [67108] = 5, + ACTIONS(2866), 1, anon_sym_PIPE, - ACTIONS(2813), 1, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2896), 1, anon_sym_LBRACE, - STATE(1333), 1, + STATE(604), 1, sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59156] = 5, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2815), 1, - anon_sym_LBRACE, - STATE(496), 1, - sym_block, + [67125] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2898), 1, + anon_sym_RPAREN, + STATE(1393), 1, + sym_named_argument, + STATE(1621), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59173] = 2, - ACTIONS(2447), 2, + [67142] = 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - ACTIONS(2817), 4, - anon_sym_DQUOTE, - aux_sym_double_string_token1, + ACTIONS(2900), 4, + anon_sym_SQUOTE, + aux_sym_single_string_token1, anon_sym_DOLLAR_LBRACE, sym_escape_sequence, - [59184] = 5, - ACTIONS(2238), 1, - anon_sym_RBRACE, - ACTIONS(2240), 1, - anon_sym_DOT_DOT, - ACTIONS(2819), 1, + [67153] = 4, + ACTIONS(2902), 1, anon_sym_COMMA, - STATE(1070), 1, - aux_sym_map_pattern_repeat1, + STATE(1141), 1, + aux_sym_function_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59201] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + ACTIONS(2864), 2, + anon_sym_RPAREN, + anon_sym_GT, + [67168] = 4, + ACTIONS(2866), 1, anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2821), 2, + ACTIONS(2905), 2, anon_sym_RPAREN, anon_sym_COMMA, - [59216] = 4, - ACTIONS(2825), 1, + [67183] = 4, + ACTIONS(2909), 1, anon_sym_COMMA, - STATE(1085), 1, - aux_sym__argument_list_repeat1, + STATE(1143), 1, + aux_sym_map_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2823), 2, - anon_sym_RPAREN, - anon_sym_RBRACK, - [59231] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2828), 1, + ACTIONS(2907), 2, anon_sym_RBRACE, - STATE(1150), 1, - sym_identifier, - STATE(1310), 1, - sym_struct_field, + anon_sym_DOT_DOT, + [67198] = 5, + ACTIONS(2912), 1, + anon_sym_COMMA, + ACTIONS(2914), 1, + anon_sym_RBRACK, + ACTIONS(2916), 1, + anon_sym_DOT_DOT, + STATE(1117), 1, + aux_sym_for_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59248] = 4, - ACTIONS(2740), 1, - anon_sym_fn, - ACTIONS(2830), 1, - anon_sym_RBRACE, + [67215] = 5, + ACTIONS(2918), 1, + anon_sym_LPAREN, + ACTIONS(2920), 1, + anon_sym_LBRACK, + ACTIONS(2922), 1, + anon_sym_LBRACE, + STATE(550), 1, + sym_macro_group, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1092), 2, - sym_function_definition, - aux_sym_impl_definition_repeat1, - [59263] = 5, - ACTIONS(2402), 1, + [67232] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2832), 1, - anon_sym_RPAREN, - STATE(1152), 1, + ACTIONS(2924), 1, + anon_sym_RBRACE, + STATE(1231), 1, + sym_struct_field_init, + STATE(1243), 1, sym_identifier, - STATE(1283), 1, - sym_trait_method_param, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59280] = 4, - ACTIONS(2708), 1, - anon_sym_fn, - ACTIONS(2834), 1, + [67249] = 4, + ACTIONS(2926), 1, anon_sym_RBRACE, + ACTIONS(2928), 1, + anon_sym_fn, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1052), 2, + STATE(1174), 2, sym_trait_method, aux_sym_trait_definition_repeat1, - [59295] = 5, - ACTIONS(2836), 1, - anon_sym_COLON, - ACTIONS(2838), 1, + [67264] = 5, + ACTIONS(2930), 1, + anon_sym_LPAREN, + ACTIONS(2932), 1, + anon_sym_LBRACK, + ACTIONS(2934), 1, + anon_sym_LBRACE, + STATE(620), 1, + sym_macro_group, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [67281] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2834), 4, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_RBRACK, + anon_sym_DOT_DOT, + [67292] = 5, + ACTIONS(2623), 1, + anon_sym_EQ_GT, + ACTIONS(2936), 1, anon_sym_PIPE, - ACTIONS(2840), 1, + ACTIONS(2939), 1, anon_sym_if, - ACTIONS(2842), 1, - anon_sym_EQ, + STATE(1164), 1, + aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59312] = 4, - ACTIONS(2641), 1, - anon_sym_case, - ACTIONS(2643), 1, - anon_sym_default, + [67309] = 5, + ACTIONS(2942), 1, + anon_sym_RBRACE, + ACTIONS(2944), 1, + anon_sym_COMMA, + ACTIONS(2946), 1, + anon_sym_DOT_DOT, + STATE(1229), 1, + aux_sym_map_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1024), 2, - sym_select_case, - aux_sym_select_expression_repeat1, - [59327] = 4, - ACTIONS(2844), 1, + [67326] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2948), 1, anon_sym_RBRACE, - ACTIONS(2846), 1, - anon_sym_fn, + STATE(1243), 1, + sym_identifier, + STATE(1441), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1092), 2, - sym_function_definition, - aux_sym_impl_definition_repeat1, - [59342] = 5, - ACTIONS(2402), 1, + [67343] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2849), 1, - anon_sym_RPAREN, - STATE(1288), 1, - sym_named_argument, - STATE(1461), 1, + ACTIONS(2950), 1, + anon_sym_RBRACE, + STATE(1243), 1, sym_identifier, + STATE(1441), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59359] = 2, - ACTIONS(2447), 2, + [67360] = 5, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2952), 1, + anon_sym_LBRACE, + STATE(1415), 1, + sym_block, + ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2817), 4, - anon_sym_SQUOTE, - aux_sym_single_string_token1, - anon_sym_DOLLAR_LBRACE, - sym_escape_sequence, - [59370] = 4, - ACTIONS(2641), 1, - anon_sym_case, - ACTIONS(2643), 1, - anon_sym_default, + [67377] = 5, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2952), 1, + anon_sym_LBRACE, + STATE(1416), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1029), 2, - sym_select_case, - aux_sym_select_expression_repeat1, - [59385] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [67394] = 4, + ACTIONS(2954), 1, anon_sym_PIPE, + ACTIONS(2957), 1, + anon_sym_if, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2851), 2, - anon_sym_RPAREN, + ACTIONS(2585), 2, + anon_sym_COLON, + anon_sym_EQ, + [67409] = 4, + ACTIONS(2962), 1, + anon_sym_COLON, + ACTIONS(2964), 1, + anon_sym_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2960), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [67424] = 5, + ACTIONS(2912), 1, anon_sym_COMMA, - [59400] = 5, - ACTIONS(2853), 1, + ACTIONS(2966), 1, + anon_sym_RBRACK, + ACTIONS(2968), 1, + anon_sym_DOT_DOT, + STATE(1144), 1, + aux_sym_for_pattern_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [67441] = 5, + ACTIONS(2970), 1, anon_sym_LPAREN, - ACTIONS(2855), 1, + ACTIONS(2972), 1, anon_sym_LBRACK, - ACTIONS(2857), 1, + ACTIONS(2974), 1, anon_sym_LBRACE, - STATE(770), 1, + STATE(842), 1, sym_macro_group, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59417] = 5, - ACTIONS(2402), 1, + [67458] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2859), 1, + ACTIONS(2976), 1, anon_sym_RBRACE, - STATE(1170), 1, - sym_struct_field_init, - STATE(1223), 1, + STATE(1243), 1, sym_identifier, + STATE(1329), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59434] = 5, - ACTIONS(2402), 1, + [67475] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2861), 1, + ACTIONS(2978), 1, anon_sym_RBRACE, - STATE(1161), 1, + STATE(1341), 1, sym_identifier, - STATE(1304), 1, - sym_import_item, + STATE(1356), 1, + sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59451] = 5, - ACTIONS(2424), 1, - anon_sym_EQ_GT, - ACTIONS(2863), 1, - anon_sym_PIPE, + [67492] = 5, ACTIONS(2866), 1, - anon_sym_if, - STATE(1123), 1, - aux_sym_or_pattern_repeat1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2980), 1, + anon_sym_LBRACE, + STATE(627), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59468] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2869), 1, - anon_sym_RPAREN, - STATE(1288), 1, - sym_named_argument, - STATE(1461), 1, - sym_identifier, + [67509] = 4, + ACTIONS(2928), 1, + anon_sym_fn, + ACTIONS(2982), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59485] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2871), 1, + STATE(1147), 2, + sym_trait_method, + aux_sym_trait_definition_repeat1, + [67524] = 4, + ACTIONS(2984), 1, anon_sym_PIPE, - STATE(1201), 1, - sym_identifier, - STATE(1245), 1, - sym_parameter, + STATE(1170), 1, + aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59502] = 5, - ACTIONS(2838), 1, - anon_sym_PIPE, - ACTIONS(2840), 1, + ACTIONS(2614), 2, + anon_sym_EQ_GT, anon_sym_if, - ACTIONS(2873), 1, - anon_sym_COLON, - ACTIONS(2875), 1, - anon_sym_EQ, + [67539] = 4, + ACTIONS(2987), 1, + anon_sym_RBRACE, + ACTIONS(2989), 1, + anon_sym_fn, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59519] = 5, - ACTIONS(2877), 1, - anon_sym_LPAREN, - ACTIONS(2879), 1, - anon_sym_LBRACK, - ACTIONS(2881), 1, - anon_sym_LBRACE, - STATE(70), 1, - sym_macro_group, + STATE(1165), 2, + sym_function_definition, + aux_sym_impl_definition_repeat1, + [67554] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2992), 1, + anon_sym_RBRACE, + STATE(1243), 1, + sym_identifier, + STATE(1441), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59536] = 5, - ACTIONS(2883), 1, + [67571] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2994), 1, anon_sym_RBRACE, - ACTIONS(2885), 1, - anon_sym_COMMA, - ACTIONS(2887), 1, - anon_sym_DOT_DOT, - STATE(1072), 1, - aux_sym_map_pattern_repeat1, + STATE(1243), 1, + sym_identifier, + STATE(1441), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59553] = 5, - ACTIONS(1744), 1, - anon_sym_RBRACK, - ACTIONS(1748), 1, - anon_sym_DOT_DOT, - ACTIONS(2889), 1, - anon_sym_COMMA, - STATE(1060), 1, - aux_sym_list_pattern_repeat1, + [67588] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(2996), 1, + anon_sym_RPAREN, + STATE(1393), 1, + sym_named_argument, + STATE(1621), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59570] = 5, - ACTIONS(2391), 1, + [67605] = 5, + ACTIONS(2590), 1, anon_sym_EQ_GT, - ACTIONS(2891), 1, + ACTIONS(2998), 1, anon_sym_PIPE, - ACTIONS(2895), 1, + ACTIONS(3002), 1, anon_sym_if, - STATE(1123), 1, + STATE(1164), 1, aux_sym_or_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59587] = 5, - ACTIONS(2402), 1, + [67622] = 4, + ACTIONS(3006), 1, + anon_sym_PIPE, + STATE(1170), 1, + aux_sym_or_pattern_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2585), 2, + anon_sym_EQ_GT, + anon_sym_if, + [67637] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2899), 1, + ACTIONS(3009), 1, anon_sym_RBRACE, - STATE(1150), 1, + STATE(1341), 1, sym_identifier, - STATE(1216), 1, + STATE(1437), 1, sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59604] = 4, - ACTIONS(2708), 1, - anon_sym_fn, - ACTIONS(2901), 1, + [67654] = 3, + ACTIONS(3013), 1, + anon_sym_SEMI, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3011), 3, + anon_sym_RBRACE, + anon_sym_case, + anon_sym_default, + [67667] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3015), 1, + anon_sym_RBRACE, + STATE(1157), 1, + sym_identifier, + STATE(1450), 1, + sym_named_param, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [67684] = 4, + ACTIONS(3017), 1, anon_sym_RBRACE, + ACTIONS(3019), 1, + anon_sym_fn, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1042), 2, + STATE(1174), 2, sym_trait_method, aux_sym_trait_definition_repeat1, - [59619] = 3, - ACTIONS(2905), 1, - anon_sym_SEMI, + [67699] = 4, + ACTIONS(3022), 1, + anon_sym_RBRACE, + ACTIONS(3024), 1, + anon_sym_fn, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2903), 3, - anon_sym_RBRACE, + STATE(1226), 2, + sym_function_definition, + aux_sym_impl_definition_repeat1, + [67714] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3026), 1, + anon_sym_RPAREN, + STATE(1336), 1, + sym_identifier, + STATE(1382), 1, + sym_trait_method_param, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [67731] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3028), 1, + anon_sym_RPAREN, + STATE(1393), 1, + sym_named_argument, + STATE(1621), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [67748] = 4, + ACTIONS(2815), 1, anon_sym_case, + ACTIONS(2817), 1, anon_sym_default, - [59632] = 5, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2815), 1, - anon_sym_LBRACE, - STATE(454), 1, - sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59649] = 5, - ACTIONS(2402), 1, + STATE(1113), 2, + sym_select_case, + aux_sym_select_expression_repeat1, + [67763] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2907), 1, + ACTIONS(3030), 1, anon_sym_RBRACE, - STATE(1223), 1, + STATE(1243), 1, sym_identifier, - STATE(1225), 1, + STATE(1441), 1, sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59666] = 5, - ACTIONS(2750), 1, - anon_sym_COMMA, - ACTIONS(2909), 1, - anon_sym_RBRACK, - ACTIONS(2911), 1, - anon_sym_DOT_DOT, - STATE(1059), 1, - aux_sym_for_pattern_repeat1, + [67780] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3032), 1, + anon_sym_RBRACE, + STATE(1157), 1, + sym_identifier, + STATE(1450), 1, + sym_named_param, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59683] = 2, + [67797] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2691), 4, + ACTIONS(3034), 2, anon_sym_RPAREN, anon_sym_COMMA, - anon_sym_RBRACK, - anon_sym_DOT_DOT, - [59694] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, + [67812] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3036), 1, + anon_sym_RPAREN, + STATE(1336), 1, + sym_identifier, + STATE(1382), 1, + sym_trait_method_param, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2913), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [59709] = 5, - ACTIONS(2402), 1, + [67829] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2915), 1, - anon_sym_RBRACE, - STATE(1223), 1, + ACTIONS(3038), 1, + anon_sym_RPAREN, + STATE(1393), 1, + sym_named_argument, + STATE(1621), 1, sym_identifier, - STATE(1330), 1, - sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59726] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2917), 1, + [67846] = 5, + ACTIONS(3040), 1, + anon_sym_LPAREN, + ACTIONS(3042), 1, + anon_sym_LBRACK, + ACTIONS(3044), 1, + anon_sym_LBRACE, + STATE(74), 1, + sym_macro_group, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [67863] = 4, + ACTIONS(3024), 1, + anon_sym_fn, + ACTIONS(3046), 1, anon_sym_RBRACE, - STATE(1150), 1, - sym_identifier, - STATE(1310), 1, - sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59743] = 5, - ACTIONS(2402), 1, + STATE(1165), 2, + sym_function_definition, + aux_sym_impl_definition_repeat1, + [67878] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2919), 1, + ACTIONS(3048), 1, anon_sym_RBRACE, - STATE(1223), 1, + STATE(1243), 1, sym_identifier, - STATE(1330), 1, + STATE(1441), 1, sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59760] = 5, - ACTIONS(2402), 1, + [67895] = 3, + ACTIONS(3052), 1, + anon_sym_SEMI, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3050), 3, + anon_sym_RBRACE, + anon_sym_case, + anon_sym_default, + [67908] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2921), 1, + ACTIONS(3054), 1, anon_sym_RBRACE, - STATE(1041), 1, + STATE(1241), 1, sym_identifier, - STATE(1354), 1, - sym_named_param, + STATE(1391), 1, + sym_import_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59777] = 4, - ACTIONS(2740), 1, - anon_sym_fn, - ACTIONS(2923), 1, - anon_sym_RBRACE, - ACTIONS(3), 2, + [67925] = 2, + ACTIONS(2631), 2, sym_line_comment, sym_block_comment, - STATE(1092), 2, - sym_function_definition, - aux_sym_impl_definition_repeat1, - [59792] = 5, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2805), 1, - anon_sym_LBRACE, - STATE(533), 1, - sym_block, + ACTIONS(2900), 4, + anon_sym_DQUOTE, + aux_sym_double_string_token1, + anon_sym_DOLLAR_LBRACE, + sym_escape_sequence, + [67936] = 3, + ACTIONS(3058), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59809] = 5, - ACTIONS(2925), 1, + ACTIONS(3056), 3, + anon_sym_RBRACE, + anon_sym_case, + anon_sym_default, + [67949] = 5, + ACTIONS(3060), 1, anon_sym_LPAREN, - ACTIONS(2927), 1, + ACTIONS(3062), 1, anon_sym_LBRACK, - ACTIONS(2929), 1, + ACTIONS(3064), 1, anon_sym_LBRACE, - STATE(534), 1, + STATE(691), 1, sym_macro_group, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59826] = 4, - ACTIONS(2931), 1, - anon_sym_PIPE, - STATE(1067), 1, - aux_sym_or_pattern_repeat1, + [67966] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3066), 1, + anon_sym_RBRACE, + STATE(1243), 1, + sym_identifier, + STATE(1322), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2432), 2, - anon_sym_EQ_GT, + [67983] = 5, + ACTIONS(3068), 1, + anon_sym_COLON, + ACTIONS(3070), 1, + anon_sym_PIPE, + ACTIONS(3072), 1, anon_sym_if, - [59841] = 5, - ACTIONS(2402), 1, + ACTIONS(3074), 1, + anon_sym_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [68000] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2934), 1, - anon_sym_RPAREN, - STATE(1288), 1, - sym_named_argument, - STATE(1461), 1, + ACTIONS(3076), 1, + anon_sym_RBRACE, + STATE(1243), 1, sym_identifier, + STATE(1245), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59858] = 5, - ACTIONS(2402), 1, + [68017] = 4, + ACTIONS(3080), 1, + anon_sym_COMMA, + STATE(1195), 1, + aux_sym__argument_list_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3078), 2, + anon_sym_RPAREN, + anon_sym_RBRACK, + [68032] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2936), 1, + ACTIONS(3083), 1, anon_sym_RBRACE, - STATE(1221), 1, + STATE(1258), 1, sym_identifier, - STATE(1302), 1, + STATE(1403), 1, sym_macro_export_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59875] = 5, - ACTIONS(2938), 1, + [68049] = 5, + ACTIONS(3085), 1, anon_sym_LPAREN, - ACTIONS(2940), 1, + ACTIONS(3087), 1, anon_sym_LBRACK, - ACTIONS(2942), 1, + ACTIONS(3089), 1, anon_sym_LBRACE, - STATE(478), 1, + STATE(569), 1, sym_macro_group, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59892] = 4, - ACTIONS(2740), 1, - anon_sym_fn, - ACTIONS(2944), 1, - anon_sym_RBRACE, + [68066] = 4, + ACTIONS(2815), 1, + anon_sym_case, + ACTIONS(2817), 1, + anon_sym_default, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - STATE(1087), 2, - sym_function_definition, - aux_sym_impl_definition_repeat1, - [59907] = 5, - ACTIONS(2402), 1, + STATE(1108), 2, + sym_select_case, + aux_sym_select_expression_repeat1, + [68081] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2946), 1, - anon_sym_RBRACE, - STATE(1223), 1, + ACTIONS(3091), 1, + anon_sym_PIPE, + STATE(1251), 1, sym_identifier, - STATE(1330), 1, - sym_struct_field_init, + STATE(1314), 1, + sym_parameter, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59924] = 5, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - ACTIONS(2948), 1, + [68098] = 5, + ACTIONS(3093), 1, anon_sym_RBRACE, - STATE(1161), 1, - sym_identifier, - STATE(1304), 1, - sym_import_item, + ACTIONS(3095), 1, + anon_sym_COMMA, + ACTIONS(3097), 1, + anon_sym_DOT_DOT, + STATE(1213), 1, + aux_sym_map_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59941] = 5, - ACTIONS(2402), 1, + [68115] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - ACTIONS(2950), 1, + ACTIONS(3099), 1, anon_sym_RBRACE, - STATE(1161), 1, + STATE(1341), 1, sym_identifier, - STATE(1304), 1, - sym_import_item, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [59958] = 5, - ACTIONS(1756), 1, - anon_sym_RBRACK, - ACTIONS(1758), 1, - anon_sym_DOT_DOT, - ACTIONS(2952), 1, - anon_sym_COMMA, - STATE(1060), 1, - aux_sym_list_pattern_repeat1, + STATE(1348), 1, + sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59975] = 5, - ACTIONS(2925), 1, - anon_sym_LPAREN, - ACTIONS(2927), 1, - anon_sym_LBRACK, - ACTIONS(2929), 1, - anon_sym_LBRACE, - STATE(504), 1, - sym_macro_group, + [68132] = 4, + ACTIONS(2928), 1, + anon_sym_fn, + ACTIONS(3101), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [59992] = 5, - ACTIONS(2938), 1, - anon_sym_LPAREN, - ACTIONS(2940), 1, - anon_sym_LBRACK, - ACTIONS(2942), 1, + STATE(1215), 2, + sym_trait_method, + aux_sym_trait_definition_repeat1, + [68147] = 5, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2980), 1, anon_sym_LBRACE, - STATE(465), 1, - sym_macro_group, + STATE(662), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60009] = 4, - ACTIONS(2954), 1, - aux_sym_identifier_token1, - ACTIONS(2956), 1, - sym_integer_literal, - STATE(73), 1, - sym_identifier, + [68164] = 4, + ACTIONS(2815), 1, + anon_sym_case, + ACTIONS(2817), 1, + anon_sym_default, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60023] = 4, - ACTIONS(962), 1, - anon_sym_RBRACE, - ACTIONS(2958), 1, - anon_sym_COMMA, - STATE(1238), 1, - aux_sym_map_expression_repeat1, + STATE(1121), 2, + sym_select_case, + aux_sym_select_expression_repeat1, + [68179] = 4, + ACTIONS(2815), 1, + anon_sym_case, + ACTIONS(2817), 1, + anon_sym_default, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60037] = 4, - ACTIONS(958), 1, - anon_sym_RBRACE, - ACTIONS(2960), 1, - anon_sym_COMMA, - STATE(1238), 1, - aux_sym_map_expression_repeat1, + STATE(1134), 2, + sym_select_case, + aux_sym_select_expression_repeat1, + [68194] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3103), 1, + anon_sym_PIPE, + STATE(1251), 1, + sym_identifier, + STATE(1314), 1, + sym_parameter, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60051] = 4, - ACTIONS(2962), 1, + [68211] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3105), 1, anon_sym_RBRACE, - ACTIONS(2964), 1, - anon_sym_COMMA, - STATE(1135), 1, - aux_sym_map_expression_repeat1, + STATE(1258), 1, + sym_identifier, + STATE(1403), 1, + sym_macro_export_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60065] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [68228] = 5, + ACTIONS(3070), 1, anon_sym_PIPE, - ACTIONS(2966), 1, + ACTIONS(3072), 1, + anon_sym_if, + ACTIONS(3107), 1, + anon_sym_COLON, + ACTIONS(3109), 1, anon_sym_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60079] = 4, - ACTIONS(2968), 1, + [68245] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3111), 1, anon_sym_RBRACE, - ACTIONS(2970), 1, - anon_sym_COMMA, - STATE(1146), 1, - aux_sym_struct_literal_repeat1, + STATE(1241), 1, + sym_identifier, + STATE(1391), 1, + sym_import_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60093] = 4, - ACTIONS(2627), 1, - anon_sym_COMMA, - ACTIONS(2972), 1, - anon_sym_RPAREN, - STATE(1050), 1, - aux_sym_function_type_repeat1, + [68262] = 5, + ACTIONS(3085), 1, + anon_sym_LPAREN, + ACTIONS(3087), 1, + anon_sym_LBRACK, + ACTIONS(3089), 1, + anon_sym_LBRACE, + STATE(579), 1, + sym_macro_group, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60107] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2974), 1, - anon_sym_GT, + [68279] = 5, + ACTIONS(2275), 1, + anon_sym_RBRACK, + ACTIONS(2279), 1, + anon_sym_DOT_DOT, + ACTIONS(3113), 1, + anon_sym_COMMA, + STATE(1214), 1, + aux_sym_list_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60121] = 2, + [68296] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2794), 3, + ACTIONS(3115), 2, anon_sym_RBRACE, anon_sym_COMMA, - anon_sym_DOT_DOT, - [60131] = 4, - ACTIONS(2976), 1, + [68311] = 5, + ACTIONS(2440), 1, anon_sym_RBRACE, - ACTIONS(2978), 1, + ACTIONS(2442), 1, + anon_sym_DOT_DOT, + ACTIONS(3117), 1, anon_sym_COMMA, - STATE(1180), 1, - aux_sym_named_params_block_repeat1, + STATE(1143), 1, + aux_sym_map_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60145] = 4, - ACTIONS(2813), 1, - anon_sym_LBRACE, - ACTIONS(2980), 1, - anon_sym_DASH_GT, - STATE(1337), 1, - sym_block, + [68328] = 4, + ACTIONS(3119), 1, + anon_sym_COMMA, + STATE(1214), 1, + aux_sym_list_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60159] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(2982), 1, - anon_sym_COMMA, + ACTIONS(2799), 2, + anon_sym_RBRACK, + anon_sym_DOT_DOT, + [68343] = 4, + ACTIONS(2928), 1, + anon_sym_fn, + ACTIONS(3122), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60173] = 4, - ACTIONS(2803), 1, + STATE(1174), 2, + sym_trait_method, + aux_sym_trait_definition_repeat1, + [68358] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3124), 1, anon_sym_RBRACE, - ACTIONS(2984), 1, - anon_sym_COMMA, - STATE(1253), 1, - aux_sym_struct_literal_repeat1, + STATE(1243), 1, + sym_identifier, + STATE(1441), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60187] = 4, - ACTIONS(2813), 1, - anon_sym_LBRACE, - ACTIONS(2986), 1, - anon_sym_DASH_GT, - STATE(1311), 1, - sym_block, + [68375] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3126), 1, + anon_sym_RBRACE, + STATE(1258), 1, + sym_identifier, + STATE(1403), 1, + sym_macro_export_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60201] = 2, + [68392] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3128), 1, + anon_sym_RBRACE, + STATE(1341), 1, + sym_identifier, + STATE(1437), 1, + sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2379), 3, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [60211] = 4, - ACTIONS(2402), 1, + [68409] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1161), 1, + ACTIONS(3130), 1, + anon_sym_RBRACE, + STATE(1258), 1, sym_identifier, - STATE(1168), 1, - sym_import_item, + STATE(1403), 1, + sym_macro_export_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60225] = 3, - ACTIONS(2990), 1, - anon_sym_COLON, + [68426] = 5, + ACTIONS(2930), 1, + anon_sym_LPAREN, + ACTIONS(2932), 1, + anon_sym_LBRACK, + ACTIONS(2934), 1, + anon_sym_LBRACE, + STATE(625), 1, + sym_macro_group, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2988), 2, + [68443] = 4, + ACTIONS(3024), 1, + anon_sym_fn, + ACTIONS(3132), 1, anon_sym_RBRACE, - anon_sym_COMMA, - [60237] = 4, - ACTIONS(2402), 1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + STATE(1185), 2, + sym_function_definition, + aux_sym_impl_definition_repeat1, + [68458] = 5, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1150), 1, + ACTIONS(3134), 1, + anon_sym_RBRACE, + STATE(1243), 1, sym_identifier, - STATE(1310), 1, - sym_struct_field, + STATE(1441), 1, + sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60251] = 3, - ACTIONS(2994), 1, - anon_sym_COLON, + [68475] = 5, + ACTIONS(2320), 1, + anon_sym_RBRACK, + ACTIONS(2322), 1, + anon_sym_DOT_DOT, + ACTIONS(3136), 1, + anon_sym_COMMA, + STATE(1214), 1, + aux_sym_list_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2992), 2, - anon_sym_RPAREN, - anon_sym_COMMA, - [60263] = 4, - ACTIONS(2996), 1, - anon_sym_RPAREN, - ACTIONS(2998), 1, - anon_sym_COMMA, - STATE(1183), 1, - aux_sym_trait_method_params_repeat1, + [68492] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3138), 1, + anon_sym_RBRACE, + STATE(1241), 1, + sym_identifier, + STATE(1391), 1, + sym_import_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60277] = 4, - ACTIONS(3000), 1, + [68509] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3140), 1, anon_sym_RBRACE, - ACTIONS(3002), 1, - anon_sym_COMMA, - STATE(1228), 1, - aux_sym_struct_definition_repeat1, + STATE(1241), 1, + sym_identifier, + STATE(1391), 1, + sym_import_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60291] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(3004), 1, - anon_sym_SEMI, + [68526] = 4, + ACTIONS(3024), 1, + anon_sym_fn, + ACTIONS(3142), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60305] = 4, - ACTIONS(3006), 1, - anon_sym_COMMA, - ACTIONS(3008), 1, - anon_sym_RBRACK, - STATE(1164), 1, - aux_sym__argument_list_repeat1, + STATE(1165), 2, + sym_function_definition, + aux_sym_impl_definition_repeat1, + [68541] = 5, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(2896), 1, + anon_sym_LBRACE, + STATE(597), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60319] = 4, - ACTIONS(3010), 1, + [68558] = 5, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + ACTIONS(3144), 1, anon_sym_RBRACE, - ACTIONS(3012), 1, - anon_sym_COMMA, - STATE(1166), 1, - aux_sym_map_expression_repeat1, + STATE(1341), 1, + sym_identifier, + STATE(1437), 1, + sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60333] = 4, - ACTIONS(3014), 1, + [68575] = 5, + ACTIONS(2444), 1, + anon_sym_RBRACE, + ACTIONS(2446), 1, + anon_sym_DOT_DOT, + ACTIONS(3146), 1, anon_sym_COMMA, - ACTIONS(3016), 1, - anon_sym_RBRACK, - STATE(1181), 1, - aux_sym__argument_list_repeat1, + STATE(1143), 1, + aux_sym_map_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60347] = 4, - ACTIONS(2849), 1, + [68592] = 4, + ACTIONS(985), 1, anon_sym_RPAREN, - ACTIONS(3018), 1, + ACTIONS(3148), 1, anon_sym_COMMA, - STATE(1165), 1, + STATE(1317), 1, aux_sym__argument_list_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60361] = 4, - ACTIONS(3020), 1, + [68606] = 4, + ACTIONS(3150), 1, anon_sym_RBRACE, - ACTIONS(3022), 1, + ACTIONS(3152), 1, anon_sym_COMMA, - STATE(1182), 1, - aux_sym_map_expression_repeat1, + STATE(1247), 1, + aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60375] = 3, - ACTIONS(3026), 1, - anon_sym_as, + [68620] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3024), 2, + ACTIONS(2542), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [68630] = 4, + ACTIONS(3009), 1, anon_sym_RBRACE, + ACTIONS(3154), 1, anon_sym_COMMA, - [60387] = 4, - ACTIONS(3028), 1, - aux_sym_identifier_token1, - ACTIONS(3030), 1, - sym_integer_literal, - STATE(773), 1, - sym_identifier, + STATE(1355), 1, + aux_sym_struct_definition_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60401] = 4, - ACTIONS(3028), 1, - aux_sym_identifier_token1, - ACTIONS(3032), 1, - sym_integer_literal, - STATE(774), 1, - sym_identifier, + [68644] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60415] = 4, - ACTIONS(1016), 1, - anon_sym_RBRACK, - ACTIONS(3034), 1, - anon_sym_COMMA, - STATE(1085), 1, - aux_sym__argument_list_repeat1, + ACTIONS(2554), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [68654] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60429] = 4, - ACTIONS(3036), 1, - anon_sym_RPAREN, - ACTIONS(3038), 1, - anon_sym_COMMA, - STATE(1165), 1, - aux_sym__argument_list_repeat2, + ACTIONS(2558), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [68664] = 4, + ACTIONS(2585), 1, + anon_sym_EQ_GT, + ACTIONS(3156), 1, + anon_sym_PIPE, + ACTIONS(3159), 1, + anon_sym_if, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60443] = 4, - ACTIONS(978), 1, - anon_sym_RBRACE, - ACTIONS(3041), 1, - anon_sym_COMMA, - STATE(1238), 1, - aux_sym_map_expression_repeat1, + [68678] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60457] = 4, - ACTIONS(2849), 1, - anon_sym_RPAREN, - ACTIONS(3018), 1, - anon_sym_COMMA, - STATE(1185), 1, - aux_sym__argument_list_repeat2, + ACTIONS(2546), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [68688] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2550), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [68698] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3162), 1, + anon_sym_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60471] = 4, - ACTIONS(3043), 1, + [68712] = 4, + ACTIONS(3015), 1, anon_sym_RBRACE, - ACTIONS(3045), 1, + ACTIONS(3164), 1, anon_sym_COMMA, - STATE(1258), 1, - aux_sym_import_statement_repeat1, + STATE(1259), 1, + aux_sym_named_params_block_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60485] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(3047), 1, - anon_sym_GT, + [68726] = 3, + ACTIONS(3168), 1, + anon_sym_as, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60499] = 4, - ACTIONS(3049), 1, + ACTIONS(3166), 2, anon_sym_RBRACE, - ACTIONS(3051), 1, anon_sym_COMMA, - STATE(1173), 1, - aux_sym_struct_literal_repeat1, + [68738] = 4, + ACTIONS(2952), 1, + anon_sym_LBRACE, + ACTIONS(3170), 1, + anon_sym_DASH_GT, + STATE(1409), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60513] = 2, + [68752] = 3, + ACTIONS(3174), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2759), 3, + ACTIONS(3172), 2, anon_sym_RBRACE, - anon_sym_case, - anon_sym_default, - [60523] = 4, - ACTIONS(3053), 1, + anon_sym_COMMA, + [68764] = 4, + ACTIONS(3176), 1, anon_sym_RBRACE, - ACTIONS(3055), 1, + ACTIONS(3178), 1, anon_sym_COMMA, - STATE(1172), 1, + STATE(1337), 1, aux_sym_import_statement_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60537] = 4, - ACTIONS(2915), 1, + [68778] = 4, + ACTIONS(3180), 1, anon_sym_RBRACE, - ACTIONS(3058), 1, + ACTIONS(3182), 1, anon_sym_COMMA, - STATE(1253), 1, + STATE(1276), 1, aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60551] = 4, - ACTIONS(2767), 1, - anon_sym_PIPE, - ACTIONS(3060), 1, + [68792] = 4, + ACTIONS(3026), 1, + anon_sym_RPAREN, + ACTIONS(3184), 1, anon_sym_COMMA, - STATE(1246), 1, - aux_sym__parameter_list_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [60565] = 4, - ACTIONS(3062), 1, - aux_sym_identifier_token1, - ACTIONS(3064), 1, - sym_integer_literal, - STATE(623), 1, - sym_identifier, + STATE(1266), 1, + aux_sym_trait_method_params_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60579] = 4, - ACTIONS(3062), 1, - aux_sym_identifier_token1, - ACTIONS(3066), 1, - sym_integer_literal, - STATE(624), 1, - sym_identifier, + [68806] = 4, + ACTIONS(2948), 1, + anon_sym_RBRACE, + ACTIONS(3186), 1, + anon_sym_COMMA, + STATE(1257), 1, + aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60593] = 4, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1161), 1, - sym_identifier, - STATE(1304), 1, - sym_import_item, + [68820] = 4, + ACTIONS(3188), 1, + anon_sym_RPAREN, + ACTIONS(3190), 1, + anon_sym_COMMA, + STATE(1298), 1, + aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60607] = 4, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1221), 1, - sym_identifier, - STATE(1237), 1, - sym_macro_export_item, + [68834] = 3, + ACTIONS(3194), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60621] = 4, - ACTIONS(2402), 1, + ACTIONS(3192), 2, + anon_sym_RPAREN, + anon_sym_COMMA, + [68846] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1221), 1, + STATE(1393), 1, + sym_named_argument, + STATE(1621), 1, sym_identifier, - STATE(1302), 1, - sym_macro_export_item, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [60635] = 4, - ACTIONS(2921), 1, - anon_sym_RBRACE, - ACTIONS(3068), 1, - anon_sym_COMMA, - STATE(1189), 1, - aux_sym_named_params_block_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [60649] = 4, - ACTIONS(1004), 1, - anon_sym_RBRACK, - ACTIONS(3070), 1, - anon_sym_COMMA, - STATE(1085), 1, - aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60663] = 4, - ACTIONS(992), 1, - anon_sym_RBRACE, - ACTIONS(3072), 1, - anon_sym_COMMA, - STATE(1238), 1, - aux_sym_map_expression_repeat1, + [68860] = 3, + ACTIONS(3196), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60677] = 4, - ACTIONS(2716), 1, - anon_sym_RPAREN, - ACTIONS(3074), 1, + ACTIONS(3192), 2, anon_sym_COMMA, - STATE(1194), 1, - aux_sym_trait_method_params_repeat1, + anon_sym_PIPE, + [68872] = 4, + ACTIONS(2952), 1, + anon_sym_LBRACE, + ACTIONS(3198), 1, + anon_sym_DASH_GT, + STATE(1413), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60691] = 4, - ACTIONS(2402), 1, + [68886] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1288), 1, - sym_named_argument, - STATE(1461), 1, + STATE(1258), 1, sym_identifier, + STATE(1374), 1, + sym_macro_export_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60705] = 4, - ACTIONS(2727), 1, + [68900] = 4, + ACTIONS(3028), 1, anon_sym_RPAREN, - ACTIONS(3076), 1, + ACTIONS(3200), 1, anon_sym_COMMA, - STATE(1165), 1, + STATE(1354), 1, aux_sym__argument_list_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60719] = 2, + [68914] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3078), 3, + ACTIONS(3202), 3, anon_sym_RBRACE, anon_sym_case, anon_sym_default, - [60729] = 4, - ACTIONS(3080), 1, + [68924] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2907), 3, anon_sym_RBRACE, - ACTIONS(3082), 1, anon_sym_COMMA, - STATE(1207), 1, - aux_sym_macro_export_repeat1, + anon_sym_DOT_DOT, + [68934] = 4, + ACTIONS(3204), 1, + anon_sym_RBRACE, + ACTIONS(3206), 1, + anon_sym_COMMA, + STATE(1257), 1, + aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60743] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(3084), 1, - anon_sym_LBRACE, + [68948] = 3, + ACTIONS(3211), 1, + anon_sym_as, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60757] = 4, - ACTIONS(3086), 1, + ACTIONS(3209), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [68960] = 4, + ACTIONS(3213), 1, anon_sym_RBRACE, - ACTIONS(3088), 1, + ACTIONS(3215), 1, anon_sym_COMMA, - STATE(1189), 1, + STATE(1259), 1, aux_sym_named_params_block_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60771] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [68974] = 4, + ACTIONS(2866), 1, anon_sym_PIPE, - ACTIONS(3091), 1, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3218), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60785] = 3, - ACTIONS(3095), 1, - anon_sym_COLON, + [68988] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3220), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3093), 2, + [69002] = 4, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1249), 1, + sym_identifier, + STATE(1314), 1, + sym_parameter, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69016] = 4, + ACTIONS(2898), 1, anon_sym_RPAREN, + ACTIONS(3222), 1, anon_sym_COMMA, - [60797] = 4, - ACTIONS(3097), 1, - anon_sym_RBRACE, - ACTIONS(3099), 1, - anon_sym_COMMA, - STATE(1220), 1, - aux_sym_struct_literal_repeat1, + STATE(1353), 1, + aux_sym__argument_list_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60811] = 4, - ACTIONS(2402), 1, + [69030] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1187), 1, - sym_macro_export_item, - STATE(1221), 1, + STATE(1263), 1, + sym_named_argument, + STATE(1621), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60825] = 4, - ACTIONS(3101), 1, + [69044] = 4, + ACTIONS(3224), 1, + aux_sym_identifier_token1, + ACTIONS(3226), 1, + sym_integer_literal, + STATE(77), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69058] = 4, + ACTIONS(3228), 1, anon_sym_RPAREN, - ACTIONS(3103), 1, + ACTIONS(3230), 1, anon_sym_COMMA, - STATE(1194), 1, + STATE(1266), 1, aux_sym_trait_method_params_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60839] = 4, - ACTIONS(3106), 1, + [69072] = 4, + ACTIONS(3233), 1, + anon_sym_COMMA, + ACTIONS(3235), 1, + anon_sym_RBRACK, + STATE(1289), 1, + aux_sym__argument_list_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69086] = 4, + ACTIONS(3237), 1, anon_sym_RBRACE, - ACTIONS(3108), 1, + ACTIONS(3239), 1, anon_sym_COMMA, - STATE(1195), 1, + STATE(1268), 1, aux_sym_for_pattern_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60853] = 4, - ACTIONS(2402), 1, + [69100] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1041), 1, + STATE(1157), 1, sym_identifier, - STATE(1354), 1, + STATE(1450), 1, sym_named_param, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60867] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [69114] = 4, + ACTIONS(2866), 1, anon_sym_PIPE, - ACTIONS(3111), 1, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3242), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60881] = 4, - ACTIONS(2402), 1, + [69128] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1152), 1, + STATE(1336), 1, sym_identifier, - STATE(1283), 1, + STATE(1382), 1, sym_trait_method_param, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60895] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(2373), 3, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [60905] = 4, - ACTIONS(2750), 1, - anon_sym_COMMA, - ACTIONS(2909), 1, - anon_sym_RPAREN, - STATE(1257), 1, - aux_sym_for_pattern_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [60919] = 3, - ACTIONS(3113), 1, - anon_sym_COLON, + [69142] = 4, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1251), 1, + sym_identifier, + STATE(1314), 1, + sym_parameter, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3093), 2, + [69156] = 4, + ACTIONS(3244), 1, + anon_sym_RBRACE, + ACTIONS(3246), 1, anon_sym_COMMA, - anon_sym_PIPE, - [60931] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(3115), 1, - anon_sym_LBRACE, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [60945] = 4, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1191), 1, - sym_identifier, - STATE(1245), 1, - sym_parameter, + STATE(1360), 1, + aux_sym_macro_export_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60959] = 4, - ACTIONS(2402), 1, + [69170] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1041), 1, + STATE(1241), 1, sym_identifier, - STATE(1143), 1, - sym_named_param, + STATE(1365), 1, + sym_import_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60973] = 2, + [69184] = 4, + ACTIONS(3248), 1, + anon_sym_RBRACE, + ACTIONS(3250), 1, + anon_sym_COMMA, + STATE(1240), 1, + aux_sym_named_params_block_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2375), 3, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [60983] = 4, - ACTIONS(2909), 1, + [69198] = 4, + ACTIONS(3048), 1, anon_sym_RBRACE, - ACTIONS(3117), 1, + ACTIONS(3252), 1, anon_sym_COMMA, - STATE(1266), 1, - aux_sym_for_pattern_repeat2, + STATE(1257), 1, + aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [60997] = 4, - ACTIONS(2936), 1, - anon_sym_RBRACE, - ACTIONS(3119), 1, + [69212] = 4, + ACTIONS(2888), 1, anon_sym_COMMA, - STATE(1219), 1, - aux_sym_macro_export_repeat1, + ACTIONS(3254), 1, + anon_sym_RPAREN, + STATE(1141), 1, + aux_sym_function_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61011] = 2, + [69226] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3256), 1, + anon_sym_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(686), 3, + [69240] = 4, + ACTIONS(2866), 1, anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [61021] = 4, - ACTIONS(2720), 1, - anon_sym_RBRACE, - ACTIONS(3121), 1, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3258), 1, anon_sym_COMMA, - STATE(1253), 1, - aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61035] = 4, - ACTIONS(2404), 1, + [69254] = 4, + ACTIONS(3260), 1, anon_sym_RPAREN, - ACTIONS(3123), 1, + ACTIONS(3262), 1, anon_sym_COMMA, - STATE(1249), 1, + STATE(1362), 1, aux_sym__parameter_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61049] = 4, - ACTIONS(2805), 1, - anon_sym_LBRACE, - ACTIONS(3125), 1, - anon_sym_DASH_GT, - STATE(510), 1, - sym_block, + [69268] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61063] = 2, + ACTIONS(2544), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [69278] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2369), 3, + ACTIONS(2556), 3, anon_sym_PIPE, anon_sym_EQ_GT, anon_sym_if, - [61073] = 2, + [69288] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2377), 3, + ACTIONS(2548), 3, anon_sym_PIPE, anon_sym_EQ_GT, anon_sym_if, - [61083] = 4, - ACTIONS(1006), 1, - anon_sym_RBRACK, - ACTIONS(3127), 1, + [69298] = 4, + ACTIONS(2912), 1, anon_sym_COMMA, - STATE(1085), 1, - aux_sym__argument_list_repeat1, + ACTIONS(2966), 1, + anon_sym_RPAREN, + STATE(1372), 1, + aux_sym_for_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61097] = 4, - ACTIONS(2815), 1, - anon_sym_LBRACE, - ACTIONS(3129), 1, - anon_sym_DASH_GT, - STATE(482), 1, - sym_block, + [69312] = 4, + ACTIONS(3264), 1, + anon_sym_COMMA, + ACTIONS(3266), 1, + anon_sym_RBRACK, + STATE(1304), 1, + aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61111] = 4, - ACTIONS(3131), 1, + [69326] = 4, + ACTIONS(3268), 1, anon_sym_RBRACE, - ACTIONS(3133), 1, + ACTIONS(3270), 1, anon_sym_COMMA, - STATE(1229), 1, - aux_sym_struct_definition_repeat1, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [61125] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, - anon_sym_PIPE, - ACTIONS(3135), 1, - anon_sym_SEMI, + STATE(1305), 1, + aux_sym_map_expression_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61139] = 2, + [69340] = 4, + ACTIONS(3272), 1, + anon_sym_COMMA, + ACTIONS(3274), 1, + anon_sym_RBRACK, + STATE(1315), 1, + aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2348), 3, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [61149] = 4, - ACTIONS(3137), 1, + [69354] = 4, + ACTIONS(3276), 1, anon_sym_RBRACE, - ACTIONS(3139), 1, + ACTIONS(3278), 1, anon_sym_COMMA, - STATE(1219), 1, - aux_sym_macro_export_repeat1, + STATE(1320), 1, + aux_sym_map_expression_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61163] = 4, - ACTIONS(2736), 1, - anon_sym_RBRACE, - ACTIONS(3142), 1, + [69368] = 4, + ACTIONS(1047), 1, + anon_sym_RBRACK, + ACTIONS(3280), 1, anon_sym_COMMA, - STATE(1253), 1, - aux_sym_struct_literal_repeat1, + STATE(1195), 1, + aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61177] = 3, - ACTIONS(3146), 1, - anon_sym_as, + [69382] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3144), 2, + ACTIONS(3056), 3, + anon_sym_RBRACE, + anon_sym_case, + anon_sym_default, + [69392] = 4, + ACTIONS(3282), 1, anon_sym_RBRACE, + ACTIONS(3284), 1, anon_sym_COMMA, - [61189] = 2, + STATE(1291), 1, + aux_sym_import_statement_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(849), 3, - anon_sym_RBRACE, - anon_sym_fn, - anon_sym_catch, - [61199] = 3, - ACTIONS(3150), 1, - anon_sym_COLON, + [69406] = 4, + ACTIONS(3287), 1, + aux_sym_identifier_token1, + ACTIONS(3289), 1, + sym_integer_literal, + STATE(553), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3148), 2, + [69420] = 4, + ACTIONS(2966), 1, anon_sym_RBRACE, + ACTIONS(3291), 1, anon_sym_COMMA, - [61211] = 4, - ACTIONS(948), 1, - anon_sym_RPAREN, - ACTIONS(3152), 1, - anon_sym_COMMA, - STATE(1085), 1, - aux_sym__argument_list_repeat1, + STATE(1361), 1, + aux_sym_for_pattern_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61225] = 4, - ACTIONS(3154), 1, + [69434] = 4, + ACTIONS(1005), 1, anon_sym_RBRACE, - ACTIONS(3156), 1, + ACTIONS(3293), 1, anon_sym_COMMA, - STATE(1209), 1, - aux_sym_struct_literal_repeat1, + STATE(1308), 1, + aux_sym_map_expression_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61239] = 4, - ACTIONS(2815), 1, - anon_sym_LBRACE, - ACTIONS(3158), 1, - anon_sym_DASH_GT, - STATE(490), 1, - sym_block, + [69448] = 4, + ACTIONS(3295), 1, + aux_sym_identifier_token1, + ACTIONS(3297), 1, + sym_integer_literal, + STATE(702), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69462] = 4, + ACTIONS(3295), 1, + aux_sym_identifier_token1, + ACTIONS(3299), 1, + sym_integer_literal, + STATE(706), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69476] = 4, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1241), 1, + sym_identifier, + STATE(1391), 1, + sym_import_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61253] = 4, - ACTIONS(3160), 1, + [69490] = 4, + ACTIONS(981), 1, anon_sym_RPAREN, - ACTIONS(3162), 1, + ACTIONS(3301), 1, anon_sym_COMMA, - STATE(1224), 1, + STATE(1195), 1, aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61267] = 4, - ACTIONS(2710), 1, - anon_sym_RBRACE, - ACTIONS(3164), 1, - anon_sym_COMMA, - STATE(1236), 1, - aux_sym_struct_definition_repeat1, + [69504] = 4, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1258), 1, + sym_identifier, + STATE(1403), 1, + sym_macro_export_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61281] = 4, - ACTIONS(2917), 1, - anon_sym_RBRACE, - ACTIONS(3166), 1, + [69518] = 4, + ACTIONS(3303), 1, anon_sym_COMMA, - STATE(1236), 1, - aux_sym_struct_definition_repeat1, + ACTIONS(3305), 1, + anon_sym_PIPE, + STATE(1332), 1, + aux_sym__parameter_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61295] = 2, + [69532] = 4, + ACTIONS(3287), 1, + aux_sym_identifier_token1, + ACTIONS(3307), 1, + sym_integer_literal, + STATE(554), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1469), 3, - anon_sym_RBRACE, - anon_sym_fn, - anon_sym_catch, - [61305] = 2, + [69546] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2361), 3, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [61315] = 2, + ACTIONS(3078), 3, + anon_sym_RPAREN, + anon_sym_COMMA, + anon_sym_RBRACK, + [69556] = 4, + ACTIONS(2888), 1, + anon_sym_COMMA, + ACTIONS(3309), 1, + anon_sym_GT, + STATE(1141), 1, + aux_sym_function_type_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2346), 3, - anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [61325] = 4, - ACTIONS(3168), 1, + [69570] = 4, + ACTIONS(1057), 1, + anon_sym_RBRACK, + ACTIONS(3311), 1, anon_sym_COMMA, - ACTIONS(3170), 1, - anon_sym_PIPE, - STATE(1174), 1, - aux_sym__parameter_list_repeat1, + STATE(1195), 1, + aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61339] = 2, + [69584] = 4, + ACTIONS(1001), 1, + anon_sym_RBRACE, + ACTIONS(3313), 1, + anon_sym_COMMA, + STATE(1308), 1, + aux_sym_map_expression_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2823), 3, - anon_sym_RPAREN, - anon_sym_COMMA, - anon_sym_RBRACK, - [61349] = 4, - ACTIONS(2722), 1, + [69598] = 4, + ACTIONS(3315), 1, anon_sym_RBRACE, - ACTIONS(3172), 1, + ACTIONS(3317), 1, anon_sym_COMMA, - STATE(1219), 1, - aux_sym_macro_export_repeat1, + STATE(1294), 1, + aux_sym_map_expression_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61363] = 4, - ACTIONS(3174), 1, - anon_sym_RBRACE, - ACTIONS(3176), 1, - anon_sym_COMMA, - STATE(1236), 1, - aux_sym_struct_definition_repeat1, + [69612] = 4, + ACTIONS(3319), 1, + aux_sym_identifier_token1, + ACTIONS(3321), 1, + sym_integer_literal, + STATE(845), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61377] = 4, - ACTIONS(3179), 1, + [69626] = 4, + ACTIONS(3323), 1, anon_sym_RBRACE, - ACTIONS(3181), 1, + ACTIONS(3325), 1, anon_sym_COMMA, - STATE(1235), 1, - aux_sym_macro_export_repeat1, + STATE(1308), 1, + aux_sym_map_expression_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61391] = 4, - ACTIONS(3183), 1, + [69640] = 4, + ACTIONS(3328), 1, anon_sym_RBRACE, - ACTIONS(3185), 1, + ACTIONS(3330), 1, anon_sym_COMMA, - STATE(1238), 1, - aux_sym_map_expression_repeat1, + STATE(1309), 1, + aux_sym_macro_export_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61405] = 4, - ACTIONS(2402), 1, + [69654] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1223), 1, + STATE(1243), 1, sym_identifier, - STATE(1330), 1, + STATE(1441), 1, sym_struct_field_init, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61419] = 4, - ACTIONS(2609), 1, + [69668] = 4, + ACTIONS(3319), 1, + aux_sym_identifier_token1, + ACTIONS(3333), 1, + sym_integer_literal, + STATE(846), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69682] = 4, + ACTIONS(2868), 1, anon_sym_QMARK, - ACTIONS(2851), 1, + ACTIONS(2905), 1, anon_sym_COMMA, - ACTIONS(3188), 1, + ACTIONS(3335), 1, anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61433] = 2, + [69696] = 4, + ACTIONS(3224), 1, + aux_sym_identifier_token1, + ACTIONS(3338), 1, + sym_integer_literal, + STATE(78), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69710] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2350), 3, + ACTIONS(3340), 3, + anon_sym_RPAREN, + anon_sym_COMMA, anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [61443] = 2, + [69720] = 4, + ACTIONS(1053), 1, + anon_sym_RBRACK, + ACTIONS(3342), 1, + anon_sym_COMMA, + STATE(1195), 1, + aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2352), 3, + [69734] = 4, + ACTIONS(3340), 1, anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [61453] = 4, - ACTIONS(2934), 1, - anon_sym_RPAREN, - ACTIONS(3191), 1, + ACTIONS(3344), 1, anon_sym_COMMA, - STATE(1159), 1, - aux_sym__argument_list_repeat2, + STATE(1316), 1, + aux_sym__parameter_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61467] = 2, + [69748] = 4, + ACTIONS(2898), 1, + anon_sym_RPAREN, + ACTIONS(3222), 1, + anon_sym_COMMA, + STATE(1354), 1, + aux_sym__argument_list_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2367), 3, + [69762] = 4, + ACTIONS(3347), 1, anon_sym_PIPE, + ACTIONS(3349), 1, anon_sym_EQ_GT, + ACTIONS(3351), 1, anon_sym_if, - [61477] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3193), 3, - anon_sym_RPAREN, + [69776] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(1450), 3, + anon_sym_RBRACE, + anon_sym_fn, + anon_sym_catch, + [69786] = 4, + ACTIONS(1029), 1, + anon_sym_RBRACE, + ACTIONS(3353), 1, anon_sym_COMMA, - anon_sym_PIPE, - [61487] = 4, - ACTIONS(3193), 1, - anon_sym_PIPE, - ACTIONS(3195), 1, + STATE(1308), 1, + aux_sym_map_expression_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69800] = 4, + ACTIONS(1063), 1, + anon_sym_RBRACK, + ACTIONS(3355), 1, anon_sym_COMMA, - STATE(1246), 1, - aux_sym__parameter_list_repeat1, + STATE(1195), 1, + aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61501] = 4, - ACTIONS(2934), 1, - anon_sym_RPAREN, - ACTIONS(3191), 1, + [69814] = 4, + ACTIONS(3357), 1, + anon_sym_RBRACE, + ACTIONS(3359), 1, anon_sym_COMMA, - STATE(1165), 1, - aux_sym__argument_list_repeat2, + STATE(1352), 1, + aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61515] = 4, - ACTIONS(2402), 1, + [69828] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1167), 1, - sym_named_argument, - STATE(1461), 1, + STATE(1341), 1, sym_identifier, + STATE(1437), 1, + sym_struct_field, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61529] = 4, - ACTIONS(3193), 1, - anon_sym_RPAREN, - ACTIONS(3198), 1, - anon_sym_COMMA, - STATE(1249), 1, - aux_sym__parameter_list_repeat1, + [69842] = 4, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1157), 1, + sym_identifier, + STATE(1275), 1, + sym_named_param, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61543] = 4, - ACTIONS(2609), 1, - anon_sym_QMARK, - ACTIONS(2611), 1, + [69856] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(2560), 3, anon_sym_PIPE, - ACTIONS(3201), 1, - anon_sym_EQ, + anon_sym_EQ_GT, + anon_sym_if, + [69866] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61557] = 4, - ACTIONS(1008), 1, - anon_sym_RBRACK, - ACTIONS(3203), 1, - anon_sym_COMMA, - STATE(1085), 1, - aux_sym__argument_list_repeat1, + ACTIONS(2573), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [69876] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61571] = 4, - ACTIONS(3205), 1, + ACTIONS(2552), 3, anon_sym_PIPE, - ACTIONS(3207), 1, anon_sym_EQ_GT, - ACTIONS(3209), 1, anon_sym_if, + [69886] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61585] = 4, - ACTIONS(3211), 1, + ACTIONS(2567), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [69896] = 4, + ACTIONS(3361), 1, anon_sym_RBRACE, - ACTIONS(3213), 1, + ACTIONS(3363), 1, anon_sym_COMMA, - STATE(1253), 1, + STATE(1345), 1, aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61599] = 4, - ACTIONS(2402), 1, + [69910] = 4, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1243), 1, - sym_named_argument, - STATE(1461), 1, + STATE(1241), 1, sym_identifier, + STATE(1244), 1, + sym_import_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61613] = 2, + [69924] = 4, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1258), 1, + sym_identifier, + STATE(1273), 1, + sym_macro_export_item, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2363), 3, + [69938] = 4, + ACTIONS(3103), 1, anon_sym_PIPE, - anon_sym_EQ_GT, - anon_sym_if, - [61623] = 4, - ACTIONS(952), 1, + ACTIONS(3365), 1, + anon_sym_COMMA, + STATE(1316), 1, + aux_sym__parameter_list_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69952] = 4, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1358), 1, + sym_named_argument, + STATE(1621), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69966] = 4, + ACTIONS(3105), 1, + anon_sym_RBRACE, + ACTIONS(3367), 1, + anon_sym_COMMA, + STATE(1309), 1, + aux_sym_macro_export_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69980] = 4, + ACTIONS(1017), 1, + anon_sym_RBRACE, + ACTIONS(3369), 1, + anon_sym_COMMA, + STATE(1308), 1, + aux_sym_map_expression_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [69994] = 3, + ACTIONS(3373), 1, + anon_sym_COLON, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3371), 2, anon_sym_RPAREN, - ACTIONS(3216), 1, anon_sym_COMMA, - STATE(1247), 1, - aux_sym__argument_list_repeat2, + [70006] = 4, + ACTIONS(3111), 1, + anon_sym_RBRACE, + ACTIONS(3375), 1, + anon_sym_COMMA, + STATE(1291), 1, + aux_sym_import_statement_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61637] = 4, - ACTIONS(2750), 1, + [70020] = 4, + ACTIONS(3377), 1, + anon_sym_RPAREN, + ACTIONS(3379), 1, anon_sym_COMMA, - ACTIONS(2752), 1, + STATE(1246), 1, + aux_sym_trait_method_params_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [70034] = 4, + ACTIONS(3340), 1, anon_sym_RPAREN, - STATE(1037), 1, - aux_sym_for_pattern_repeat1, + ACTIONS(3381), 1, + anon_sym_COMMA, + STATE(1339), 1, + aux_sym__parameter_list_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [70048] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3384), 1, + anon_sym_EQ, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [70062] = 3, + ACTIONS(3388), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61651] = 4, - ACTIONS(2861), 1, + ACTIONS(3386), 2, anon_sym_RBRACE, - ACTIONS(3218), 1, anon_sym_COMMA, - STATE(1172), 1, - aux_sym_import_statement_repeat1, + [70074] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3390), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61665] = 4, - ACTIONS(2954), 1, - aux_sym_identifier_token1, - ACTIONS(3220), 1, - sym_integer_literal, - STATE(74), 1, - sym_identifier, + [70088] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61679] = 4, - ACTIONS(3222), 1, + ACTIONS(862), 3, + anon_sym_RBRACE, + anon_sym_fn, + anon_sym_catch, + [70098] = 4, + ACTIONS(3392), 1, anon_sym_COMMA, - ACTIONS(3224), 1, + ACTIONS(3394), 1, anon_sym_RBRACK, - STATE(1214), 1, + STATE(1321), 1, aux_sym__argument_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61693] = 4, - ACTIONS(3226), 1, + [70112] = 4, + ACTIONS(2992), 1, anon_sym_RBRACE, - ACTIONS(3228), 1, + ACTIONS(3396), 1, anon_sym_COMMA, - STATE(1136), 1, - aux_sym_map_expression_repeat1, + STATE(1257), 1, + aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61707] = 2, + [70126] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2365), 3, + ACTIONS(718), 3, anon_sym_PIPE, anon_sym_EQ_GT, anon_sym_if, - [61717] = 4, - ACTIONS(2410), 1, - anon_sym_EQ_GT, - ACTIONS(3230), 1, + [70136] = 4, + ACTIONS(2896), 1, + anon_sym_LBRACE, + ACTIONS(3398), 1, + anon_sym_DASH_GT, + STATE(583), 1, + sym_block, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [70150] = 4, + ACTIONS(3400), 1, + anon_sym_RBRACE, + ACTIONS(3402), 1, + anon_sym_COMMA, + STATE(1359), 1, + aux_sym_struct_definition_repeat1, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [70164] = 4, + ACTIONS(2866), 1, anon_sym_PIPE, - ACTIONS(3233), 1, - anon_sym_if, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3404), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61731] = 2, + [70178] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2359), 3, + ACTIONS(2571), 3, anon_sym_PIPE, anon_sym_EQ_GT, anon_sym_if, - [61741] = 2, + [70188] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2381), 3, + ACTIONS(2540), 3, anon_sym_PIPE, anon_sym_EQ_GT, anon_sym_if, - [61751] = 4, - ACTIONS(2752), 1, + [70198] = 4, + ACTIONS(3124), 1, anon_sym_RBRACE, - ACTIONS(3117), 1, + ACTIONS(3406), 1, anon_sym_COMMA, - STATE(1195), 1, - aux_sym_for_pattern_repeat2, + STATE(1257), 1, + aux_sym_struct_literal_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61765] = 4, - ACTIONS(2627), 1, + [70212] = 4, + ACTIONS(2996), 1, + anon_sym_RPAREN, + ACTIONS(3408), 1, anon_sym_COMMA, - ACTIONS(3236), 1, - anon_sym_GT, - STATE(1050), 1, - aux_sym_function_type_repeat1, + STATE(1354), 1, + aux_sym__argument_list_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61779] = 4, - ACTIONS(3238), 1, + [70226] = 4, + ACTIONS(3410), 1, + anon_sym_RPAREN, + ACTIONS(3412), 1, anon_sym_COMMA, - ACTIONS(3240), 1, - anon_sym_RBRACK, - STATE(1251), 1, - aux_sym__argument_list_repeat1, + STATE(1354), 1, + aux_sym__argument_list_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61793] = 4, - ACTIONS(2805), 1, - anon_sym_LBRACE, - ACTIONS(3242), 1, - anon_sym_DASH_GT, - STATE(539), 1, - sym_block, + [70240] = 4, + ACTIONS(3415), 1, + anon_sym_RBRACE, + ACTIONS(3417), 1, + anon_sym_COMMA, + STATE(1355), 1, + aux_sym_struct_definition_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61807] = 4, - ACTIONS(3244), 1, - anon_sym_RPAREN, - ACTIONS(3246), 1, + [70254] = 4, + ACTIONS(3420), 1, + anon_sym_RBRACE, + ACTIONS(3422), 1, anon_sym_COMMA, - STATE(1210), 1, - aux_sym__parameter_list_repeat1, + STATE(1233), 1, + aux_sym_struct_definition_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61821] = 4, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1201), 1, - sym_identifier, - STATE(1245), 1, - sym_parameter, + [70268] = 4, + ACTIONS(2896), 1, + anon_sym_LBRACE, + ACTIONS(3424), 1, + anon_sym_DASH_GT, + STATE(590), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61835] = 4, - ACTIONS(3248), 1, - anon_sym_RBRACE, - ACTIONS(3250), 1, + [70282] = 4, + ACTIONS(2996), 1, + anon_sym_RPAREN, + ACTIONS(3408), 1, anon_sym_COMMA, - STATE(1275), 1, - aux_sym_import_statement_repeat1, + STATE(1254), 1, + aux_sym__argument_list_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61849] = 4, - ACTIONS(3252), 1, - aux_sym_identifier_token1, - ACTIONS(3254), 1, - sym_integer_literal, - STATE(608), 1, - sym_identifier, + [70296] = 4, + ACTIONS(2894), 1, + anon_sym_RBRACE, + ACTIONS(3426), 1, + anon_sym_COMMA, + STATE(1355), 1, + aux_sym_struct_definition_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61863] = 4, - ACTIONS(3252), 1, - aux_sym_identifier_token1, - ACTIONS(3256), 1, - sym_integer_literal, - STATE(609), 1, - sym_identifier, + [70310] = 4, + ACTIONS(3130), 1, + anon_sym_RBRACE, + ACTIONS(3428), 1, + anon_sym_COMMA, + STATE(1309), 1, + aux_sym_macro_export_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61877] = 4, - ACTIONS(2948), 1, + [70324] = 4, + ACTIONS(2914), 1, anon_sym_RBRACE, - ACTIONS(3258), 1, + ACTIONS(3291), 1, anon_sym_COMMA, - STATE(1172), 1, - aux_sym_import_statement_repeat1, + STATE(1268), 1, + aux_sym_for_pattern_repeat2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61891] = 2, + [70338] = 4, + ACTIONS(2608), 1, + anon_sym_RPAREN, + ACTIONS(3430), 1, + anon_sym_COMMA, + STATE(1339), 1, + aux_sym__parameter_list_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3260), 3, - anon_sym_RBRACE, - anon_sym_case, - anon_sym_default, - [61901] = 4, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1161), 1, - sym_identifier, - STATE(1272), 1, - sym_import_item, + [70352] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61915] = 2, + ACTIONS(2569), 3, + anon_sym_PIPE, + anon_sym_EQ_GT, + anon_sym_if, + [70362] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2371), 3, + ACTIONS(2575), 3, anon_sym_PIPE, anon_sym_EQ_GT, anon_sym_if, - [61925] = 3, - ACTIONS(3262), 1, - anon_sym_SEMI, - ACTIONS(3264), 1, - anon_sym_as, + [70372] = 4, + ACTIONS(3432), 1, + anon_sym_RBRACE, + ACTIONS(3434), 1, + anon_sym_COMMA, + STATE(1367), 1, + aux_sym_import_statement_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61936] = 2, + [70386] = 4, + ACTIONS(3436), 1, + anon_sym_RBRACE, + ACTIONS(3438), 1, + anon_sym_COMMA, + STATE(1335), 1, + aux_sym_map_expression_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3266), 2, + [70400] = 4, + ACTIONS(3138), 1, anon_sym_RBRACE, + ACTIONS(3440), 1, anon_sym_COMMA, - [61945] = 2, + STATE(1291), 1, + aux_sym_import_statement_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3106), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [61954] = 2, + [70414] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3442), 1, + anon_sym_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3268), 2, - anon_sym_RBRACE, - anon_sym_fn, - [61963] = 2, + [70428] = 4, + ACTIONS(2866), 1, + anon_sym_PIPE, + ACTIONS(2868), 1, + anon_sym_QMARK, + ACTIONS(3444), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3101), 2, - anon_sym_RPAREN, - anon_sym_COMMA, - [61972] = 3, - ACTIONS(2408), 1, + [70442] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3446), 3, + anon_sym_RBRACE, + anon_sym_case, + anon_sym_default, + [70452] = 4, + ACTIONS(2980), 1, anon_sym_LBRACE, - STATE(1456), 1, - sym_named_params_block, + ACTIONS(3448), 1, + anon_sym_DASH_GT, + STATE(646), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61983] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1133), 1, - sym_identifier, + [70466] = 4, + ACTIONS(2912), 1, + anon_sym_COMMA, + ACTIONS(2914), 1, + anon_sym_RPAREN, + STATE(1117), 1, + aux_sym_for_pattern_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [61994] = 2, + [70480] = 4, + ACTIONS(2980), 1, + anon_sym_LBRACE, + ACTIONS(3450), 1, + anon_sym_DASH_GT, + STATE(665), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3270), 2, - anon_sym_RPAREN, + [70494] = 4, + ACTIONS(3452), 1, + anon_sym_RBRACE, + ACTIONS(3454), 1, anon_sym_COMMA, - [62003] = 2, + STATE(1334), 1, + aux_sym_macro_export_repeat1, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3272), 2, - anon_sym_RBRACE, - anon_sym_fn, - [62012] = 2, + [70508] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3036), 2, - anon_sym_RPAREN, + ACTIONS(3456), 2, + anon_sym_RBRACE, anon_sym_COMMA, - [62021] = 3, - ACTIONS(2402), 1, + [70517] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1511), 1, + STATE(1597), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62032] = 3, - ACTIONS(2402), 1, + [70528] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1126), 1, + STATE(1599), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62043] = 2, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - ACTIONS(3274), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [62052] = 2, + [70539] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3276), 2, + ACTIONS(3458), 2, anon_sym_RBRACE, anon_sym_fn, - [62061] = 3, - ACTIONS(2402), 1, + [70548] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1307), 1, + STATE(1624), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62072] = 3, - ACTIONS(2402), 1, + [70559] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1380), 1, + STATE(1633), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62083] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1383), 1, - sym_identifier, + [70570] = 3, + ACTIONS(3460), 1, + anon_sym_SEMI, + ACTIONS(3462), 1, + anon_sym_DASH_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62094] = 3, - ACTIONS(2402), 1, + [70581] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3228), 2, + anon_sym_RPAREN, + anon_sym_COMMA, + [70590] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1409), 1, + STATE(1646), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62105] = 3, - ACTIONS(2402), 1, + [70601] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1410), 1, + STATE(1527), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62116] = 3, - ACTIONS(2408), 1, - anon_sym_LBRACE, - STATE(1443), 1, - sym_named_params_block, + [70612] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62127] = 3, - ACTIONS(3278), 1, - anon_sym_RPAREN, - ACTIONS(3280), 1, + ACTIONS(3464), 2, + anon_sym_RBRACE, + anon_sym_fn, + [70621] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3466), 2, + anon_sym_RBRACE, anon_sym_COMMA, + [70630] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62138] = 3, - ACTIONS(2402), 1, + ACTIONS(3468), 2, + anon_sym_RBRACE, + anon_sym_fn, + [70639] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1430), 1, + STATE(1431), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62149] = 3, - ACTIONS(2402), 1, + [70650] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1360), 1, + STATE(1489), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62160] = 2, + [70661] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3137), 2, + ACTIONS(3470), 2, anon_sym_RBRACE, anon_sym_COMMA, - [62169] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1384), 1, - sym_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [62180] = 2, + [70670] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3053), 2, + ACTIONS(3282), 2, anon_sym_RBRACE, anon_sym_COMMA, - [62189] = 3, - ACTIONS(3282), 1, - anon_sym_SEMI, - ACTIONS(3284), 1, - anon_sym_as, + [70679] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62200] = 3, - ACTIONS(3286), 1, - aux_sym_identifier_token1, - STATE(1399), 1, - sym_type_identifier, + ACTIONS(3472), 2, + anon_sym_RPAREN, + anon_sym_COMMA, + [70688] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62211] = 3, - ACTIONS(2815), 1, + ACTIONS(3410), 2, + anon_sym_RPAREN, + anon_sym_COMMA, + [70697] = 3, + ACTIONS(2952), 1, anon_sym_LBRACE, - STATE(486), 1, + STATE(1493), 1, sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62222] = 3, - ACTIONS(2402), 1, + [70708] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1357), 1, + STATE(1220), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62233] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1122), 1, - sym_identifier, + [70719] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62244] = 2, + ACTIONS(3237), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [70728] = 3, + ACTIONS(3474), 1, + anon_sym_COLON, + ACTIONS(3476), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3174), 2, - anon_sym_RBRACE, + [70739] = 3, + ACTIONS(3478), 1, + anon_sym_SEMI, + ACTIONS(3480), 1, + anon_sym_as, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [70750] = 3, + ACTIONS(3482), 1, + anon_sym_RPAREN, + ACTIONS(3484), 1, anon_sym_COMMA, - [62253] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1429), 2, - anon_sym_RBRACE, - anon_sym_fn, - [62262] = 3, - ACTIONS(3288), 1, - anon_sym_SEMI, - ACTIONS(3290), 1, - anon_sym_DASH_GT, + [70761] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62273] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1459), 1, - sym_identifier, + ACTIONS(3486), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [70770] = 3, + ACTIONS(3488), 1, + anon_sym_RPAREN, + ACTIONS(3490), 1, + anon_sym_COMMA, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62284] = 3, - ACTIONS(2402), 1, + [70781] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1393), 1, + STATE(1470), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62295] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1548), 1, - sym_identifier, + [70792] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62306] = 3, - ACTIONS(2402), 1, + ACTIONS(3328), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [70801] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1336), 1, + STATE(1524), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62317] = 2, + [70812] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3292), 2, + ACTIONS(3492), 2, anon_sym_RBRACE, anon_sym_COMMA, - [62326] = 2, + [70821] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3294), 2, + ACTIONS(3323), 2, anon_sym_RBRACE, anon_sym_COMMA, - [62335] = 3, - ACTIONS(3296), 1, - anon_sym_COLON, - ACTIONS(3298), 1, - anon_sym_SEMI, + [70830] = 3, + ACTIONS(3494), 1, + aux_sym_identifier_token1, + STATE(1463), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62346] = 2, + [70841] = 3, + ACTIONS(3496), 1, + anon_sym_RPAREN, + ACTIONS(3498), 1, + anon_sym_COMMA, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3183), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [62355] = 3, - ACTIONS(3286), 1, - aux_sym_identifier_token1, - STATE(1400), 1, - sym_type_identifier, + [70852] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62366] = 3, - ACTIONS(2402), 1, + ACTIONS(1502), 2, + anon_sym_RBRACE, + anon_sym_fn, + [70861] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1132), 1, + STATE(1560), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62377] = 3, - ACTIONS(2402), 1, + [70872] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1483), 1, + STATE(1634), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62388] = 2, + [70883] = 3, + ACTIONS(3494), 1, + aux_sym_identifier_token1, + STATE(1480), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3300), 2, - anon_sym_RBRACE, - anon_sym_fn, - [62397] = 2, + [70894] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1453), 2, + ACTIONS(1522), 2, anon_sym_RBRACE, anon_sym_fn, - [62406] = 3, - ACTIONS(3302), 1, - anon_sym_LBRACE, - ACTIONS(3304), 1, - anon_sym_macro_rules, + [70903] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1579), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62417] = 3, - ACTIONS(3286), 1, - aux_sym_identifier_token1, - STATE(1364), 1, - sym_type_identifier, + [70914] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62428] = 3, - ACTIONS(3286), 1, - aux_sym_identifier_token1, - STATE(1366), 1, - sym_type_identifier, + ACTIONS(1550), 2, + anon_sym_RBRACE, + anon_sym_fn, + [70923] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62439] = 3, - ACTIONS(2402), 1, + ACTIONS(1566), 2, + anon_sym_RBRACE, + anon_sym_fn, + [70932] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1386), 1, + STATE(1644), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62450] = 2, + [70943] = 3, + ACTIONS(2612), 1, + anon_sym_LBRACE, + STATE(1636), 1, + sym_named_params_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3211), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [62459] = 3, - ACTIONS(3286), 1, + [70954] = 3, + ACTIONS(3494), 1, aux_sym_identifier_token1, - STATE(1412), 1, + STATE(1630), 1, sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62470] = 3, - ACTIONS(3306), 1, - anon_sym_SEMI, - ACTIONS(3308), 1, - anon_sym_DASH_GT, + [70965] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1487), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62481] = 2, + [70976] = 3, + ACTIONS(3494), 1, + aux_sym_identifier_token1, + STATE(1467), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1311), 2, - anon_sym_RBRACE, - anon_sym_fn, - [62490] = 3, - ACTIONS(2813), 1, - anon_sym_LBRACE, - STATE(1458), 1, - sym_block, + [70987] = 3, + ACTIONS(3500), 1, + anon_sym_SEMI, + ACTIONS(3502), 1, + anon_sym_DASH_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62501] = 2, + [70998] = 3, + ACTIONS(3504), 1, + anon_sym_SEMI, + ACTIONS(3506), 1, + anon_sym_as, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3310), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [62510] = 2, + [71009] = 3, + ACTIONS(3508), 1, + anon_sym_LT_EQ, + ACTIONS(3510), 1, + anon_sym_EQ_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3312), 2, - anon_sym_RBRACE, - anon_sym_COMMA, - [62519] = 2, + [71020] = 3, + ACTIONS(3494), 1, + aux_sym_identifier_token1, + STATE(1610), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(1405), 2, - anon_sym_RBRACE, - anon_sym_fn, - [62528] = 3, - ACTIONS(2402), 1, + [71031] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1431), 1, + STATE(1148), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62539] = 3, - ACTIONS(2402), 1, + [71042] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1418), 1, + STATE(1390), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62550] = 3, - ACTIONS(3314), 1, - anon_sym_LT_EQ, - ACTIONS(3316), 1, - anon_sym_EQ_GT, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [62561] = 3, - ACTIONS(3318), 1, + [71053] = 3, + ACTIONS(3512), 1, anon_sym_LBRACE, - ACTIONS(3320), 1, + ACTIONS(3514), 1, anon_sym_macro_rules, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62572] = 3, - ACTIONS(3322), 1, - anon_sym_RPAREN, - ACTIONS(3324), 1, - anon_sym_COMMA, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [62583] = 3, - ACTIONS(2402), 1, + [71064] = 3, + ACTIONS(3494), 1, aux_sym_identifier_token1, - STATE(1440), 1, - sym_identifier, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [62594] = 3, - ACTIONS(3326), 1, - anon_sym_RPAREN, - ACTIONS(3328), 1, - anon_sym_COMMA, + STATE(1596), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62605] = 3, - ACTIONS(2402), 1, + [71075] = 3, + ACTIONS(3494), 1, aux_sym_identifier_token1, - STATE(1401), 1, - sym_identifier, + STATE(1598), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62616] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1420), 1, - sym_identifier, + [71086] = 3, + ACTIONS(2980), 1, + anon_sym_LBRACE, + STATE(653), 1, + sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62627] = 3, - ACTIONS(3286), 1, - aux_sym_identifier_token1, - STATE(1398), 1, - sym_type_identifier, + [71097] = 3, + ACTIONS(2612), 1, + anon_sym_LBRACE, + STATE(1516), 1, + sym_named_params_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62638] = 3, - ACTIONS(2402), 1, - aux_sym_identifier_token1, - STATE(1477), 1, - sym_identifier, + [71108] = 3, + ACTIONS(3516), 1, + anon_sym_LBRACE, + ACTIONS(3518), 1, + anon_sym_macro_rules, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62649] = 3, - ACTIONS(3286), 1, - aux_sym_identifier_token1, - STATE(1478), 1, - sym_type_identifier, + [71119] = 3, + ACTIONS(3520), 1, + anon_sym_RPAREN, + ACTIONS(3522), 1, + anon_sym_COMMA, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62660] = 3, - ACTIONS(2813), 1, + [71130] = 3, + ACTIONS(2896), 1, anon_sym_LBRACE, - STATE(1479), 1, + STATE(587), 1, sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62671] = 3, - ACTIONS(2402), 1, + [71141] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1335), 1, + STATE(1197), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62682] = 2, + [71152] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(2607), 2, + ACTIONS(3415), 2, anon_sym_RBRACE, anon_sym_COMMA, - [62691] = 3, - ACTIONS(2402), 1, + [71161] = 2, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + ACTIONS(3524), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [71170] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1485), 1, + STATE(1400), 1, + sym_identifier, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [71181] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1518), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62702] = 2, + [71192] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - ACTIONS(3086), 2, + ACTIONS(3204), 2, anon_sym_RBRACE, anon_sym_COMMA, - [62711] = 3, - ACTIONS(2402), 1, + [71201] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1486), 1, + STATE(1565), 1, sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62722] = 3, - ACTIONS(2402), 1, + [71212] = 3, + ACTIONS(3494), 1, aux_sym_identifier_token1, - STATE(1518), 1, - sym_identifier, + STATE(1566), 1, + sym_type_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62733] = 3, - ACTIONS(2805), 1, + [71223] = 3, + ACTIONS(2952), 1, anon_sym_LBRACE, - STATE(525), 1, + STATE(1567), 1, sym_block, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62744] = 3, - ACTIONS(3286), 1, + [71234] = 3, + ACTIONS(2606), 1, aux_sym_identifier_token1, - STATE(1539), 1, - sym_type_identifier, + STATE(1547), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62755] = 3, - ACTIONS(3330), 1, - anon_sym_RPAREN, - ACTIONS(3332), 1, - anon_sym_COMMA, + [71245] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1548), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62766] = 2, - ACTIONS(3334), 1, - anon_sym_RBRACE, + [71256] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1210), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62774] = 2, - ACTIONS(3336), 1, - anon_sym_RBRACK, + [71267] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1573), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62782] = 2, - ACTIONS(3338), 1, - anon_sym_COLON, + [71278] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62790] = 2, - ACTIONS(3340), 1, - anon_sym_COLON, + ACTIONS(2882), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [71287] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62798] = 2, - ACTIONS(3342), 1, - anon_sym_LBRACE, + ACTIONS(3213), 2, + anon_sym_RBRACE, + anon_sym_COMMA, + [71296] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1461), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62806] = 2, - ACTIONS(3344), 1, - anon_sym_RBRACK, + [71307] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1435), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62814] = 2, - ACTIONS(3346), 1, - anon_sym_LBRACE, + [71318] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1609), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62822] = 2, - ACTIONS(3348), 1, - anon_sym_COLON, + [71329] = 3, + ACTIONS(2606), 1, + aux_sym_identifier_token1, + STATE(1515), 1, + sym_identifier, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62830] = 2, - ACTIONS(3350), 1, - anon_sym_RPAREN, + [71340] = 2, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62838] = 2, - ACTIONS(3352), 1, - anon_sym_LBRACE, + ACTIONS(3526), 2, + anon_sym_RBRACE, + anon_sym_fn, + [71349] = 2, + ACTIONS(3528), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62846] = 2, - ACTIONS(3354), 1, - anon_sym_SEMI, + [71357] = 2, + ACTIONS(3530), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62854] = 2, - ACTIONS(3356), 1, - anon_sym_SEMI, + [71365] = 2, + ACTIONS(3532), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62862] = 2, - ACTIONS(3358), 1, - anon_sym_SEMI, + [71373] = 2, + ACTIONS(3534), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62870] = 2, - ACTIONS(3360), 1, - anon_sym_SEMI, + [71381] = 2, + ACTIONS(3536), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62878] = 2, - ACTIONS(3362), 1, - anon_sym_RPAREN, + [71389] = 2, + ACTIONS(3538), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62886] = 2, - ACTIONS(3364), 1, - anon_sym_SEMI, + [71397] = 2, + ACTIONS(3540), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62894] = 2, - ACTIONS(3366), 1, - anon_sym_SEMI, + [71405] = 2, + ACTIONS(3542), 1, + anon_sym_for, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62902] = 2, - ACTIONS(3368), 1, - anon_sym_SEMI, + [71413] = 2, + ACTIONS(3544), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62910] = 2, - ACTIONS(3370), 1, - anon_sym_RPAREN, + [71421] = 2, + ACTIONS(3546), 1, + anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62918] = 2, - ACTIONS(3372), 1, - anon_sym_COMMA, + [71429] = 2, + ACTIONS(3548), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62926] = 2, - ACTIONS(1760), 1, - anon_sym_RBRACK, + [71437] = 2, + ACTIONS(3550), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62934] = 2, - ACTIONS(3374), 1, + [71445] = 2, + ACTIONS(3552), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62942] = 2, - ACTIONS(3376), 1, + [71453] = 2, + ACTIONS(3554), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62950] = 2, - ACTIONS(2246), 1, - anon_sym_RBRACE, + [71461] = 2, + ACTIONS(3556), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62958] = 2, - ACTIONS(3378), 1, - anon_sym_SEMI, + [71469] = 2, + ACTIONS(3558), 1, + anon_sym_BANG, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62966] = 2, - ACTIONS(3380), 1, - anon_sym_SEMI, + [71477] = 2, + ACTIONS(3560), 1, + anon_sym_DASH_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62974] = 2, - ACTIONS(3382), 1, - anon_sym_RBRACK, + [71485] = 2, + ACTIONS(3562), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62982] = 2, - ACTIONS(3384), 1, + [71493] = 2, + ACTIONS(3564), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62990] = 2, - ACTIONS(3386), 1, - anon_sym_COLON, + [71501] = 2, + ACTIONS(3566), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [62998] = 2, - ACTIONS(3388), 1, + [71509] = 2, + ACTIONS(3568), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63006] = 2, - ACTIONS(3390), 1, + [71517] = 2, + ACTIONS(3570), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63014] = 2, - ACTIONS(3392), 1, - anon_sym_RPAREN, + [71525] = 2, + ACTIONS(3572), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63022] = 2, - ACTIONS(3394), 1, - anon_sym_in, + [71533] = 2, + ACTIONS(3574), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63030] = 2, - ACTIONS(3396), 1, - anon_sym_LPAREN, + [71541] = 2, + ACTIONS(3576), 1, + anon_sym_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63038] = 2, - ACTIONS(3398), 1, - anon_sym_RBRACK, + [71549] = 2, + ACTIONS(3578), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63046] = 2, - ACTIONS(3400), 1, - anon_sym_from, + [71557] = 2, + ACTIONS(3580), 1, + anon_sym_LT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63054] = 2, - ACTIONS(3402), 1, - anon_sym_RBRACK, + [71565] = 2, + ACTIONS(3582), 1, + anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63062] = 2, - ACTIONS(3404), 1, - anon_sym_EQ_GT, + [71573] = 2, + ACTIONS(3584), 1, + anon_sym_LT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63070] = 2, - ACTIONS(3406), 1, + [71581] = 2, + ACTIONS(3586), 1, anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63078] = 2, - ACTIONS(3408), 1, - anon_sym_EQ, + [71589] = 2, + ACTIONS(3588), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63086] = 2, - ACTIONS(3410), 1, - anon_sym_LBRACE, + [71597] = 2, + ACTIONS(3590), 1, + anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63094] = 2, - ACTIONS(3412), 1, + [71605] = 2, + ACTIONS(3592), 1, + anon_sym_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [71613] = 2, + ACTIONS(3594), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63102] = 2, - ACTIONS(3414), 1, + [71621] = 2, + ACTIONS(3596), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63110] = 2, - ACTIONS(3416), 1, - anon_sym_LPAREN, + [71629] = 2, + ACTIONS(3598), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63118] = 2, - ACTIONS(3418), 1, - anon_sym_COLON, + [71637] = 2, + ACTIONS(3600), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63126] = 2, - ACTIONS(3420), 1, - anon_sym_LBRACE, + [71645] = 2, + ACTIONS(3602), 1, + anon_sym_catch, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63134] = 2, - ACTIONS(3422), 1, - anon_sym_PIPE, + [71653] = 2, + ACTIONS(3604), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63142] = 2, - ACTIONS(3424), 1, + [71661] = 2, + ACTIONS(3606), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63150] = 2, - ACTIONS(3426), 1, - anon_sym_SEMI, + [71669] = 2, + ACTIONS(3608), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63158] = 2, - ACTIONS(3428), 1, - anon_sym_RBRACK, + [71677] = 2, + ACTIONS(3610), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63166] = 2, - ACTIONS(3430), 1, - anon_sym_RBRACE, + [71685] = 2, + ACTIONS(3612), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63174] = 2, - ACTIONS(3432), 1, - anon_sym_SEMI, + [71693] = 2, + ACTIONS(3614), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63182] = 2, - ACTIONS(3434), 1, - anon_sym_for, + [71701] = 2, + ACTIONS(3616), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63190] = 2, - ACTIONS(3298), 1, - anon_sym_SEMI, + [71709] = 2, + ACTIONS(3618), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63198] = 2, - ACTIONS(3436), 1, + [71717] = 2, + ACTIONS(3620), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63206] = 2, - ACTIONS(3438), 1, - anon_sym_SEMI, + [71725] = 2, + ACTIONS(3622), 1, + anon_sym_COMMA, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63214] = 2, - ACTIONS(3440), 1, - anon_sym_RPAREN, + [71733] = 2, + ACTIONS(3624), 1, + anon_sym_from, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63222] = 2, - ACTIONS(3442), 1, - anon_sym_COLON, + [71741] = 2, + ACTIONS(3626), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63230] = 2, - ACTIONS(3444), 1, - anon_sym_RBRACE, + [71749] = 2, + ACTIONS(3628), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63238] = 2, - ACTIONS(3446), 1, - anon_sym_COLON, + [71757] = 2, + ACTIONS(3630), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63246] = 2, - ACTIONS(1752), 1, - anon_sym_RBRACK, + [71765] = 2, + ACTIONS(3632), 1, + ts_builtin_sym_end, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63254] = 2, - ACTIONS(3448), 1, - anon_sym_LPAREN, + [71773] = 2, + ACTIONS(3634), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63262] = 2, - ACTIONS(3450), 1, - anon_sym_RPAREN, + [71781] = 2, + ACTIONS(3636), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63270] = 2, - ACTIONS(3452), 1, - anon_sym_RPAREN, + [71789] = 2, + ACTIONS(3638), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63278] = 2, - ACTIONS(3454), 1, + [71797] = 2, + ACTIONS(3640), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63286] = 2, - ACTIONS(3456), 1, + [71805] = 2, + ACTIONS(3642), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63294] = 2, - ACTIONS(3458), 1, - anon_sym_LPAREN, + [71813] = 2, + ACTIONS(3474), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63302] = 2, - ACTIONS(3460), 1, - anon_sym_DASH_GT, + [71821] = 2, + ACTIONS(3644), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63310] = 2, - ACTIONS(3462), 1, - anon_sym_PIPE, + [71829] = 2, + ACTIONS(3646), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63318] = 2, - ACTIONS(3464), 1, + [71837] = 2, + ACTIONS(3648), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63326] = 2, - ACTIONS(3466), 1, - anon_sym_RBRACK, + [71845] = 2, + ACTIONS(2432), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63334] = 2, - ACTIONS(3468), 1, - anon_sym_RBRACK, + [71853] = 2, + ACTIONS(3650), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63342] = 2, - ACTIONS(3470), 1, - anon_sym_LPAREN, + [71861] = 2, + ACTIONS(3652), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63350] = 2, - ACTIONS(3472), 1, + [71869] = 2, + ACTIONS(3654), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63358] = 2, - ACTIONS(3296), 1, - anon_sym_COLON, + [71877] = 2, + ACTIONS(3656), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63366] = 2, - ACTIONS(3474), 1, + [71885] = 2, + ACTIONS(3658), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63374] = 2, - ACTIONS(3476), 1, - anon_sym_SEMI, + [71893] = 2, + ACTIONS(3660), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63382] = 2, - ACTIONS(3478), 1, + [71901] = 2, + ACTIONS(3662), 1, anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63390] = 2, - ACTIONS(3480), 1, + [71909] = 2, + ACTIONS(3664), 1, anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63398] = 2, - ACTIONS(3482), 1, - anon_sym_RPAREN, + [71917] = 2, + ACTIONS(3666), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63406] = 2, - ACTIONS(2250), 1, - anon_sym_RBRACE, + [71925] = 2, + ACTIONS(3668), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63414] = 2, - ACTIONS(3484), 1, - anon_sym_SEMI, + [71933] = 2, + ACTIONS(3670), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63422] = 2, - ACTIONS(3486), 1, - ts_builtin_sym_end, + [71941] = 2, + ACTIONS(3672), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63430] = 2, - ACTIONS(3488), 1, - anon_sym_RPAREN, + [71949] = 2, + ACTIONS(3674), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63438] = 2, - ACTIONS(3490), 1, + [71957] = 2, + ACTIONS(3676), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63446] = 2, - ACTIONS(3492), 1, - anon_sym_LPAREN, + [71965] = 2, + ACTIONS(3678), 1, + anon_sym_as, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63454] = 2, - ACTIONS(3494), 1, - anon_sym_PIPE, + [71973] = 2, + ACTIONS(3680), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63462] = 2, - ACTIONS(3496), 1, + [71981] = 2, + ACTIONS(3682), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63470] = 2, - ACTIONS(3498), 1, - anon_sym_RPAREN, + [71989] = 2, + ACTIONS(3684), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63478] = 2, - ACTIONS(3500), 1, + [71997] = 2, + ACTIONS(3686), 1, anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63486] = 2, - ACTIONS(3502), 1, - anon_sym_COLON, + [72005] = 2, + ACTIONS(3688), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63494] = 2, - ACTIONS(3504), 1, - anon_sym_LT, + [72013] = 2, + ACTIONS(3690), 1, + anon_sym_BANG, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63502] = 2, - ACTIONS(3506), 1, - anon_sym_LT, + [72021] = 2, + ACTIONS(3692), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63510] = 2, - ACTIONS(3508), 1, + [72029] = 2, + ACTIONS(3694), 1, anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63518] = 2, - ACTIONS(3510), 1, - anon_sym_SEMI, + [72037] = 2, + ACTIONS(3696), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63526] = 2, - ACTIONS(3512), 1, + [72045] = 2, + ACTIONS(3698), 1, anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63534] = 2, - ACTIONS(3514), 1, + [72053] = 2, + ACTIONS(2619), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63542] = 2, - ACTIONS(3516), 1, + [72061] = 2, + ACTIONS(3700), 1, anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63550] = 2, - ACTIONS(3518), 1, - anon_sym_catch, + [72069] = 2, + ACTIONS(3702), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63558] = 2, - ACTIONS(3520), 1, + [72077] = 2, + ACTIONS(2300), 1, anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63566] = 2, - ACTIONS(3522), 1, - anon_sym_COLON, + [72085] = 2, + ACTIONS(3704), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63574] = 2, - ACTIONS(1994), 1, - anon_sym_COLON, + [72093] = 2, + ACTIONS(3706), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63582] = 2, - ACTIONS(3524), 1, + [72101] = 2, + ACTIONS(3708), 1, anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63590] = 2, - ACTIONS(3526), 1, - anon_sym_RPAREN, + [72109] = 2, + ACTIONS(3710), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63598] = 2, - ACTIONS(3528), 1, + [72117] = 2, + ACTIONS(3712), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63606] = 2, - ACTIONS(3530), 1, + [72125] = 2, + ACTIONS(3714), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63614] = 2, - ACTIONS(3532), 1, + [72133] = 2, + ACTIONS(3716), 1, anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63622] = 2, - ACTIONS(3534), 1, - anon_sym_RBRACE, + [72141] = 2, + ACTIONS(3718), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63630] = 2, - ACTIONS(3536), 1, - anon_sym_RBRACK, + [72149] = 2, + ACTIONS(3720), 1, + anon_sym_BANG, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63638] = 2, - ACTIONS(3538), 1, - anon_sym_RPAREN, + [72157] = 2, + ACTIONS(3722), 1, + anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63646] = 2, - ACTIONS(3540), 1, - anon_sym_LBRACE, + [72165] = 2, + ACTIONS(3724), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63654] = 2, - ACTIONS(3542), 1, - anon_sym_PIPE, + [72173] = 2, + ACTIONS(3726), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63662] = 2, - ACTIONS(3544), 1, - anon_sym_RPAREN, + [72181] = 2, + ACTIONS(3728), 1, + anon_sym_from, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63670] = 2, - ACTIONS(3546), 1, + [72189] = 2, + ACTIONS(3730), 1, anon_sym_BANG, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63678] = 2, - ACTIONS(3548), 1, - anon_sym_from, + [72197] = 2, + ACTIONS(3732), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63686] = 2, - ACTIONS(3550), 1, - anon_sym_RPAREN, + [72205] = 2, + ACTIONS(3734), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63694] = 2, - ACTIONS(3552), 1, - anon_sym_in, + [72213] = 2, + ACTIONS(3736), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63702] = 2, - ACTIONS(3554), 1, + [72221] = 2, + ACTIONS(3738), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63710] = 2, - ACTIONS(3556), 1, + [72229] = 2, + ACTIONS(3740), 1, anon_sym_EQ, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63718] = 2, - ACTIONS(3558), 1, + [72237] = 2, + ACTIONS(3742), 1, anon_sym_catch, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63726] = 2, - ACTIONS(3560), 1, - anon_sym_RBRACE, + [72245] = 2, + ACTIONS(3744), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63734] = 2, - ACTIONS(3562), 1, + [72253] = 2, + ACTIONS(3746), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63742] = 2, - ACTIONS(3564), 1, + [72261] = 2, + ACTIONS(3748), 1, anon_sym_COMMA, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63750] = 2, - ACTIONS(3566), 1, - anon_sym_RBRACK, + [72269] = 2, + ACTIONS(3750), 1, + anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63758] = 2, - ACTIONS(3568), 1, - anon_sym_RPAREN, + [72277] = 2, + ACTIONS(3752), 1, + anon_sym_DASH_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63766] = 2, - ACTIONS(3570), 1, + [72285] = 2, + ACTIONS(3754), 1, anon_sym_from, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63774] = 2, - ACTIONS(3572), 1, - anon_sym_RBRACE, + [72293] = 2, + ACTIONS(3756), 1, + anon_sym_EQ_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63782] = 2, - ACTIONS(3574), 1, + [72301] = 2, + ACTIONS(3758), 1, anon_sym_from, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63790] = 2, - ACTIONS(3576), 1, + [72309] = 2, + ACTIONS(3760), 1, anon_sym_from, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63798] = 2, - ACTIONS(3578), 1, - anon_sym_RPAREN, + [72317] = 2, + ACTIONS(3762), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63806] = 2, - ACTIONS(3580), 1, - anon_sym_PIPE, + [72325] = 2, + ACTIONS(3764), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63814] = 2, - ACTIONS(3582), 1, - anon_sym_PIPE, + [72333] = 2, + ACTIONS(3766), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63822] = 2, - ACTIONS(3584), 1, + [72341] = 2, + ACTIONS(3768), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63830] = 2, - ACTIONS(3586), 1, - anon_sym_BANG, + [72349] = 2, + ACTIONS(3770), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63838] = 2, - ACTIONS(3588), 1, - anon_sym_COLON, + [72357] = 2, + ACTIONS(3772), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63846] = 2, - ACTIONS(3590), 1, - anon_sym_DASH_GT, + [72365] = 2, + ACTIONS(3774), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63854] = 2, - ACTIONS(3592), 1, - anon_sym_LBRACE, + [72373] = 2, + ACTIONS(3776), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63862] = 2, - ACTIONS(3594), 1, - anon_sym_LBRACK, + [72381] = 2, + ACTIONS(3778), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63870] = 2, - ACTIONS(3596), 1, - anon_sym_COLON, + [72389] = 2, + ACTIONS(3780), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63878] = 2, - ACTIONS(3598), 1, + [72397] = 2, + ACTIONS(3782), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63886] = 2, - ACTIONS(3600), 1, - anon_sym_RPAREN, + [72405] = 2, + ACTIONS(3784), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63894] = 2, - ACTIONS(3602), 1, - anon_sym_DASH_GT, + [72413] = 2, + ACTIONS(3786), 1, + anon_sym_in, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63902] = 2, - ACTIONS(3604), 1, - anon_sym_RPAREN, + [72421] = 2, + ACTIONS(3788), 1, + anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63910] = 2, - ACTIONS(3606), 1, + [72429] = 2, + ACTIONS(3790), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63918] = 2, - ACTIONS(3608), 1, + [72437] = 2, + ACTIONS(3792), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63926] = 2, - ACTIONS(2420), 1, + [72445] = 2, + ACTIONS(3794), 1, + anon_sym_COLON, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72453] = 2, + ACTIONS(3796), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63934] = 2, - ACTIONS(3610), 1, + [72461] = 2, + ACTIONS(3798), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63942] = 2, - ACTIONS(3612), 1, - anon_sym_PIPE, + [72469] = 2, + ACTIONS(3800), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63950] = 2, - ACTIONS(3614), 1, + [72477] = 2, + ACTIONS(2283), 1, anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63958] = 2, - ACTIONS(3616), 1, - anon_sym_RPAREN, + [72485] = 2, + ACTIONS(3802), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63966] = 2, - ACTIONS(3618), 1, - anon_sym_RPAREN, + [72493] = 2, + ACTIONS(2436), 1, + anon_sym_RBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63974] = 2, - ACTIONS(3620), 1, + [72501] = 2, + ACTIONS(3804), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72509] = 2, + ACTIONS(3806), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63982] = 2, - ACTIONS(3622), 1, + [72517] = 2, + ACTIONS(3808), 1, + anon_sym_SEMI, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72525] = 2, + ACTIONS(3810), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63990] = 2, - ACTIONS(3624), 1, + [72533] = 2, + ACTIONS(3812), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [63998] = 2, - ACTIONS(3626), 1, + [72541] = 2, + ACTIONS(3814), 1, anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64006] = 2, - ACTIONS(3628), 1, - anon_sym_BANG, + [72549] = 2, + ACTIONS(3816), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64014] = 2, - ACTIONS(3630), 1, - anon_sym_RPAREN, + [72557] = 2, + ACTIONS(3818), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64022] = 2, - ACTIONS(3632), 1, + [72565] = 2, + ACTIONS(3820), 1, anon_sym_in, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64030] = 2, - ACTIONS(3634), 1, + [72573] = 2, + ACTIONS(3822), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64038] = 2, - ACTIONS(3636), 1, - anon_sym_PIPE, + [72581] = 2, + ACTIONS(3824), 1, + anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64046] = 2, - ACTIONS(3638), 1, + [72589] = 2, + ACTIONS(3826), 1, anon_sym_COMMA, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64054] = 2, - ACTIONS(3640), 1, - anon_sym_COLON, + [72597] = 2, + ACTIONS(3828), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64062] = 2, - ACTIONS(3642), 1, + [72605] = 2, + ACTIONS(3830), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64070] = 2, - ACTIONS(3644), 1, + [72613] = 2, + ACTIONS(3832), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64078] = 2, - ACTIONS(3646), 1, + [72621] = 2, + ACTIONS(3834), 1, anon_sym_LBRACE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64086] = 2, - ACTIONS(3648), 1, - anon_sym_SEMI, + [72629] = 2, + ACTIONS(3836), 1, + anon_sym_PIPE, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64094] = 2, - ACTIONS(3650), 1, + [72637] = 2, + ACTIONS(3838), 1, anon_sym_in, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64102] = 2, - ACTIONS(3652), 1, - anon_sym_PIPE, + [72645] = 2, + ACTIONS(3840), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64110] = 2, - ACTIONS(3654), 1, + [72653] = 2, + ACTIONS(3842), 1, anon_sym_COMMA, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64118] = 2, - ACTIONS(3656), 1, - anon_sym_RPAREN, - ACTIONS(3), 2, - sym_line_comment, - sym_block_comment, - [64126] = 2, - ACTIONS(3658), 1, + [72661] = 2, + ACTIONS(3844), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64134] = 2, - ACTIONS(3660), 1, - anon_sym_PIPE, + [72669] = 2, + ACTIONS(1990), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64142] = 2, - ACTIONS(3662), 1, + [72677] = 2, + ACTIONS(3846), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64150] = 2, - ACTIONS(3664), 1, - anon_sym_COLON, + [72685] = 2, + ACTIONS(3848), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64158] = 2, - ACTIONS(3666), 1, - anon_sym_PIPE, + [72693] = 2, + ACTIONS(3850), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64166] = 2, - ACTIONS(3668), 1, + [72701] = 2, + ACTIONS(3852), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64174] = 2, - ACTIONS(3670), 1, - anon_sym_RPAREN, + [72709] = 2, + ACTIONS(3854), 1, + anon_sym_EQ_GT, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64182] = 2, - ACTIONS(3672), 1, - anon_sym_RPAREN, + [72717] = 2, + ACTIONS(3856), 1, + anon_sym_LBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64190] = 2, - ACTIONS(3674), 1, + [72725] = 2, + ACTIONS(3858), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64198] = 2, - ACTIONS(3676), 1, + [72733] = 2, + ACTIONS(3860), 1, + anon_sym_DASH_GT, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72741] = 2, + ACTIONS(3862), 1, anon_sym_for, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64206] = 2, - ACTIONS(3678), 1, + [72749] = 2, + ACTIONS(3864), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64214] = 2, - ACTIONS(3680), 1, + [72757] = 2, + ACTIONS(3866), 1, + anon_sym_from, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72765] = 2, + ACTIONS(3868), 1, + anon_sym_RBRACE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72773] = 2, + ACTIONS(3870), 1, anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64222] = 2, - ACTIONS(3682), 1, + [72781] = 2, + ACTIONS(3872), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64230] = 2, - ACTIONS(3684), 1, - anon_sym_EQ_GT, + [72789] = 2, + ACTIONS(3874), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64238] = 2, - ACTIONS(3686), 1, + [72797] = 2, + ACTIONS(3876), 1, + anon_sym_SEMI, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72805] = 2, + ACTIONS(3878), 1, anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64246] = 2, - ACTIONS(3688), 1, - anon_sym_RBRACK, + [72813] = 2, + ACTIONS(3880), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64254] = 2, - ACTIONS(3690), 1, - anon_sym_SEMI, + [72821] = 2, + ACTIONS(3882), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64262] = 2, - ACTIONS(3692), 1, - anon_sym_SEMI, + [72829] = 2, + ACTIONS(3884), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64270] = 2, - ACTIONS(3694), 1, - anon_sym_from, + [72837] = 2, + ACTIONS(3886), 1, + anon_sym_COLON, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64278] = 2, - ACTIONS(3696), 1, + [72845] = 2, + ACTIONS(3888), 1, + anon_sym_PIPE, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72853] = 2, + ACTIONS(3890), 1, + anon_sym_RBRACK, + ACTIONS(3), 2, + sym_line_comment, + sym_block_comment, + [72861] = 2, + ACTIONS(3892), 1, anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64286] = 2, - ACTIONS(3698), 1, - anon_sym_COLON, + [72869] = 2, + ACTIONS(3894), 1, + anon_sym_RBRACK, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64294] = 2, - ACTIONS(3700), 1, + [72877] = 2, + ACTIONS(3896), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64302] = 2, - ACTIONS(3702), 1, - anon_sym_RPAREN, + [72885] = 2, + ACTIONS(3476), 1, + anon_sym_SEMI, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64310] = 2, - ACTIONS(3704), 1, - anon_sym_as, + [72893] = 2, + ACTIONS(3898), 1, + anon_sym_RPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64318] = 2, - ACTIONS(3706), 1, + [72901] = 2, + ACTIONS(3900), 1, anon_sym_as, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64326] = 2, - ACTIONS(3708), 1, + [72909] = 2, + ACTIONS(3902), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64334] = 2, - ACTIONS(3710), 1, + [72917] = 2, + ACTIONS(3904), 1, anon_sym_LPAREN, ACTIONS(3), 2, sym_line_comment, sym_block_comment, - [64342] = 2, - ACTIONS(3712), 1, - anon_sym_BANG, + [72925] = 2, + ACTIONS(3906), 1, + anon_sym_in, ACTIONS(3), 2, sym_line_comment, sym_block_comment, }; static const uint32_t ts_small_parse_table_map[] = { - [SMALL_STATE(102)] = 0, - [SMALL_STATE(103)] = 78, - [SMALL_STATE(104)] = 156, - [SMALL_STATE(105)] = 234, - [SMALL_STATE(106)] = 311, - [SMALL_STATE(107)] = 380, - [SMALL_STATE(108)] = 459, - [SMALL_STATE(109)] = 544, - [SMALL_STATE(110)] = 629, - [SMALL_STATE(111)] = 730, - [SMALL_STATE(112)] = 831, - [SMALL_STATE(113)] = 900, - [SMALL_STATE(114)] = 971, - [SMALL_STATE(115)] = 1046, - [SMALL_STATE(116)] = 1127, - [SMALL_STATE(117)] = 1244, - [SMALL_STATE(118)] = 1361, - [SMALL_STATE(119)] = 1475, - [SMALL_STATE(120)] = 1589, - [SMALL_STATE(121)] = 1703, - [SMALL_STATE(122)] = 1817, - [SMALL_STATE(123)] = 1931, - [SMALL_STATE(124)] = 2045, - [SMALL_STATE(125)] = 2159, - [SMALL_STATE(126)] = 2273, - [SMALL_STATE(127)] = 2387, - [SMALL_STATE(128)] = 2501, - [SMALL_STATE(129)] = 2615, - [SMALL_STATE(130)] = 2729, - [SMALL_STATE(131)] = 2843, - [SMALL_STATE(132)] = 2957, - [SMALL_STATE(133)] = 3071, - [SMALL_STATE(134)] = 3185, - [SMALL_STATE(135)] = 3299, - [SMALL_STATE(136)] = 3413, - [SMALL_STATE(137)] = 3527, - [SMALL_STATE(138)] = 3641, - [SMALL_STATE(139)] = 3752, - [SMALL_STATE(140)] = 3863, - [SMALL_STATE(141)] = 3974, - [SMALL_STATE(142)] = 4085, - [SMALL_STATE(143)] = 4196, - [SMALL_STATE(144)] = 4307, - [SMALL_STATE(145)] = 4418, - [SMALL_STATE(146)] = 4529, - [SMALL_STATE(147)] = 4640, - [SMALL_STATE(148)] = 4751, - [SMALL_STATE(149)] = 4862, - [SMALL_STATE(150)] = 4973, - [SMALL_STATE(151)] = 5084, - [SMALL_STATE(152)] = 5195, - [SMALL_STATE(153)] = 5306, - [SMALL_STATE(154)] = 5414, - [SMALL_STATE(155)] = 5522, - [SMALL_STATE(156)] = 5630, - [SMALL_STATE(157)] = 5738, - [SMALL_STATE(158)] = 5846, - [SMALL_STATE(159)] = 5954, - [SMALL_STATE(160)] = 6062, - [SMALL_STATE(161)] = 6170, - [SMALL_STATE(162)] = 6278, - [SMALL_STATE(163)] = 6386, - [SMALL_STATE(164)] = 6494, - [SMALL_STATE(165)] = 6602, - [SMALL_STATE(166)] = 6710, - [SMALL_STATE(167)] = 6818, - [SMALL_STATE(168)] = 6926, - [SMALL_STATE(169)] = 7034, - [SMALL_STATE(170)] = 7142, - [SMALL_STATE(171)] = 7250, - [SMALL_STATE(172)] = 7358, - [SMALL_STATE(173)] = 7466, - [SMALL_STATE(174)] = 7574, - [SMALL_STATE(175)] = 7682, - [SMALL_STATE(176)] = 7790, - [SMALL_STATE(177)] = 7898, - [SMALL_STATE(178)] = 8006, - [SMALL_STATE(179)] = 8114, - [SMALL_STATE(180)] = 8222, - [SMALL_STATE(181)] = 8330, - [SMALL_STATE(182)] = 8438, - [SMALL_STATE(183)] = 8546, - [SMALL_STATE(184)] = 8628, - [SMALL_STATE(185)] = 8736, - [SMALL_STATE(186)] = 8844, - [SMALL_STATE(187)] = 8952, - [SMALL_STATE(188)] = 9060, - [SMALL_STATE(189)] = 9168, - [SMALL_STATE(190)] = 9276, - [SMALL_STATE(191)] = 9384, - [SMALL_STATE(192)] = 9492, - [SMALL_STATE(193)] = 9600, - [SMALL_STATE(194)] = 9708, - [SMALL_STATE(195)] = 9816, - [SMALL_STATE(196)] = 9924, - [SMALL_STATE(197)] = 10032, - [SMALL_STATE(198)] = 10140, - [SMALL_STATE(199)] = 10248, - [SMALL_STATE(200)] = 10356, - [SMALL_STATE(201)] = 10464, - [SMALL_STATE(202)] = 10572, - [SMALL_STATE(203)] = 10680, - [SMALL_STATE(204)] = 10788, - [SMALL_STATE(205)] = 10896, - [SMALL_STATE(206)] = 11004, - [SMALL_STATE(207)] = 11112, - [SMALL_STATE(208)] = 11220, - [SMALL_STATE(209)] = 11328, - [SMALL_STATE(210)] = 11436, - [SMALL_STATE(211)] = 11544, - [SMALL_STATE(212)] = 11652, - [SMALL_STATE(213)] = 11760, - [SMALL_STATE(214)] = 11868, - [SMALL_STATE(215)] = 11976, - [SMALL_STATE(216)] = 12084, - [SMALL_STATE(217)] = 12192, - [SMALL_STATE(218)] = 12300, - [SMALL_STATE(219)] = 12408, - [SMALL_STATE(220)] = 12516, - [SMALL_STATE(221)] = 12624, - [SMALL_STATE(222)] = 12732, - [SMALL_STATE(223)] = 12840, - [SMALL_STATE(224)] = 12948, - [SMALL_STATE(225)] = 13056, - [SMALL_STATE(226)] = 13164, - [SMALL_STATE(227)] = 13272, - [SMALL_STATE(228)] = 13380, - [SMALL_STATE(229)] = 13488, - [SMALL_STATE(230)] = 13596, - [SMALL_STATE(231)] = 13704, - [SMALL_STATE(232)] = 13812, - [SMALL_STATE(233)] = 13920, - [SMALL_STATE(234)] = 14028, - [SMALL_STATE(235)] = 14136, - [SMALL_STATE(236)] = 14244, - [SMALL_STATE(237)] = 14352, - [SMALL_STATE(238)] = 14460, - [SMALL_STATE(239)] = 14568, - [SMALL_STATE(240)] = 14676, - [SMALL_STATE(241)] = 14784, - [SMALL_STATE(242)] = 14892, - [SMALL_STATE(243)] = 15000, - [SMALL_STATE(244)] = 15108, - [SMALL_STATE(245)] = 15216, - [SMALL_STATE(246)] = 15324, - [SMALL_STATE(247)] = 15432, - [SMALL_STATE(248)] = 15540, - [SMALL_STATE(249)] = 15648, - [SMALL_STATE(250)] = 15756, - [SMALL_STATE(251)] = 15864, - [SMALL_STATE(252)] = 15972, - [SMALL_STATE(253)] = 16080, - [SMALL_STATE(254)] = 16188, - [SMALL_STATE(255)] = 16296, - [SMALL_STATE(256)] = 16404, - [SMALL_STATE(257)] = 16512, - [SMALL_STATE(258)] = 16620, - [SMALL_STATE(259)] = 16728, - [SMALL_STATE(260)] = 16836, - [SMALL_STATE(261)] = 16944, - [SMALL_STATE(262)] = 17052, - [SMALL_STATE(263)] = 17160, - [SMALL_STATE(264)] = 17268, - [SMALL_STATE(265)] = 17376, - [SMALL_STATE(266)] = 17484, - [SMALL_STATE(267)] = 17592, - [SMALL_STATE(268)] = 17700, - [SMALL_STATE(269)] = 17808, - [SMALL_STATE(270)] = 17916, - [SMALL_STATE(271)] = 18024, - [SMALL_STATE(272)] = 18132, - [SMALL_STATE(273)] = 18240, - [SMALL_STATE(274)] = 18348, - [SMALL_STATE(275)] = 18456, - [SMALL_STATE(276)] = 18564, - [SMALL_STATE(277)] = 18672, - [SMALL_STATE(278)] = 18780, - [SMALL_STATE(279)] = 18888, - [SMALL_STATE(280)] = 18996, - [SMALL_STATE(281)] = 19104, - [SMALL_STATE(282)] = 19212, - [SMALL_STATE(283)] = 19320, - [SMALL_STATE(284)] = 19428, - [SMALL_STATE(285)] = 19536, - [SMALL_STATE(286)] = 19644, - [SMALL_STATE(287)] = 19752, - [SMALL_STATE(288)] = 19860, - [SMALL_STATE(289)] = 19968, - [SMALL_STATE(290)] = 20076, - [SMALL_STATE(291)] = 20184, - [SMALL_STATE(292)] = 20292, - [SMALL_STATE(293)] = 20400, - [SMALL_STATE(294)] = 20508, - [SMALL_STATE(295)] = 20612, - [SMALL_STATE(296)] = 20716, - [SMALL_STATE(297)] = 20820, - [SMALL_STATE(298)] = 20900, - [SMALL_STATE(299)] = 20980, - [SMALL_STATE(300)] = 21084, - [SMALL_STATE(301)] = 21188, - [SMALL_STATE(302)] = 21292, - [SMALL_STATE(303)] = 21396, - [SMALL_STATE(304)] = 21500, - [SMALL_STATE(305)] = 21604, - [SMALL_STATE(306)] = 21684, - [SMALL_STATE(307)] = 21788, - [SMALL_STATE(308)] = 21868, - [SMALL_STATE(309)] = 21972, - [SMALL_STATE(310)] = 22052, - [SMALL_STATE(311)] = 22156, - [SMALL_STATE(312)] = 22260, - [SMALL_STATE(313)] = 22364, - [SMALL_STATE(314)] = 22468, - [SMALL_STATE(315)] = 22572, - [SMALL_STATE(316)] = 22676, - [SMALL_STATE(317)] = 22780, - [SMALL_STATE(318)] = 22884, - [SMALL_STATE(319)] = 22964, - [SMALL_STATE(320)] = 23068, - [SMALL_STATE(321)] = 23172, - [SMALL_STATE(322)] = 23276, - [SMALL_STATE(323)] = 23380, - [SMALL_STATE(324)] = 23484, - [SMALL_STATE(325)] = 23588, - [SMALL_STATE(326)] = 23668, - [SMALL_STATE(327)] = 23772, - [SMALL_STATE(328)] = 23876, - [SMALL_STATE(329)] = 23980, - [SMALL_STATE(330)] = 24084, - [SMALL_STATE(331)] = 24188, - [SMALL_STATE(332)] = 24292, - [SMALL_STATE(333)] = 24396, - [SMALL_STATE(334)] = 24500, - [SMALL_STATE(335)] = 24604, - [SMALL_STATE(336)] = 24708, - [SMALL_STATE(337)] = 24812, - [SMALL_STATE(338)] = 24916, - [SMALL_STATE(339)] = 25020, - [SMALL_STATE(340)] = 25100, - [SMALL_STATE(341)] = 25180, - [SMALL_STATE(342)] = 25260, - [SMALL_STATE(343)] = 25364, - [SMALL_STATE(344)] = 25444, - [SMALL_STATE(345)] = 25524, - [SMALL_STATE(346)] = 25604, - [SMALL_STATE(347)] = 25708, - [SMALL_STATE(348)] = 25812, - [SMALL_STATE(349)] = 25916, - [SMALL_STATE(350)] = 26020, - [SMALL_STATE(351)] = 26124, - [SMALL_STATE(352)] = 26228, - [SMALL_STATE(353)] = 26332, - [SMALL_STATE(354)] = 26412, - [SMALL_STATE(355)] = 26492, - [SMALL_STATE(356)] = 26572, - [SMALL_STATE(357)] = 26676, - [SMALL_STATE(358)] = 26756, - [SMALL_STATE(359)] = 26836, - [SMALL_STATE(360)] = 26916, - [SMALL_STATE(361)] = 26996, - [SMALL_STATE(362)] = 27100, - [SMALL_STATE(363)] = 27180, - [SMALL_STATE(364)] = 27260, - [SMALL_STATE(365)] = 27340, - [SMALL_STATE(366)] = 27444, - [SMALL_STATE(367)] = 27524, - [SMALL_STATE(368)] = 27604, - [SMALL_STATE(369)] = 27684, - [SMALL_STATE(370)] = 27788, - [SMALL_STATE(371)] = 27892, - [SMALL_STATE(372)] = 27996, - [SMALL_STATE(373)] = 28100, - [SMALL_STATE(374)] = 28204, - [SMALL_STATE(375)] = 28284, - [SMALL_STATE(376)] = 28364, - [SMALL_STATE(377)] = 28444, - [SMALL_STATE(378)] = 28524, - [SMALL_STATE(379)] = 28604, - [SMALL_STATE(380)] = 28684, - [SMALL_STATE(381)] = 28764, - [SMALL_STATE(382)] = 28844, - [SMALL_STATE(383)] = 28924, - [SMALL_STATE(384)] = 29004, - [SMALL_STATE(385)] = 29084, - [SMALL_STATE(386)] = 29164, - [SMALL_STATE(387)] = 29268, - [SMALL_STATE(388)] = 29348, - [SMALL_STATE(389)] = 29428, - [SMALL_STATE(390)] = 29508, - [SMALL_STATE(391)] = 29588, - [SMALL_STATE(392)] = 29668, - [SMALL_STATE(393)] = 29748, - [SMALL_STATE(394)] = 29852, - [SMALL_STATE(395)] = 29956, - [SMALL_STATE(396)] = 30060, - [SMALL_STATE(397)] = 30164, - [SMALL_STATE(398)] = 30268, - [SMALL_STATE(399)] = 30372, - [SMALL_STATE(400)] = 30476, - [SMALL_STATE(401)] = 30580, - [SMALL_STATE(402)] = 30684, - [SMALL_STATE(403)] = 30788, - [SMALL_STATE(404)] = 30892, - [SMALL_STATE(405)] = 30996, - [SMALL_STATE(406)] = 31100, - [SMALL_STATE(407)] = 31204, - [SMALL_STATE(408)] = 31308, - [SMALL_STATE(409)] = 31412, - [SMALL_STATE(410)] = 31516, - [SMALL_STATE(411)] = 31620, - [SMALL_STATE(412)] = 31724, - [SMALL_STATE(413)] = 31828, - [SMALL_STATE(414)] = 31932, - [SMALL_STATE(415)] = 32036, - [SMALL_STATE(416)] = 32140, - [SMALL_STATE(417)] = 32244, - [SMALL_STATE(418)] = 32348, - [SMALL_STATE(419)] = 32452, - [SMALL_STATE(420)] = 32556, - [SMALL_STATE(421)] = 32660, - [SMALL_STATE(422)] = 32764, - [SMALL_STATE(423)] = 32868, - [SMALL_STATE(424)] = 32972, - [SMALL_STATE(425)] = 33076, - [SMALL_STATE(426)] = 33180, - [SMALL_STATE(427)] = 33284, - [SMALL_STATE(428)] = 33388, - [SMALL_STATE(429)] = 33492, - [SMALL_STATE(430)] = 33596, - [SMALL_STATE(431)] = 33700, - [SMALL_STATE(432)] = 33804, - [SMALL_STATE(433)] = 33908, - [SMALL_STATE(434)] = 34012, - [SMALL_STATE(435)] = 34116, - [SMALL_STATE(436)] = 34220, - [SMALL_STATE(437)] = 34324, - [SMALL_STATE(438)] = 34428, - [SMALL_STATE(439)] = 34532, - [SMALL_STATE(440)] = 34636, - [SMALL_STATE(441)] = 34740, - [SMALL_STATE(442)] = 34795, - [SMALL_STATE(443)] = 34850, - [SMALL_STATE(444)] = 34905, - [SMALL_STATE(445)] = 34960, - [SMALL_STATE(446)] = 35015, - [SMALL_STATE(447)] = 35070, - [SMALL_STATE(448)] = 35125, - [SMALL_STATE(449)] = 35180, - [SMALL_STATE(450)] = 35235, - [SMALL_STATE(451)] = 35288, - [SMALL_STATE(452)] = 35341, - [SMALL_STATE(453)] = 35394, - [SMALL_STATE(454)] = 35444, - [SMALL_STATE(455)] = 35494, - [SMALL_STATE(456)] = 35544, - [SMALL_STATE(457)] = 35594, - [SMALL_STATE(458)] = 35644, - [SMALL_STATE(459)] = 35694, - [SMALL_STATE(460)] = 35744, - [SMALL_STATE(461)] = 35794, - [SMALL_STATE(462)] = 35846, - [SMALL_STATE(463)] = 35896, - [SMALL_STATE(464)] = 35948, - [SMALL_STATE(465)] = 36000, - [SMALL_STATE(466)] = 36050, - [SMALL_STATE(467)] = 36100, - [SMALL_STATE(468)] = 36150, - [SMALL_STATE(469)] = 36200, - [SMALL_STATE(470)] = 36250, - [SMALL_STATE(471)] = 36300, - [SMALL_STATE(472)] = 36350, - [SMALL_STATE(473)] = 36400, - [SMALL_STATE(474)] = 36450, - [SMALL_STATE(475)] = 36500, - [SMALL_STATE(476)] = 36550, - [SMALL_STATE(477)] = 36600, - [SMALL_STATE(478)] = 36650, - [SMALL_STATE(479)] = 36700, - [SMALL_STATE(480)] = 36750, - [SMALL_STATE(481)] = 36800, - [SMALL_STATE(482)] = 36850, - [SMALL_STATE(483)] = 36900, - [SMALL_STATE(484)] = 36950, - [SMALL_STATE(485)] = 37000, - [SMALL_STATE(486)] = 37050, - [SMALL_STATE(487)] = 37100, - [SMALL_STATE(488)] = 37150, - [SMALL_STATE(489)] = 37200, - [SMALL_STATE(490)] = 37250, - [SMALL_STATE(491)] = 37300, - [SMALL_STATE(492)] = 37350, - [SMALL_STATE(493)] = 37400, - [SMALL_STATE(494)] = 37450, - [SMALL_STATE(495)] = 37500, - [SMALL_STATE(496)] = 37550, - [SMALL_STATE(497)] = 37600, - [SMALL_STATE(498)] = 37650, - [SMALL_STATE(499)] = 37700, - [SMALL_STATE(500)] = 37750, - [SMALL_STATE(501)] = 37800, - [SMALL_STATE(502)] = 37849, - [SMALL_STATE(503)] = 37898, - [SMALL_STATE(504)] = 37947, - [SMALL_STATE(505)] = 37996, - [SMALL_STATE(506)] = 38045, - [SMALL_STATE(507)] = 38096, - [SMALL_STATE(508)] = 38147, - [SMALL_STATE(509)] = 38196, - [SMALL_STATE(510)] = 38245, - [SMALL_STATE(511)] = 38294, - [SMALL_STATE(512)] = 38343, - [SMALL_STATE(513)] = 38392, - [SMALL_STATE(514)] = 38441, - [SMALL_STATE(515)] = 38492, - [SMALL_STATE(516)] = 38541, - [SMALL_STATE(517)] = 38592, - [SMALL_STATE(518)] = 38641, - [SMALL_STATE(519)] = 38690, - [SMALL_STATE(520)] = 38739, - [SMALL_STATE(521)] = 38790, - [SMALL_STATE(522)] = 38841, - [SMALL_STATE(523)] = 38892, - [SMALL_STATE(524)] = 38941, - [SMALL_STATE(525)] = 38990, - [SMALL_STATE(526)] = 39039, - [SMALL_STATE(527)] = 39088, - [SMALL_STATE(528)] = 39137, - [SMALL_STATE(529)] = 39186, - [SMALL_STATE(530)] = 39235, - [SMALL_STATE(531)] = 39284, - [SMALL_STATE(532)] = 39333, - [SMALL_STATE(533)] = 39382, - [SMALL_STATE(534)] = 39431, - [SMALL_STATE(535)] = 39480, - [SMALL_STATE(536)] = 39529, - [SMALL_STATE(537)] = 39578, - [SMALL_STATE(538)] = 39627, - [SMALL_STATE(539)] = 39676, - [SMALL_STATE(540)] = 39725, - [SMALL_STATE(541)] = 39774, - [SMALL_STATE(542)] = 39823, - [SMALL_STATE(543)] = 39872, - [SMALL_STATE(544)] = 39921, - [SMALL_STATE(545)] = 39970, - [SMALL_STATE(546)] = 40019, - [SMALL_STATE(547)] = 40068, - [SMALL_STATE(548)] = 40119, - [SMALL_STATE(549)] = 40168, - [SMALL_STATE(550)] = 40217, - [SMALL_STATE(551)] = 40266, - [SMALL_STATE(552)] = 40315, - [SMALL_STATE(553)] = 40364, - [SMALL_STATE(554)] = 40413, - [SMALL_STATE(555)] = 40461, - [SMALL_STATE(556)] = 40507, - [SMALL_STATE(557)] = 40561, - [SMALL_STATE(558)] = 40607, - [SMALL_STATE(559)] = 40671, - [SMALL_STATE(560)] = 40717, - [SMALL_STATE(561)] = 40765, - [SMALL_STATE(562)] = 40811, - [SMALL_STATE(563)] = 40857, - [SMALL_STATE(564)] = 40903, - [SMALL_STATE(565)] = 40949, - [SMALL_STATE(566)] = 40995, - [SMALL_STATE(567)] = 41041, - [SMALL_STATE(568)] = 41087, - [SMALL_STATE(569)] = 41133, - [SMALL_STATE(570)] = 41179, - [SMALL_STATE(571)] = 41225, - [SMALL_STATE(572)] = 41271, - [SMALL_STATE(573)] = 41317, - [SMALL_STATE(574)] = 41363, - [SMALL_STATE(575)] = 41409, - [SMALL_STATE(576)] = 41455, - [SMALL_STATE(577)] = 41501, - [SMALL_STATE(578)] = 41547, - [SMALL_STATE(579)] = 41595, - [SMALL_STATE(580)] = 41641, - [SMALL_STATE(581)] = 41687, - [SMALL_STATE(582)] = 41733, - [SMALL_STATE(583)] = 41779, - [SMALL_STATE(584)] = 41825, - [SMALL_STATE(585)] = 41885, - [SMALL_STATE(586)] = 41943, - [SMALL_STATE(587)] = 42007, - [SMALL_STATE(588)] = 42053, - [SMALL_STATE(589)] = 42133, - [SMALL_STATE(590)] = 42213, - [SMALL_STATE(591)] = 42259, - [SMALL_STATE(592)] = 42305, - [SMALL_STATE(593)] = 42351, - [SMALL_STATE(594)] = 42397, - [SMALL_STATE(595)] = 42443, - [SMALL_STATE(596)] = 42489, - [SMALL_STATE(597)] = 42535, - [SMALL_STATE(598)] = 42581, - [SMALL_STATE(599)] = 42627, - [SMALL_STATE(600)] = 42673, - [SMALL_STATE(601)] = 42721, - [SMALL_STATE(602)] = 42767, - [SMALL_STATE(603)] = 42813, - [SMALL_STATE(604)] = 42859, - [SMALL_STATE(605)] = 42905, - [SMALL_STATE(606)] = 42951, - [SMALL_STATE(607)] = 42997, - [SMALL_STATE(608)] = 43043, - [SMALL_STATE(609)] = 43089, - [SMALL_STATE(610)] = 43135, - [SMALL_STATE(611)] = 43185, - [SMALL_STATE(612)] = 43231, - [SMALL_STATE(613)] = 43277, - [SMALL_STATE(614)] = 43319, - [SMALL_STATE(615)] = 43361, - [SMALL_STATE(616)] = 43403, - [SMALL_STATE(617)] = 43445, - [SMALL_STATE(618)] = 43487, - [SMALL_STATE(619)] = 43529, - [SMALL_STATE(620)] = 43571, - [SMALL_STATE(621)] = 43615, - [SMALL_STATE(622)] = 43657, - [SMALL_STATE(623)] = 43699, - [SMALL_STATE(624)] = 43741, - [SMALL_STATE(625)] = 43783, - [SMALL_STATE(626)] = 43825, - [SMALL_STATE(627)] = 43867, - [SMALL_STATE(628)] = 43909, - [SMALL_STATE(629)] = 43951, - [SMALL_STATE(630)] = 43993, - [SMALL_STATE(631)] = 44035, - [SMALL_STATE(632)] = 44077, - [SMALL_STATE(633)] = 44119, - [SMALL_STATE(634)] = 44161, - [SMALL_STATE(635)] = 44203, - [SMALL_STATE(636)] = 44245, - [SMALL_STATE(637)] = 44287, - [SMALL_STATE(638)] = 44329, - [SMALL_STATE(639)] = 44371, - [SMALL_STATE(640)] = 44413, - [SMALL_STATE(641)] = 44457, - [SMALL_STATE(642)] = 44499, - [SMALL_STATE(643)] = 44541, - [SMALL_STATE(644)] = 44583, - [SMALL_STATE(645)] = 44625, - [SMALL_STATE(646)] = 44667, - [SMALL_STATE(647)] = 44709, - [SMALL_STATE(648)] = 44751, - [SMALL_STATE(649)] = 44793, - [SMALL_STATE(650)] = 44835, - [SMALL_STATE(651)] = 44877, - [SMALL_STATE(652)] = 44919, - [SMALL_STATE(653)] = 44961, - [SMALL_STATE(654)] = 45003, - [SMALL_STATE(655)] = 45045, - [SMALL_STATE(656)] = 45087, - [SMALL_STATE(657)] = 45129, - [SMALL_STATE(658)] = 45171, - [SMALL_STATE(659)] = 45213, - [SMALL_STATE(660)] = 45255, - [SMALL_STATE(661)] = 45297, - [SMALL_STATE(662)] = 45348, - [SMALL_STATE(663)] = 45396, - [SMALL_STATE(664)] = 45444, - [SMALL_STATE(665)] = 45515, - [SMALL_STATE(666)] = 45588, - [SMALL_STATE(667)] = 45661, - [SMALL_STATE(668)] = 45718, - [SMALL_STATE(669)] = 45791, - [SMALL_STATE(670)] = 45838, - [SMALL_STATE(671)] = 45911, - [SMALL_STATE(672)] = 45984, - [SMALL_STATE(673)] = 46041, - [SMALL_STATE(674)] = 46084, - [SMALL_STATE(675)] = 46157, - [SMALL_STATE(676)] = 46230, - [SMALL_STATE(677)] = 46303, - [SMALL_STATE(678)] = 46354, - [SMALL_STATE(679)] = 46425, - [SMALL_STATE(680)] = 46498, - [SMALL_STATE(681)] = 46539, - [SMALL_STATE(682)] = 46592, - [SMALL_STATE(683)] = 46664, - [SMALL_STATE(684)] = 46736, - [SMALL_STATE(685)] = 46808, - [SMALL_STATE(686)] = 46880, - [SMALL_STATE(687)] = 46935, - [SMALL_STATE(688)] = 47004, - [SMALL_STATE(689)] = 47053, - [SMALL_STATE(690)] = 47108, - [SMALL_STATE(691)] = 47179, - [SMALL_STATE(692)] = 47248, - [SMALL_STATE(693)] = 47293, - [SMALL_STATE(694)] = 47364, - [SMALL_STATE(695)] = 47403, - [SMALL_STATE(696)] = 47444, - [SMALL_STATE(697)] = 47483, - [SMALL_STATE(698)] = 47534, - [SMALL_STATE(699)] = 47582, - [SMALL_STATE(700)] = 47622, - [SMALL_STATE(701)] = 47660, - [SMALL_STATE(702)] = 47704, - [SMALL_STATE(703)] = 47770, - [SMALL_STATE(704)] = 47820, - [SMALL_STATE(705)] = 47868, - [SMALL_STATE(706)] = 47922, - [SMALL_STATE(707)] = 47972, - [SMALL_STATE(708)] = 48020, - [SMALL_STATE(709)] = 48074, - [SMALL_STATE(710)] = 48144, - [SMALL_STATE(711)] = 48210, - [SMALL_STATE(712)] = 48278, - [SMALL_STATE(713)] = 48348, - [SMALL_STATE(714)] = 48416, - [SMALL_STATE(715)] = 48482, - [SMALL_STATE(716)] = 48548, - [SMALL_STATE(717)] = 48614, - [SMALL_STATE(718)] = 48680, - [SMALL_STATE(719)] = 48746, - [SMALL_STATE(720)] = 48800, - [SMALL_STATE(721)] = 48866, - [SMALL_STATE(722)] = 48920, - [SMALL_STATE(723)] = 48960, - [SMALL_STATE(724)] = 48998, - [SMALL_STATE(725)] = 49042, - [SMALL_STATE(726)] = 49082, - [SMALL_STATE(727)] = 49120, - [SMALL_STATE(728)] = 49164, - [SMALL_STATE(729)] = 49214, - [SMALL_STATE(730)] = 49268, - [SMALL_STATE(731)] = 49334, - [SMALL_STATE(732)] = 49402, - [SMALL_STATE(733)] = 49468, - [SMALL_STATE(734)] = 49522, - [SMALL_STATE(735)] = 49588, - [SMALL_STATE(736)] = 49654, - [SMALL_STATE(737)] = 49720, - [SMALL_STATE(738)] = 49755, - [SMALL_STATE(739)] = 49790, - [SMALL_STATE(740)] = 49825, - [SMALL_STATE(741)] = 49860, - [SMALL_STATE(742)] = 49927, - [SMALL_STATE(743)] = 49962, - [SMALL_STATE(744)] = 49997, - [SMALL_STATE(745)] = 50032, - [SMALL_STATE(746)] = 50067, - [SMALL_STATE(747)] = 50102, - [SMALL_STATE(748)] = 50137, - [SMALL_STATE(749)] = 50172, - [SMALL_STATE(750)] = 50209, - [SMALL_STATE(751)] = 50244, - [SMALL_STATE(752)] = 50279, - [SMALL_STATE(753)] = 50314, - [SMALL_STATE(754)] = 50349, - [SMALL_STATE(755)] = 50388, - [SMALL_STATE(756)] = 50423, - [SMALL_STATE(757)] = 50458, - [SMALL_STATE(758)] = 50493, - [SMALL_STATE(759)] = 50528, - [SMALL_STATE(760)] = 50563, - [SMALL_STATE(761)] = 50598, - [SMALL_STATE(762)] = 50633, - [SMALL_STATE(763)] = 50668, - [SMALL_STATE(764)] = 50737, - [SMALL_STATE(765)] = 50772, - [SMALL_STATE(766)] = 50841, - [SMALL_STATE(767)] = 50876, - [SMALL_STATE(768)] = 50911, - [SMALL_STATE(769)] = 50946, - [SMALL_STATE(770)] = 50981, - [SMALL_STATE(771)] = 51016, - [SMALL_STATE(772)] = 51051, - [SMALL_STATE(773)] = 51086, - [SMALL_STATE(774)] = 51121, - [SMALL_STATE(775)] = 51156, - [SMALL_STATE(776)] = 51209, - [SMALL_STATE(777)] = 51258, - [SMALL_STATE(778)] = 51305, - [SMALL_STATE(779)] = 51340, - [SMALL_STATE(780)] = 51393, - [SMALL_STATE(781)] = 51428, - [SMALL_STATE(782)] = 51463, - [SMALL_STATE(783)] = 51498, - [SMALL_STATE(784)] = 51533, - [SMALL_STATE(785)] = 51572, - [SMALL_STATE(786)] = 51609, - [SMALL_STATE(787)] = 51652, - [SMALL_STATE(788)] = 51701, - [SMALL_STATE(789)] = 51748, - [SMALL_STATE(790)] = 51801, - [SMALL_STATE(791)] = 51836, - [SMALL_STATE(792)] = 51875, - [SMALL_STATE(793)] = 51912, - [SMALL_STATE(794)] = 51955, - [SMALL_STATE(795)] = 51990, - [SMALL_STATE(796)] = 52025, - [SMALL_STATE(797)] = 52060, - [SMALL_STATE(798)] = 52113, - [SMALL_STATE(799)] = 52148, - [SMALL_STATE(800)] = 52183, - [SMALL_STATE(801)] = 52218, - [SMALL_STATE(802)] = 52253, - [SMALL_STATE(803)] = 52289, - [SMALL_STATE(804)] = 52337, - [SMALL_STATE(805)] = 52389, - [SMALL_STATE(806)] = 52435, - [SMALL_STATE(807)] = 52477, - [SMALL_STATE(808)] = 52543, - [SMALL_STATE(809)] = 52595, - [SMALL_STATE(810)] = 52633, - [SMALL_STATE(811)] = 52698, - [SMALL_STATE(812)] = 52739, - [SMALL_STATE(813)] = 52776, - [SMALL_STATE(814)] = 52843, - [SMALL_STATE(815)] = 52908, - [SMALL_STATE(816)] = 52955, - [SMALL_STATE(817)] = 52992, - [SMALL_STATE(818)] = 53043, - [SMALL_STATE(819)] = 53088, - [SMALL_STATE(820)] = 53139, - [SMALL_STATE(821)] = 53174, - [SMALL_STATE(822)] = 53214, - [SMALL_STATE(823)] = 53250, - [SMALL_STATE(824)] = 53284, - [SMALL_STATE(825)] = 53346, - [SMALL_STATE(826)] = 53392, - [SMALL_STATE(827)] = 53436, - [SMALL_STATE(828)] = 53498, - [SMALL_STATE(829)] = 53560, - [SMALL_STATE(830)] = 53610, - [SMALL_STATE(831)] = 53672, - [SMALL_STATE(832)] = 53722, - [SMALL_STATE(833)] = 53763, - [SMALL_STATE(834)] = 53801, - [SMALL_STATE(835)] = 53839, - [SMALL_STATE(836)] = 53877, - [SMALL_STATE(837)] = 53915, - [SMALL_STATE(838)] = 53953, - [SMALL_STATE(839)] = 53991, - [SMALL_STATE(840)] = 54029, - [SMALL_STATE(841)] = 54067, - [SMALL_STATE(842)] = 54105, - [SMALL_STATE(843)] = 54143, - [SMALL_STATE(844)] = 54181, - [SMALL_STATE(845)] = 54219, - [SMALL_STATE(846)] = 54257, - [SMALL_STATE(847)] = 54295, - [SMALL_STATE(848)] = 54333, - [SMALL_STATE(849)] = 54371, - [SMALL_STATE(850)] = 54409, - [SMALL_STATE(851)] = 54447, - [SMALL_STATE(852)] = 54485, - [SMALL_STATE(853)] = 54523, - [SMALL_STATE(854)] = 54561, - [SMALL_STATE(855)] = 54599, - [SMALL_STATE(856)] = 54637, - [SMALL_STATE(857)] = 54675, - [SMALL_STATE(858)] = 54713, - [SMALL_STATE(859)] = 54751, - [SMALL_STATE(860)] = 54789, - [SMALL_STATE(861)] = 54827, - [SMALL_STATE(862)] = 54865, - [SMALL_STATE(863)] = 54903, - [SMALL_STATE(864)] = 54949, - [SMALL_STATE(865)] = 54995, - [SMALL_STATE(866)] = 55041, - [SMALL_STATE(867)] = 55064, - [SMALL_STATE(868)] = 55090, - [SMALL_STATE(869)] = 55129, - [SMALL_STATE(870)] = 55168, - [SMALL_STATE(871)] = 55207, - [SMALL_STATE(872)] = 55229, - [SMALL_STATE(873)] = 55263, - [SMALL_STATE(874)] = 55297, - [SMALL_STATE(875)] = 55317, - [SMALL_STATE(876)] = 55351, - [SMALL_STATE(877)] = 55371, - [SMALL_STATE(878)] = 55405, - [SMALL_STATE(879)] = 55439, - [SMALL_STATE(880)] = 55459, - [SMALL_STATE(881)] = 55479, - [SMALL_STATE(882)] = 55513, - [SMALL_STATE(883)] = 55533, - [SMALL_STATE(884)] = 55551, - [SMALL_STATE(885)] = 55570, - [SMALL_STATE(886)] = 55593, - [SMALL_STATE(887)] = 55624, - [SMALL_STATE(888)] = 55645, - [SMALL_STATE(889)] = 55676, - [SMALL_STATE(890)] = 55697, - [SMALL_STATE(891)] = 55728, - [SMALL_STATE(892)] = 55759, - [SMALL_STATE(893)] = 55782, - [SMALL_STATE(894)] = 55798, - [SMALL_STATE(895)] = 55826, - [SMALL_STATE(896)] = 55846, - [SMALL_STATE(897)] = 55862, - [SMALL_STATE(898)] = 55882, - [SMALL_STATE(899)] = 55898, - [SMALL_STATE(900)] = 55916, - [SMALL_STATE(901)] = 55932, - [SMALL_STATE(902)] = 55950, - [SMALL_STATE(903)] = 55978, - [SMALL_STATE(904)] = 55994, - [SMALL_STATE(905)] = 56012, - [SMALL_STATE(906)] = 56032, - [SMALL_STATE(907)] = 56052, - [SMALL_STATE(908)] = 56068, - [SMALL_STATE(909)] = 56096, - [SMALL_STATE(910)] = 56111, - [SMALL_STATE(911)] = 56126, - [SMALL_STATE(912)] = 56151, - [SMALL_STATE(913)] = 56176, - [SMALL_STATE(914)] = 56201, - [SMALL_STATE(915)] = 56226, - [SMALL_STATE(916)] = 56241, - [SMALL_STATE(917)] = 56256, - [SMALL_STATE(918)] = 56275, - [SMALL_STATE(919)] = 56290, - [SMALL_STATE(920)] = 56305, - [SMALL_STATE(921)] = 56330, - [SMALL_STATE(922)] = 56345, - [SMALL_STATE(923)] = 56360, - [SMALL_STATE(924)] = 56375, - [SMALL_STATE(925)] = 56390, - [SMALL_STATE(926)] = 56415, - [SMALL_STATE(927)] = 56430, - [SMALL_STATE(928)] = 56445, - [SMALL_STATE(929)] = 56460, - [SMALL_STATE(930)] = 56475, - [SMALL_STATE(931)] = 56490, - [SMALL_STATE(932)] = 56505, - [SMALL_STATE(933)] = 56531, - [SMALL_STATE(934)] = 56551, - [SMALL_STATE(935)] = 56577, - [SMALL_STATE(936)] = 56595, - [SMALL_STATE(937)] = 56613, - [SMALL_STATE(938)] = 56639, - [SMALL_STATE(939)] = 56665, - [SMALL_STATE(940)] = 56691, - [SMALL_STATE(941)] = 56717, - [SMALL_STATE(942)] = 56737, - [SMALL_STATE(943)] = 56763, - [SMALL_STATE(944)] = 56781, - [SMALL_STATE(945)] = 56807, - [SMALL_STATE(946)] = 56833, - [SMALL_STATE(947)] = 56859, - [SMALL_STATE(948)] = 56882, - [SMALL_STATE(949)] = 56901, - [SMALL_STATE(950)] = 56914, - [SMALL_STATE(951)] = 56933, - [SMALL_STATE(952)] = 56952, - [SMALL_STATE(953)] = 56971, - [SMALL_STATE(954)] = 56990, - [SMALL_STATE(955)] = 57009, - [SMALL_STATE(956)] = 57028, - [SMALL_STATE(957)] = 57041, - [SMALL_STATE(958)] = 57064, - [SMALL_STATE(959)] = 57077, - [SMALL_STATE(960)] = 57090, - [SMALL_STATE(961)] = 57103, - [SMALL_STATE(962)] = 57122, - [SMALL_STATE(963)] = 57135, - [SMALL_STATE(964)] = 57152, - [SMALL_STATE(965)] = 57165, - [SMALL_STATE(966)] = 57184, - [SMALL_STATE(967)] = 57203, - [SMALL_STATE(968)] = 57222, - [SMALL_STATE(969)] = 57241, - [SMALL_STATE(970)] = 57260, - [SMALL_STATE(971)] = 57279, - [SMALL_STATE(972)] = 57298, - [SMALL_STATE(973)] = 57317, - [SMALL_STATE(974)] = 57330, - [SMALL_STATE(975)] = 57343, - [SMALL_STATE(976)] = 57360, - [SMALL_STATE(977)] = 57373, - [SMALL_STATE(978)] = 57386, - [SMALL_STATE(979)] = 57405, - [SMALL_STATE(980)] = 57424, - [SMALL_STATE(981)] = 57443, - [SMALL_STATE(982)] = 57462, - [SMALL_STATE(983)] = 57481, - [SMALL_STATE(984)] = 57500, - [SMALL_STATE(985)] = 57519, - [SMALL_STATE(986)] = 57538, - [SMALL_STATE(987)] = 57551, - [SMALL_STATE(988)] = 57570, - [SMALL_STATE(989)] = 57583, - [SMALL_STATE(990)] = 57602, - [SMALL_STATE(991)] = 57621, - [SMALL_STATE(992)] = 57640, - [SMALL_STATE(993)] = 57659, - [SMALL_STATE(994)] = 57672, - [SMALL_STATE(995)] = 57691, - [SMALL_STATE(996)] = 57710, - [SMALL_STATE(997)] = 57723, - [SMALL_STATE(998)] = 57742, - [SMALL_STATE(999)] = 57761, - [SMALL_STATE(1000)] = 57780, - [SMALL_STATE(1001)] = 57796, - [SMALL_STATE(1002)] = 57816, - [SMALL_STATE(1003)] = 57836, - [SMALL_STATE(1004)] = 57854, - [SMALL_STATE(1005)] = 57874, - [SMALL_STATE(1006)] = 57890, - [SMALL_STATE(1007)] = 57910, - [SMALL_STATE(1008)] = 57930, - [SMALL_STATE(1009)] = 57944, - [SMALL_STATE(1010)] = 57964, - [SMALL_STATE(1011)] = 57978, - [SMALL_STATE(1012)] = 57998, - [SMALL_STATE(1013)] = 58012, - [SMALL_STATE(1014)] = 58026, - [SMALL_STATE(1015)] = 58046, - [SMALL_STATE(1016)] = 58062, - [SMALL_STATE(1017)] = 58082, - [SMALL_STATE(1018)] = 58102, - [SMALL_STATE(1019)] = 58122, - [SMALL_STATE(1020)] = 58140, - [SMALL_STATE(1021)] = 58154, - [SMALL_STATE(1022)] = 58172, - [SMALL_STATE(1023)] = 58192, - [SMALL_STATE(1024)] = 58212, - [SMALL_STATE(1025)] = 58230, - [SMALL_STATE(1026)] = 58248, - [SMALL_STATE(1027)] = 58268, - [SMALL_STATE(1028)] = 58286, - [SMALL_STATE(1029)] = 58306, - [SMALL_STATE(1030)] = 58324, - [SMALL_STATE(1031)] = 58340, - [SMALL_STATE(1032)] = 58354, - [SMALL_STATE(1033)] = 58370, - [SMALL_STATE(1034)] = 58390, - [SMALL_STATE(1035)] = 58406, - [SMALL_STATE(1036)] = 58420, - [SMALL_STATE(1037)] = 58438, - [SMALL_STATE(1038)] = 58454, - [SMALL_STATE(1039)] = 58474, - [SMALL_STATE(1040)] = 58494, - [SMALL_STATE(1041)] = 58508, - [SMALL_STATE(1042)] = 58523, - [SMALL_STATE(1043)] = 58538, - [SMALL_STATE(1044)] = 58555, - [SMALL_STATE(1045)] = 58572, - [SMALL_STATE(1046)] = 58589, - [SMALL_STATE(1047)] = 58606, - [SMALL_STATE(1048)] = 58623, - [SMALL_STATE(1049)] = 58640, - [SMALL_STATE(1050)] = 58657, - [SMALL_STATE(1051)] = 58672, - [SMALL_STATE(1052)] = 58689, - [SMALL_STATE(1053)] = 58704, - [SMALL_STATE(1054)] = 58719, - [SMALL_STATE(1055)] = 58736, - [SMALL_STATE(1056)] = 58751, - [SMALL_STATE(1057)] = 58766, - [SMALL_STATE(1058)] = 58783, - [SMALL_STATE(1059)] = 58800, - [SMALL_STATE(1060)] = 58817, - [SMALL_STATE(1061)] = 58832, - [SMALL_STATE(1062)] = 58845, - [SMALL_STATE(1063)] = 58858, - [SMALL_STATE(1064)] = 58875, - [SMALL_STATE(1065)] = 58892, - [SMALL_STATE(1066)] = 58909, - [SMALL_STATE(1067)] = 58926, - [SMALL_STATE(1068)] = 58941, - [SMALL_STATE(1069)] = 58958, - [SMALL_STATE(1070)] = 58973, - [SMALL_STATE(1071)] = 58988, - [SMALL_STATE(1072)] = 59005, - [SMALL_STATE(1073)] = 59022, - [SMALL_STATE(1074)] = 59039, - [SMALL_STATE(1075)] = 59056, - [SMALL_STATE(1076)] = 59073, - [SMALL_STATE(1077)] = 59090, - [SMALL_STATE(1078)] = 59107, - [SMALL_STATE(1079)] = 59124, - [SMALL_STATE(1080)] = 59139, - [SMALL_STATE(1081)] = 59156, - [SMALL_STATE(1082)] = 59173, - [SMALL_STATE(1083)] = 59184, - [SMALL_STATE(1084)] = 59201, - [SMALL_STATE(1085)] = 59216, - [SMALL_STATE(1086)] = 59231, - [SMALL_STATE(1087)] = 59248, - [SMALL_STATE(1088)] = 59263, - [SMALL_STATE(1089)] = 59280, - [SMALL_STATE(1090)] = 59295, - [SMALL_STATE(1091)] = 59312, - [SMALL_STATE(1092)] = 59327, - [SMALL_STATE(1093)] = 59342, - [SMALL_STATE(1094)] = 59359, - [SMALL_STATE(1095)] = 59370, - [SMALL_STATE(1096)] = 59385, - [SMALL_STATE(1097)] = 59400, - [SMALL_STATE(1098)] = 59417, - [SMALL_STATE(1099)] = 59434, - [SMALL_STATE(1100)] = 59451, - [SMALL_STATE(1101)] = 59468, - [SMALL_STATE(1102)] = 59485, - [SMALL_STATE(1103)] = 59502, - [SMALL_STATE(1104)] = 59519, - [SMALL_STATE(1105)] = 59536, - [SMALL_STATE(1106)] = 59553, - [SMALL_STATE(1107)] = 59570, - [SMALL_STATE(1108)] = 59587, - [SMALL_STATE(1109)] = 59604, - [SMALL_STATE(1110)] = 59619, - [SMALL_STATE(1111)] = 59632, - [SMALL_STATE(1112)] = 59649, - [SMALL_STATE(1113)] = 59666, - [SMALL_STATE(1114)] = 59683, - [SMALL_STATE(1115)] = 59694, - [SMALL_STATE(1116)] = 59709, - [SMALL_STATE(1117)] = 59726, - [SMALL_STATE(1118)] = 59743, - [SMALL_STATE(1119)] = 59760, - [SMALL_STATE(1120)] = 59777, - [SMALL_STATE(1121)] = 59792, - [SMALL_STATE(1122)] = 59809, - [SMALL_STATE(1123)] = 59826, - [SMALL_STATE(1124)] = 59841, - [SMALL_STATE(1125)] = 59858, - [SMALL_STATE(1126)] = 59875, - [SMALL_STATE(1127)] = 59892, - [SMALL_STATE(1128)] = 59907, - [SMALL_STATE(1129)] = 59924, - [SMALL_STATE(1130)] = 59941, - [SMALL_STATE(1131)] = 59958, - [SMALL_STATE(1132)] = 59975, - [SMALL_STATE(1133)] = 59992, - [SMALL_STATE(1134)] = 60009, - [SMALL_STATE(1135)] = 60023, - [SMALL_STATE(1136)] = 60037, - [SMALL_STATE(1137)] = 60051, - [SMALL_STATE(1138)] = 60065, - [SMALL_STATE(1139)] = 60079, - [SMALL_STATE(1140)] = 60093, - [SMALL_STATE(1141)] = 60107, - [SMALL_STATE(1142)] = 60121, - [SMALL_STATE(1143)] = 60131, - [SMALL_STATE(1144)] = 60145, - [SMALL_STATE(1145)] = 60159, - [SMALL_STATE(1146)] = 60173, - [SMALL_STATE(1147)] = 60187, - [SMALL_STATE(1148)] = 60201, - [SMALL_STATE(1149)] = 60211, - [SMALL_STATE(1150)] = 60225, - [SMALL_STATE(1151)] = 60237, - [SMALL_STATE(1152)] = 60251, - [SMALL_STATE(1153)] = 60263, - [SMALL_STATE(1154)] = 60277, - [SMALL_STATE(1155)] = 60291, - [SMALL_STATE(1156)] = 60305, - [SMALL_STATE(1157)] = 60319, - [SMALL_STATE(1158)] = 60333, - [SMALL_STATE(1159)] = 60347, - [SMALL_STATE(1160)] = 60361, - [SMALL_STATE(1161)] = 60375, - [SMALL_STATE(1162)] = 60387, - [SMALL_STATE(1163)] = 60401, - [SMALL_STATE(1164)] = 60415, - [SMALL_STATE(1165)] = 60429, - [SMALL_STATE(1166)] = 60443, - [SMALL_STATE(1167)] = 60457, - [SMALL_STATE(1168)] = 60471, - [SMALL_STATE(1169)] = 60485, - [SMALL_STATE(1170)] = 60499, - [SMALL_STATE(1171)] = 60513, - [SMALL_STATE(1172)] = 60523, - [SMALL_STATE(1173)] = 60537, - [SMALL_STATE(1174)] = 60551, - [SMALL_STATE(1175)] = 60565, - [SMALL_STATE(1176)] = 60579, - [SMALL_STATE(1177)] = 60593, - [SMALL_STATE(1178)] = 60607, - [SMALL_STATE(1179)] = 60621, - [SMALL_STATE(1180)] = 60635, - [SMALL_STATE(1181)] = 60649, - [SMALL_STATE(1182)] = 60663, - [SMALL_STATE(1183)] = 60677, - [SMALL_STATE(1184)] = 60691, - [SMALL_STATE(1185)] = 60705, - [SMALL_STATE(1186)] = 60719, - [SMALL_STATE(1187)] = 60729, - [SMALL_STATE(1188)] = 60743, - [SMALL_STATE(1189)] = 60757, - [SMALL_STATE(1190)] = 60771, - [SMALL_STATE(1191)] = 60785, - [SMALL_STATE(1192)] = 60797, - [SMALL_STATE(1193)] = 60811, - [SMALL_STATE(1194)] = 60825, - [SMALL_STATE(1195)] = 60839, - [SMALL_STATE(1196)] = 60853, - [SMALL_STATE(1197)] = 60867, - [SMALL_STATE(1198)] = 60881, - [SMALL_STATE(1199)] = 60895, - [SMALL_STATE(1200)] = 60905, - [SMALL_STATE(1201)] = 60919, - [SMALL_STATE(1202)] = 60931, - [SMALL_STATE(1203)] = 60945, - [SMALL_STATE(1204)] = 60959, - [SMALL_STATE(1205)] = 60973, - [SMALL_STATE(1206)] = 60983, - [SMALL_STATE(1207)] = 60997, - [SMALL_STATE(1208)] = 61011, - [SMALL_STATE(1209)] = 61021, - [SMALL_STATE(1210)] = 61035, - [SMALL_STATE(1211)] = 61049, - [SMALL_STATE(1212)] = 61063, - [SMALL_STATE(1213)] = 61073, - [SMALL_STATE(1214)] = 61083, - [SMALL_STATE(1215)] = 61097, - [SMALL_STATE(1216)] = 61111, - [SMALL_STATE(1217)] = 61125, - [SMALL_STATE(1218)] = 61139, - [SMALL_STATE(1219)] = 61149, - [SMALL_STATE(1220)] = 61163, - [SMALL_STATE(1221)] = 61177, - [SMALL_STATE(1222)] = 61189, - [SMALL_STATE(1223)] = 61199, - [SMALL_STATE(1224)] = 61211, - [SMALL_STATE(1225)] = 61225, - [SMALL_STATE(1226)] = 61239, - [SMALL_STATE(1227)] = 61253, - [SMALL_STATE(1228)] = 61267, - [SMALL_STATE(1229)] = 61281, - [SMALL_STATE(1230)] = 61295, - [SMALL_STATE(1231)] = 61305, - [SMALL_STATE(1232)] = 61315, - [SMALL_STATE(1233)] = 61325, - [SMALL_STATE(1234)] = 61339, - [SMALL_STATE(1235)] = 61349, - [SMALL_STATE(1236)] = 61363, - [SMALL_STATE(1237)] = 61377, - [SMALL_STATE(1238)] = 61391, - [SMALL_STATE(1239)] = 61405, - [SMALL_STATE(1240)] = 61419, - [SMALL_STATE(1241)] = 61433, - [SMALL_STATE(1242)] = 61443, - [SMALL_STATE(1243)] = 61453, - [SMALL_STATE(1244)] = 61467, - [SMALL_STATE(1245)] = 61477, - [SMALL_STATE(1246)] = 61487, - [SMALL_STATE(1247)] = 61501, - [SMALL_STATE(1248)] = 61515, - [SMALL_STATE(1249)] = 61529, - [SMALL_STATE(1250)] = 61543, - [SMALL_STATE(1251)] = 61557, - [SMALL_STATE(1252)] = 61571, - [SMALL_STATE(1253)] = 61585, - [SMALL_STATE(1254)] = 61599, - [SMALL_STATE(1255)] = 61613, - [SMALL_STATE(1256)] = 61623, - [SMALL_STATE(1257)] = 61637, - [SMALL_STATE(1258)] = 61651, - [SMALL_STATE(1259)] = 61665, - [SMALL_STATE(1260)] = 61679, - [SMALL_STATE(1261)] = 61693, - [SMALL_STATE(1262)] = 61707, - [SMALL_STATE(1263)] = 61717, - [SMALL_STATE(1264)] = 61731, - [SMALL_STATE(1265)] = 61741, - [SMALL_STATE(1266)] = 61751, - [SMALL_STATE(1267)] = 61765, - [SMALL_STATE(1268)] = 61779, - [SMALL_STATE(1269)] = 61793, - [SMALL_STATE(1270)] = 61807, - [SMALL_STATE(1271)] = 61821, - [SMALL_STATE(1272)] = 61835, - [SMALL_STATE(1273)] = 61849, - [SMALL_STATE(1274)] = 61863, - [SMALL_STATE(1275)] = 61877, - [SMALL_STATE(1276)] = 61891, - [SMALL_STATE(1277)] = 61901, - [SMALL_STATE(1278)] = 61915, - [SMALL_STATE(1279)] = 61925, - [SMALL_STATE(1280)] = 61936, - [SMALL_STATE(1281)] = 61945, - [SMALL_STATE(1282)] = 61954, - [SMALL_STATE(1283)] = 61963, - [SMALL_STATE(1284)] = 61972, - [SMALL_STATE(1285)] = 61983, - [SMALL_STATE(1286)] = 61994, - [SMALL_STATE(1287)] = 62003, - [SMALL_STATE(1288)] = 62012, - [SMALL_STATE(1289)] = 62021, - [SMALL_STATE(1290)] = 62032, - [SMALL_STATE(1291)] = 62043, - [SMALL_STATE(1292)] = 62052, - [SMALL_STATE(1293)] = 62061, - [SMALL_STATE(1294)] = 62072, - [SMALL_STATE(1295)] = 62083, - [SMALL_STATE(1296)] = 62094, - [SMALL_STATE(1297)] = 62105, - [SMALL_STATE(1298)] = 62116, - [SMALL_STATE(1299)] = 62127, - [SMALL_STATE(1300)] = 62138, - [SMALL_STATE(1301)] = 62149, - [SMALL_STATE(1302)] = 62160, - [SMALL_STATE(1303)] = 62169, - [SMALL_STATE(1304)] = 62180, - [SMALL_STATE(1305)] = 62189, - [SMALL_STATE(1306)] = 62200, - [SMALL_STATE(1307)] = 62211, - [SMALL_STATE(1308)] = 62222, - [SMALL_STATE(1309)] = 62233, - [SMALL_STATE(1310)] = 62244, - [SMALL_STATE(1311)] = 62253, - [SMALL_STATE(1312)] = 62262, - [SMALL_STATE(1313)] = 62273, - [SMALL_STATE(1314)] = 62284, - [SMALL_STATE(1315)] = 62295, - [SMALL_STATE(1316)] = 62306, - [SMALL_STATE(1317)] = 62317, - [SMALL_STATE(1318)] = 62326, - [SMALL_STATE(1319)] = 62335, - [SMALL_STATE(1320)] = 62346, - [SMALL_STATE(1321)] = 62355, - [SMALL_STATE(1322)] = 62366, - [SMALL_STATE(1323)] = 62377, - [SMALL_STATE(1324)] = 62388, - [SMALL_STATE(1325)] = 62397, - [SMALL_STATE(1326)] = 62406, - [SMALL_STATE(1327)] = 62417, - [SMALL_STATE(1328)] = 62428, - [SMALL_STATE(1329)] = 62439, - [SMALL_STATE(1330)] = 62450, - [SMALL_STATE(1331)] = 62459, - [SMALL_STATE(1332)] = 62470, - [SMALL_STATE(1333)] = 62481, - [SMALL_STATE(1334)] = 62490, - [SMALL_STATE(1335)] = 62501, - [SMALL_STATE(1336)] = 62510, - [SMALL_STATE(1337)] = 62519, - [SMALL_STATE(1338)] = 62528, - [SMALL_STATE(1339)] = 62539, - [SMALL_STATE(1340)] = 62550, - [SMALL_STATE(1341)] = 62561, - [SMALL_STATE(1342)] = 62572, - [SMALL_STATE(1343)] = 62583, - [SMALL_STATE(1344)] = 62594, - [SMALL_STATE(1345)] = 62605, - [SMALL_STATE(1346)] = 62616, - [SMALL_STATE(1347)] = 62627, - [SMALL_STATE(1348)] = 62638, - [SMALL_STATE(1349)] = 62649, - [SMALL_STATE(1350)] = 62660, - [SMALL_STATE(1351)] = 62671, - [SMALL_STATE(1352)] = 62682, - [SMALL_STATE(1353)] = 62691, - [SMALL_STATE(1354)] = 62702, - [SMALL_STATE(1355)] = 62711, - [SMALL_STATE(1356)] = 62722, - [SMALL_STATE(1357)] = 62733, - [SMALL_STATE(1358)] = 62744, - [SMALL_STATE(1359)] = 62755, - [SMALL_STATE(1360)] = 62766, - [SMALL_STATE(1361)] = 62774, - [SMALL_STATE(1362)] = 62782, - [SMALL_STATE(1363)] = 62790, - [SMALL_STATE(1364)] = 62798, - [SMALL_STATE(1365)] = 62806, - [SMALL_STATE(1366)] = 62814, - [SMALL_STATE(1367)] = 62822, - [SMALL_STATE(1368)] = 62830, - [SMALL_STATE(1369)] = 62838, - [SMALL_STATE(1370)] = 62846, - [SMALL_STATE(1371)] = 62854, - [SMALL_STATE(1372)] = 62862, - [SMALL_STATE(1373)] = 62870, - [SMALL_STATE(1374)] = 62878, - [SMALL_STATE(1375)] = 62886, - [SMALL_STATE(1376)] = 62894, - [SMALL_STATE(1377)] = 62902, - [SMALL_STATE(1378)] = 62910, - [SMALL_STATE(1379)] = 62918, - [SMALL_STATE(1380)] = 62926, - [SMALL_STATE(1381)] = 62934, - [SMALL_STATE(1382)] = 62942, - [SMALL_STATE(1383)] = 62950, - [SMALL_STATE(1384)] = 62958, - [SMALL_STATE(1385)] = 62966, - [SMALL_STATE(1386)] = 62974, - [SMALL_STATE(1387)] = 62982, - [SMALL_STATE(1388)] = 62990, - [SMALL_STATE(1389)] = 62998, - [SMALL_STATE(1390)] = 63006, - [SMALL_STATE(1391)] = 63014, - [SMALL_STATE(1392)] = 63022, - [SMALL_STATE(1393)] = 63030, - [SMALL_STATE(1394)] = 63038, - [SMALL_STATE(1395)] = 63046, - [SMALL_STATE(1396)] = 63054, - [SMALL_STATE(1397)] = 63062, - [SMALL_STATE(1398)] = 63070, - [SMALL_STATE(1399)] = 63078, - [SMALL_STATE(1400)] = 63086, - [SMALL_STATE(1401)] = 63094, - [SMALL_STATE(1402)] = 63102, - [SMALL_STATE(1403)] = 63110, - [SMALL_STATE(1404)] = 63118, - [SMALL_STATE(1405)] = 63126, - [SMALL_STATE(1406)] = 63134, - [SMALL_STATE(1407)] = 63142, - [SMALL_STATE(1408)] = 63150, - [SMALL_STATE(1409)] = 63158, - [SMALL_STATE(1410)] = 63166, - [SMALL_STATE(1411)] = 63174, - [SMALL_STATE(1412)] = 63182, - [SMALL_STATE(1413)] = 63190, - [SMALL_STATE(1414)] = 63198, - [SMALL_STATE(1415)] = 63206, - [SMALL_STATE(1416)] = 63214, - [SMALL_STATE(1417)] = 63222, - [SMALL_STATE(1418)] = 63230, - [SMALL_STATE(1419)] = 63238, - [SMALL_STATE(1420)] = 63246, - [SMALL_STATE(1421)] = 63254, - [SMALL_STATE(1422)] = 63262, - [SMALL_STATE(1423)] = 63270, - [SMALL_STATE(1424)] = 63278, - [SMALL_STATE(1425)] = 63286, - [SMALL_STATE(1426)] = 63294, - [SMALL_STATE(1427)] = 63302, - [SMALL_STATE(1428)] = 63310, - [SMALL_STATE(1429)] = 63318, - [SMALL_STATE(1430)] = 63326, - [SMALL_STATE(1431)] = 63334, - [SMALL_STATE(1432)] = 63342, - [SMALL_STATE(1433)] = 63350, - [SMALL_STATE(1434)] = 63358, - [SMALL_STATE(1435)] = 63366, - [SMALL_STATE(1436)] = 63374, - [SMALL_STATE(1437)] = 63382, - [SMALL_STATE(1438)] = 63390, - [SMALL_STATE(1439)] = 63398, - [SMALL_STATE(1440)] = 63406, - [SMALL_STATE(1441)] = 63414, - [SMALL_STATE(1442)] = 63422, - [SMALL_STATE(1443)] = 63430, - [SMALL_STATE(1444)] = 63438, - [SMALL_STATE(1445)] = 63446, - [SMALL_STATE(1446)] = 63454, - [SMALL_STATE(1447)] = 63462, - [SMALL_STATE(1448)] = 63470, - [SMALL_STATE(1449)] = 63478, - [SMALL_STATE(1450)] = 63486, - [SMALL_STATE(1451)] = 63494, - [SMALL_STATE(1452)] = 63502, - [SMALL_STATE(1453)] = 63510, - [SMALL_STATE(1454)] = 63518, - [SMALL_STATE(1455)] = 63526, - [SMALL_STATE(1456)] = 63534, - [SMALL_STATE(1457)] = 63542, - [SMALL_STATE(1458)] = 63550, - [SMALL_STATE(1459)] = 63558, - [SMALL_STATE(1460)] = 63566, - [SMALL_STATE(1461)] = 63574, - [SMALL_STATE(1462)] = 63582, - [SMALL_STATE(1463)] = 63590, - [SMALL_STATE(1464)] = 63598, - [SMALL_STATE(1465)] = 63606, - [SMALL_STATE(1466)] = 63614, - [SMALL_STATE(1467)] = 63622, - [SMALL_STATE(1468)] = 63630, - [SMALL_STATE(1469)] = 63638, - [SMALL_STATE(1470)] = 63646, - [SMALL_STATE(1471)] = 63654, - [SMALL_STATE(1472)] = 63662, - [SMALL_STATE(1473)] = 63670, - [SMALL_STATE(1474)] = 63678, - [SMALL_STATE(1475)] = 63686, - [SMALL_STATE(1476)] = 63694, - [SMALL_STATE(1477)] = 63702, - [SMALL_STATE(1478)] = 63710, - [SMALL_STATE(1479)] = 63718, - [SMALL_STATE(1480)] = 63726, - [SMALL_STATE(1481)] = 63734, - [SMALL_STATE(1482)] = 63742, - [SMALL_STATE(1483)] = 63750, - [SMALL_STATE(1484)] = 63758, - [SMALL_STATE(1485)] = 63766, - [SMALL_STATE(1486)] = 63774, - [SMALL_STATE(1487)] = 63782, - [SMALL_STATE(1488)] = 63790, - [SMALL_STATE(1489)] = 63798, - [SMALL_STATE(1490)] = 63806, - [SMALL_STATE(1491)] = 63814, - [SMALL_STATE(1492)] = 63822, - [SMALL_STATE(1493)] = 63830, - [SMALL_STATE(1494)] = 63838, - [SMALL_STATE(1495)] = 63846, - [SMALL_STATE(1496)] = 63854, - [SMALL_STATE(1497)] = 63862, - [SMALL_STATE(1498)] = 63870, - [SMALL_STATE(1499)] = 63878, - [SMALL_STATE(1500)] = 63886, - [SMALL_STATE(1501)] = 63894, - [SMALL_STATE(1502)] = 63902, - [SMALL_STATE(1503)] = 63910, - [SMALL_STATE(1504)] = 63918, - [SMALL_STATE(1505)] = 63926, - [SMALL_STATE(1506)] = 63934, - [SMALL_STATE(1507)] = 63942, - [SMALL_STATE(1508)] = 63950, - [SMALL_STATE(1509)] = 63958, - [SMALL_STATE(1510)] = 63966, - [SMALL_STATE(1511)] = 63974, - [SMALL_STATE(1512)] = 63982, - [SMALL_STATE(1513)] = 63990, - [SMALL_STATE(1514)] = 63998, - [SMALL_STATE(1515)] = 64006, - [SMALL_STATE(1516)] = 64014, - [SMALL_STATE(1517)] = 64022, - [SMALL_STATE(1518)] = 64030, - [SMALL_STATE(1519)] = 64038, - [SMALL_STATE(1520)] = 64046, - [SMALL_STATE(1521)] = 64054, - [SMALL_STATE(1522)] = 64062, - [SMALL_STATE(1523)] = 64070, - [SMALL_STATE(1524)] = 64078, - [SMALL_STATE(1525)] = 64086, - [SMALL_STATE(1526)] = 64094, - [SMALL_STATE(1527)] = 64102, - [SMALL_STATE(1528)] = 64110, - [SMALL_STATE(1529)] = 64118, - [SMALL_STATE(1530)] = 64126, - [SMALL_STATE(1531)] = 64134, - [SMALL_STATE(1532)] = 64142, - [SMALL_STATE(1533)] = 64150, - [SMALL_STATE(1534)] = 64158, - [SMALL_STATE(1535)] = 64166, - [SMALL_STATE(1536)] = 64174, - [SMALL_STATE(1537)] = 64182, - [SMALL_STATE(1538)] = 64190, - [SMALL_STATE(1539)] = 64198, - [SMALL_STATE(1540)] = 64206, - [SMALL_STATE(1541)] = 64214, - [SMALL_STATE(1542)] = 64222, - [SMALL_STATE(1543)] = 64230, - [SMALL_STATE(1544)] = 64238, - [SMALL_STATE(1545)] = 64246, - [SMALL_STATE(1546)] = 64254, - [SMALL_STATE(1547)] = 64262, - [SMALL_STATE(1548)] = 64270, - [SMALL_STATE(1549)] = 64278, - [SMALL_STATE(1550)] = 64286, - [SMALL_STATE(1551)] = 64294, - [SMALL_STATE(1552)] = 64302, - [SMALL_STATE(1553)] = 64310, - [SMALL_STATE(1554)] = 64318, - [SMALL_STATE(1555)] = 64326, - [SMALL_STATE(1556)] = 64334, - [SMALL_STATE(1557)] = 64342, + [SMALL_STATE(120)] = 0, + [SMALL_STATE(121)] = 118, + [SMALL_STATE(122)] = 236, + [SMALL_STATE(123)] = 351, + [SMALL_STATE(124)] = 466, + [SMALL_STATE(125)] = 581, + [SMALL_STATE(126)] = 696, + [SMALL_STATE(127)] = 811, + [SMALL_STATE(128)] = 926, + [SMALL_STATE(129)] = 1041, + [SMALL_STATE(130)] = 1156, + [SMALL_STATE(131)] = 1271, + [SMALL_STATE(132)] = 1386, + [SMALL_STATE(133)] = 1501, + [SMALL_STATE(134)] = 1616, + [SMALL_STATE(135)] = 1731, + [SMALL_STATE(136)] = 1846, + [SMALL_STATE(137)] = 1961, + [SMALL_STATE(138)] = 2076, + [SMALL_STATE(139)] = 2191, + [SMALL_STATE(140)] = 2306, + [SMALL_STATE(141)] = 2421, + [SMALL_STATE(142)] = 2536, + [SMALL_STATE(143)] = 2648, + [SMALL_STATE(144)] = 2760, + [SMALL_STATE(145)] = 2872, + [SMALL_STATE(146)] = 2984, + [SMALL_STATE(147)] = 3096, + [SMALL_STATE(148)] = 3208, + [SMALL_STATE(149)] = 3320, + [SMALL_STATE(150)] = 3432, + [SMALL_STATE(151)] = 3544, + [SMALL_STATE(152)] = 3656, + [SMALL_STATE(153)] = 3768, + [SMALL_STATE(154)] = 3880, + [SMALL_STATE(155)] = 3992, + [SMALL_STATE(156)] = 4104, + [SMALL_STATE(157)] = 4216, + [SMALL_STATE(158)] = 4325, + [SMALL_STATE(159)] = 4434, + [SMALL_STATE(160)] = 4543, + [SMALL_STATE(161)] = 4652, + [SMALL_STATE(162)] = 4761, + [SMALL_STATE(163)] = 4870, + [SMALL_STATE(164)] = 4979, + [SMALL_STATE(165)] = 5088, + [SMALL_STATE(166)] = 5197, + [SMALL_STATE(167)] = 5306, + [SMALL_STATE(168)] = 5415, + [SMALL_STATE(169)] = 5524, + [SMALL_STATE(170)] = 5633, + [SMALL_STATE(171)] = 5742, + [SMALL_STATE(172)] = 5851, + [SMALL_STATE(173)] = 5960, + [SMALL_STATE(174)] = 6069, + [SMALL_STATE(175)] = 6178, + [SMALL_STATE(176)] = 6287, + [SMALL_STATE(177)] = 6396, + [SMALL_STATE(178)] = 6505, + [SMALL_STATE(179)] = 6614, + [SMALL_STATE(180)] = 6723, + [SMALL_STATE(181)] = 6832, + [SMALL_STATE(182)] = 6941, + [SMALL_STATE(183)] = 7050, + [SMALL_STATE(184)] = 7159, + [SMALL_STATE(185)] = 7268, + [SMALL_STATE(186)] = 7377, + [SMALL_STATE(187)] = 7486, + [SMALL_STATE(188)] = 7595, + [SMALL_STATE(189)] = 7704, + [SMALL_STATE(190)] = 7813, + [SMALL_STATE(191)] = 7922, + [SMALL_STATE(192)] = 8031, + [SMALL_STATE(193)] = 8140, + [SMALL_STATE(194)] = 8249, + [SMALL_STATE(195)] = 8358, + [SMALL_STATE(196)] = 8467, + [SMALL_STATE(197)] = 8576, + [SMALL_STATE(198)] = 8685, + [SMALL_STATE(199)] = 8794, + [SMALL_STATE(200)] = 8903, + [SMALL_STATE(201)] = 9012, + [SMALL_STATE(202)] = 9121, + [SMALL_STATE(203)] = 9230, + [SMALL_STATE(204)] = 9339, + [SMALL_STATE(205)] = 9448, + [SMALL_STATE(206)] = 9557, + [SMALL_STATE(207)] = 9666, + [SMALL_STATE(208)] = 9775, + [SMALL_STATE(209)] = 9884, + [SMALL_STATE(210)] = 9993, + [SMALL_STATE(211)] = 10102, + [SMALL_STATE(212)] = 10211, + [SMALL_STATE(213)] = 10320, + [SMALL_STATE(214)] = 10429, + [SMALL_STATE(215)] = 10538, + [SMALL_STATE(216)] = 10647, + [SMALL_STATE(217)] = 10756, + [SMALL_STATE(218)] = 10865, + [SMALL_STATE(219)] = 10974, + [SMALL_STATE(220)] = 11083, + [SMALL_STATE(221)] = 11192, + [SMALL_STATE(222)] = 11301, + [SMALL_STATE(223)] = 11410, + [SMALL_STATE(224)] = 11519, + [SMALL_STATE(225)] = 11628, + [SMALL_STATE(226)] = 11737, + [SMALL_STATE(227)] = 11846, + [SMALL_STATE(228)] = 11955, + [SMALL_STATE(229)] = 12064, + [SMALL_STATE(230)] = 12173, + [SMALL_STATE(231)] = 12282, + [SMALL_STATE(232)] = 12391, + [SMALL_STATE(233)] = 12500, + [SMALL_STATE(234)] = 12609, + [SMALL_STATE(235)] = 12718, + [SMALL_STATE(236)] = 12827, + [SMALL_STATE(237)] = 12936, + [SMALL_STATE(238)] = 13045, + [SMALL_STATE(239)] = 13154, + [SMALL_STATE(240)] = 13263, + [SMALL_STATE(241)] = 13372, + [SMALL_STATE(242)] = 13481, + [SMALL_STATE(243)] = 13590, + [SMALL_STATE(244)] = 13699, + [SMALL_STATE(245)] = 13808, + [SMALL_STATE(246)] = 13917, + [SMALL_STATE(247)] = 14026, + [SMALL_STATE(248)] = 14135, + [SMALL_STATE(249)] = 14244, + [SMALL_STATE(250)] = 14353, + [SMALL_STATE(251)] = 14462, + [SMALL_STATE(252)] = 14571, + [SMALL_STATE(253)] = 14680, + [SMALL_STATE(254)] = 14789, + [SMALL_STATE(255)] = 14898, + [SMALL_STATE(256)] = 15007, + [SMALL_STATE(257)] = 15116, + [SMALL_STATE(258)] = 15225, + [SMALL_STATE(259)] = 15334, + [SMALL_STATE(260)] = 15443, + [SMALL_STATE(261)] = 15552, + [SMALL_STATE(262)] = 15661, + [SMALL_STATE(263)] = 15770, + [SMALL_STATE(264)] = 15879, + [SMALL_STATE(265)] = 15988, + [SMALL_STATE(266)] = 16097, + [SMALL_STATE(267)] = 16206, + [SMALL_STATE(268)] = 16315, + [SMALL_STATE(269)] = 16424, + [SMALL_STATE(270)] = 16533, + [SMALL_STATE(271)] = 16642, + [SMALL_STATE(272)] = 16751, + [SMALL_STATE(273)] = 16860, + [SMALL_STATE(274)] = 16969, + [SMALL_STATE(275)] = 17078, + [SMALL_STATE(276)] = 17187, + [SMALL_STATE(277)] = 17296, + [SMALL_STATE(278)] = 17405, + [SMALL_STATE(279)] = 17514, + [SMALL_STATE(280)] = 17623, + [SMALL_STATE(281)] = 17732, + [SMALL_STATE(282)] = 17841, + [SMALL_STATE(283)] = 17950, + [SMALL_STATE(284)] = 18059, + [SMALL_STATE(285)] = 18168, + [SMALL_STATE(286)] = 18277, + [SMALL_STATE(287)] = 18386, + [SMALL_STATE(288)] = 18495, + [SMALL_STATE(289)] = 18604, + [SMALL_STATE(290)] = 18713, + [SMALL_STATE(291)] = 18822, + [SMALL_STATE(292)] = 18931, + [SMALL_STATE(293)] = 19040, + [SMALL_STATE(294)] = 19149, + [SMALL_STATE(295)] = 19258, + [SMALL_STATE(296)] = 19367, + [SMALL_STATE(297)] = 19476, + [SMALL_STATE(298)] = 19558, + [SMALL_STATE(299)] = 19663, + [SMALL_STATE(300)] = 19768, + [SMALL_STATE(301)] = 19873, + [SMALL_STATE(302)] = 19978, + [SMALL_STATE(303)] = 20083, + [SMALL_STATE(304)] = 20188, + [SMALL_STATE(305)] = 20293, + [SMALL_STATE(306)] = 20398, + [SMALL_STATE(307)] = 20503, + [SMALL_STATE(308)] = 20608, + [SMALL_STATE(309)] = 20713, + [SMALL_STATE(310)] = 20818, + [SMALL_STATE(311)] = 20923, + [SMALL_STATE(312)] = 21028, + [SMALL_STATE(313)] = 21133, + [SMALL_STATE(314)] = 21238, + [SMALL_STATE(315)] = 21343, + [SMALL_STATE(316)] = 21448, + [SMALL_STATE(317)] = 21553, + [SMALL_STATE(318)] = 21658, + [SMALL_STATE(319)] = 21763, + [SMALL_STATE(320)] = 21868, + [SMALL_STATE(321)] = 21973, + [SMALL_STATE(322)] = 22078, + [SMALL_STATE(323)] = 22183, + [SMALL_STATE(324)] = 22288, + [SMALL_STATE(325)] = 22393, + [SMALL_STATE(326)] = 22498, + [SMALL_STATE(327)] = 22603, + [SMALL_STATE(328)] = 22708, + [SMALL_STATE(329)] = 22813, + [SMALL_STATE(330)] = 22918, + [SMALL_STATE(331)] = 23023, + [SMALL_STATE(332)] = 23128, + [SMALL_STATE(333)] = 23233, + [SMALL_STATE(334)] = 23338, + [SMALL_STATE(335)] = 23443, + [SMALL_STATE(336)] = 23548, + [SMALL_STATE(337)] = 23653, + [SMALL_STATE(338)] = 23758, + [SMALL_STATE(339)] = 23863, + [SMALL_STATE(340)] = 23968, + [SMALL_STATE(341)] = 24073, + [SMALL_STATE(342)] = 24178, + [SMALL_STATE(343)] = 24283, + [SMALL_STATE(344)] = 24388, + [SMALL_STATE(345)] = 24493, + [SMALL_STATE(346)] = 24598, + [SMALL_STATE(347)] = 24703, + [SMALL_STATE(348)] = 24808, + [SMALL_STATE(349)] = 24913, + [SMALL_STATE(350)] = 25018, + [SMALL_STATE(351)] = 25123, + [SMALL_STATE(352)] = 25228, + [SMALL_STATE(353)] = 25333, + [SMALL_STATE(354)] = 25438, + [SMALL_STATE(355)] = 25543, + [SMALL_STATE(356)] = 25648, + [SMALL_STATE(357)] = 25753, + [SMALL_STATE(358)] = 25858, + [SMALL_STATE(359)] = 25963, + [SMALL_STATE(360)] = 26068, + [SMALL_STATE(361)] = 26173, + [SMALL_STATE(362)] = 26278, + [SMALL_STATE(363)] = 26383, + [SMALL_STATE(364)] = 26488, + [SMALL_STATE(365)] = 26593, + [SMALL_STATE(366)] = 26698, + [SMALL_STATE(367)] = 26803, + [SMALL_STATE(368)] = 26908, + [SMALL_STATE(369)] = 27013, + [SMALL_STATE(370)] = 27118, + [SMALL_STATE(371)] = 27223, + [SMALL_STATE(372)] = 27328, + [SMALL_STATE(373)] = 27433, + [SMALL_STATE(374)] = 27538, + [SMALL_STATE(375)] = 27643, + [SMALL_STATE(376)] = 27748, + [SMALL_STATE(377)] = 27853, + [SMALL_STATE(378)] = 27958, + [SMALL_STATE(379)] = 28063, + [SMALL_STATE(380)] = 28168, + [SMALL_STATE(381)] = 28273, + [SMALL_STATE(382)] = 28378, + [SMALL_STATE(383)] = 28483, + [SMALL_STATE(384)] = 28588, + [SMALL_STATE(385)] = 28693, + [SMALL_STATE(386)] = 28798, + [SMALL_STATE(387)] = 28903, + [SMALL_STATE(388)] = 29008, + [SMALL_STATE(389)] = 29113, + [SMALL_STATE(390)] = 29218, + [SMALL_STATE(391)] = 29323, + [SMALL_STATE(392)] = 29428, + [SMALL_STATE(393)] = 29533, + [SMALL_STATE(394)] = 29638, + [SMALL_STATE(395)] = 29743, + [SMALL_STATE(396)] = 29848, + [SMALL_STATE(397)] = 29953, + [SMALL_STATE(398)] = 30058, + [SMALL_STATE(399)] = 30163, + [SMALL_STATE(400)] = 30268, + [SMALL_STATE(401)] = 30373, + [SMALL_STATE(402)] = 30478, + [SMALL_STATE(403)] = 30583, + [SMALL_STATE(404)] = 30688, + [SMALL_STATE(405)] = 30793, + [SMALL_STATE(406)] = 30898, + [SMALL_STATE(407)] = 31003, + [SMALL_STATE(408)] = 31108, + [SMALL_STATE(409)] = 31213, + [SMALL_STATE(410)] = 31318, + [SMALL_STATE(411)] = 31423, + [SMALL_STATE(412)] = 31528, + [SMALL_STATE(413)] = 31633, + [SMALL_STATE(414)] = 31738, + [SMALL_STATE(415)] = 31843, + [SMALL_STATE(416)] = 31948, + [SMALL_STATE(417)] = 32053, + [SMALL_STATE(418)] = 32158, + [SMALL_STATE(419)] = 32263, + [SMALL_STATE(420)] = 32368, + [SMALL_STATE(421)] = 32473, + [SMALL_STATE(422)] = 32578, + [SMALL_STATE(423)] = 32683, + [SMALL_STATE(424)] = 32788, + [SMALL_STATE(425)] = 32893, + [SMALL_STATE(426)] = 32998, + [SMALL_STATE(427)] = 33103, + [SMALL_STATE(428)] = 33208, + [SMALL_STATE(429)] = 33313, + [SMALL_STATE(430)] = 33418, + [SMALL_STATE(431)] = 33523, + [SMALL_STATE(432)] = 33628, + [SMALL_STATE(433)] = 33733, + [SMALL_STATE(434)] = 33838, + [SMALL_STATE(435)] = 33943, + [SMALL_STATE(436)] = 34048, + [SMALL_STATE(437)] = 34153, + [SMALL_STATE(438)] = 34258, + [SMALL_STATE(439)] = 34363, + [SMALL_STATE(440)] = 34468, + [SMALL_STATE(441)] = 34573, + [SMALL_STATE(442)] = 34678, + [SMALL_STATE(443)] = 34783, + [SMALL_STATE(444)] = 34888, + [SMALL_STATE(445)] = 34993, + [SMALL_STATE(446)] = 35098, + [SMALL_STATE(447)] = 35203, + [SMALL_STATE(448)] = 35308, + [SMALL_STATE(449)] = 35413, + [SMALL_STATE(450)] = 35493, + [SMALL_STATE(451)] = 35573, + [SMALL_STATE(452)] = 35653, + [SMALL_STATE(453)] = 35733, + [SMALL_STATE(454)] = 35813, + [SMALL_STATE(455)] = 35893, + [SMALL_STATE(456)] = 35973, + [SMALL_STATE(457)] = 36053, + [SMALL_STATE(458)] = 36133, + [SMALL_STATE(459)] = 36213, + [SMALL_STATE(460)] = 36293, + [SMALL_STATE(461)] = 36373, + [SMALL_STATE(462)] = 36453, + [SMALL_STATE(463)] = 36533, + [SMALL_STATE(464)] = 36613, + [SMALL_STATE(465)] = 36693, + [SMALL_STATE(466)] = 36773, + [SMALL_STATE(467)] = 36853, + [SMALL_STATE(468)] = 36933, + [SMALL_STATE(469)] = 37013, + [SMALL_STATE(470)] = 37093, + [SMALL_STATE(471)] = 37173, + [SMALL_STATE(472)] = 37253, + [SMALL_STATE(473)] = 37333, + [SMALL_STATE(474)] = 37413, + [SMALL_STATE(475)] = 37493, + [SMALL_STATE(476)] = 37573, + [SMALL_STATE(477)] = 37653, + [SMALL_STATE(478)] = 37733, + [SMALL_STATE(479)] = 37813, + [SMALL_STATE(480)] = 37893, + [SMALL_STATE(481)] = 37973, + [SMALL_STATE(482)] = 38053, + [SMALL_STATE(483)] = 38133, + [SMALL_STATE(484)] = 38213, + [SMALL_STATE(485)] = 38293, + [SMALL_STATE(486)] = 38373, + [SMALL_STATE(487)] = 38453, + [SMALL_STATE(488)] = 38533, + [SMALL_STATE(489)] = 38613, + [SMALL_STATE(490)] = 38693, + [SMALL_STATE(491)] = 38773, + [SMALL_STATE(492)] = 38853, + [SMALL_STATE(493)] = 38933, + [SMALL_STATE(494)] = 38989, + [SMALL_STATE(495)] = 39044, + [SMALL_STATE(496)] = 39099, + [SMALL_STATE(497)] = 39154, + [SMALL_STATE(498)] = 39209, + [SMALL_STATE(499)] = 39264, + [SMALL_STATE(500)] = 39319, + [SMALL_STATE(501)] = 39374, + [SMALL_STATE(502)] = 39429, + [SMALL_STATE(503)] = 39483, + [SMALL_STATE(504)] = 39537, + [SMALL_STATE(505)] = 39591, + [SMALL_STATE(506)] = 39642, + [SMALL_STATE(507)] = 39693, + [SMALL_STATE(508)] = 39744, + [SMALL_STATE(509)] = 39805, + [SMALL_STATE(510)] = 39858, + [SMALL_STATE(511)] = 39913, + [SMALL_STATE(512)] = 39970, + [SMALL_STATE(513)] = 40029, + [SMALL_STATE(514)] = 40094, + [SMALL_STATE(515)] = 40161, + [SMALL_STATE(516)] = 40212, + [SMALL_STATE(517)] = 40263, + [SMALL_STATE(518)] = 40314, + [SMALL_STATE(519)] = 40367, + [SMALL_STATE(520)] = 40418, + [SMALL_STATE(521)] = 40469, + [SMALL_STATE(522)] = 40522, + [SMALL_STATE(523)] = 40573, + [SMALL_STATE(524)] = 40624, + [SMALL_STATE(525)] = 40675, + [SMALL_STATE(526)] = 40726, + [SMALL_STATE(527)] = 40777, + [SMALL_STATE(528)] = 40828, + [SMALL_STATE(529)] = 40879, + [SMALL_STATE(530)] = 40930, + [SMALL_STATE(531)] = 40981, + [SMALL_STATE(532)] = 41032, + [SMALL_STATE(533)] = 41105, + [SMALL_STATE(534)] = 41176, + [SMALL_STATE(535)] = 41253, + [SMALL_STATE(536)] = 41346, + [SMALL_STATE(537)] = 41439, + [SMALL_STATE(538)] = 41490, + [SMALL_STATE(539)] = 41541, + [SMALL_STATE(540)] = 41592, + [SMALL_STATE(541)] = 41645, + [SMALL_STATE(542)] = 41696, + [SMALL_STATE(543)] = 41747, + [SMALL_STATE(544)] = 41798, + [SMALL_STATE(545)] = 41849, + [SMALL_STATE(546)] = 41900, + [SMALL_STATE(547)] = 41951, + [SMALL_STATE(548)] = 42002, + [SMALL_STATE(549)] = 42053, + [SMALL_STATE(550)] = 42104, + [SMALL_STATE(551)] = 42155, + [SMALL_STATE(552)] = 42206, + [SMALL_STATE(553)] = 42257, + [SMALL_STATE(554)] = 42308, + [SMALL_STATE(555)] = 42359, + [SMALL_STATE(556)] = 42410, + [SMALL_STATE(557)] = 42461, + [SMALL_STATE(558)] = 42512, + [SMALL_STATE(559)] = 42563, + [SMALL_STATE(560)] = 42614, + [SMALL_STATE(561)] = 42665, + [SMALL_STATE(562)] = 42716, + [SMALL_STATE(563)] = 42767, + [SMALL_STATE(564)] = 42818, + [SMALL_STATE(565)] = 42869, + [SMALL_STATE(566)] = 42920, + [SMALL_STATE(567)] = 42971, + [SMALL_STATE(568)] = 43022, + [SMALL_STATE(569)] = 43073, + [SMALL_STATE(570)] = 43124, + [SMALL_STATE(571)] = 43175, + [SMALL_STATE(572)] = 43226, + [SMALL_STATE(573)] = 43277, + [SMALL_STATE(574)] = 43328, + [SMALL_STATE(575)] = 43379, + [SMALL_STATE(576)] = 43430, + [SMALL_STATE(577)] = 43481, + [SMALL_STATE(578)] = 43532, + [SMALL_STATE(579)] = 43583, + [SMALL_STATE(580)] = 43634, + [SMALL_STATE(581)] = 43685, + [SMALL_STATE(582)] = 43736, + [SMALL_STATE(583)] = 43787, + [SMALL_STATE(584)] = 43838, + [SMALL_STATE(585)] = 43889, + [SMALL_STATE(586)] = 43940, + [SMALL_STATE(587)] = 43991, + [SMALL_STATE(588)] = 44042, + [SMALL_STATE(589)] = 44093, + [SMALL_STATE(590)] = 44144, + [SMALL_STATE(591)] = 44195, + [SMALL_STATE(592)] = 44246, + [SMALL_STATE(593)] = 44297, + [SMALL_STATE(594)] = 44348, + [SMALL_STATE(595)] = 44399, + [SMALL_STATE(596)] = 44450, + [SMALL_STATE(597)] = 44501, + [SMALL_STATE(598)] = 44552, + [SMALL_STATE(599)] = 44603, + [SMALL_STATE(600)] = 44654, + [SMALL_STATE(601)] = 44705, + [SMALL_STATE(602)] = 44756, + [SMALL_STATE(603)] = 44807, + [SMALL_STATE(604)] = 44858, + [SMALL_STATE(605)] = 44909, + [SMALL_STATE(606)] = 44960, + [SMALL_STATE(607)] = 45011, + [SMALL_STATE(608)] = 45062, + [SMALL_STATE(609)] = 45113, + [SMALL_STATE(610)] = 45166, + [SMALL_STATE(611)] = 45217, + [SMALL_STATE(612)] = 45294, + [SMALL_STATE(613)] = 45347, + [SMALL_STATE(614)] = 45398, + [SMALL_STATE(615)] = 45448, + [SMALL_STATE(616)] = 45498, + [SMALL_STATE(617)] = 45548, + [SMALL_STATE(618)] = 45598, + [SMALL_STATE(619)] = 45648, + [SMALL_STATE(620)] = 45698, + [SMALL_STATE(621)] = 45748, + [SMALL_STATE(622)] = 45798, + [SMALL_STATE(623)] = 45848, + [SMALL_STATE(624)] = 45898, + [SMALL_STATE(625)] = 45948, + [SMALL_STATE(626)] = 45998, + [SMALL_STATE(627)] = 46048, + [SMALL_STATE(628)] = 46098, + [SMALL_STATE(629)] = 46148, + [SMALL_STATE(630)] = 46200, + [SMALL_STATE(631)] = 46252, + [SMALL_STATE(632)] = 46302, + [SMALL_STATE(633)] = 46352, + [SMALL_STATE(634)] = 46402, + [SMALL_STATE(635)] = 46454, + [SMALL_STATE(636)] = 46506, + [SMALL_STATE(637)] = 46558, + [SMALL_STATE(638)] = 46608, + [SMALL_STATE(639)] = 46658, + [SMALL_STATE(640)] = 46708, + [SMALL_STATE(641)] = 46758, + [SMALL_STATE(642)] = 46808, + [SMALL_STATE(643)] = 46858, + [SMALL_STATE(644)] = 46908, + [SMALL_STATE(645)] = 46958, + [SMALL_STATE(646)] = 47010, + [SMALL_STATE(647)] = 47060, + [SMALL_STATE(648)] = 47110, + [SMALL_STATE(649)] = 47160, + [SMALL_STATE(650)] = 47212, + [SMALL_STATE(651)] = 47262, + [SMALL_STATE(652)] = 47312, + [SMALL_STATE(653)] = 47362, + [SMALL_STATE(654)] = 47412, + [SMALL_STATE(655)] = 47462, + [SMALL_STATE(656)] = 47512, + [SMALL_STATE(657)] = 47562, + [SMALL_STATE(658)] = 47612, + [SMALL_STATE(659)] = 47662, + [SMALL_STATE(660)] = 47712, + [SMALL_STATE(661)] = 47762, + [SMALL_STATE(662)] = 47812, + [SMALL_STATE(663)] = 47862, + [SMALL_STATE(664)] = 47912, + [SMALL_STATE(665)] = 47962, + [SMALL_STATE(666)] = 48012, + [SMALL_STATE(667)] = 48064, + [SMALL_STATE(668)] = 48113, + [SMALL_STATE(669)] = 48163, + [SMALL_STATE(670)] = 48212, + [SMALL_STATE(671)] = 48258, + [SMALL_STATE(672)] = 48304, + [SMALL_STATE(673)] = 48350, + [SMALL_STATE(674)] = 48396, + [SMALL_STATE(675)] = 48442, + [SMALL_STATE(676)] = 48488, + [SMALL_STATE(677)] = 48534, + [SMALL_STATE(678)] = 48582, + [SMALL_STATE(679)] = 48628, + [SMALL_STATE(680)] = 48674, + [SMALL_STATE(681)] = 48720, + [SMALL_STATE(682)] = 48766, + [SMALL_STATE(683)] = 48812, + [SMALL_STATE(684)] = 48858, + [SMALL_STATE(685)] = 48904, + [SMALL_STATE(686)] = 48950, + [SMALL_STATE(687)] = 48996, + [SMALL_STATE(688)] = 49042, + [SMALL_STATE(689)] = 49088, + [SMALL_STATE(690)] = 49134, + [SMALL_STATE(691)] = 49180, + [SMALL_STATE(692)] = 49226, + [SMALL_STATE(693)] = 49282, + [SMALL_STATE(694)] = 49328, + [SMALL_STATE(695)] = 49374, + [SMALL_STATE(696)] = 49420, + [SMALL_STATE(697)] = 49466, + [SMALL_STATE(698)] = 49512, + [SMALL_STATE(699)] = 49558, + [SMALL_STATE(700)] = 49604, + [SMALL_STATE(701)] = 49650, + [SMALL_STATE(702)] = 49696, + [SMALL_STATE(703)] = 49742, + [SMALL_STATE(704)] = 49788, + [SMALL_STATE(705)] = 49834, + [SMALL_STATE(706)] = 49880, + [SMALL_STATE(707)] = 49926, + [SMALL_STATE(708)] = 49972, + [SMALL_STATE(709)] = 50018, + [SMALL_STATE(710)] = 50064, + [SMALL_STATE(711)] = 50110, + [SMALL_STATE(712)] = 50156, + [SMALL_STATE(713)] = 50202, + [SMALL_STATE(714)] = 50248, + [SMALL_STATE(715)] = 50294, + [SMALL_STATE(716)] = 50340, + [SMALL_STATE(717)] = 50386, + [SMALL_STATE(718)] = 50432, + [SMALL_STATE(719)] = 50485, + [SMALL_STATE(720)] = 50538, + [SMALL_STATE(721)] = 50608, + [SMALL_STATE(722)] = 50674, + [SMALL_STATE(723)] = 50724, + [SMALL_STATE(724)] = 50808, + [SMALL_STATE(725)] = 50860, + [SMALL_STATE(726)] = 50918, + [SMALL_STATE(727)] = 50988, + [SMALL_STATE(728)] = 51048, + [SMALL_STATE(729)] = 51112, + [SMALL_STATE(730)] = 51166, + [SMALL_STATE(731)] = 51212, + [SMALL_STATE(732)] = 51296, + [SMALL_STATE(733)] = 51344, + [SMALL_STATE(734)] = 51401, + [SMALL_STATE(735)] = 51468, + [SMALL_STATE(736)] = 51549, + [SMALL_STATE(737)] = 51630, + [SMALL_STATE(738)] = 51711, + [SMALL_STATE(739)] = 51792, + [SMALL_STATE(740)] = 51859, + [SMALL_STATE(741)] = 51910, + [SMALL_STATE(742)] = 51953, + [SMALL_STATE(743)] = 51998, + [SMALL_STATE(744)] = 52045, + [SMALL_STATE(745)] = 52094, + [SMALL_STATE(746)] = 52149, + [SMALL_STATE(747)] = 52206, + [SMALL_STATE(748)] = 52249, + [SMALL_STATE(749)] = 52302, + [SMALL_STATE(750)] = 52369, + [SMALL_STATE(751)] = 52420, + [SMALL_STATE(752)] = 52463, + [SMALL_STATE(753)] = 52508, + [SMALL_STATE(754)] = 52555, + [SMALL_STATE(755)] = 52604, + [SMALL_STATE(756)] = 52659, + [SMALL_STATE(757)] = 52722, + [SMALL_STATE(758)] = 52783, + [SMALL_STATE(759)] = 52850, + [SMALL_STATE(760)] = 52913, + [SMALL_STATE(761)] = 52974, + [SMALL_STATE(762)] = 53024, + [SMALL_STATE(763)] = 53074, + [SMALL_STATE(764)] = 53124, + [SMALL_STATE(765)] = 53190, + [SMALL_STATE(766)] = 53240, + [SMALL_STATE(767)] = 53290, + [SMALL_STATE(768)] = 53340, + [SMALL_STATE(769)] = 53390, + [SMALL_STATE(770)] = 53440, + [SMALL_STATE(771)] = 53518, + [SMALL_STATE(772)] = 53568, + [SMALL_STATE(773)] = 53618, + [SMALL_STATE(774)] = 53668, + [SMALL_STATE(775)] = 53718, + [SMALL_STATE(776)] = 53768, + [SMALL_STATE(777)] = 53818, + [SMALL_STATE(778)] = 53868, + [SMALL_STATE(779)] = 53910, + [SMALL_STATE(780)] = 53954, + [SMALL_STATE(781)] = 54000, + [SMALL_STATE(782)] = 54048, + [SMALL_STATE(783)] = 54102, + [SMALL_STATE(784)] = 54158, + [SMALL_STATE(785)] = 54208, + [SMALL_STATE(786)] = 54258, + [SMALL_STATE(787)] = 54302, + [SMALL_STATE(788)] = 54348, + [SMALL_STATE(789)] = 54396, + [SMALL_STATE(790)] = 54450, + [SMALL_STATE(791)] = 54506, + [SMALL_STATE(792)] = 54568, + [SMALL_STATE(793)] = 54628, + [SMALL_STATE(794)] = 54694, + [SMALL_STATE(795)] = 54744, + [SMALL_STATE(796)] = 54794, + [SMALL_STATE(797)] = 54844, + [SMALL_STATE(798)] = 54894, + [SMALL_STATE(799)] = 54944, + [SMALL_STATE(800)] = 54994, + [SMALL_STATE(801)] = 55044, + [SMALL_STATE(802)] = 55094, + [SMALL_STATE(803)] = 55144, + [SMALL_STATE(804)] = 55194, + [SMALL_STATE(805)] = 55244, + [SMALL_STATE(806)] = 55294, + [SMALL_STATE(807)] = 55344, + [SMALL_STATE(808)] = 55426, + [SMALL_STATE(809)] = 55476, + [SMALL_STATE(810)] = 55542, + [SMALL_STATE(811)] = 55604, + [SMALL_STATE(812)] = 55664, + [SMALL_STATE(813)] = 55746, + [SMALL_STATE(814)] = 55796, + [SMALL_STATE(815)] = 55862, + [SMALL_STATE(816)] = 55906, + [SMALL_STATE(817)] = 55948, + [SMALL_STATE(818)] = 55987, + [SMALL_STATE(819)] = 56030, + [SMALL_STATE(820)] = 56075, + [SMALL_STATE(821)] = 56122, + [SMALL_STATE(822)] = 56195, + [SMALL_STATE(823)] = 56248, + [SMALL_STATE(824)] = 56303, + [SMALL_STATE(825)] = 56382, + [SMALL_STATE(826)] = 56455, + [SMALL_STATE(827)] = 56528, + [SMALL_STATE(828)] = 56607, + [SMALL_STATE(829)] = 56646, + [SMALL_STATE(830)] = 56687, + [SMALL_STATE(831)] = 56726, + [SMALL_STATE(832)] = 56765, + [SMALL_STATE(833)] = 56804, + [SMALL_STATE(834)] = 56883, + [SMALL_STATE(835)] = 56922, + [SMALL_STATE(836)] = 56961, + [SMALL_STATE(837)] = 57000, + [SMALL_STATE(838)] = 57039, + [SMALL_STATE(839)] = 57078, + [SMALL_STATE(840)] = 57117, + [SMALL_STATE(841)] = 57190, + [SMALL_STATE(842)] = 57229, + [SMALL_STATE(843)] = 57268, + [SMALL_STATE(844)] = 57307, + [SMALL_STATE(845)] = 57346, + [SMALL_STATE(846)] = 57385, + [SMALL_STATE(847)] = 57424, + [SMALL_STATE(848)] = 57485, + [SMALL_STATE(849)] = 57544, + [SMALL_STATE(850)] = 57583, + [SMALL_STATE(851)] = 57656, + [SMALL_STATE(852)] = 57695, + [SMALL_STATE(853)] = 57760, + [SMALL_STATE(854)] = 57799, + [SMALL_STATE(855)] = 57838, + [SMALL_STATE(856)] = 57877, + [SMALL_STATE(857)] = 57916, + [SMALL_STATE(858)] = 57965, + [SMALL_STATE(859)] = 58006, + [SMALL_STATE(860)] = 58049, + [SMALL_STATE(861)] = 58094, + [SMALL_STATE(862)] = 58141, + [SMALL_STATE(863)] = 58194, + [SMALL_STATE(864)] = 58249, + [SMALL_STATE(865)] = 58310, + [SMALL_STATE(866)] = 58369, + [SMALL_STATE(867)] = 58434, + [SMALL_STATE(868)] = 58473, + [SMALL_STATE(869)] = 58512, + [SMALL_STATE(870)] = 58551, + [SMALL_STATE(871)] = 58590, + [SMALL_STATE(872)] = 58629, + [SMALL_STATE(873)] = 58668, + [SMALL_STATE(874)] = 58707, + [SMALL_STATE(875)] = 58746, + [SMALL_STATE(876)] = 58785, + [SMALL_STATE(877)] = 58824, + [SMALL_STATE(878)] = 58863, + [SMALL_STATE(879)] = 58936, + [SMALL_STATE(880)] = 58975, + [SMALL_STATE(881)] = 59048, + [SMALL_STATE(882)] = 59087, + [SMALL_STATE(883)] = 59126, + [SMALL_STATE(884)] = 59165, + [SMALL_STATE(885)] = 59204, + [SMALL_STATE(886)] = 59243, + [SMALL_STATE(887)] = 59282, + [SMALL_STATE(888)] = 59347, + [SMALL_STATE(889)] = 59386, + [SMALL_STATE(890)] = 59425, + [SMALL_STATE(891)] = 59464, + [SMALL_STATE(892)] = 59537, + [SMALL_STATE(893)] = 59602, + [SMALL_STATE(894)] = 59641, + [SMALL_STATE(895)] = 59690, + [SMALL_STATE(896)] = 59763, + [SMALL_STATE(897)] = 59804, + [SMALL_STATE(898)] = 59843, + [SMALL_STATE(899)] = 59889, + [SMALL_STATE(900)] = 59967, + [SMALL_STATE(901)] = 60031, + [SMALL_STATE(902)] = 60071, + [SMALL_STATE(903)] = 60143, + [SMALL_STATE(904)] = 60215, + [SMALL_STATE(905)] = 60291, + [SMALL_STATE(906)] = 60355, + [SMALL_STATE(907)] = 60399, + [SMALL_STATE(908)] = 60441, + [SMALL_STATE(909)] = 60489, + [SMALL_STATE(910)] = 60531, + [SMALL_STATE(911)] = 60609, + [SMALL_STATE(912)] = 60669, + [SMALL_STATE(913)] = 60721, + [SMALL_STATE(914)] = 60793, + [SMALL_STATE(915)] = 60841, + [SMALL_STATE(916)] = 60881, + [SMALL_STATE(917)] = 60923, + [SMALL_STATE(918)] = 60967, + [SMALL_STATE(919)] = 61013, + [SMALL_STATE(920)] = 61065, + [SMALL_STATE(921)] = 61119, + [SMALL_STATE(922)] = 61173, + [SMALL_STATE(923)] = 61245, + [SMALL_STATE(924)] = 61303, + [SMALL_STATE(925)] = 61363, + [SMALL_STATE(926)] = 61421, + [SMALL_STATE(927)] = 61485, + [SMALL_STATE(928)] = 61549, + [SMALL_STATE(929)] = 61629, + [SMALL_STATE(930)] = 61698, + [SMALL_STATE(931)] = 61761, + [SMALL_STATE(932)] = 61820, + [SMALL_STATE(933)] = 61895, + [SMALL_STATE(934)] = 61964, + [SMALL_STATE(935)] = 62011, + [SMALL_STATE(936)] = 62086, + [SMALL_STATE(937)] = 62143, + [SMALL_STATE(938)] = 62182, + [SMALL_STATE(939)] = 62223, + [SMALL_STATE(940)] = 62266, + [SMALL_STATE(941)] = 62311, + [SMALL_STATE(942)] = 62386, + [SMALL_STATE(943)] = 62437, + [SMALL_STATE(944)] = 62490, + [SMALL_STATE(945)] = 62565, + [SMALL_STATE(946)] = 62628, + [SMALL_STATE(947)] = 62694, + [SMALL_STATE(948)] = 62760, + [SMALL_STATE(949)] = 62826, + [SMALL_STATE(950)] = 62892, + [SMALL_STATE(951)] = 62958, + [SMALL_STATE(952)] = 63024, + [SMALL_STATE(953)] = 63090, + [SMALL_STATE(954)] = 63156, + [SMALL_STATE(955)] = 63222, + [SMALL_STATE(956)] = 63288, + [SMALL_STATE(957)] = 63354, + [SMALL_STATE(958)] = 63420, + [SMALL_STATE(959)] = 63486, + [SMALL_STATE(960)] = 63532, + [SMALL_STATE(961)] = 63578, + [SMALL_STATE(962)] = 63624, + [SMALL_STATE(963)] = 63647, + [SMALL_STATE(964)] = 63673, + [SMALL_STATE(965)] = 63712, + [SMALL_STATE(966)] = 63751, + [SMALL_STATE(967)] = 63790, + [SMALL_STATE(968)] = 63824, + [SMALL_STATE(969)] = 63858, + [SMALL_STATE(970)] = 63880, + [SMALL_STATE(971)] = 63898, + [SMALL_STATE(972)] = 63918, + [SMALL_STATE(973)] = 63952, + [SMALL_STATE(974)] = 63986, + [SMALL_STATE(975)] = 64006, + [SMALL_STATE(976)] = 64040, + [SMALL_STATE(977)] = 64060, + [SMALL_STATE(978)] = 64094, + [SMALL_STATE(979)] = 64114, + [SMALL_STATE(980)] = 64134, + [SMALL_STATE(981)] = 64165, + [SMALL_STATE(982)] = 64186, + [SMALL_STATE(983)] = 64209, + [SMALL_STATE(984)] = 64232, + [SMALL_STATE(985)] = 64263, + [SMALL_STATE(986)] = 64282, + [SMALL_STATE(987)] = 64313, + [SMALL_STATE(988)] = 64334, + [SMALL_STATE(989)] = 64365, + [SMALL_STATE(990)] = 64381, + [SMALL_STATE(991)] = 64409, + [SMALL_STATE(992)] = 64427, + [SMALL_STATE(993)] = 64443, + [SMALL_STATE(994)] = 64463, + [SMALL_STATE(995)] = 64483, + [SMALL_STATE(996)] = 64499, + [SMALL_STATE(997)] = 64519, + [SMALL_STATE(998)] = 64535, + [SMALL_STATE(999)] = 64551, + [SMALL_STATE(1000)] = 64579, + [SMALL_STATE(1001)] = 64599, + [SMALL_STATE(1002)] = 64615, + [SMALL_STATE(1003)] = 64633, + [SMALL_STATE(1004)] = 64651, + [SMALL_STATE(1005)] = 64679, + [SMALL_STATE(1006)] = 64704, + [SMALL_STATE(1007)] = 64719, + [SMALL_STATE(1008)] = 64734, + [SMALL_STATE(1009)] = 64759, + [SMALL_STATE(1010)] = 64774, + [SMALL_STATE(1011)] = 64789, + [SMALL_STATE(1012)] = 64804, + [SMALL_STATE(1013)] = 64829, + [SMALL_STATE(1014)] = 64844, + [SMALL_STATE(1015)] = 64869, + [SMALL_STATE(1016)] = 64884, + [SMALL_STATE(1017)] = 64909, + [SMALL_STATE(1018)] = 64924, + [SMALL_STATE(1019)] = 64939, + [SMALL_STATE(1020)] = 64954, + [SMALL_STATE(1021)] = 64969, + [SMALL_STATE(1022)] = 64994, + [SMALL_STATE(1023)] = 65013, + [SMALL_STATE(1024)] = 65028, + [SMALL_STATE(1025)] = 65043, + [SMALL_STATE(1026)] = 65058, + [SMALL_STATE(1027)] = 65073, + [SMALL_STATE(1028)] = 65088, + [SMALL_STATE(1029)] = 65114, + [SMALL_STATE(1030)] = 65132, + [SMALL_STATE(1031)] = 65152, + [SMALL_STATE(1032)] = 65170, + [SMALL_STATE(1033)] = 65196, + [SMALL_STATE(1034)] = 65222, + [SMALL_STATE(1035)] = 65248, + [SMALL_STATE(1036)] = 65274, + [SMALL_STATE(1037)] = 65300, + [SMALL_STATE(1038)] = 65318, + [SMALL_STATE(1039)] = 65344, + [SMALL_STATE(1040)] = 65370, + [SMALL_STATE(1041)] = 65390, + [SMALL_STATE(1042)] = 65416, + [SMALL_STATE(1043)] = 65442, + [SMALL_STATE(1044)] = 65461, + [SMALL_STATE(1045)] = 65480, + [SMALL_STATE(1046)] = 65499, + [SMALL_STATE(1047)] = 65512, + [SMALL_STATE(1048)] = 65531, + [SMALL_STATE(1049)] = 65544, + [SMALL_STATE(1050)] = 65563, + [SMALL_STATE(1051)] = 65582, + [SMALL_STATE(1052)] = 65601, + [SMALL_STATE(1053)] = 65620, + [SMALL_STATE(1054)] = 65637, + [SMALL_STATE(1055)] = 65656, + [SMALL_STATE(1056)] = 65669, + [SMALL_STATE(1057)] = 65688, + [SMALL_STATE(1058)] = 65707, + [SMALL_STATE(1059)] = 65726, + [SMALL_STATE(1060)] = 65749, + [SMALL_STATE(1061)] = 65768, + [SMALL_STATE(1062)] = 65787, + [SMALL_STATE(1063)] = 65804, + [SMALL_STATE(1064)] = 65823, + [SMALL_STATE(1065)] = 65842, + [SMALL_STATE(1066)] = 65861, + [SMALL_STATE(1067)] = 65874, + [SMALL_STATE(1068)] = 65887, + [SMALL_STATE(1069)] = 65900, + [SMALL_STATE(1070)] = 65913, + [SMALL_STATE(1071)] = 65932, + [SMALL_STATE(1072)] = 65951, + [SMALL_STATE(1073)] = 65970, + [SMALL_STATE(1074)] = 65989, + [SMALL_STATE(1075)] = 66002, + [SMALL_STATE(1076)] = 66015, + [SMALL_STATE(1077)] = 66028, + [SMALL_STATE(1078)] = 66047, + [SMALL_STATE(1079)] = 66066, + [SMALL_STATE(1080)] = 66085, + [SMALL_STATE(1081)] = 66104, + [SMALL_STATE(1082)] = 66117, + [SMALL_STATE(1083)] = 66130, + [SMALL_STATE(1084)] = 66149, + [SMALL_STATE(1085)] = 66168, + [SMALL_STATE(1086)] = 66187, + [SMALL_STATE(1087)] = 66200, + [SMALL_STATE(1088)] = 66213, + [SMALL_STATE(1089)] = 66232, + [SMALL_STATE(1090)] = 66251, + [SMALL_STATE(1091)] = 66270, + [SMALL_STATE(1092)] = 66289, + [SMALL_STATE(1093)] = 66308, + [SMALL_STATE(1094)] = 66327, + [SMALL_STATE(1095)] = 66350, + [SMALL_STATE(1096)] = 66363, + [SMALL_STATE(1097)] = 66377, + [SMALL_STATE(1098)] = 66391, + [SMALL_STATE(1099)] = 66411, + [SMALL_STATE(1100)] = 66431, + [SMALL_STATE(1101)] = 66451, + [SMALL_STATE(1102)] = 66467, + [SMALL_STATE(1103)] = 66481, + [SMALL_STATE(1104)] = 66495, + [SMALL_STATE(1105)] = 66509, + [SMALL_STATE(1106)] = 66529, + [SMALL_STATE(1107)] = 66545, + [SMALL_STATE(1108)] = 66563, + [SMALL_STATE(1109)] = 66581, + [SMALL_STATE(1110)] = 66597, + [SMALL_STATE(1111)] = 66613, + [SMALL_STATE(1112)] = 66627, + [SMALL_STATE(1113)] = 66641, + [SMALL_STATE(1114)] = 66659, + [SMALL_STATE(1115)] = 66679, + [SMALL_STATE(1116)] = 66699, + [SMALL_STATE(1117)] = 66719, + [SMALL_STATE(1118)] = 66735, + [SMALL_STATE(1119)] = 66755, + [SMALL_STATE(1120)] = 66773, + [SMALL_STATE(1121)] = 66789, + [SMALL_STATE(1122)] = 66807, + [SMALL_STATE(1123)] = 66827, + [SMALL_STATE(1124)] = 66847, + [SMALL_STATE(1125)] = 66867, + [SMALL_STATE(1126)] = 66887, + [SMALL_STATE(1127)] = 66907, + [SMALL_STATE(1128)] = 66923, + [SMALL_STATE(1129)] = 66941, + [SMALL_STATE(1130)] = 66961, + [SMALL_STATE(1131)] = 66975, + [SMALL_STATE(1132)] = 66995, + [SMALL_STATE(1133)] = 67015, + [SMALL_STATE(1134)] = 67033, + [SMALL_STATE(1135)] = 67051, + [SMALL_STATE(1136)] = 67071, + [SMALL_STATE(1137)] = 67091, + [SMALL_STATE(1138)] = 67108, + [SMALL_STATE(1139)] = 67125, + [SMALL_STATE(1140)] = 67142, + [SMALL_STATE(1141)] = 67153, + [SMALL_STATE(1142)] = 67168, + [SMALL_STATE(1143)] = 67183, + [SMALL_STATE(1144)] = 67198, + [SMALL_STATE(1145)] = 67215, + [SMALL_STATE(1146)] = 67232, + [SMALL_STATE(1147)] = 67249, + [SMALL_STATE(1148)] = 67264, + [SMALL_STATE(1149)] = 67281, + [SMALL_STATE(1150)] = 67292, + [SMALL_STATE(1151)] = 67309, + [SMALL_STATE(1152)] = 67326, + [SMALL_STATE(1153)] = 67343, + [SMALL_STATE(1154)] = 67360, + [SMALL_STATE(1155)] = 67377, + [SMALL_STATE(1156)] = 67394, + [SMALL_STATE(1157)] = 67409, + [SMALL_STATE(1158)] = 67424, + [SMALL_STATE(1159)] = 67441, + [SMALL_STATE(1160)] = 67458, + [SMALL_STATE(1161)] = 67475, + [SMALL_STATE(1162)] = 67492, + [SMALL_STATE(1163)] = 67509, + [SMALL_STATE(1164)] = 67524, + [SMALL_STATE(1165)] = 67539, + [SMALL_STATE(1166)] = 67554, + [SMALL_STATE(1167)] = 67571, + [SMALL_STATE(1168)] = 67588, + [SMALL_STATE(1169)] = 67605, + [SMALL_STATE(1170)] = 67622, + [SMALL_STATE(1171)] = 67637, + [SMALL_STATE(1172)] = 67654, + [SMALL_STATE(1173)] = 67667, + [SMALL_STATE(1174)] = 67684, + [SMALL_STATE(1175)] = 67699, + [SMALL_STATE(1176)] = 67714, + [SMALL_STATE(1177)] = 67731, + [SMALL_STATE(1178)] = 67748, + [SMALL_STATE(1179)] = 67763, + [SMALL_STATE(1180)] = 67780, + [SMALL_STATE(1181)] = 67797, + [SMALL_STATE(1182)] = 67812, + [SMALL_STATE(1183)] = 67829, + [SMALL_STATE(1184)] = 67846, + [SMALL_STATE(1185)] = 67863, + [SMALL_STATE(1186)] = 67878, + [SMALL_STATE(1187)] = 67895, + [SMALL_STATE(1188)] = 67908, + [SMALL_STATE(1189)] = 67925, + [SMALL_STATE(1190)] = 67936, + [SMALL_STATE(1191)] = 67949, + [SMALL_STATE(1192)] = 67966, + [SMALL_STATE(1193)] = 67983, + [SMALL_STATE(1194)] = 68000, + [SMALL_STATE(1195)] = 68017, + [SMALL_STATE(1196)] = 68032, + [SMALL_STATE(1197)] = 68049, + [SMALL_STATE(1198)] = 68066, + [SMALL_STATE(1199)] = 68081, + [SMALL_STATE(1200)] = 68098, + [SMALL_STATE(1201)] = 68115, + [SMALL_STATE(1202)] = 68132, + [SMALL_STATE(1203)] = 68147, + [SMALL_STATE(1204)] = 68164, + [SMALL_STATE(1205)] = 68179, + [SMALL_STATE(1206)] = 68194, + [SMALL_STATE(1207)] = 68211, + [SMALL_STATE(1208)] = 68228, + [SMALL_STATE(1209)] = 68245, + [SMALL_STATE(1210)] = 68262, + [SMALL_STATE(1211)] = 68279, + [SMALL_STATE(1212)] = 68296, + [SMALL_STATE(1213)] = 68311, + [SMALL_STATE(1214)] = 68328, + [SMALL_STATE(1215)] = 68343, + [SMALL_STATE(1216)] = 68358, + [SMALL_STATE(1217)] = 68375, + [SMALL_STATE(1218)] = 68392, + [SMALL_STATE(1219)] = 68409, + [SMALL_STATE(1220)] = 68426, + [SMALL_STATE(1221)] = 68443, + [SMALL_STATE(1222)] = 68458, + [SMALL_STATE(1223)] = 68475, + [SMALL_STATE(1224)] = 68492, + [SMALL_STATE(1225)] = 68509, + [SMALL_STATE(1226)] = 68526, + [SMALL_STATE(1227)] = 68541, + [SMALL_STATE(1228)] = 68558, + [SMALL_STATE(1229)] = 68575, + [SMALL_STATE(1230)] = 68592, + [SMALL_STATE(1231)] = 68606, + [SMALL_STATE(1232)] = 68620, + [SMALL_STATE(1233)] = 68630, + [SMALL_STATE(1234)] = 68644, + [SMALL_STATE(1235)] = 68654, + [SMALL_STATE(1236)] = 68664, + [SMALL_STATE(1237)] = 68678, + [SMALL_STATE(1238)] = 68688, + [SMALL_STATE(1239)] = 68698, + [SMALL_STATE(1240)] = 68712, + [SMALL_STATE(1241)] = 68726, + [SMALL_STATE(1242)] = 68738, + [SMALL_STATE(1243)] = 68752, + [SMALL_STATE(1244)] = 68764, + [SMALL_STATE(1245)] = 68778, + [SMALL_STATE(1246)] = 68792, + [SMALL_STATE(1247)] = 68806, + [SMALL_STATE(1248)] = 68820, + [SMALL_STATE(1249)] = 68834, + [SMALL_STATE(1250)] = 68846, + [SMALL_STATE(1251)] = 68860, + [SMALL_STATE(1252)] = 68872, + [SMALL_STATE(1253)] = 68886, + [SMALL_STATE(1254)] = 68900, + [SMALL_STATE(1255)] = 68914, + [SMALL_STATE(1256)] = 68924, + [SMALL_STATE(1257)] = 68934, + [SMALL_STATE(1258)] = 68948, + [SMALL_STATE(1259)] = 68960, + [SMALL_STATE(1260)] = 68974, + [SMALL_STATE(1261)] = 68988, + [SMALL_STATE(1262)] = 69002, + [SMALL_STATE(1263)] = 69016, + [SMALL_STATE(1264)] = 69030, + [SMALL_STATE(1265)] = 69044, + [SMALL_STATE(1266)] = 69058, + [SMALL_STATE(1267)] = 69072, + [SMALL_STATE(1268)] = 69086, + [SMALL_STATE(1269)] = 69100, + [SMALL_STATE(1270)] = 69114, + [SMALL_STATE(1271)] = 69128, + [SMALL_STATE(1272)] = 69142, + [SMALL_STATE(1273)] = 69156, + [SMALL_STATE(1274)] = 69170, + [SMALL_STATE(1275)] = 69184, + [SMALL_STATE(1276)] = 69198, + [SMALL_STATE(1277)] = 69212, + [SMALL_STATE(1278)] = 69226, + [SMALL_STATE(1279)] = 69240, + [SMALL_STATE(1280)] = 69254, + [SMALL_STATE(1281)] = 69268, + [SMALL_STATE(1282)] = 69278, + [SMALL_STATE(1283)] = 69288, + [SMALL_STATE(1284)] = 69298, + [SMALL_STATE(1285)] = 69312, + [SMALL_STATE(1286)] = 69326, + [SMALL_STATE(1287)] = 69340, + [SMALL_STATE(1288)] = 69354, + [SMALL_STATE(1289)] = 69368, + [SMALL_STATE(1290)] = 69382, + [SMALL_STATE(1291)] = 69392, + [SMALL_STATE(1292)] = 69406, + [SMALL_STATE(1293)] = 69420, + [SMALL_STATE(1294)] = 69434, + [SMALL_STATE(1295)] = 69448, + [SMALL_STATE(1296)] = 69462, + [SMALL_STATE(1297)] = 69476, + [SMALL_STATE(1298)] = 69490, + [SMALL_STATE(1299)] = 69504, + [SMALL_STATE(1300)] = 69518, + [SMALL_STATE(1301)] = 69532, + [SMALL_STATE(1302)] = 69546, + [SMALL_STATE(1303)] = 69556, + [SMALL_STATE(1304)] = 69570, + [SMALL_STATE(1305)] = 69584, + [SMALL_STATE(1306)] = 69598, + [SMALL_STATE(1307)] = 69612, + [SMALL_STATE(1308)] = 69626, + [SMALL_STATE(1309)] = 69640, + [SMALL_STATE(1310)] = 69654, + [SMALL_STATE(1311)] = 69668, + [SMALL_STATE(1312)] = 69682, + [SMALL_STATE(1313)] = 69696, + [SMALL_STATE(1314)] = 69710, + [SMALL_STATE(1315)] = 69720, + [SMALL_STATE(1316)] = 69734, + [SMALL_STATE(1317)] = 69748, + [SMALL_STATE(1318)] = 69762, + [SMALL_STATE(1319)] = 69776, + [SMALL_STATE(1320)] = 69786, + [SMALL_STATE(1321)] = 69800, + [SMALL_STATE(1322)] = 69814, + [SMALL_STATE(1323)] = 69828, + [SMALL_STATE(1324)] = 69842, + [SMALL_STATE(1325)] = 69856, + [SMALL_STATE(1326)] = 69866, + [SMALL_STATE(1327)] = 69876, + [SMALL_STATE(1328)] = 69886, + [SMALL_STATE(1329)] = 69896, + [SMALL_STATE(1330)] = 69910, + [SMALL_STATE(1331)] = 69924, + [SMALL_STATE(1332)] = 69938, + [SMALL_STATE(1333)] = 69952, + [SMALL_STATE(1334)] = 69966, + [SMALL_STATE(1335)] = 69980, + [SMALL_STATE(1336)] = 69994, + [SMALL_STATE(1337)] = 70006, + [SMALL_STATE(1338)] = 70020, + [SMALL_STATE(1339)] = 70034, + [SMALL_STATE(1340)] = 70048, + [SMALL_STATE(1341)] = 70062, + [SMALL_STATE(1342)] = 70074, + [SMALL_STATE(1343)] = 70088, + [SMALL_STATE(1344)] = 70098, + [SMALL_STATE(1345)] = 70112, + [SMALL_STATE(1346)] = 70126, + [SMALL_STATE(1347)] = 70136, + [SMALL_STATE(1348)] = 70150, + [SMALL_STATE(1349)] = 70164, + [SMALL_STATE(1350)] = 70178, + [SMALL_STATE(1351)] = 70188, + [SMALL_STATE(1352)] = 70198, + [SMALL_STATE(1353)] = 70212, + [SMALL_STATE(1354)] = 70226, + [SMALL_STATE(1355)] = 70240, + [SMALL_STATE(1356)] = 70254, + [SMALL_STATE(1357)] = 70268, + [SMALL_STATE(1358)] = 70282, + [SMALL_STATE(1359)] = 70296, + [SMALL_STATE(1360)] = 70310, + [SMALL_STATE(1361)] = 70324, + [SMALL_STATE(1362)] = 70338, + [SMALL_STATE(1363)] = 70352, + [SMALL_STATE(1364)] = 70362, + [SMALL_STATE(1365)] = 70372, + [SMALL_STATE(1366)] = 70386, + [SMALL_STATE(1367)] = 70400, + [SMALL_STATE(1368)] = 70414, + [SMALL_STATE(1369)] = 70428, + [SMALL_STATE(1370)] = 70442, + [SMALL_STATE(1371)] = 70452, + [SMALL_STATE(1372)] = 70466, + [SMALL_STATE(1373)] = 70480, + [SMALL_STATE(1374)] = 70494, + [SMALL_STATE(1375)] = 70508, + [SMALL_STATE(1376)] = 70517, + [SMALL_STATE(1377)] = 70528, + [SMALL_STATE(1378)] = 70539, + [SMALL_STATE(1379)] = 70548, + [SMALL_STATE(1380)] = 70559, + [SMALL_STATE(1381)] = 70570, + [SMALL_STATE(1382)] = 70581, + [SMALL_STATE(1383)] = 70590, + [SMALL_STATE(1384)] = 70601, + [SMALL_STATE(1385)] = 70612, + [SMALL_STATE(1386)] = 70621, + [SMALL_STATE(1387)] = 70630, + [SMALL_STATE(1388)] = 70639, + [SMALL_STATE(1389)] = 70650, + [SMALL_STATE(1390)] = 70661, + [SMALL_STATE(1391)] = 70670, + [SMALL_STATE(1392)] = 70679, + [SMALL_STATE(1393)] = 70688, + [SMALL_STATE(1394)] = 70697, + [SMALL_STATE(1395)] = 70708, + [SMALL_STATE(1396)] = 70719, + [SMALL_STATE(1397)] = 70728, + [SMALL_STATE(1398)] = 70739, + [SMALL_STATE(1399)] = 70750, + [SMALL_STATE(1400)] = 70761, + [SMALL_STATE(1401)] = 70770, + [SMALL_STATE(1402)] = 70781, + [SMALL_STATE(1403)] = 70792, + [SMALL_STATE(1404)] = 70801, + [SMALL_STATE(1405)] = 70812, + [SMALL_STATE(1406)] = 70821, + [SMALL_STATE(1407)] = 70830, + [SMALL_STATE(1408)] = 70841, + [SMALL_STATE(1409)] = 70852, + [SMALL_STATE(1410)] = 70861, + [SMALL_STATE(1411)] = 70872, + [SMALL_STATE(1412)] = 70883, + [SMALL_STATE(1413)] = 70894, + [SMALL_STATE(1414)] = 70903, + [SMALL_STATE(1415)] = 70914, + [SMALL_STATE(1416)] = 70923, + [SMALL_STATE(1417)] = 70932, + [SMALL_STATE(1418)] = 70943, + [SMALL_STATE(1419)] = 70954, + [SMALL_STATE(1420)] = 70965, + [SMALL_STATE(1421)] = 70976, + [SMALL_STATE(1422)] = 70987, + [SMALL_STATE(1423)] = 70998, + [SMALL_STATE(1424)] = 71009, + [SMALL_STATE(1425)] = 71020, + [SMALL_STATE(1426)] = 71031, + [SMALL_STATE(1427)] = 71042, + [SMALL_STATE(1428)] = 71053, + [SMALL_STATE(1429)] = 71064, + [SMALL_STATE(1430)] = 71075, + [SMALL_STATE(1431)] = 71086, + [SMALL_STATE(1432)] = 71097, + [SMALL_STATE(1433)] = 71108, + [SMALL_STATE(1434)] = 71119, + [SMALL_STATE(1435)] = 71130, + [SMALL_STATE(1436)] = 71141, + [SMALL_STATE(1437)] = 71152, + [SMALL_STATE(1438)] = 71161, + [SMALL_STATE(1439)] = 71170, + [SMALL_STATE(1440)] = 71181, + [SMALL_STATE(1441)] = 71192, + [SMALL_STATE(1442)] = 71201, + [SMALL_STATE(1443)] = 71212, + [SMALL_STATE(1444)] = 71223, + [SMALL_STATE(1445)] = 71234, + [SMALL_STATE(1446)] = 71245, + [SMALL_STATE(1447)] = 71256, + [SMALL_STATE(1448)] = 71267, + [SMALL_STATE(1449)] = 71278, + [SMALL_STATE(1450)] = 71287, + [SMALL_STATE(1451)] = 71296, + [SMALL_STATE(1452)] = 71307, + [SMALL_STATE(1453)] = 71318, + [SMALL_STATE(1454)] = 71329, + [SMALL_STATE(1455)] = 71340, + [SMALL_STATE(1456)] = 71349, + [SMALL_STATE(1457)] = 71357, + [SMALL_STATE(1458)] = 71365, + [SMALL_STATE(1459)] = 71373, + [SMALL_STATE(1460)] = 71381, + [SMALL_STATE(1461)] = 71389, + [SMALL_STATE(1462)] = 71397, + [SMALL_STATE(1463)] = 71405, + [SMALL_STATE(1464)] = 71413, + [SMALL_STATE(1465)] = 71421, + [SMALL_STATE(1466)] = 71429, + [SMALL_STATE(1467)] = 71437, + [SMALL_STATE(1468)] = 71445, + [SMALL_STATE(1469)] = 71453, + [SMALL_STATE(1470)] = 71461, + [SMALL_STATE(1471)] = 71469, + [SMALL_STATE(1472)] = 71477, + [SMALL_STATE(1473)] = 71485, + [SMALL_STATE(1474)] = 71493, + [SMALL_STATE(1475)] = 71501, + [SMALL_STATE(1476)] = 71509, + [SMALL_STATE(1477)] = 71517, + [SMALL_STATE(1478)] = 71525, + [SMALL_STATE(1479)] = 71533, + [SMALL_STATE(1480)] = 71541, + [SMALL_STATE(1481)] = 71549, + [SMALL_STATE(1482)] = 71557, + [SMALL_STATE(1483)] = 71565, + [SMALL_STATE(1484)] = 71573, + [SMALL_STATE(1485)] = 71581, + [SMALL_STATE(1486)] = 71589, + [SMALL_STATE(1487)] = 71597, + [SMALL_STATE(1488)] = 71605, + [SMALL_STATE(1489)] = 71613, + [SMALL_STATE(1490)] = 71621, + [SMALL_STATE(1491)] = 71629, + [SMALL_STATE(1492)] = 71637, + [SMALL_STATE(1493)] = 71645, + [SMALL_STATE(1494)] = 71653, + [SMALL_STATE(1495)] = 71661, + [SMALL_STATE(1496)] = 71669, + [SMALL_STATE(1497)] = 71677, + [SMALL_STATE(1498)] = 71685, + [SMALL_STATE(1499)] = 71693, + [SMALL_STATE(1500)] = 71701, + [SMALL_STATE(1501)] = 71709, + [SMALL_STATE(1502)] = 71717, + [SMALL_STATE(1503)] = 71725, + [SMALL_STATE(1504)] = 71733, + [SMALL_STATE(1505)] = 71741, + [SMALL_STATE(1506)] = 71749, + [SMALL_STATE(1507)] = 71757, + [SMALL_STATE(1508)] = 71765, + [SMALL_STATE(1509)] = 71773, + [SMALL_STATE(1510)] = 71781, + [SMALL_STATE(1511)] = 71789, + [SMALL_STATE(1512)] = 71797, + [SMALL_STATE(1513)] = 71805, + [SMALL_STATE(1514)] = 71813, + [SMALL_STATE(1515)] = 71821, + [SMALL_STATE(1516)] = 71829, + [SMALL_STATE(1517)] = 71837, + [SMALL_STATE(1518)] = 71845, + [SMALL_STATE(1519)] = 71853, + [SMALL_STATE(1520)] = 71861, + [SMALL_STATE(1521)] = 71869, + [SMALL_STATE(1522)] = 71877, + [SMALL_STATE(1523)] = 71885, + [SMALL_STATE(1524)] = 71893, + [SMALL_STATE(1525)] = 71901, + [SMALL_STATE(1526)] = 71909, + [SMALL_STATE(1527)] = 71917, + [SMALL_STATE(1528)] = 71925, + [SMALL_STATE(1529)] = 71933, + [SMALL_STATE(1530)] = 71941, + [SMALL_STATE(1531)] = 71949, + [SMALL_STATE(1532)] = 71957, + [SMALL_STATE(1533)] = 71965, + [SMALL_STATE(1534)] = 71973, + [SMALL_STATE(1535)] = 71981, + [SMALL_STATE(1536)] = 71989, + [SMALL_STATE(1537)] = 71997, + [SMALL_STATE(1538)] = 72005, + [SMALL_STATE(1539)] = 72013, + [SMALL_STATE(1540)] = 72021, + [SMALL_STATE(1541)] = 72029, + [SMALL_STATE(1542)] = 72037, + [SMALL_STATE(1543)] = 72045, + [SMALL_STATE(1544)] = 72053, + [SMALL_STATE(1545)] = 72061, + [SMALL_STATE(1546)] = 72069, + [SMALL_STATE(1547)] = 72077, + [SMALL_STATE(1548)] = 72085, + [SMALL_STATE(1549)] = 72093, + [SMALL_STATE(1550)] = 72101, + [SMALL_STATE(1551)] = 72109, + [SMALL_STATE(1552)] = 72117, + [SMALL_STATE(1553)] = 72125, + [SMALL_STATE(1554)] = 72133, + [SMALL_STATE(1555)] = 72141, + [SMALL_STATE(1556)] = 72149, + [SMALL_STATE(1557)] = 72157, + [SMALL_STATE(1558)] = 72165, + [SMALL_STATE(1559)] = 72173, + [SMALL_STATE(1560)] = 72181, + [SMALL_STATE(1561)] = 72189, + [SMALL_STATE(1562)] = 72197, + [SMALL_STATE(1563)] = 72205, + [SMALL_STATE(1564)] = 72213, + [SMALL_STATE(1565)] = 72221, + [SMALL_STATE(1566)] = 72229, + [SMALL_STATE(1567)] = 72237, + [SMALL_STATE(1568)] = 72245, + [SMALL_STATE(1569)] = 72253, + [SMALL_STATE(1570)] = 72261, + [SMALL_STATE(1571)] = 72269, + [SMALL_STATE(1572)] = 72277, + [SMALL_STATE(1573)] = 72285, + [SMALL_STATE(1574)] = 72293, + [SMALL_STATE(1575)] = 72301, + [SMALL_STATE(1576)] = 72309, + [SMALL_STATE(1577)] = 72317, + [SMALL_STATE(1578)] = 72325, + [SMALL_STATE(1579)] = 72333, + [SMALL_STATE(1580)] = 72341, + [SMALL_STATE(1581)] = 72349, + [SMALL_STATE(1582)] = 72357, + [SMALL_STATE(1583)] = 72365, + [SMALL_STATE(1584)] = 72373, + [SMALL_STATE(1585)] = 72381, + [SMALL_STATE(1586)] = 72389, + [SMALL_STATE(1587)] = 72397, + [SMALL_STATE(1588)] = 72405, + [SMALL_STATE(1589)] = 72413, + [SMALL_STATE(1590)] = 72421, + [SMALL_STATE(1591)] = 72429, + [SMALL_STATE(1592)] = 72437, + [SMALL_STATE(1593)] = 72445, + [SMALL_STATE(1594)] = 72453, + [SMALL_STATE(1595)] = 72461, + [SMALL_STATE(1596)] = 72469, + [SMALL_STATE(1597)] = 72477, + [SMALL_STATE(1598)] = 72485, + [SMALL_STATE(1599)] = 72493, + [SMALL_STATE(1600)] = 72501, + [SMALL_STATE(1601)] = 72509, + [SMALL_STATE(1602)] = 72517, + [SMALL_STATE(1603)] = 72525, + [SMALL_STATE(1604)] = 72533, + [SMALL_STATE(1605)] = 72541, + [SMALL_STATE(1606)] = 72549, + [SMALL_STATE(1607)] = 72557, + [SMALL_STATE(1608)] = 72565, + [SMALL_STATE(1609)] = 72573, + [SMALL_STATE(1610)] = 72581, + [SMALL_STATE(1611)] = 72589, + [SMALL_STATE(1612)] = 72597, + [SMALL_STATE(1613)] = 72605, + [SMALL_STATE(1614)] = 72613, + [SMALL_STATE(1615)] = 72621, + [SMALL_STATE(1616)] = 72629, + [SMALL_STATE(1617)] = 72637, + [SMALL_STATE(1618)] = 72645, + [SMALL_STATE(1619)] = 72653, + [SMALL_STATE(1620)] = 72661, + [SMALL_STATE(1621)] = 72669, + [SMALL_STATE(1622)] = 72677, + [SMALL_STATE(1623)] = 72685, + [SMALL_STATE(1624)] = 72693, + [SMALL_STATE(1625)] = 72701, + [SMALL_STATE(1626)] = 72709, + [SMALL_STATE(1627)] = 72717, + [SMALL_STATE(1628)] = 72725, + [SMALL_STATE(1629)] = 72733, + [SMALL_STATE(1630)] = 72741, + [SMALL_STATE(1631)] = 72749, + [SMALL_STATE(1632)] = 72757, + [SMALL_STATE(1633)] = 72765, + [SMALL_STATE(1634)] = 72773, + [SMALL_STATE(1635)] = 72781, + [SMALL_STATE(1636)] = 72789, + [SMALL_STATE(1637)] = 72797, + [SMALL_STATE(1638)] = 72805, + [SMALL_STATE(1639)] = 72813, + [SMALL_STATE(1640)] = 72821, + [SMALL_STATE(1641)] = 72829, + [SMALL_STATE(1642)] = 72837, + [SMALL_STATE(1643)] = 72845, + [SMALL_STATE(1644)] = 72853, + [SMALL_STATE(1645)] = 72861, + [SMALL_STATE(1646)] = 72869, + [SMALL_STATE(1647)] = 72877, + [SMALL_STATE(1648)] = 72885, + [SMALL_STATE(1649)] = 72893, + [SMALL_STATE(1650)] = 72901, + [SMALL_STATE(1651)] = 72909, + [SMALL_STATE(1652)] = 72917, + [SMALL_STATE(1653)] = 72925, }; static const TSParseActionEntry ts_parse_actions[] = { @@ -70515,1689 +79921,1768 @@ static const TSParseActionEntry ts_parse_actions[] = { [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(), [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT_EXTRA(), [5] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_program, 0, 0, 0), - [7] = {.entry = {.count = 1, .reusable = false}}, SHIFT(620), - [9] = {.entry = {.count = 1, .reusable = false}}, SHIFT(97), - [11] = {.entry = {.count = 1, .reusable = true}}, SHIFT(97), - [13] = {.entry = {.count = 1, .reusable = false}}, SHIFT(56), - [15] = {.entry = {.count = 1, .reusable = false}}, SHIFT(76), - [17] = {.entry = {.count = 1, .reusable = true}}, SHIFT(965), - [19] = {.entry = {.count = 1, .reusable = true}}, SHIFT(998), - [21] = {.entry = {.count = 1, .reusable = true}}, SHIFT(67), - [23] = {.entry = {.count = 1, .reusable = true}}, SHIFT(186), - [25] = {.entry = {.count = 1, .reusable = true}}, SHIFT(144), - [27] = {.entry = {.count = 1, .reusable = true}}, SHIFT(10), - [29] = {.entry = {.count = 1, .reusable = true}}, SHIFT(329), - [31] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1039), - [33] = {.entry = {.count = 1, .reusable = false}}, SHIFT(349), - [35] = {.entry = {.count = 1, .reusable = false}}, SHIFT(244), - [37] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1535), - [39] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1421), - [41] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1426), - [43] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1445), - [45] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1496), - [47] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1497), - [49] = {.entry = {.count = 1, .reusable = false}}, SHIFT(891), - [51] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1341), - [53] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1515), - [55] = {.entry = {.count = 1, .reusable = false}}, SHIFT(734), - [57] = {.entry = {.count = 1, .reusable = false}}, SHIFT(154), - [59] = {.entry = {.count = 1, .reusable = false}}, SHIFT(944), - [61] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1314), - [63] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1347), - [65] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1306), - [67] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1321), - [69] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1331), - [71] = {.entry = {.count = 1, .reusable = false}}, SHIFT(143), - [73] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1546), - [75] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1549), - [77] = {.entry = {.count = 1, .reusable = false}}, SHIFT(238), - [79] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1334), - [81] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(620), - [84] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(97), - [87] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(97), - [90] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(56), - [93] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(76), - [96] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(965), - [99] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(998), - [102] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(67), - [105] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(186), + [7] = {.entry = {.count = 1, .reusable = false}}, SHIFT(669), + [9] = {.entry = {.count = 1, .reusable = false}}, SHIFT(101), + [11] = {.entry = {.count = 1, .reusable = true}}, SHIFT(101), + [13] = {.entry = {.count = 1, .reusable = false}}, SHIFT(59), + [15] = {.entry = {.count = 1, .reusable = false}}, SHIFT(85), + [17] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1080), + [19] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1065), + [21] = {.entry = {.count = 1, .reusable = true}}, SHIFT(84), + [23] = {.entry = {.count = 1, .reusable = true}}, SHIFT(206), + [25] = {.entry = {.count = 1, .reusable = true}}, SHIFT(145), + [27] = {.entry = {.count = 1, .reusable = true}}, SHIFT(13), + [29] = {.entry = {.count = 1, .reusable = true}}, SHIFT(298), + [31] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1132), + [33] = {.entry = {.count = 1, .reusable = false}}, SHIFT(358), + [35] = {.entry = {.count = 1, .reusable = false}}, SHIFT(208), + [37] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1557), + [39] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1590), + [41] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1625), + [43] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1465), + [45] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1522), + [47] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1627), + [49] = {.entry = {.count = 1, .reusable = false}}, SHIFT(986), + [51] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1433), + [53] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1471), + [55] = {.entry = {.count = 1, .reusable = false}}, SHIFT(948), + [57] = {.entry = {.count = 1, .reusable = false}}, SHIFT(219), + [59] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1032), + [61] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1420), + [63] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1421), + [65] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1412), + [67] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1425), + [69] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1407), + [71] = {.entry = {.count = 1, .reusable = false}}, SHIFT(142), + [73] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1491), + [75] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1512), + [77] = {.entry = {.count = 1, .reusable = false}}, SHIFT(182), + [79] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1394), + [81] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(669), + [84] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(101), + [87] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(101), + [90] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(59), + [93] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(85), + [96] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1080), + [99] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1065), + [102] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(84), + [105] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(206), [108] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), [110] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), - [112] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(144), + [112] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(145), [115] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(10), - [118] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(329), - [121] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1039), - [124] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(349), - [127] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(244), - [130] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1535), - [133] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1421), - [136] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1426), - [139] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1445), - [142] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1496), - [145] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1497), - [148] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(891), - [151] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1341), - [154] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1515), - [157] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(734), - [160] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(154), - [163] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(944), - [166] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1314), - [169] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1347), - [172] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1306), - [175] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1321), - [178] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1331), - [181] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(143), - [184] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1546), - [187] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1549), - [190] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(238), - [193] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1334), - [196] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(13), - [199] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(217), - [202] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(890), - [205] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1326), - [208] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1557), - [211] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(714), - [214] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(247), - [217] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(946), - [220] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1348), - [223] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1327), - [226] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1349), - [229] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1328), - [232] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1358), - [235] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(150), - [238] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1385), - [241] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1387), - [244] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(219), - [247] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1350), - [250] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(11), - [253] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(275), - [256] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(218), - [259] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(945), - [262] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(12), - [265] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(246), - [268] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(258), - [271] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(938), - [274] = {.entry = {.count = 1, .reusable = true}}, SHIFT(105), - [276] = {.entry = {.count = 1, .reusable = true}}, SHIFT(11), - [278] = {.entry = {.count = 1, .reusable = false}}, SHIFT(275), - [280] = {.entry = {.count = 1, .reusable = false}}, SHIFT(218), - [282] = {.entry = {.count = 1, .reusable = false}}, SHIFT(945), - [284] = {.entry = {.count = 1, .reusable = true}}, SHIFT(102), - [286] = {.entry = {.count = 1, .reusable = true}}, SHIFT(104), - [288] = {.entry = {.count = 1, .reusable = true}}, SHIFT(103), - [290] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1222), - [292] = {.entry = {.count = 1, .reusable = true}}, SHIFT(502), - [294] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(620), - [297] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(97), - [300] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(97), - [303] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(56), - [306] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(76), - [309] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(965), - [312] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(998), - [315] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(67), - [318] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), - [320] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(186), - [323] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(144), - [326] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(11), - [329] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(329), - [332] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1039), - [335] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(349), - [338] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(275), - [341] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1535), - [344] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1421), - [347] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1426), - [350] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1445), - [353] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1496), - [356] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1497), - [359] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(891), - [362] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1341), - [365] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1515), - [368] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(734), - [371] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(218), - [374] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(945), - [377] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1314), - [380] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1347), - [383] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1306), - [386] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1321), - [389] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1331), - [392] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(143), - [395] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1546), - [398] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1549), - [401] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(238), - [404] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1334), - [407] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_program, 1, 0, 0), - [409] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), - [411] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(620), - [414] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(97), - [417] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(97), - [420] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(56), - [423] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(76), - [426] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(965), - [429] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(998), - [432] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(67), - [435] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(186), - [438] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(144), - [441] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(10), - [444] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(329), - [447] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1039), - [450] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(349), - [453] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(244), - [456] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1535), - [459] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1421), - [462] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1426), - [465] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1445), - [468] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1496), - [471] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1497), - [474] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(891), - [477] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1341), - [480] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1515), - [483] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(734), - [486] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(154), - [489] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(944), - [492] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1314), - [495] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1347), - [498] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1306), - [501] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1321), - [504] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1331), - [507] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(143), - [510] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1546), - [513] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1549), - [516] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(238), - [519] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1334), - [522] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1230), - [524] = {.entry = {.count = 1, .reusable = true}}, SHIFT(505), - [526] = {.entry = {.count = 1, .reusable = true}}, SHIFT(500), - [528] = {.entry = {.count = 1, .reusable = true}}, SHIFT(462), - [530] = {.entry = {.count = 1, .reusable = true}}, SHIFT(12), - [532] = {.entry = {.count = 1, .reusable = false}}, SHIFT(246), - [534] = {.entry = {.count = 1, .reusable = false}}, SHIFT(890), - [536] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1326), - [538] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1557), - [540] = {.entry = {.count = 1, .reusable = false}}, SHIFT(714), - [542] = {.entry = {.count = 1, .reusable = false}}, SHIFT(258), - [544] = {.entry = {.count = 1, .reusable = false}}, SHIFT(938), - [546] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1348), - [548] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1327), - [550] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1349), - [552] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1328), - [554] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1358), - [556] = {.entry = {.count = 1, .reusable = false}}, SHIFT(150), - [558] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1385), - [560] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1387), - [562] = {.entry = {.count = 1, .reusable = false}}, SHIFT(219), - [564] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1350), - [566] = {.entry = {.count = 1, .reusable = true}}, SHIFT(13), - [568] = {.entry = {.count = 1, .reusable = false}}, SHIFT(217), - [570] = {.entry = {.count = 1, .reusable = false}}, SHIFT(247), - [572] = {.entry = {.count = 1, .reusable = false}}, SHIFT(946), - [574] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_range_expression, 2, 0, 4), - [576] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_range_expression, 2, 0, 4), - [578] = {.entry = {.count = 1, .reusable = false}}, SHIFT(612), - [580] = {.entry = {.count = 1, .reusable = true}}, SHIFT(137), - [582] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1039), - [584] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1038), - [586] = {.entry = {.count = 1, .reusable = false}}, SHIFT(438), - [588] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1512), - [590] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1403), - [592] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1555), - [594] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1513), - [596] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1514), - [598] = {.entry = {.count = 1, .reusable = false}}, SHIFT(638), - [600] = {.entry = {.count = 1, .reusable = true}}, SHIFT(638), - [602] = {.entry = {.count = 1, .reusable = false}}, SHIFT(657), - [604] = {.entry = {.count = 1, .reusable = false}}, SHIFT(614), - [606] = {.entry = {.count = 1, .reusable = true}}, SHIFT(981), - [608] = {.entry = {.count = 1, .reusable = true}}, SHIFT(982), - [610] = {.entry = {.count = 1, .reusable = true}}, SHIFT(658), - [612] = {.entry = {.count = 1, .reusable = true}}, SHIFT(129), - [614] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1016), - [616] = {.entry = {.count = 1, .reusable = false}}, SHIFT(429), - [618] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1464), - [620] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1432), - [622] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1551), - [624] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1465), - [626] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1466), - [628] = {.entry = {.count = 1, .reusable = false}}, SHIFT(694), - [630] = {.entry = {.count = 1, .reusable = false}}, SHIFT(747), - [632] = {.entry = {.count = 1, .reusable = true}}, SHIFT(747), - [634] = {.entry = {.count = 1, .reusable = false}}, SHIFT(751), - [636] = {.entry = {.count = 1, .reusable = false}}, SHIFT(752), - [638] = {.entry = {.count = 1, .reusable = true}}, SHIFT(989), - [640] = {.entry = {.count = 1, .reusable = true}}, SHIFT(948), - [642] = {.entry = {.count = 1, .reusable = true}}, SHIFT(757), - [644] = {.entry = {.count = 1, .reusable = true}}, SHIFT(123), - [646] = {.entry = {.count = 1, .reusable = false}}, SHIFT(439), - [648] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1522), - [650] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1435), - [652] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1556), - [654] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1523), - [656] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1524), - [658] = {.entry = {.count = 1, .reusable = false}}, SHIFT(112), - [660] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1028), - [662] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1007), - [664] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 6, 0, 2), - [666] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 6, 0, 2), - [668] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_boolean_literal, 1, 0, 0), - [670] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_boolean_literal, 1, 0, 0), - [672] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_group, 2, 0, 0), - [674] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_group, 2, 0, 0), - [676] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_group, 3, 0, 0), - [678] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_group, 3, 0, 0), - [680] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_ternary_expression, 5, 0, 20), - [682] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_ternary_expression, 5, 0, 20), - [684] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_identifier, 1, 0, 0), - [686] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_identifier, 1, 0, 0), - [688] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_double_string, 3, 0, 0), - [690] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_double_string, 3, 0, 0), - [692] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_single_string, 3, 0, 0), - [694] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_single_string, 3, 0, 0), - [696] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 2, 0, 0), - [698] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 2, 0, 0), - [700] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 3, 0, 0), - [702] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 3, 0, 0), - [704] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 3, 0, 0), - [706] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 3, 0, 0), - [708] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_string_literal, 1, 0, 0), - [710] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_string_literal, 1, 0, 0), - [712] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_closure, 3, 0, 5), - [714] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_closure, 3, 0, 5), - [716] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 3, 0, 0), - [718] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 3, 0, 0), - [720] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_invocation, 3, 1, 2), - [722] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_invocation, 3, 1, 2), - [724] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 3, 0, 2), - [726] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 3, 0, 2), - [728] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_call_expression, 3, 0, 6), - [730] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_call_expression, 3, 0, 6), - [732] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_field_access, 3, 0, 7), - [734] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_field_access, 3, 0, 7), - [736] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_optional_field_access, 3, 0, 7), - [738] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_optional_field_access, 3, 0, 7), - [740] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_binary_expression, 3, 0, 9), - [742] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_binary_expression, 3, 0, 9), - [744] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_nil_literal, 1, 0, 0), - [746] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_nil_literal, 1, 0, 0), - [748] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 4, 0, 0), - [750] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 4, 0, 0), - [752] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_closure, 4, 0, 12), - [754] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_closure, 4, 0, 12), - [756] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_expression, 4, 0, 13), - [758] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_expression, 4, 0, 13), - [760] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_double_string, 2, 0, 0), - [762] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_double_string, 2, 0, 0), - [764] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_single_string, 2, 0, 0), - [766] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_single_string, 2, 0, 0), - [768] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_spawn_expression, 4, 0, 0), - [770] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_spawn_expression, 4, 0, 0), - [772] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 4, 0, 0), - [774] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 4, 0, 0), - [776] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_recv_expression, 4, 0, 0), - [778] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_recv_expression, 4, 0, 0), - [780] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_select_expression, 4, 0, 0), - [782] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_expression, 4, 0, 0), - [784] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), - [786] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_primary_expression, 1, 0, 0), - [788] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1104), - [791] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_unwrap_expression, 2, 0, 3), - [793] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_unwrap_expression, 2, 0, 3), - [795] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 4, 0, 2), - [797] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 4, 0, 2), - [799] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_call_expression, 4, 0, 6), - [801] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_call_expression, 4, 0, 6), - [803] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_index_access, 4, 0, 15), - [805] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_index_access, 4, 0, 15), - [807] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_optional_index_access, 4, 0, 15), - [809] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_optional_index_access, 4, 0, 15), - [811] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 5, 0, 0), - [813] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 5, 0, 0), - [815] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 5, 0, 0), - [817] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 5, 0, 0), - [819] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_expression, 5, 0, 13), - [821] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_expression, 5, 0, 13), - [823] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 5, 0, 0), - [825] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 5, 0, 0), - [827] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 5, 0, 2), - [829] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 5, 0, 2), - [831] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 2, 0, 0), - [833] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 2, 0, 0), - [835] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 6, 0, 0), - [837] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 6, 0, 0), - [839] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_send_expression, 6, 0, 0), - [841] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_send_expression, 6, 0, 0), - [843] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 4, 0, 0), - [845] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 4, 0, 0), - [847] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_block, 2, 0, 0), - [849] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_block, 2, 0, 0), - [851] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_map_expression, 2, 0, 0), REDUCE(sym_block, 2, 0, 0), - [854] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_map_expression, 2, 0, 0), REDUCE(sym_block, 2, 0, 0), - [857] = {.entry = {.count = 1, .reusable = true}}, SHIFT(418), - [859] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_binary_expression, 3, 0, 8), - [861] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_binary_expression, 3, 0, 8), - [863] = {.entry = {.count = 1, .reusable = true}}, SHIFT(416), - [865] = {.entry = {.count = 1, .reusable = false}}, SHIFT(416), - [867] = {.entry = {.count = 1, .reusable = true}}, SHIFT(417), - [869] = {.entry = {.count = 1, .reusable = false}}, SHIFT(43), - [871] = {.entry = {.count = 1, .reusable = true}}, SHIFT(419), - [873] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_nullish_coalescing_expression, 3, 0, 9), - [875] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_nullish_coalescing_expression, 3, 0, 9), - [877] = {.entry = {.count = 1, .reusable = true}}, SHIFT(350), - [879] = {.entry = {.count = 1, .reusable = false}}, SHIFT(350), - [881] = {.entry = {.count = 1, .reusable = true}}, SHIFT(351), - [883] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_unary_expression, 2, 0, 1), - [885] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_unary_expression, 2, 0, 1), - [887] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), - [889] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), - [891] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(122), - [894] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1134), - [896] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1259), - [898] = {.entry = {.count = 1, .reusable = true}}, SHIFT(202), - [900] = {.entry = {.count = 1, .reusable = true}}, SHIFT(205), - [902] = {.entry = {.count = 1, .reusable = false}}, SHIFT(87), - [904] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(350), - [907] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(350), - [910] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(351), - [913] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(416), - [916] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(416), - [919] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(417), - [922] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(418), - [925] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(352), - [928] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(43), - [931] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(419), - [934] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(287), - [937] = {.entry = {.count = 1, .reusable = true}}, SHIFT(352), - [939] = {.entry = {.count = 1, .reusable = false}}, SHIFT(278), - [941] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_identifier, 1, 0, 0), REDUCE(sym_type_identifier, 1, 0, 0), - [944] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_range_expression, 3, 0, 9), - [946] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_range_expression, 3, 0, 9), - [948] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 2, 0, 0), - [950] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1254), - [952] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 3, 0, 0), - [954] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1248), - [956] = {.entry = {.count = 1, .reusable = true}}, SHIFT(607), - [958] = {.entry = {.count = 1, .reusable = true}}, SHIFT(562), - [960] = {.entry = {.count = 1, .reusable = true}}, SHIFT(590), - [962] = {.entry = {.count = 1, .reusable = true}}, SHIFT(77), - [964] = {.entry = {.count = 1, .reusable = true}}, SHIFT(72), - [966] = {.entry = {.count = 1, .reusable = true}}, SHIFT(762), - [968] = {.entry = {.count = 1, .reusable = true}}, SHIFT(769), - [970] = {.entry = {.count = 1, .reusable = true}}, SHIFT(264), - [972] = {.entry = {.count = 1, .reusable = true}}, SHIFT(69), - [974] = {.entry = {.count = 1, .reusable = true}}, SHIFT(188), - [976] = {.entry = {.count = 1, .reusable = true}}, SHIFT(772), - [978] = {.entry = {.count = 1, .reusable = true}}, SHIFT(781), - [980] = {.entry = {.count = 1, .reusable = true}}, SHIFT(740), - [982] = {.entry = {.count = 1, .reusable = true}}, SHIFT(634), - [984] = {.entry = {.count = 1, .reusable = true}}, SHIFT(622), - [986] = {.entry = {.count = 1, .reusable = true}}, SHIFT(581), - [988] = {.entry = {.count = 1, .reusable = true}}, SHIFT(651), - [990] = {.entry = {.count = 1, .reusable = true}}, SHIFT(230), - [992] = {.entry = {.count = 1, .reusable = true}}, SHIFT(633), - [994] = {.entry = {.count = 1, .reusable = true}}, SHIFT(603), - [996] = {.entry = {.count = 1, .reusable = true}}, SHIFT(253), - [998] = {.entry = {.count = 1, .reusable = true}}, SHIFT(636), - [1000] = {.entry = {.count = 1, .reusable = true}}, SHIFT(93), - [1002] = {.entry = {.count = 1, .reusable = true}}, SHIFT(63), - [1004] = {.entry = {.count = 1, .reusable = true}}, SHIFT(626), - [1006] = {.entry = {.count = 1, .reusable = true}}, SHIFT(561), - [1008] = {.entry = {.count = 1, .reusable = true}}, SHIFT(101), - [1010] = {.entry = {.count = 1, .reusable = true}}, SHIFT(604), - [1012] = {.entry = {.count = 1, .reusable = true}}, SHIFT(531), - [1014] = {.entry = {.count = 1, .reusable = true}}, SHIFT(98), - [1016] = {.entry = {.count = 1, .reusable = true}}, SHIFT(780), - [1018] = {.entry = {.count = 1, .reusable = true}}, SHIFT(750), - [1020] = {.entry = {.count = 1, .reusable = true}}, SHIFT(632), - [1022] = {.entry = {.count = 1, .reusable = true}}, SHIFT(739), - [1024] = {.entry = {.count = 1, .reusable = true}}, SHIFT(641), - [1026] = {.entry = {.count = 1, .reusable = true}}, SHIFT(471), - [1028] = {.entry = {.count = 1, .reusable = true}}, SHIFT(587), - [1030] = {.entry = {.count = 1, .reusable = true}}, SHIFT(92), - [1032] = {.entry = {.count = 1, .reusable = true}}, SHIFT(281), - [1034] = {.entry = {.count = 1, .reusable = true}}, SHIFT(415), - [1036] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1022), - [1038] = {.entry = {.count = 1, .reusable = true}}, SHIFT(216), - [1040] = {.entry = {.count = 1, .reusable = true}}, SHIFT(149), - [1042] = {.entry = {.count = 1, .reusable = true}}, SHIFT(296), - [1044] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1016), - [1046] = {.entry = {.count = 1, .reusable = true}}, SHIFT(394), - [1048] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1002), - [1050] = {.entry = {.count = 1, .reusable = true}}, SHIFT(424), - [1052] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1004), - [1054] = {.entry = {.count = 1, .reusable = false}}, SHIFT(560), - [1056] = {.entry = {.count = 1, .reusable = false}}, SHIFT(557), - [1058] = {.entry = {.count = 1, .reusable = true}}, SHIFT(557), - [1060] = {.entry = {.count = 1, .reusable = false}}, SHIFT(572), - [1062] = {.entry = {.count = 1, .reusable = false}}, SHIFT(573), - [1064] = {.entry = {.count = 1, .reusable = true}}, SHIFT(971), - [1066] = {.entry = {.count = 1, .reusable = true}}, SHIFT(978), - [1068] = {.entry = {.count = 1, .reusable = true}}, SHIFT(611), - [1070] = {.entry = {.count = 1, .reusable = true}}, SHIFT(245), - [1072] = {.entry = {.count = 1, .reusable = true}}, SHIFT(142), - [1074] = {.entry = {.count = 1, .reusable = true}}, SHIFT(131), - [1076] = {.entry = {.count = 1, .reusable = true}}, SHIFT(308), - [1078] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1038), - [1080] = {.entry = {.count = 1, .reusable = true}}, SHIFT(257), - [1082] = {.entry = {.count = 1, .reusable = true}}, SHIFT(146), - [1084] = {.entry = {.count = 1, .reusable = true}}, SHIFT(407), - [1086] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1006), - [1088] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(446), - [1091] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(183), - [1094] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(183), - [1097] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(979), - [1100] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(980), - [1103] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(449), - [1106] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), - [1108] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(307), - [1111] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(309), - [1114] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(318), - [1117] = {.entry = {.count = 1, .reusable = true}}, SHIFT(431), - [1119] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1028), - [1121] = {.entry = {.count = 1, .reusable = true}}, SHIFT(276), - [1123] = {.entry = {.count = 1, .reusable = true}}, SHIFT(277), - [1125] = {.entry = {.count = 1, .reusable = true}}, SHIFT(221), - [1127] = {.entry = {.count = 1, .reusable = true}}, SHIFT(282), - [1129] = {.entry = {.count = 1, .reusable = true}}, SHIFT(283), - [1131] = {.entry = {.count = 1, .reusable = true}}, SHIFT(285), - [1133] = {.entry = {.count = 1, .reusable = true}}, SHIFT(290), - [1135] = {.entry = {.count = 1, .reusable = false}}, SHIFT(446), - [1137] = {.entry = {.count = 1, .reusable = false}}, SHIFT(183), - [1139] = {.entry = {.count = 1, .reusable = true}}, SHIFT(183), - [1141] = {.entry = {.count = 1, .reusable = true}}, SHIFT(979), - [1143] = {.entry = {.count = 1, .reusable = true}}, SHIFT(980), - [1145] = {.entry = {.count = 1, .reusable = true}}, SHIFT(449), - [1147] = {.entry = {.count = 1, .reusable = true}}, SHIFT(307), - [1149] = {.entry = {.count = 1, .reusable = true}}, SHIFT(447), - [1151] = {.entry = {.count = 1, .reusable = true}}, SHIFT(309), - [1153] = {.entry = {.count = 1, .reusable = true}}, SHIFT(318), - [1155] = {.entry = {.count = 1, .reusable = false}}, SHIFT(297), - [1157] = {.entry = {.count = 1, .reusable = true}}, SHIFT(297), - [1159] = {.entry = {.count = 1, .reusable = true}}, SHIFT(441), - [1161] = {.entry = {.count = 1, .reusable = false}}, SHIFT(298), - [1163] = {.entry = {.count = 1, .reusable = true}}, SHIFT(298), - [1165] = {.entry = {.count = 1, .reusable = true}}, SHIFT(317), - [1167] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1001), - [1169] = {.entry = {.count = 1, .reusable = false}}, SHIFT(305), - [1171] = {.entry = {.count = 1, .reusable = true}}, SHIFT(305), - [1173] = {.entry = {.count = 1, .reusable = true}}, SHIFT(993), - [1175] = {.entry = {.count = 1, .reusable = true}}, SHIFT(361), - [1177] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1018), - [1179] = {.entry = {.count = 1, .reusable = true}}, SHIFT(330), - [1181] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1007), - [1183] = {.entry = {.count = 1, .reusable = false}}, SHIFT(343), - [1185] = {.entry = {.count = 1, .reusable = true}}, SHIFT(343), - [1187] = {.entry = {.count = 1, .reusable = true}}, SHIFT(57), - [1189] = {.entry = {.count = 1, .reusable = false}}, SHIFT(344), - [1191] = {.entry = {.count = 1, .reusable = true}}, SHIFT(344), - [1193] = {.entry = {.count = 1, .reusable = false}}, SHIFT(345), - [1195] = {.entry = {.count = 1, .reusable = true}}, SHIFT(345), - [1197] = {.entry = {.count = 1, .reusable = true}}, SHIFT(402), - [1199] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1026), - [1201] = {.entry = {.count = 1, .reusable = true}}, SHIFT(58), - [1203] = {.entry = {.count = 1, .reusable = false}}, SHIFT(357), - [1205] = {.entry = {.count = 1, .reusable = true}}, SHIFT(357), - [1207] = {.entry = {.count = 1, .reusable = true}}, SHIFT(536), - [1209] = {.entry = {.count = 1, .reusable = false}}, SHIFT(358), - [1211] = {.entry = {.count = 1, .reusable = true}}, SHIFT(358), - [1213] = {.entry = {.count = 1, .reusable = false}}, SHIFT(359), - [1215] = {.entry = {.count = 1, .reusable = true}}, SHIFT(359), - [1217] = {.entry = {.count = 1, .reusable = true}}, SHIFT(541), - [1219] = {.entry = {.count = 1, .reusable = false}}, SHIFT(325), - [1221] = {.entry = {.count = 1, .reusable = true}}, SHIFT(325), - [1223] = {.entry = {.count = 1, .reusable = true}}, SHIFT(986), - [1225] = {.entry = {.count = 1, .reusable = false}}, SHIFT(366), - [1227] = {.entry = {.count = 1, .reusable = true}}, SHIFT(366), - [1229] = {.entry = {.count = 1, .reusable = true}}, SHIFT(474), - [1231] = {.entry = {.count = 1, .reusable = false}}, SHIFT(367), - [1233] = {.entry = {.count = 1, .reusable = true}}, SHIFT(367), - [1235] = {.entry = {.count = 1, .reusable = false}}, SHIFT(368), - [1237] = {.entry = {.count = 1, .reusable = true}}, SHIFT(368), - [1239] = {.entry = {.count = 1, .reusable = true}}, SHIFT(489), - [1241] = {.entry = {.count = 1, .reusable = false}}, SHIFT(377), - [1243] = {.entry = {.count = 1, .reusable = true}}, SHIFT(377), - [1245] = {.entry = {.count = 1, .reusable = true}}, SHIFT(652), - [1247] = {.entry = {.count = 1, .reusable = false}}, SHIFT(378), - [1249] = {.entry = {.count = 1, .reusable = true}}, SHIFT(378), - [1251] = {.entry = {.count = 1, .reusable = false}}, SHIFT(379), - [1253] = {.entry = {.count = 1, .reusable = true}}, SHIFT(379), - [1255] = {.entry = {.count = 1, .reusable = true}}, SHIFT(656), - [1257] = {.entry = {.count = 1, .reusable = false}}, SHIFT(383), - [1259] = {.entry = {.count = 1, .reusable = true}}, SHIFT(383), - [1261] = {.entry = {.count = 1, .reusable = true}}, SHIFT(576), - [1263] = {.entry = {.count = 1, .reusable = false}}, SHIFT(384), - [1265] = {.entry = {.count = 1, .reusable = true}}, SHIFT(384), - [1267] = {.entry = {.count = 1, .reusable = false}}, SHIFT(385), - [1269] = {.entry = {.count = 1, .reusable = true}}, SHIFT(385), - [1271] = {.entry = {.count = 1, .reusable = true}}, SHIFT(577), - [1273] = {.entry = {.count = 1, .reusable = false}}, SHIFT(390), - [1275] = {.entry = {.count = 1, .reusable = true}}, SHIFT(390), - [1277] = {.entry = {.count = 1, .reusable = true}}, SHIFT(755), - [1279] = {.entry = {.count = 1, .reusable = false}}, SHIFT(391), - [1281] = {.entry = {.count = 1, .reusable = true}}, SHIFT(391), - [1283] = {.entry = {.count = 1, .reusable = false}}, SHIFT(392), - [1285] = {.entry = {.count = 1, .reusable = true}}, SHIFT(392), - [1287] = {.entry = {.count = 1, .reusable = true}}, SHIFT(756), - [1289] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 4, 0, 0), - [1291] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 4, 0, 0), - [1293] = {.entry = {.count = 1, .reusable = true}}, SHIFT(477), - [1295] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 5, 0, 0), - [1297] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 5, 0, 0), - [1299] = {.entry = {.count = 1, .reusable = true}}, SHIFT(488), - [1301] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 6, 0, 0), - [1303] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 6, 0, 0), - [1305] = {.entry = {.count = 1, .reusable = true}}, SHIFT(494), - [1307] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 6, 0, 0), - [1309] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 6, 0, 0), - [1311] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 8, 0, 17), - [1313] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 8, 0, 17), - [1315] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_attributed_item, 2, 0, 0), - [1317] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_attributed_item, 2, 0, 0), - [1319] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 9, 0, 0), - [1321] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 9, 0, 0), - [1323] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 3, 0, 0), - [1325] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 3, 0, 0), - [1327] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_while_statement, 3, 0, 0), - [1329] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_while_statement, 3, 0, 0), - [1331] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_return_statement, 3, 0, 0), - [1333] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_return_statement, 3, 0, 0), - [1335] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_go_statement, 3, 0, 0), - [1337] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_go_statement, 3, 0, 0), - [1339] = {.entry = {.count = 1, .reusable = true}}, SHIFT(553), - [1341] = {.entry = {.count = 1, .reusable = true}}, SHIFT(530), - [1343] = {.entry = {.count = 1, .reusable = true}}, SHIFT(508), - [1345] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_definition, 4, 0, 14), - [1347] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_definition, 4, 0, 14), - [1349] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 4, 0, 0), - [1351] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 4, 0, 0), - [1353] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_definition, 4, 0, 0), - [1355] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_trait_definition, 4, 0, 0), - [1357] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_assignment_statement, 4, 0, 0), - [1359] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_assignment_statement, 4, 0, 0), - [1361] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_define_statement, 4, 0, 0), - [1363] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_define_statement, 4, 0, 0), - [1365] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_compound_assignment_statement, 4, 0, 0), - [1367] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_compound_assignment_statement, 4, 0, 0), - [1369] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_return_statement, 2, 0, 0), - [1371] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_return_statement, 2, 0, 0), - [1373] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_break_statement, 2, 0, 0), - [1375] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_break_statement, 2, 0, 0), - [1377] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_continue_statement, 2, 0, 0), - [1379] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_continue_statement, 2, 0, 0), - [1381] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_if_statement, 5, 0, 0), - [1383] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_if_statement, 5, 0, 0), - [1385] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 5, 0, 0), - [1387] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 5, 0, 0), - [1389] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_definition, 5, 0, 16), - [1391] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_definition, 5, 0, 16), - [1393] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_let_statement, 5, 0, 0), - [1395] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_let_statement, 5, 0, 0), - [1397] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_while_statement, 5, 0, 0), - [1399] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_while_statement, 5, 0, 0), - [1401] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_statement, 5, 0, 0), - [1403] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_for_statement, 5, 0, 0), - [1405] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 5, 0, 17), - [1407] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 5, 0, 17), - [1409] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 5, 0, 0), - [1411] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 5, 0, 0), - [1413] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_alias_definition, 5, 0, 0), - [1415] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_type_alias_definition, 5, 0, 0), - [1417] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_definition, 5, 0, 0), - [1419] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_trait_definition, 5, 0, 0), - [1421] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_try_statement, 5, 0, 18), - [1423] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_try_statement, 5, 0, 18), - [1425] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_expression_statement, 2, 0, 0), - [1427] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_expression_statement, 2, 0, 0), - [1429] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 6, 0, 17), - [1431] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 6, 0, 17), - [1433] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_impl_definition, 6, 0, 22), - [1435] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_impl_definition, 6, 0, 22), - [1437] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_if_statement, 7, 0, 0), - [1439] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_if_statement, 7, 0, 0), - [1441] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 7, 0, 0), - [1443] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 7, 0, 0), - [1445] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 7, 0, 0), - [1447] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 7, 0, 0), - [1449] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_let_statement, 7, 0, 0), - [1451] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_let_statement, 7, 0, 0), - [1453] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 7, 0, 17), - [1455] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 7, 0, 17), - [1457] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 7, 0, 0), - [1459] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 7, 0, 0), - [1461] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_impl_definition, 7, 0, 22), - [1463] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_impl_definition, 7, 0, 22), - [1465] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 8, 0, 0), - [1467] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 8, 0, 0), - [1469] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_block, 3, 0, 0), - [1471] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_block, 3, 0, 0), - [1473] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_if_statement, 3, 0, 0), - [1475] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_if_statement, 3, 0, 0), - [1477] = {.entry = {.count = 1, .reusable = false}}, SHIFT(28), - [1479] = {.entry = {.count = 1, .reusable = false}}, SHIFT(31), - [1481] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 5, 0, 0), SHIFT(36), - [1484] = {.entry = {.count = 1, .reusable = false}}, SHIFT(26), - [1486] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 3, 0, 0), SHIFT(23), - [1489] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 5, 0, 0), SHIFT(40), - [1492] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 3, 0, 0), SHIFT(34), - [1495] = {.entry = {.count = 1, .reusable = false}}, SHIFT(32), - [1497] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__statement, 1, 0, 0), REDUCE(aux_sym_program_repeat1, 1, 0, 0), - [1500] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__statement, 1, 0, 0), REDUCE(aux_sym_program_repeat1, 1, 0, 0), - [1503] = {.entry = {.count = 1, .reusable = true}}, SHIFT(312), - [1505] = {.entry = {.count = 1, .reusable = false}}, SHIFT(312), - [1507] = {.entry = {.count = 1, .reusable = true}}, SHIFT(313), - [1509] = {.entry = {.count = 1, .reusable = true}}, SHIFT(314), - [1511] = {.entry = {.count = 1, .reusable = true}}, SHIFT(310), - [1513] = {.entry = {.count = 1, .reusable = false}}, SHIFT(310), - [1515] = {.entry = {.count = 1, .reusable = true}}, SHIFT(311), - [1517] = {.entry = {.count = 1, .reusable = false}}, SHIFT(45), - [1519] = {.entry = {.count = 1, .reusable = true}}, SHIFT(316), - [1521] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(118), - [1524] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1273), - [1526] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1274), - [1528] = {.entry = {.count = 1, .reusable = true}}, SHIFT(250), - [1530] = {.entry = {.count = 1, .reusable = true}}, SHIFT(251), - [1532] = {.entry = {.count = 1, .reusable = false}}, SHIFT(602), - [1534] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(310), - [1537] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(310), - [1540] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(311), - [1543] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(312), - [1546] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(312), - [1549] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(313), - [1552] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(314), - [1555] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(315), - [1558] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(45), - [1561] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(316), - [1564] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(270), - [1567] = {.entry = {.count = 1, .reusable = true}}, SHIFT(118), - [1569] = {.entry = {.count = 1, .reusable = true}}, SHIFT(315), - [1571] = {.entry = {.count = 1, .reusable = false}}, SHIFT(252), - [1573] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1057), - [1576] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_identifier, 1, 0, 0), - [1578] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1064), - [1581] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(155), - [1584] = {.entry = {.count = 1, .reusable = false}}, SHIFT(172), - [1586] = {.entry = {.count = 1, .reusable = true}}, SHIFT(174), - [1588] = {.entry = {.count = 1, .reusable = true}}, SHIFT(180), - [1590] = {.entry = {.count = 1, .reusable = false}}, SHIFT(224), - [1592] = {.entry = {.count = 1, .reusable = true}}, SHIFT(225), - [1594] = {.entry = {.count = 1, .reusable = true}}, SHIFT(226), - [1596] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(393), - [1599] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(393), - [1602] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(420), - [1605] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(430), - [1608] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(430), - [1611] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(295), - [1614] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(306), - [1617] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(328), - [1620] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(44), - [1623] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(331), - [1626] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(284), - [1629] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1208), - [1631] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1040), - [1633] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1040), - [1635] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1012), - [1637] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1013), - [1639] = {.entry = {.count = 1, .reusable = true}}, SHIFT(966), - [1641] = {.entry = {.count = 1, .reusable = true}}, SHIFT(967), - [1643] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1031), - [1645] = {.entry = {.count = 1, .reusable = true}}, SHIFT(564), - [1647] = {.entry = {.count = 1, .reusable = true}}, SHIFT(691), - [1649] = {.entry = {.count = 1, .reusable = true}}, SHIFT(886), - [1651] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1212), - [1653] = {.entry = {.count = 1, .reusable = true}}, SHIFT(79), - [1655] = {.entry = {.count = 1, .reusable = true}}, SHIFT(393), - [1657] = {.entry = {.count = 1, .reusable = false}}, SHIFT(393), - [1659] = {.entry = {.count = 1, .reusable = true}}, SHIFT(420), - [1661] = {.entry = {.count = 1, .reusable = true}}, SHIFT(430), - [1663] = {.entry = {.count = 1, .reusable = false}}, SHIFT(430), - [1665] = {.entry = {.count = 1, .reusable = true}}, SHIFT(295), - [1667] = {.entry = {.count = 1, .reusable = true}}, SHIFT(306), - [1669] = {.entry = {.count = 1, .reusable = false}}, SHIFT(44), - [1671] = {.entry = {.count = 1, .reusable = true}}, SHIFT(331), - [1673] = {.entry = {.count = 1, .reusable = true}}, SHIFT(801), - [1675] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1208), - [1678] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1040), - [1681] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1040), - [1684] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1012), - [1687] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1013), - [1690] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(966), - [1693] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(967), - [1696] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1031), - [1699] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), - [1701] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(691), - [1704] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(886), - [1707] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1212), - [1710] = {.entry = {.count = 1, .reusable = true}}, SHIFT(783), - [1712] = {.entry = {.count = 1, .reusable = true}}, SHIFT(94), - [1714] = {.entry = {.count = 1, .reusable = true}}, SHIFT(591), - [1716] = {.entry = {.count = 1, .reusable = true}}, SHIFT(637), - [1718] = {.entry = {.count = 1, .reusable = true}}, SHIFT(122), - [1720] = {.entry = {.count = 1, .reusable = true}}, SHIFT(328), - [1722] = {.entry = {.count = 1, .reusable = false}}, SHIFT(233), - [1724] = {.entry = {.count = 1, .reusable = true}}, SHIFT(654), - [1726] = {.entry = {.count = 1, .reusable = false}}, SHIFT(866), - [1728] = {.entry = {.count = 1, .reusable = false}}, SHIFT(904), - [1730] = {.entry = {.count = 1, .reusable = true}}, SHIFT(904), - [1732] = {.entry = {.count = 1, .reusable = false}}, SHIFT(901), - [1734] = {.entry = {.count = 1, .reusable = false}}, SHIFT(899), - [1736] = {.entry = {.count = 1, .reusable = true}}, SHIFT(952), - [1738] = {.entry = {.count = 1, .reusable = true}}, SHIFT(953), - [1740] = {.entry = {.count = 1, .reusable = true}}, SHIFT(876), - [1742] = {.entry = {.count = 1, .reusable = true}}, SHIFT(687), - [1744] = {.entry = {.count = 1, .reusable = true}}, SHIFT(918), - [1746] = {.entry = {.count = 1, .reusable = true}}, SHIFT(888), - [1748] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1313), - [1750] = {.entry = {.count = 1, .reusable = false}}, SHIFT(924), - [1752] = {.entry = {.count = 1, .reusable = true}}, SHIFT(930), - [1754] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1329), - [1756] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1264), - [1758] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1296), - [1760] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1148), - [1762] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1300), - [1764] = {.entry = {.count = 1, .reusable = true}}, SHIFT(421), - [1766] = {.entry = {.count = 1, .reusable = false}}, SHIFT(421), - [1768] = {.entry = {.count = 1, .reusable = true}}, SHIFT(422), - [1770] = {.entry = {.count = 1, .reusable = true}}, SHIFT(425), - [1772] = {.entry = {.count = 1, .reusable = false}}, SHIFT(425), - [1774] = {.entry = {.count = 1, .reusable = true}}, SHIFT(426), - [1776] = {.entry = {.count = 1, .reusable = true}}, SHIFT(427), - [1778] = {.entry = {.count = 1, .reusable = false}}, SHIFT(47), - [1780] = {.entry = {.count = 1, .reusable = true}}, SHIFT(428), - [1782] = {.entry = {.count = 1, .reusable = true}}, SHIFT(919), - [1784] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(130), - [1787] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1175), - [1789] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1176), - [1791] = {.entry = {.count = 1, .reusable = true}}, SHIFT(227), - [1793] = {.entry = {.count = 1, .reusable = true}}, SHIFT(228), - [1795] = {.entry = {.count = 1, .reusable = false}}, SHIFT(648), - [1797] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(421), - [1800] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(421), - [1803] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(422), - [1806] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(425), - [1809] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(425), - [1812] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(426), - [1815] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(427), - [1818] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(423), - [1821] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(47), + [118] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(298), + [121] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(298), + [124] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1132), + [127] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(358), + [130] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(217), + [133] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1557), + [136] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1590), + [139] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1625), + [142] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1465), + [145] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1522), + [148] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1627), + [151] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(988), + [154] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1428), + [157] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1556), + [160] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(955), + [163] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(250), + [166] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1041), + [169] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1442), + [172] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1429), + [175] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1443), + [178] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1430), + [181] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1419), + [184] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(148), + [187] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1517), + [190] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1520), + [193] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(157), + [196] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1444), + [199] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(11), + [202] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(249), + [205] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(261), + [208] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1028), + [211] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(13), + [214] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(208), + [217] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(986), + [220] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1433), + [223] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1471), + [226] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(948), + [229] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(219), + [232] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1032), + [235] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1420), + [238] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1421), + [241] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1412), + [244] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1425), + [247] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1407), + [250] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(142), + [253] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1491), + [256] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1512), + [259] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(182), + [262] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1394), + [265] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(12), + [268] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(278), + [271] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(218), + [274] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_parenthesized_expression, 3, 0, 0), SHIFT(1036), + [277] = {.entry = {.count = 1, .reusable = true}}, SHIFT(103), + [279] = {.entry = {.count = 1, .reusable = true}}, SHIFT(12), + [281] = {.entry = {.count = 1, .reusable = false}}, SHIFT(278), + [283] = {.entry = {.count = 1, .reusable = false}}, SHIFT(218), + [285] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1036), + [287] = {.entry = {.count = 1, .reusable = true}}, SHIFT(102), + [289] = {.entry = {.count = 1, .reusable = true}}, SHIFT(104), + [291] = {.entry = {.count = 1, .reusable = true}}, SHIFT(105), + [293] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1319), + [295] = {.entry = {.count = 1, .reusable = true}}, SHIFT(641), + [297] = {.entry = {.count = 1, .reusable = true}}, SHIFT(651), + [299] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1343), + [301] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), + [303] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(669), + [306] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(101), + [309] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(101), + [312] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(59), + [315] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(85), + [318] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1080), + [321] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1065), + [324] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(84), + [327] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(206), + [330] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(145), + [333] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(13), + [336] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(298), + [339] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1132), + [342] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(358), + [345] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(208), + [348] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1557), + [351] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1590), + [354] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1625), + [357] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1465), + [360] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1522), + [363] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1627), + [366] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(986), + [369] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1433), + [372] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1471), + [375] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(948), + [378] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(219), + [381] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1032), + [384] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1420), + [387] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1421), + [390] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1412), + [393] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1425), + [396] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1407), + [399] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(142), + [402] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1491), + [405] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1512), + [408] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(182), + [411] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_program_repeat1, 2, 0, 0), SHIFT_REPEAT(1394), + [414] = {.entry = {.count = 1, .reusable = true}}, SHIFT(546), + [416] = {.entry = {.count = 1, .reusable = true}}, SHIFT(549), + [418] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(669), + [421] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(101), + [424] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(101), + [427] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(59), + [430] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(85), + [433] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1080), + [436] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1065), + [439] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(84), + [442] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), + [444] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(206), + [447] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(145), + [450] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(12), + [453] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(298), + [456] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1132), + [459] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(358), + [462] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(278), + [465] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1557), + [468] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1590), + [471] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1625), + [474] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1465), + [477] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1522), + [480] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1627), + [483] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(986), + [486] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1433), + [489] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1471), + [492] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(948), + [495] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(218), + [498] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1036), + [501] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1420), + [504] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1421), + [507] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1412), + [510] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1425), + [513] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1407), + [516] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(142), + [519] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1491), + [522] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1512), + [525] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(182), + [528] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1394), + [531] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_program, 1, 0, 0), + [533] = {.entry = {.count = 1, .reusable = true}}, SHIFT(10), + [535] = {.entry = {.count = 1, .reusable = false}}, SHIFT(217), + [537] = {.entry = {.count = 1, .reusable = false}}, SHIFT(988), + [539] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1428), + [541] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1556), + [543] = {.entry = {.count = 1, .reusable = false}}, SHIFT(955), + [545] = {.entry = {.count = 1, .reusable = false}}, SHIFT(250), + [547] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1041), + [549] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1442), + [551] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1429), + [553] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1443), + [555] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1430), + [557] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1419), + [559] = {.entry = {.count = 1, .reusable = false}}, SHIFT(148), + [561] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1517), + [563] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1520), + [565] = {.entry = {.count = 1, .reusable = false}}, SHIFT(157), + [567] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1444), + [569] = {.entry = {.count = 1, .reusable = true}}, SHIFT(11), + [571] = {.entry = {.count = 1, .reusable = false}}, SHIFT(249), + [573] = {.entry = {.count = 1, .reusable = false}}, SHIFT(261), + [575] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1028), + [577] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_range_expression, 2, 0, 4), + [579] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_range_expression, 2, 0, 4), + [581] = {.entry = {.count = 1, .reusable = false}}, SHIFT(668), + [583] = {.entry = {.count = 1, .reusable = true}}, SHIFT(141), + [585] = {.entry = {.count = 1, .reusable = false}}, SHIFT(717), + [587] = {.entry = {.count = 1, .reusable = true}}, SHIFT(717), + [589] = {.entry = {.count = 1, .reusable = false}}, SHIFT(709), + [591] = {.entry = {.count = 1, .reusable = false}}, SHIFT(710), + [593] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1049), + [595] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1050), + [597] = {.entry = {.count = 1, .reusable = true}}, SHIFT(688), + [599] = {.entry = {.count = 1, .reusable = true}}, SHIFT(124), + [601] = {.entry = {.count = 1, .reusable = true}}, SHIFT(312), + [603] = {.entry = {.count = 1, .reusable = false}}, SHIFT(434), + [605] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1552), + [607] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1571), + [609] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1647), + [611] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1553), + [613] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1554), + [615] = {.entry = {.count = 1, .reusable = true}}, SHIFT(332), + [617] = {.entry = {.count = 1, .reusable = false}}, SHIFT(447), + [619] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1603), + [621] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1483), + [623] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1651), + [625] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1604), + [627] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1605), + [629] = {.entry = {.count = 1, .reusable = true}}, SHIFT(425), + [631] = {.entry = {.count = 1, .reusable = true}}, SHIFT(392), + [633] = {.entry = {.count = 1, .reusable = false}}, SHIFT(747), + [635] = {.entry = {.count = 1, .reusable = true}}, SHIFT(380), + [637] = {.entry = {.count = 1, .reusable = true}}, SHIFT(300), + [639] = {.entry = {.count = 1, .reusable = false}}, SHIFT(828), + [641] = {.entry = {.count = 1, .reusable = true}}, SHIFT(828), + [643] = {.entry = {.count = 1, .reusable = false}}, SHIFT(832), + [645] = {.entry = {.count = 1, .reusable = false}}, SHIFT(834), + [647] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1084), + [649] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1043), + [651] = {.entry = {.count = 1, .reusable = true}}, SHIFT(886), + [653] = {.entry = {.count = 1, .reusable = true}}, SHIFT(135), + [655] = {.entry = {.count = 1, .reusable = true}}, SHIFT(401), + [657] = {.entry = {.count = 1, .reusable = false}}, SHIFT(299), + [659] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1613), + [661] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1523), + [663] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1652), + [665] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1614), + [667] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1615), + [669] = {.entry = {.count = 1, .reusable = true}}, SHIFT(346), + [671] = {.entry = {.count = 1, .reusable = false}}, SHIFT(109), + [673] = {.entry = {.count = 1, .reusable = true}}, SHIFT(435), + [675] = {.entry = {.count = 1, .reusable = true}}, SHIFT(359), + [677] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 6, 0, 2), + [679] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 6, 0, 2), + [681] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_unwrap_expression, 2, 0, 3), + [683] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_unwrap_expression, 2, 0, 3), + [685] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 6, 0, 0), + [687] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 6, 0, 0), + [689] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_send_expression, 6, 0, 0), + [691] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_send_expression, 6, 0, 0), + [693] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_boolean_literal, 1, 0, 0), + [695] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_boolean_literal, 1, 0, 0), + [697] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), + [699] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_primary_expression, 1, 0, 0), + [701] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1184), + [704] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_group, 2, 0, 0), + [706] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_group, 2, 0, 0), + [708] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_group, 3, 0, 0), + [710] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_group, 3, 0, 0), + [712] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_ternary_expression, 5, 0, 20), + [714] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_ternary_expression, 5, 0, 20), + [716] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_identifier, 1, 0, 0), + [718] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_identifier, 1, 0, 0), + [720] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 2, 0, 0), + [722] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 2, 0, 0), + [724] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_double_string, 3, 0, 0), + [726] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_double_string, 3, 0, 0), + [728] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_single_string, 3, 0, 0), + [730] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_single_string, 3, 0, 0), + [732] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 2, 0, 0), + [734] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 2, 0, 0), + [736] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 3, 0, 0), + [738] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 3, 0, 0), + [740] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 3, 0, 0), + [742] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 3, 0, 0), + [744] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_closure, 3, 0, 5), + [746] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_closure, 3, 0, 5), + [748] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 3, 0, 0), + [750] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 3, 0, 0), + [752] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_invocation, 3, 1, 2), + [754] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_invocation, 3, 1, 2), + [756] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 3, 0, 2), + [758] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 3, 0, 2), + [760] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_call_expression, 3, 0, 6), + [762] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_call_expression, 3, 0, 6), + [764] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_field_access, 3, 0, 7), + [766] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_field_access, 3, 0, 7), + [768] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_optional_field_access, 3, 0, 7), + [770] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_optional_field_access, 3, 0, 7), + [772] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_binary_expression, 3, 0, 9), + [774] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_binary_expression, 3, 0, 9), + [776] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 4, 0, 0), + [778] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 4, 0, 0), + [780] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 4, 0, 0), + [782] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 4, 0, 0), + [784] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_closure, 4, 0, 12), + [786] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_closure, 4, 0, 12), + [788] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_expression, 4, 0, 13), + [790] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_expression, 4, 0, 13), + [792] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_string_literal, 1, 0, 0), + [794] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_string_literal, 1, 0, 0), + [796] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_nil_literal, 1, 0, 0), + [798] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_nil_literal, 1, 0, 0), + [800] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_spawn_expression, 4, 0, 0), + [802] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_spawn_expression, 4, 0, 0), + [804] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 4, 0, 0), + [806] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 4, 0, 0), + [808] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_recv_expression, 4, 0, 0), + [810] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_recv_expression, 4, 0, 0), + [812] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_select_expression, 4, 0, 0), + [814] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_expression, 4, 0, 0), + [816] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_double_string, 2, 0, 0), + [818] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_double_string, 2, 0, 0), + [820] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_single_string, 2, 0, 0), + [822] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_single_string, 2, 0, 0), + [824] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 4, 0, 2), + [826] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 4, 0, 2), + [828] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_call_expression, 4, 0, 6), + [830] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_call_expression, 4, 0, 6), + [832] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_index_access, 4, 0, 15), + [834] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_index_access, 4, 0, 15), + [836] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_optional_index_access, 4, 0, 15), + [838] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_optional_index_access, 4, 0, 15), + [840] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_list_expression, 5, 0, 0), + [842] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_expression, 5, 0, 0), + [844] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_map_expression, 5, 0, 0), + [846] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_expression, 5, 0, 0), + [848] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_expression, 5, 0, 13), + [850] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_expression, 5, 0, 13), + [852] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_chan_expression, 5, 0, 0), + [854] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_chan_expression, 5, 0, 0), + [856] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_literal, 5, 0, 2), + [858] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_literal, 5, 0, 2), + [860] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_block, 2, 0, 0), + [862] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_block, 2, 0, 0), + [864] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_map_expression, 2, 0, 0), REDUCE(sym_block, 2, 0, 0), + [867] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_map_expression, 2, 0, 0), REDUCE(sym_block, 2, 0, 0), + [870] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_unary_expression, 2, 0, 1), + [872] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_unary_expression, 2, 0, 1), + [874] = {.entry = {.count = 1, .reusable = true}}, SHIFT(375), + [876] = {.entry = {.count = 1, .reusable = false}}, SHIFT(375), + [878] = {.entry = {.count = 1, .reusable = true}}, SHIFT(376), + [880] = {.entry = {.count = 1, .reusable = true}}, SHIFT(414), + [882] = {.entry = {.count = 1, .reusable = false}}, SHIFT(414), + [884] = {.entry = {.count = 1, .reusable = true}}, SHIFT(415), + [886] = {.entry = {.count = 1, .reusable = true}}, SHIFT(416), + [888] = {.entry = {.count = 1, .reusable = false}}, SHIFT(417), + [890] = {.entry = {.count = 1, .reusable = true}}, SHIFT(418), + [892] = {.entry = {.count = 1, .reusable = false}}, SHIFT(419), + [894] = {.entry = {.count = 1, .reusable = true}}, SHIFT(420), + [896] = {.entry = {.count = 1, .reusable = false}}, SHIFT(43), + [898] = {.entry = {.count = 1, .reusable = true}}, SHIFT(421), + [900] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_binary_expression, 3, 0, 8), + [902] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_binary_expression, 3, 0, 8), + [904] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_nullish_coalescing_expression, 3, 0, 9), + [906] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_nullish_coalescing_expression, 3, 0, 9), + [908] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_identifier, 1, 0, 0), REDUCE(sym_type_identifier, 1, 0, 0), + [911] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), + [913] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), + [915] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(140), + [918] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1265), + [920] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1313), + [922] = {.entry = {.count = 1, .reusable = true}}, SHIFT(245), + [924] = {.entry = {.count = 1, .reusable = true}}, SHIFT(247), + [926] = {.entry = {.count = 1, .reusable = false}}, SHIFT(56), + [928] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(375), + [931] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(375), + [934] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(376), + [937] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(414), + [940] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(414), + [943] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(415), + [946] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(416), + [949] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(417), + [952] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(418), + [955] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(419), + [958] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(420), + [961] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(377), + [964] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(43), + [967] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(421), + [970] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(288), + [973] = {.entry = {.count = 1, .reusable = true}}, SHIFT(377), + [975] = {.entry = {.count = 1, .reusable = false}}, SHIFT(281), + [977] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_range_expression, 3, 0, 9), + [979] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_range_expression, 3, 0, 9), + [981] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 2, 0, 0), + [983] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1264), + [985] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 3, 0, 0), + [987] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1333), + [989] = {.entry = {.count = 1, .reusable = true}}, SHIFT(543), + [991] = {.entry = {.count = 1, .reusable = true}}, SHIFT(97), + [993] = {.entry = {.count = 1, .reusable = true}}, SHIFT(713), + [995] = {.entry = {.count = 1, .reusable = true}}, SHIFT(683), + [997] = {.entry = {.count = 1, .reusable = true}}, SHIFT(230), + [999] = {.entry = {.count = 1, .reusable = true}}, SHIFT(670), + [1001] = {.entry = {.count = 1, .reusable = true}}, SHIFT(693), + [1003] = {.entry = {.count = 1, .reusable = true}}, SHIFT(675), + [1005] = {.entry = {.count = 1, .reusable = true}}, SHIFT(81), + [1007] = {.entry = {.count = 1, .reusable = true}}, SHIFT(73), + [1009] = {.entry = {.count = 1, .reusable = true}}, SHIFT(179), + [1011] = {.entry = {.count = 1, .reusable = true}}, SHIFT(548), + [1013] = {.entry = {.count = 1, .reusable = true}}, SHIFT(256), + [1015] = {.entry = {.count = 1, .reusable = true}}, SHIFT(552), + [1017] = {.entry = {.count = 1, .reusable = true}}, SHIFT(557), + [1019] = {.entry = {.count = 1, .reusable = true}}, SHIFT(578), + [1021] = {.entry = {.count = 1, .reusable = true}}, SHIFT(835), + [1023] = {.entry = {.count = 1, .reusable = true}}, SHIFT(841), + [1025] = {.entry = {.count = 1, .reusable = true}}, SHIFT(267), + [1027] = {.entry = {.count = 1, .reusable = true}}, SHIFT(844), + [1029] = {.entry = {.count = 1, .reusable = true}}, SHIFT(854), + [1031] = {.entry = {.count = 1, .reusable = true}}, SHIFT(876), + [1033] = {.entry = {.count = 1, .reusable = true}}, SHIFT(76), + [1035] = {.entry = {.count = 1, .reusable = true}}, SHIFT(68), + [1037] = {.entry = {.count = 1, .reusable = true}}, SHIFT(654), + [1039] = {.entry = {.count = 1, .reusable = true}}, SHIFT(674), + [1041] = {.entry = {.count = 1, .reusable = true}}, SHIFT(568), + [1043] = {.entry = {.count = 1, .reusable = true}}, SHIFT(65), + [1045] = {.entry = {.count = 1, .reusable = true}}, SHIFT(830), + [1047] = {.entry = {.count = 1, .reusable = true}}, SHIFT(80), + [1049] = {.entry = {.count = 1, .reusable = true}}, SHIFT(506), + [1051] = {.entry = {.count = 1, .reusable = true}}, SHIFT(610), + [1053] = {.entry = {.count = 1, .reusable = true}}, SHIFT(853), + [1055] = {.entry = {.count = 1, .reusable = true}}, SHIFT(875), + [1057] = {.entry = {.count = 1, .reusable = true}}, SHIFT(690), + [1059] = {.entry = {.count = 1, .reusable = true}}, SHIFT(682), + [1061] = {.entry = {.count = 1, .reusable = true}}, SHIFT(96), + [1063] = {.entry = {.count = 1, .reusable = true}}, SHIFT(556), + [1065] = {.entry = {.count = 1, .reusable = true}}, SHIFT(260), + [1067] = {.entry = {.count = 1, .reusable = true}}, SHIFT(146), + [1069] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1123), + [1071] = {.entry = {.count = 1, .reusable = true}}, SHIFT(413), + [1073] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1099), + [1075] = {.entry = {.count = 1, .reusable = true}}, SHIFT(216), + [1077] = {.entry = {.count = 1, .reusable = true}}, SHIFT(154), + [1079] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1116), + [1081] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1124), + [1083] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1118), + [1085] = {.entry = {.count = 1, .reusable = false}}, SHIFT(518), + [1087] = {.entry = {.count = 1, .reusable = false}}, SHIFT(606), + [1089] = {.entry = {.count = 1, .reusable = true}}, SHIFT(606), + [1091] = {.entry = {.count = 1, .reusable = false}}, SHIFT(522), + [1093] = {.entry = {.count = 1, .reusable = false}}, SHIFT(523), + [1095] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1070), + [1097] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1071), + [1099] = {.entry = {.count = 1, .reusable = true}}, SHIFT(527), + [1101] = {.entry = {.count = 1, .reusable = true}}, SHIFT(248), + [1103] = {.entry = {.count = 1, .reusable = true}}, SHIFT(149), + [1105] = {.entry = {.count = 1, .reusable = true}}, SHIFT(122), + [1107] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1115), + [1109] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1114), + [1111] = {.entry = {.count = 1, .reusable = true}}, SHIFT(178), + [1113] = {.entry = {.count = 1, .reusable = true}}, SHIFT(279), + [1115] = {.entry = {.count = 1, .reusable = true}}, SHIFT(280), + [1117] = {.entry = {.count = 1, .reusable = true}}, SHIFT(200), + [1119] = {.entry = {.count = 1, .reusable = true}}, SHIFT(283), + [1121] = {.entry = {.count = 1, .reusable = true}}, SHIFT(284), + [1123] = {.entry = {.count = 1, .reusable = true}}, SHIFT(286), + [1125] = {.entry = {.count = 1, .reusable = true}}, SHIFT(291), + [1127] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(495), + [1130] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(297), + [1133] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(297), + [1136] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(1077), + [1139] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(1078), + [1142] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(500), + [1145] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), + [1147] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(457), + [1150] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(458), + [1153] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attribute_repeat1, 2, 0, 0), SHIFT_REPEAT(459), + [1156] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1125), + [1158] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1129), + [1160] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1098), + [1162] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1126), + [1164] = {.entry = {.count = 1, .reusable = false}}, SHIFT(495), + [1166] = {.entry = {.count = 1, .reusable = false}}, SHIFT(474), + [1168] = {.entry = {.count = 1, .reusable = true}}, SHIFT(474), + [1170] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1077), + [1172] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1078), + [1174] = {.entry = {.count = 1, .reusable = true}}, SHIFT(500), + [1176] = {.entry = {.count = 1, .reusable = true}}, SHIFT(457), + [1178] = {.entry = {.count = 1, .reusable = true}}, SHIFT(708), + [1180] = {.entry = {.count = 1, .reusable = true}}, SHIFT(458), + [1182] = {.entry = {.count = 1, .reusable = true}}, SHIFT(459), + [1184] = {.entry = {.count = 1, .reusable = false}}, SHIFT(297), + [1186] = {.entry = {.count = 1, .reusable = true}}, SHIFT(297), + [1188] = {.entry = {.count = 1, .reusable = true}}, SHIFT(498), + [1190] = {.entry = {.count = 1, .reusable = false}}, SHIFT(454), + [1192] = {.entry = {.count = 1, .reusable = true}}, SHIFT(454), + [1194] = {.entry = {.count = 1, .reusable = true}}, SHIFT(61), + [1196] = {.entry = {.count = 1, .reusable = false}}, SHIFT(455), + [1198] = {.entry = {.count = 1, .reusable = true}}, SHIFT(455), + [1200] = {.entry = {.count = 1, .reusable = false}}, SHIFT(456), + [1202] = {.entry = {.count = 1, .reusable = true}}, SHIFT(456), + [1204] = {.entry = {.count = 1, .reusable = true}}, SHIFT(62), + [1206] = {.entry = {.count = 1, .reusable = false}}, SHIFT(490), + [1208] = {.entry = {.count = 1, .reusable = true}}, SHIFT(490), + [1210] = {.entry = {.count = 1, .reusable = true}}, SHIFT(496), + [1212] = {.entry = {.count = 1, .reusable = false}}, SHIFT(491), + [1214] = {.entry = {.count = 1, .reusable = true}}, SHIFT(491), + [1216] = {.entry = {.count = 1, .reusable = false}}, SHIFT(450), + [1218] = {.entry = {.count = 1, .reusable = true}}, SHIFT(450), + [1220] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1095), + [1222] = {.entry = {.count = 1, .reusable = false}}, SHIFT(464), + [1224] = {.entry = {.count = 1, .reusable = true}}, SHIFT(464), + [1226] = {.entry = {.count = 1, .reusable = true}}, SHIFT(626), + [1228] = {.entry = {.count = 1, .reusable = false}}, SHIFT(465), + [1230] = {.entry = {.count = 1, .reusable = true}}, SHIFT(465), + [1232] = {.entry = {.count = 1, .reusable = false}}, SHIFT(466), + [1234] = {.entry = {.count = 1, .reusable = true}}, SHIFT(466), + [1236] = {.entry = {.count = 1, .reusable = true}}, SHIFT(657), + [1238] = {.entry = {.count = 1, .reusable = false}}, SHIFT(492), + [1240] = {.entry = {.count = 1, .reusable = true}}, SHIFT(492), + [1242] = {.entry = {.count = 1, .reusable = true}}, SHIFT(515), + [1244] = {.entry = {.count = 1, .reusable = false}}, SHIFT(470), + [1246] = {.entry = {.count = 1, .reusable = true}}, SHIFT(470), + [1248] = {.entry = {.count = 1, .reusable = false}}, SHIFT(471), + [1250] = {.entry = {.count = 1, .reusable = true}}, SHIFT(471), + [1252] = {.entry = {.count = 1, .reusable = true}}, SHIFT(516), + [1254] = {.entry = {.count = 1, .reusable = false}}, SHIFT(475), + [1256] = {.entry = {.count = 1, .reusable = true}}, SHIFT(475), + [1258] = {.entry = {.count = 1, .reusable = false}}, SHIFT(476), + [1260] = {.entry = {.count = 1, .reusable = true}}, SHIFT(476), + [1262] = {.entry = {.count = 1, .reusable = true}}, SHIFT(716), + [1264] = {.entry = {.count = 1, .reusable = false}}, SHIFT(480), + [1266] = {.entry = {.count = 1, .reusable = true}}, SHIFT(480), + [1268] = {.entry = {.count = 1, .reusable = true}}, SHIFT(525), + [1270] = {.entry = {.count = 1, .reusable = false}}, SHIFT(481), + [1272] = {.entry = {.count = 1, .reusable = true}}, SHIFT(481), + [1274] = {.entry = {.count = 1, .reusable = false}}, SHIFT(482), + [1276] = {.entry = {.count = 1, .reusable = true}}, SHIFT(482), + [1278] = {.entry = {.count = 1, .reusable = true}}, SHIFT(526), + [1280] = {.entry = {.count = 1, .reusable = false}}, SHIFT(486), + [1282] = {.entry = {.count = 1, .reusable = true}}, SHIFT(486), + [1284] = {.entry = {.count = 1, .reusable = true}}, SHIFT(849), + [1286] = {.entry = {.count = 1, .reusable = false}}, SHIFT(487), + [1288] = {.entry = {.count = 1, .reusable = true}}, SHIFT(487), + [1290] = {.entry = {.count = 1, .reusable = false}}, SHIFT(488), + [1292] = {.entry = {.count = 1, .reusable = true}}, SHIFT(488), + [1294] = {.entry = {.count = 1, .reusable = true}}, SHIFT(885), + [1296] = {.entry = {.count = 1, .reusable = false}}, SHIFT(460), + [1298] = {.entry = {.count = 1, .reusable = true}}, SHIFT(460), + [1300] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1081), + [1302] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 5, 0, 0), + [1304] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 5, 0, 0), + [1306] = {.entry = {.count = 1, .reusable = true}}, SHIFT(588), + [1308] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 4, 0, 0), + [1310] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 4, 0, 0), + [1312] = {.entry = {.count = 1, .reusable = true}}, SHIFT(577), + [1314] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 6, 0, 0), + [1316] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 6, 0, 0), + [1318] = {.entry = {.count = 1, .reusable = true}}, SHIFT(595), + [1320] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_return_statement, 2, 0, 0), + [1322] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_return_statement, 2, 0, 0), + [1324] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_break_statement, 2, 0, 0), + [1326] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_break_statement, 2, 0, 0), + [1328] = {.entry = {.count = 1, .reusable = true}}, SHIFT(338), + [1330] = {.entry = {.count = 1, .reusable = true}}, SHIFT(339), + [1332] = {.entry = {.count = 1, .reusable = false}}, SHIFT(340), + [1334] = {.entry = {.count = 1, .reusable = true}}, SHIFT(448), + [1336] = {.entry = {.count = 1, .reusable = false}}, SHIFT(341), + [1338] = {.entry = {.count = 1, .reusable = true}}, SHIFT(337), + [1340] = {.entry = {.count = 1, .reusable = false}}, SHIFT(337), + [1342] = {.entry = {.count = 1, .reusable = true}}, SHIFT(342), + [1344] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_continue_statement, 2, 0, 0), + [1346] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_continue_statement, 2, 0, 0), + [1348] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_expression_statement, 2, 0, 0), + [1350] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_expression_statement, 2, 0, 0), + [1352] = {.entry = {.count = 1, .reusable = true}}, SHIFT(643), + [1354] = {.entry = {.count = 1, .reusable = true}}, SHIFT(336), + [1356] = {.entry = {.count = 1, .reusable = false}}, SHIFT(46), + [1358] = {.entry = {.count = 1, .reusable = true}}, SHIFT(344), + [1360] = {.entry = {.count = 1, .reusable = true}}, SHIFT(335), + [1362] = {.entry = {.count = 1, .reusable = false}}, SHIFT(335), + [1364] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(132), + [1367] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1292), + [1369] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1301), + [1371] = {.entry = {.count = 1, .reusable = true}}, SHIFT(253), + [1373] = {.entry = {.count = 1, .reusable = true}}, SHIFT(254), + [1375] = {.entry = {.count = 1, .reusable = false}}, SHIFT(561), + [1377] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(335), + [1380] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(335), + [1383] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(336), + [1386] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(337), + [1389] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(337), + [1392] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(338), + [1395] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(339), + [1398] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(340), + [1401] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(448), + [1404] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(341), + [1407] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(342), + [1410] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(343), + [1413] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(46), + [1416] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(344), + [1419] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(273), + [1422] = {.entry = {.count = 1, .reusable = true}}, SHIFT(132), + [1424] = {.entry = {.count = 1, .reusable = true}}, SHIFT(343), + [1426] = {.entry = {.count = 1, .reusable = false}}, SHIFT(255), + [1428] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_attributed_item, 2, 0, 0), + [1430] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_attributed_item, 2, 0, 0), + [1432] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 3, 0, 0), + [1434] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 3, 0, 0), + [1436] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_while_statement, 3, 0, 0), + [1438] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_while_statement, 3, 0, 0), + [1440] = {.entry = {.count = 1, .reusable = true}}, SHIFT(621), + [1442] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_return_statement, 3, 0, 0), + [1444] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_return_statement, 3, 0, 0), + [1446] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_go_statement, 3, 0, 0), + [1448] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_go_statement, 3, 0, 0), + [1450] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_block, 3, 0, 0), + [1452] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_block, 3, 0, 0), + [1454] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_definition, 4, 0, 14), + [1456] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_definition, 4, 0, 14), + [1458] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 4, 0, 0), + [1460] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 4, 0, 0), + [1462] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_definition, 4, 0, 0), + [1464] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_trait_definition, 4, 0, 0), + [1466] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_assignment_statement, 4, 0, 0), + [1468] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_assignment_statement, 4, 0, 0), + [1470] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_define_statement, 4, 0, 0), + [1472] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_define_statement, 4, 0, 0), + [1474] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_compound_assignment_statement, 4, 0, 0), + [1476] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_compound_assignment_statement, 4, 0, 0), + [1478] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_if_statement, 5, 0, 0), + [1480] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_if_statement, 5, 0, 0), + [1482] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 5, 0, 0), + [1484] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 5, 0, 0), + [1486] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_definition, 5, 0, 16), + [1488] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_definition, 5, 0, 16), + [1490] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_let_statement, 5, 0, 0), + [1492] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_let_statement, 5, 0, 0), + [1494] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_while_statement, 5, 0, 0), + [1496] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_while_statement, 5, 0, 0), + [1498] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_statement, 5, 0, 0), + [1500] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_for_statement, 5, 0, 0), + [1502] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 5, 0, 17), + [1504] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 5, 0, 17), + [1506] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 5, 0, 0), + [1508] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 5, 0, 0), + [1510] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_alias_definition, 5, 0, 0), + [1512] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_type_alias_definition, 5, 0, 0), + [1514] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_definition, 5, 0, 0), + [1516] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_trait_definition, 5, 0, 0), + [1518] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_try_statement, 5, 0, 18), + [1520] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_try_statement, 5, 0, 18), + [1522] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 6, 0, 17), + [1524] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 6, 0, 17), + [1526] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 6, 0, 0), + [1528] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 6, 0, 0), + [1530] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_impl_definition, 6, 0, 22), + [1532] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_impl_definition, 6, 0, 22), + [1534] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_if_statement, 7, 0, 0), + [1536] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_if_statement, 7, 0, 0), + [1538] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 7, 0, 0), + [1540] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 7, 0, 0), + [1542] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export, 7, 0, 0), + [1544] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_macro_export, 7, 0, 0), + [1546] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_let_statement, 7, 0, 0), + [1548] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_let_statement, 7, 0, 0), + [1550] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 7, 0, 17), + [1552] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 7, 0, 17), + [1554] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_definition, 7, 0, 0), + [1556] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_struct_definition, 7, 0, 0), + [1558] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_impl_definition, 7, 0, 22), + [1560] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_impl_definition, 7, 0, 22), + [1562] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 8, 0, 0), + [1564] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 8, 0, 0), + [1566] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_definition, 8, 0, 17), + [1568] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_function_definition, 8, 0, 17), + [1570] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_statement, 9, 0, 0), + [1572] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_import_statement, 9, 0, 0), + [1574] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1145), + [1577] = {.entry = {.count = 1, .reusable = true}}, SHIFT(660), + [1579] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_if_statement, 3, 0, 0), + [1581] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_if_statement, 3, 0, 0), + [1583] = {.entry = {.count = 1, .reusable = false}}, SHIFT(29), + [1585] = {.entry = {.count = 1, .reusable = false}}, SHIFT(30), + [1587] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 3, 0, 0), SHIFT(37), + [1590] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 5, 0, 0), SHIFT(40), + [1593] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 5, 0, 0), SHIFT(34), + [1596] = {.entry = {.count = 1, .reusable = false}}, SHIFT(23), + [1598] = {.entry = {.count = 1, .reusable = false}}, SHIFT(36), + [1600] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_if_statement, 3, 0, 0), SHIFT(31), + [1603] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__statement, 1, 0, 0), REDUCE(aux_sym_program_repeat1, 1, 0, 0), + [1606] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__statement, 1, 0, 0), REDUCE(aux_sym_program_repeat1, 1, 0, 0), + [1609] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_type_identifier, 1, 0, 0), + [1611] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1191), + [1614] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(201), + [1617] = {.entry = {.count = 1, .reusable = false}}, SHIFT(237), + [1619] = {.entry = {.count = 1, .reusable = true}}, SHIFT(238), + [1621] = {.entry = {.count = 1, .reusable = true}}, SHIFT(240), + [1623] = {.entry = {.count = 1, .reusable = false}}, SHIFT(224), + [1625] = {.entry = {.count = 1, .reusable = true}}, SHIFT(225), + [1627] = {.entry = {.count = 1, .reusable = true}}, SHIFT(226), + [1629] = {.entry = {.count = 1, .reusable = true}}, SHIFT(313), + [1631] = {.entry = {.count = 1, .reusable = false}}, SHIFT(313), + [1633] = {.entry = {.count = 1, .reusable = true}}, SHIFT(314), + [1635] = {.entry = {.count = 1, .reusable = true}}, SHIFT(325), + [1637] = {.entry = {.count = 1, .reusable = false}}, SHIFT(325), + [1639] = {.entry = {.count = 1, .reusable = true}}, SHIFT(327), + [1641] = {.entry = {.count = 1, .reusable = true}}, SHIFT(328), + [1643] = {.entry = {.count = 1, .reusable = false}}, SHIFT(329), + [1645] = {.entry = {.count = 1, .reusable = true}}, SHIFT(330), + [1647] = {.entry = {.count = 1, .reusable = false}}, SHIFT(331), + [1649] = {.entry = {.count = 1, .reusable = true}}, SHIFT(333), + [1651] = {.entry = {.count = 1, .reusable = false}}, SHIFT(44), + [1653] = {.entry = {.count = 1, .reusable = true}}, SHIFT(345), + [1655] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(313), + [1658] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(313), + [1661] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(314), + [1664] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(325), + [1667] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(325), + [1670] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(327), + [1673] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(328), + [1676] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(329), + [1679] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(330), + [1682] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(331), + [1685] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(333), + [1688] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(334), + [1691] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(44), + [1694] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(345), + [1697] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(285), + [1700] = {.entry = {.count = 1, .reusable = true}}, SHIFT(140), + [1702] = {.entry = {.count = 1, .reusable = true}}, SHIFT(334), + [1704] = {.entry = {.count = 1, .reusable = false}}, SHIFT(181), + [1706] = {.entry = {.count = 1, .reusable = true}}, SHIFT(426), + [1708] = {.entry = {.count = 1, .reusable = false}}, SHIFT(426), + [1710] = {.entry = {.count = 1, .reusable = true}}, SHIFT(427), + [1712] = {.entry = {.count = 1, .reusable = true}}, SHIFT(428), + [1714] = {.entry = {.count = 1, .reusable = false}}, SHIFT(429), + [1716] = {.entry = {.count = 1, .reusable = true}}, SHIFT(430), + [1718] = {.entry = {.count = 1, .reusable = false}}, SHIFT(431), + [1720] = {.entry = {.count = 1, .reusable = true}}, SHIFT(432), + [1722] = {.entry = {.count = 1, .reusable = true}}, SHIFT(315), + [1724] = {.entry = {.count = 1, .reusable = false}}, SHIFT(315), + [1726] = {.entry = {.count = 1, .reusable = true}}, SHIFT(316), + [1728] = {.entry = {.count = 1, .reusable = true}}, SHIFT(317), + [1730] = {.entry = {.count = 1, .reusable = false}}, SHIFT(317), + [1732] = {.entry = {.count = 1, .reusable = true}}, SHIFT(318), + [1734] = {.entry = {.count = 1, .reusable = true}}, SHIFT(319), + [1736] = {.entry = {.count = 1, .reusable = false}}, SHIFT(320), + [1738] = {.entry = {.count = 1, .reusable = true}}, SHIFT(321), + [1740] = {.entry = {.count = 1, .reusable = false}}, SHIFT(322), + [1742] = {.entry = {.count = 1, .reusable = true}}, SHIFT(323), + [1744] = {.entry = {.count = 1, .reusable = false}}, SHIFT(45), + [1746] = {.entry = {.count = 1, .reusable = true}}, SHIFT(326), + [1748] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(126), + [1751] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1295), + [1753] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1296), + [1755] = {.entry = {.count = 1, .reusable = true}}, SHIFT(227), + [1757] = {.entry = {.count = 1, .reusable = true}}, SHIFT(228), + [1759] = {.entry = {.count = 1, .reusable = false}}, SHIFT(711), + [1761] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(315), + [1764] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(315), + [1767] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(316), + [1770] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(317), + [1773] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(317), + [1776] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(318), + [1779] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(319), + [1782] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(320), + [1785] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(321), + [1788] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(322), + [1791] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(323), + [1794] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(324), + [1797] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(45), + [1800] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(326), + [1803] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(271), + [1806] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(422), + [1809] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(422), + [1812] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(423), + [1815] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(426), + [1818] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(426), + [1821] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(427), [1824] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(428), - [1827] = {.entry = {.count = 1, .reusable = false}}, SHIFT(269), - [1829] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1231), - [1831] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(273), - [1834] = {.entry = {.count = 1, .reusable = true}}, SHIFT(397), - [1836] = {.entry = {.count = 1, .reusable = false}}, SHIFT(397), - [1838] = {.entry = {.count = 1, .reusable = true}}, SHIFT(398), - [1840] = {.entry = {.count = 1, .reusable = true}}, SHIFT(399), - [1842] = {.entry = {.count = 1, .reusable = false}}, SHIFT(48), - [1844] = {.entry = {.count = 1, .reusable = true}}, SHIFT(401), - [1846] = {.entry = {.count = 1, .reusable = true}}, SHIFT(302), - [1848] = {.entry = {.count = 1, .reusable = true}}, SHIFT(303), - [1850] = {.entry = {.count = 1, .reusable = true}}, SHIFT(301), - [1852] = {.entry = {.count = 1, .reusable = false}}, SHIFT(301), - [1854] = {.entry = {.count = 1, .reusable = true}}, SHIFT(300), - [1856] = {.entry = {.count = 1, .reusable = false}}, SHIFT(46), - [1858] = {.entry = {.count = 1, .reusable = true}}, SHIFT(440), - [1860] = {.entry = {.count = 1, .reusable = true}}, SHIFT(299), - [1862] = {.entry = {.count = 1, .reusable = false}}, SHIFT(299), - [1864] = {.entry = {.count = 1, .reusable = true}}, SHIFT(347), - [1866] = {.entry = {.count = 1, .reusable = true}}, SHIFT(403), - [1868] = {.entry = {.count = 1, .reusable = false}}, SHIFT(403), - [1870] = {.entry = {.count = 1, .reusable = true}}, SHIFT(404), - [1872] = {.entry = {.count = 1, .reusable = true}}, SHIFT(405), - [1874] = {.entry = {.count = 1, .reusable = false}}, SHIFT(49), - [1876] = {.entry = {.count = 1, .reusable = true}}, SHIFT(406), - [1878] = {.entry = {.count = 1, .reusable = true}}, SHIFT(346), - [1880] = {.entry = {.count = 1, .reusable = false}}, SHIFT(346), - [1882] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(395), - [1885] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(395), - [1888] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(396), - [1891] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(397), - [1894] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(397), - [1897] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(398), - [1900] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(399), - [1903] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(400), - [1906] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(48), - [1909] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(401), - [1912] = {.entry = {.count = 1, .reusable = false}}, SHIFT(263), - [1914] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(299), - [1917] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(299), - [1920] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(300), - [1923] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(301), - [1926] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(301), - [1929] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(302), - [1932] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(303), - [1935] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(304), - [1938] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(46), - [1941] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(440), - [1944] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(268), - [1947] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(272), - [1950] = {.entry = {.count = 1, .reusable = true}}, SHIFT(130), - [1952] = {.entry = {.count = 1, .reusable = true}}, SHIFT(304), - [1954] = {.entry = {.count = 1, .reusable = false}}, SHIFT(293), - [1956] = {.entry = {.count = 1, .reusable = true}}, SHIFT(396), - [1958] = {.entry = {.count = 1, .reusable = true}}, SHIFT(395), - [1960] = {.entry = {.count = 1, .reusable = false}}, SHIFT(395), - [1962] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_guarded_pattern, 3, 0, 0), - [1964] = {.entry = {.count = 1, .reusable = true}}, SHIFT(348), - [1966] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_guarded_pattern, 3, 0, 0), SHIFT(49), - [1969] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_guarded_pattern, 3, 0, 0), - [1971] = {.entry = {.count = 1, .reusable = true}}, SHIFT(365), - [1973] = {.entry = {.count = 1, .reusable = false}}, SHIFT(365), - [1975] = {.entry = {.count = 1, .reusable = true}}, SHIFT(369), - [1977] = {.entry = {.count = 1, .reusable = true}}, SHIFT(370), - [1979] = {.entry = {.count = 1, .reusable = false}}, SHIFT(370), - [1981] = {.entry = {.count = 1, .reusable = true}}, SHIFT(371), - [1983] = {.entry = {.count = 1, .reusable = true}}, SHIFT(372), - [1985] = {.entry = {.count = 1, .reusable = true}}, SHIFT(373), - [1987] = {.entry = {.count = 1, .reusable = false}}, SHIFT(50), - [1989] = {.entry = {.count = 1, .reusable = true}}, SHIFT(386), - [1991] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1097), - [1994] = {.entry = {.count = 1, .reusable = true}}, SHIFT(156), - [1996] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(126), - [1999] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1162), - [2001] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1163), - [2003] = {.entry = {.count = 1, .reusable = true}}, SHIFT(261), - [2005] = {.entry = {.count = 1, .reusable = true}}, SHIFT(262), - [2007] = {.entry = {.count = 1, .reusable = false}}, SHIFT(760), - [2009] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(408), - [2012] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(408), - [2015] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(409), - [2018] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(410), - [2021] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(410), - [2024] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(411), - [2027] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(412), - [2030] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(413), - [2033] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(51), - [2036] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(414), - [2039] = {.entry = {.count = 1, .reusable = false}}, SHIFT(271), - [2041] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(274), - [2044] = {.entry = {.count = 1, .reusable = true}}, SHIFT(411), - [2046] = {.entry = {.count = 1, .reusable = true}}, SHIFT(412), - [2048] = {.entry = {.count = 1, .reusable = true}}, SHIFT(410), - [2050] = {.entry = {.count = 1, .reusable = false}}, SHIFT(410), - [2052] = {.entry = {.count = 1, .reusable = true}}, SHIFT(409), - [2054] = {.entry = {.count = 1, .reusable = false}}, SHIFT(51), - [2056] = {.entry = {.count = 1, .reusable = true}}, SHIFT(414), - [2058] = {.entry = {.count = 1, .reusable = true}}, SHIFT(408), - [2060] = {.entry = {.count = 1, .reusable = false}}, SHIFT(408), - [2062] = {.entry = {.count = 1, .reusable = true}}, SHIFT(323), - [2064] = {.entry = {.count = 1, .reusable = true}}, SHIFT(320), - [2066] = {.entry = {.count = 1, .reusable = true}}, SHIFT(321), - [2068] = {.entry = {.count = 1, .reusable = false}}, SHIFT(321), - [2070] = {.entry = {.count = 1, .reusable = true}}, SHIFT(322), - [2072] = {.entry = {.count = 1, .reusable = false}}, SHIFT(52), - [2074] = {.entry = {.count = 1, .reusable = true}}, SHIFT(326), - [2076] = {.entry = {.count = 1, .reusable = true}}, SHIFT(319), - [2078] = {.entry = {.count = 1, .reusable = false}}, SHIFT(319), - [2080] = {.entry = {.count = 1, .reusable = true}}, SHIFT(126), - [2082] = {.entry = {.count = 1, .reusable = true}}, SHIFT(324), - [2084] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(432), - [2087] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(432), - [2090] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(433), - [2093] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(434), - [2096] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(434), - [2099] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(435), - [2102] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(436), - [2105] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(437), - [2108] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(53), - [2111] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(294), - [2114] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(289), - [2117] = {.entry = {.count = 1, .reusable = true}}, SHIFT(434), - [2119] = {.entry = {.count = 1, .reusable = false}}, SHIFT(434), - [2121] = {.entry = {.count = 1, .reusable = true}}, SHIFT(435), - [2123] = {.entry = {.count = 1, .reusable = true}}, SHIFT(436), - [2125] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(155), - [2128] = {.entry = {.count = 1, .reusable = false}}, SHIFT(288), - [2130] = {.entry = {.count = 1, .reusable = false}}, SHIFT(286), - [2132] = {.entry = {.count = 1, .reusable = true}}, SHIFT(433), - [2134] = {.entry = {.count = 1, .reusable = false}}, SHIFT(53), - [2136] = {.entry = {.count = 1, .reusable = true}}, SHIFT(294), - [2138] = {.entry = {.count = 1, .reusable = true}}, SHIFT(432), - [2140] = {.entry = {.count = 1, .reusable = false}}, SHIFT(432), - [2142] = {.entry = {.count = 1, .reusable = true}}, SHIFT(334), - [2144] = {.entry = {.count = 1, .reusable = false}}, SHIFT(334), - [2146] = {.entry = {.count = 1, .reusable = true}}, SHIFT(335), - [2148] = {.entry = {.count = 1, .reusable = true}}, SHIFT(336), - [2150] = {.entry = {.count = 1, .reusable = true}}, SHIFT(665), - [2152] = {.entry = {.count = 1, .reusable = true}}, SHIFT(332), - [2154] = {.entry = {.count = 1, .reusable = false}}, SHIFT(332), - [2156] = {.entry = {.count = 1, .reusable = true}}, SHIFT(333), - [2158] = {.entry = {.count = 1, .reusable = true}}, SHIFT(337), - [2160] = {.entry = {.count = 1, .reusable = false}}, SHIFT(54), - [2162] = {.entry = {.count = 1, .reusable = true}}, SHIFT(338), - [2164] = {.entry = {.count = 1, .reusable = true}}, SHIFT(666), - [2166] = {.entry = {.count = 1, .reusable = true}}, SHIFT(679), - [2168] = {.entry = {.count = 1, .reusable = true}}, SHIFT(671), - [2170] = {.entry = {.count = 1, .reusable = false}}, SHIFT(883), - [2172] = {.entry = {.count = 1, .reusable = true}}, SHIFT(832), - [2174] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1495), - [2176] = {.entry = {.count = 1, .reusable = false}}, SHIFT(896), - [2178] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1451), - [2180] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1452), - [2182] = {.entry = {.count = 1, .reusable = false}}, SHIFT(976), - [2184] = {.entry = {.count = 1, .reusable = true}}, SHIFT(976), - [2186] = {.entry = {.count = 1, .reusable = false}}, SHIFT(949), - [2188] = {.entry = {.count = 1, .reusable = false}}, SHIFT(959), - [2190] = {.entry = {.count = 1, .reusable = true}}, SHIFT(990), - [2192] = {.entry = {.count = 1, .reusable = true}}, SHIFT(991), - [2194] = {.entry = {.count = 1, .reusable = true}}, SHIFT(973), - [2196] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_arm, 3, 0, 21), - [2198] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_arm, 3, 0, 21), - [2200] = {.entry = {.count = 1, .reusable = true}}, SHIFT(871), - [2202] = {.entry = {.count = 1, .reusable = true}}, SHIFT(901), - [2204] = {.entry = {.count = 1, .reusable = true}}, SHIFT(899), - [2206] = {.entry = {.count = 1, .reusable = true}}, SHIFT(949), - [2208] = {.entry = {.count = 1, .reusable = true}}, SHIFT(959), - [2210] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1012), - [2212] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1013), - [2214] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_arm, 4, 0, 21), - [2216] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_arm, 4, 0, 21), - [2218] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1348), - [2220] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1327), - [2222] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1349), - [2224] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1328), - [2226] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1358), - [2228] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1314), - [2230] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1347), - [2232] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1306), - [2234] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1321), - [2236] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1331), - [2238] = {.entry = {.count = 1, .reusable = true}}, SHIFT(931), - [2240] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1355), - [2242] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1265), - [2244] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1297), - [2246] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1218), - [2248] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1301), - [2250] = {.entry = {.count = 1, .reusable = true}}, SHIFT(910), - [2252] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1339), - [2254] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__type, 1, 0, 0), REDUCE(sym_named_type, 1, 0, 0), - [2257] = {.entry = {.count = 1, .reusable = true}}, SHIFT(855), - [2259] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_union_type, 3, 0, 0), - [2261] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_union_type, 3, 0, 0), SHIFT(893), - [2264] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_union_type, 3, 0, 0), SHIFT(858), - [2267] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1232), - [2269] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), - [2271] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), SHIFT_REPEAT(848), - [2274] = {.entry = {.count = 1, .reusable = true}}, SHIFT(909), - [2276] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_union_type, 4, 0, 0), - [2278] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_union_type, 4, 0, 0), SHIFT(848), - [2281] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1277), - [2283] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1554), - [2285] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1149), - [2287] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1553), - [2289] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), REDUCE(sym_union_type, 3, 0, 0), - [2292] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), REDUCE(sym_union_type, 3, 0, 0), SHIFT(893), - [2296] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), REDUCE(sym_union_type, 3, 0, 0), SHIFT(858), - [2300] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_optional_type, 2, 0, 0), - [2302] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_type, 4, 0, 0), - [2304] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 4, 0, 0), SHIFT(893), - [2307] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 4, 0, 0), SHIFT(860), - [2310] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_primitive_type, 1, 0, 0), - [2312] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_type, 6, 0, 0), - [2314] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 6, 0, 0), SHIFT(893), - [2317] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 6, 0, 0), SHIFT(860), - [2320] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_type, 4, 0, 0), - [2322] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_type, 4, 0, 0), - [2324] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_type, 6, 0, 0), - [2326] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_literal_pattern, 1, 0, 0), - [2328] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_literal_pattern, 1, 0, 0), - [2330] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_type, 5, 0, 0), - [2332] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 5, 0, 0), SHIFT(893), - [2335] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 5, 0, 0), SHIFT(860), - [2338] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), SHIFT(893), - [2341] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), SHIFT(860), - [2344] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_type, 5, 0, 0), - [2346] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 2, 0, 0), - [2348] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 5, 0, 0), - [2350] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 3, 0, 0), - [2352] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 3, 0, 0), - [2354] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attributed_item_repeat1, 2, 0, 0), SHIFT_REPEAT(1497), - [2357] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_attributed_item_repeat1, 2, 0, 0), - [2359] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 4, 0, 0), - [2361] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 2, 0, 0), - [2363] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 6, 0, 0), - [2365] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 6, 0, 0), - [2367] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_range_pattern, 3, 0, 9), - [2369] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_wildcard_pattern, 1, 0, 0), - [2371] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_pattern, 1, 0, 0), - [2373] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 7, 0, 0), - [2375] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 7, 0, 0), - [2377] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_identifier_pattern, 1, 0, 0), - [2379] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 5, 0, 0), - [2381] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 4, 0, 0), - [2383] = {.entry = {.count = 1, .reusable = true}}, SHIFT(939), - [2385] = {.entry = {.count = 1, .reusable = true}}, SHIFT(940), - [2387] = {.entry = {.count = 1, .reusable = true}}, SHIFT(894), - [2389] = {.entry = {.count = 1, .reusable = false}}, SHIFT(956), - [2391] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), - [2394] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(717), - [2398] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(342), - [2402] = {.entry = {.count = 1, .reusable = true}}, SHIFT(866), - [2404] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 2, 0, 0), - [2406] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1298), - [2408] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1204), - [2410] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), - [2412] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(718), - [2415] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_pattern, 1, 0, 0), SHIFT(869), - [2418] = {.entry = {.count = 1, .reusable = true}}, SHIFT(864), - [2420] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 3, 0, 0), - [2422] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1284), - [2424] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), - [2426] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(717), - [2429] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(342), - [2432] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), - [2434] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), SHIFT(718), - [2437] = {.entry = {.count = 1, .reusable = true}}, SHIFT(682), - [2439] = {.entry = {.count = 1, .reusable = true}}, SHIFT(915), - [2441] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1346), - [2443] = {.entry = {.count = 1, .reusable = true}}, SHIFT(715), - [2445] = {.entry = {.count = 1, .reusable = true}}, SHIFT(342), - [2447] = {.entry = {.count = 1, .reusable = false}}, SHIFT_EXTRA(), - [2449] = {.entry = {.count = 1, .reusable = false}}, SHIFT(759), - [2451] = {.entry = {.count = 1, .reusable = false}}, SHIFT(955), - [2453] = {.entry = {.count = 1, .reusable = false}}, SHIFT(220), - [2455] = {.entry = {.count = 1, .reusable = false}}, REDUCE(aux_sym_double_string_repeat1, 2, 0, 0), - [2457] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_double_string_repeat1, 2, 0, 0), SHIFT_REPEAT(950), - [2460] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_double_string_repeat1, 2, 0, 0), SHIFT_REPEAT(161), - [2463] = {.entry = {.count = 1, .reusable = false}}, SHIFT(737), - [2465] = {.entry = {.count = 1, .reusable = false}}, SHIFT(950), - [2467] = {.entry = {.count = 1, .reusable = false}}, SHIFT(161), - [2469] = {.entry = {.count = 1, .reusable = false}}, SHIFT(879), - [2471] = {.entry = {.count = 1, .reusable = false}}, SHIFT(970), - [2473] = {.entry = {.count = 1, .reusable = false}}, SHIFT(880), - [2475] = {.entry = {.count = 1, .reusable = false}}, SHIFT(999), - [2477] = {.entry = {.count = 1, .reusable = false}}, REDUCE(aux_sym_single_string_repeat1, 2, 0, 0), - [2479] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_single_string_repeat1, 2, 0, 0), SHIFT_REPEAT(954), - [2482] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_single_string_repeat1, 2, 0, 0), SHIFT_REPEAT(220), - [2485] = {.entry = {.count = 1, .reusable = false}}, SHIFT(761), - [2487] = {.entry = {.count = 1, .reusable = false}}, SHIFT(954), - [2489] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 1, 0, 0), - [2491] = {.entry = {.count = 1, .reusable = true}}, SHIFT(684), - [2493] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1241), - [2495] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1294), - [2497] = {.entry = {.count = 1, .reusable = false}}, SHIFT(582), - [2499] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 6, 0, 0), - [2501] = {.entry = {.count = 1, .reusable = false}}, SHIFT(868), - [2503] = {.entry = {.count = 1, .reusable = true}}, SHIFT(865), - [2505] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 3, 0, 0), - [2507] = {.entry = {.count = 1, .reusable = false}}, SHIFT(80), - [2509] = {.entry = {.count = 1, .reusable = false}}, SHIFT(985), - [2511] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1035), - [2513] = {.entry = {.count = 1, .reusable = false}}, SHIFT(968), - [2515] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1020), - [2517] = {.entry = {.count = 1, .reusable = false}}, SHIFT(969), - [2519] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1008), - [2521] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1010), - [2523] = {.entry = {.count = 1, .reusable = false}}, SHIFT(882), - [2525] = {.entry = {.count = 1, .reusable = false}}, SHIFT(579), - [2527] = {.entry = {.count = 1, .reusable = false}}, SHIFT(961), - [2529] = {.entry = {.count = 1, .reusable = false}}, SHIFT(583), - [2531] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(715), - [2534] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(342), - [2537] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 5, 0, 0), - [2539] = {.entry = {.count = 1, .reusable = false}}, SHIFT(580), - [2541] = {.entry = {.count = 1, .reusable = false}}, SHIFT(972), - [2543] = {.entry = {.count = 1, .reusable = false}}, SHIFT(444), - [2545] = {.entry = {.count = 1, .reusable = false}}, SHIFT(995), - [2547] = {.entry = {.count = 1, .reusable = false}}, SHIFT(448), - [2549] = {.entry = {.count = 1, .reusable = false}}, SHIFT(997), - [2551] = {.entry = {.count = 1, .reusable = false}}, SHIFT(659), - [2553] = {.entry = {.count = 1, .reusable = false}}, SHIFT(983), - [2555] = {.entry = {.count = 1, .reusable = false}}, SHIFT(615), - [2557] = {.entry = {.count = 1, .reusable = false}}, SHIFT(984), - [2559] = {.entry = {.count = 1, .reusable = false}}, SHIFT(660), - [2561] = {.entry = {.count = 1, .reusable = false}}, SHIFT(618), - [2563] = {.entry = {.count = 1, .reusable = false}}, SHIFT(61), - [2565] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_attribute, 3, 0, 0), - [2567] = {.entry = {.count = 1, .reusable = false}}, SHIFT(62), - [2569] = {.entry = {.count = 1, .reusable = false}}, SHIFT(758), - [2571] = {.entry = {.count = 1, .reusable = false}}, SHIFT(951), - [2573] = {.entry = {.count = 1, .reusable = false}}, SHIFT(974), - [2575] = {.entry = {.count = 1, .reusable = false}}, SHIFT(992), - [2577] = {.entry = {.count = 1, .reusable = false}}, SHIFT(988), - [2579] = {.entry = {.count = 1, .reusable = false}}, SHIFT(994), - [2581] = {.entry = {.count = 1, .reusable = false}}, SHIFT(958), - [2583] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_attribute, 4, 0, 0), - [2585] = {.entry = {.count = 1, .reusable = false}}, SHIFT(960), - [2587] = {.entry = {.count = 1, .reusable = false}}, SHIFT(442), - [2589] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 4, 0, 0), - [2591] = {.entry = {.count = 1, .reusable = false}}, SHIFT(443), - [2593] = {.entry = {.count = 1, .reusable = false}}, SHIFT(81), - [2595] = {.entry = {.count = 1, .reusable = false}}, SHIFT(987), - [2597] = {.entry = {.count = 1, .reusable = false}}, SHIFT(874), - [2599] = {.entry = {.count = 1, .reusable = false}}, SHIFT(870), - [2601] = {.entry = {.count = 1, .reusable = true}}, SHIFT(863), - [2603] = {.entry = {.count = 1, .reusable = true}}, SHIFT(203), - [2605] = {.entry = {.count = 1, .reusable = true}}, SHIFT(162), - [2607] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_param, 3, 0, 2), - [2609] = {.entry = {.count = 1, .reusable = true}}, SHIFT(893), - [2611] = {.entry = {.count = 1, .reusable = true}}, SHIFT(860), - [2613] = {.entry = {.count = 1, .reusable = true}}, SHIFT(159), - [2615] = {.entry = {.count = 1, .reusable = true}}, SHIFT(193), - [2617] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_list_pattern_repeat1, 2, 0, 0), - [2619] = {.entry = {.count = 1, .reusable = true}}, SHIFT(169), - [2621] = {.entry = {.count = 1, .reusable = true}}, SHIFT(240), - [2623] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1215), - [2625] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1312), - [2627] = {.entry = {.count = 1, .reusable = true}}, SHIFT(842), - [2629] = {.entry = {.count = 1, .reusable = true}}, SHIFT(900), - [2631] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_function_type_repeat1, 2, 0, 0), - [2633] = {.entry = {.count = 1, .reusable = true}}, SHIFT(185), - [2635] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1269), - [2637] = {.entry = {.count = 1, .reusable = true}}, SHIFT(173), - [2639] = {.entry = {.count = 1, .reusable = true}}, SHIFT(85), - [2641] = {.entry = {.count = 1, .reusable = true}}, SHIFT(191), - [2643] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1397), - [2645] = {.entry = {.count = 1, .reusable = true}}, SHIFT(568), - [2647] = {.entry = {.count = 1, .reusable = true}}, SHIFT(177), - [2649] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1144), - [2651] = {.entry = {.count = 1, .reusable = true}}, SHIFT(617), - [2653] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(702), - [2656] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(327), - [2659] = {.entry = {.count = 1, .reusable = true}}, SHIFT(165), - [2661] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(702), - [2665] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(327), - [2669] = {.entry = {.count = 1, .reusable = true}}, SHIFT(198), - [2671] = {.entry = {.count = 1, .reusable = true}}, SHIFT(796), - [2673] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), SHIFT(716), - [2676] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern_entry, 3, 0, 0), - [2678] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1427), - [2680] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(716), - [2683] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_select_expression_repeat1, 2, 0, 0), - [2685] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_select_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(191), - [2688] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_select_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1397), - [2691] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat1, 2, 0, 0), - [2693] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(932), - [2696] = {.entry = {.count = 1, .reusable = true}}, SHIFT(197), - [2698] = {.entry = {.count = 1, .reusable = true}}, SHIFT(209), - [2700] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_param, 1, 0, 2), - [2702] = {.entry = {.count = 1, .reusable = true}}, SHIFT(834), - [2704] = {.entry = {.count = 1, .reusable = true}}, SHIFT(158), - [2706] = {.entry = {.count = 1, .reusable = true}}, SHIFT(485), - [2708] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1345), - [2710] = {.entry = {.count = 1, .reusable = true}}, SHIFT(511), - [2712] = {.entry = {.count = 1, .reusable = true}}, SHIFT(540), - [2714] = {.entry = {.count = 1, .reusable = true}}, SHIFT(461), - [2716] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_params, 2, 0, 0), - [2718] = {.entry = {.count = 1, .reusable = true}}, SHIFT(503), - [2720] = {.entry = {.count = 1, .reusable = true}}, SHIFT(96), - [2722] = {.entry = {.count = 1, .reusable = true}}, SHIFT(464), - [2724] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_function_type_repeat1, 2, 0, 0), SHIFT_REPEAT(842), - [2727] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 6, 0, 0), - [2729] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_trait_definition_repeat1, 2, 0, 0), - [2731] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_trait_definition_repeat1, 2, 0, 0), SHIFT_REPEAT(1345), - [2734] = {.entry = {.count = 1, .reusable = true}}, SHIFT(528), - [2736] = {.entry = {.count = 1, .reusable = true}}, SHIFT(647), - [2738] = {.entry = {.count = 1, .reusable = true}}, SHIFT(513), - [2740] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1356), - [2742] = {.entry = {.count = 1, .reusable = true}}, SHIFT(380), - [2744] = {.entry = {.count = 1, .reusable = true}}, SHIFT(381), - [2746] = {.entry = {.count = 1, .reusable = true}}, SHIFT(382), - [2748] = {.entry = {.count = 1, .reusable = true}}, SHIFT(606), - [2750] = {.entry = {.count = 1, .reusable = true}}, SHIFT(932), - [2752] = {.entry = {.count = 1, .reusable = true}}, SHIFT(996), - [2754] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1323), - [2756] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_list_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(736), - [2759] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 4, 0, 0), - [2761] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1276), - [2763] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 3, 0, 0), - [2765] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1171), - [2767] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__parameter_list, 2, 0, 0), - [2769] = {.entry = {.count = 1, .reusable = true}}, SHIFT(374), - [2771] = {.entry = {.count = 1, .reusable = true}}, SHIFT(375), - [2773] = {.entry = {.count = 1, .reusable = true}}, SHIFT(376), - [2775] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1381), - [2777] = {.entry = {.count = 1, .reusable = true}}, SHIFT(621), - [2779] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(732), - [2782] = {.entry = {.count = 1, .reusable = true}}, SHIFT(916), - [2784] = {.entry = {.count = 1, .reusable = true}}, SHIFT(875), - [2786] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1343), - [2788] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(720), - [2791] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(327), - [2794] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_map_pattern_repeat1, 2, 0, 0), - [2796] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_map_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(902), - [2799] = {.entry = {.count = 1, .reusable = true}}, SHIFT(55), - [2801] = {.entry = {.count = 1, .reusable = true}}, SHIFT(878), - [2803] = {.entry = {.count = 1, .reusable = true}}, SHIFT(594), - [2805] = {.entry = {.count = 1, .reusable = true}}, SHIFT(20), - [2807] = {.entry = {.count = 1, .reusable = true}}, SHIFT(452), - [2809] = {.entry = {.count = 1, .reusable = true}}, SHIFT(597), - [2811] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1395), - [2813] = {.entry = {.count = 1, .reusable = true}}, SHIFT(14), - [2815] = {.entry = {.count = 1, .reusable = true}}, SHIFT(22), - [2817] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_string_interpolation, 3, 0, 0), - [2819] = {.entry = {.count = 1, .reusable = true}}, SHIFT(881), - [2821] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_param, 3, 0, 0), - [2823] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat1, 2, 0, 0), - [2825] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat1, 2, 0, 0), SHIFT_REPEAT(211), - [2828] = {.entry = {.count = 1, .reusable = true}}, SHIFT(497), - [2830] = {.entry = {.count = 1, .reusable = true}}, SHIFT(498), - [2832] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_params, 3, 0, 0), - [2834] = {.entry = {.count = 1, .reusable = true}}, SHIFT(524), - [2836] = {.entry = {.count = 1, .reusable = true}}, SHIFT(859), - [2838] = {.entry = {.count = 1, .reusable = true}}, SHIFT(720), - [2840] = {.entry = {.count = 1, .reusable = true}}, SHIFT(327), - [2842] = {.entry = {.count = 1, .reusable = true}}, SHIFT(196), - [2844] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_impl_definition_repeat1, 2, 0, 0), - [2846] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_impl_definition_repeat1, 2, 0, 0), SHIFT_REPEAT(1356), - [2849] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 5, 0, 0), - [2851] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parameter, 3, 0, 11), - [2853] = {.entry = {.count = 1, .reusable = true}}, SHIFT(387), - [2855] = {.entry = {.count = 1, .reusable = true}}, SHIFT(388), - [2857] = {.entry = {.count = 1, .reusable = true}}, SHIFT(389), - [2859] = {.entry = {.count = 1, .reusable = true}}, SHIFT(771), - [2861] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1474), - [2863] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(735), - [2866] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(356), - [2869] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 7, 0, 0), - [2871] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__parameter_list, 3, 0, 0), - [2873] = {.entry = {.count = 1, .reusable = true}}, SHIFT(862), - [2875] = {.entry = {.count = 1, .reusable = true}}, SHIFT(231), - [2877] = {.entry = {.count = 1, .reusable = true}}, SHIFT(339), - [2879] = {.entry = {.count = 1, .reusable = true}}, SHIFT(340), - [2881] = {.entry = {.count = 1, .reusable = true}}, SHIFT(341), - [2883] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1242), - [2885] = {.entry = {.count = 1, .reusable = true}}, SHIFT(877), - [2887] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1295), - [2889] = {.entry = {.count = 1, .reusable = true}}, SHIFT(683), - [2891] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(735), - [2895] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(356), - [2899] = {.entry = {.count = 1, .reusable = true}}, SHIFT(466), - [2901] = {.entry = {.count = 1, .reusable = true}}, SHIFT(467), - [2903] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 6, 0, 0), - [2905] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1186), - [2907] = {.entry = {.count = 1, .reusable = true}}, SHIFT(71), - [2909] = {.entry = {.count = 1, .reusable = true}}, SHIFT(964), - [2911] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1338), - [2913] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field, 3, 0, 0), - [2915] = {.entry = {.count = 1, .reusable = true}}, SHIFT(743), - [2917] = {.entry = {.count = 1, .reusable = true}}, SHIFT(453), - [2919] = {.entry = {.count = 1, .reusable = true}}, SHIFT(746), - [2921] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1542), - [2923] = {.entry = {.count = 1, .reusable = true}}, SHIFT(543), - [2925] = {.entry = {.count = 1, .reusable = true}}, SHIFT(353), - [2927] = {.entry = {.count = 1, .reusable = true}}, SHIFT(354), - [2929] = {.entry = {.count = 1, .reusable = true}}, SHIFT(355), - [2931] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), SHIFT(732), - [2934] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 4, 0, 0), - [2936] = {.entry = {.count = 1, .reusable = true}}, SHIFT(451), - [2938] = {.entry = {.count = 1, .reusable = true}}, SHIFT(362), - [2940] = {.entry = {.count = 1, .reusable = true}}, SHIFT(363), - [2942] = {.entry = {.count = 1, .reusable = true}}, SHIFT(364), - [2944] = {.entry = {.count = 1, .reusable = true}}, SHIFT(491), - [2946] = {.entry = {.count = 1, .reusable = true}}, SHIFT(655), - [2948] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1487), - [2950] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1488), - [2952] = {.entry = {.count = 1, .reusable = true}}, SHIFT(685), - [2954] = {.entry = {.count = 1, .reusable = true}}, SHIFT(60), - [2956] = {.entry = {.count = 1, .reusable = true}}, SHIFT(73), - [2958] = {.entry = {.count = 1, .reusable = true}}, SHIFT(136), - [2960] = {.entry = {.count = 1, .reusable = true}}, SHIFT(120), - [2962] = {.entry = {.count = 1, .reusable = true}}, SHIFT(66), - [2964] = {.entry = {.count = 1, .reusable = true}}, SHIFT(121), - [2966] = {.entry = {.count = 1, .reusable = true}}, SHIFT(243), - [2968] = {.entry = {.count = 1, .reusable = true}}, SHIFT(569), - [2970] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1073), - [2972] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1501), - [2974] = {.entry = {.count = 1, .reusable = true}}, SHIFT(898), - [2976] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1402), - [2978] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1119), - [2980] = {.entry = {.count = 1, .reusable = true}}, SHIFT(850), - [2982] = {.entry = {.count = 1, .reusable = true}}, SHIFT(857), - [2984] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1076), - [2986] = {.entry = {.count = 1, .reusable = true}}, SHIFT(851), - [2988] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field, 1, 0, 0), - [2990] = {.entry = {.count = 1, .reusable = true}}, SHIFT(849), - [2992] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_param, 1, 0, 0), - [2994] = {.entry = {.count = 1, .reusable = true}}, SHIFT(836), - [2996] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_params, 1, 0, 0), - [2998] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1046), - [3000] = {.entry = {.count = 1, .reusable = true}}, SHIFT(542), - [3002] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1043), - [3004] = {.entry = {.count = 1, .reusable = true}}, SHIFT(544), - [3006] = {.entry = {.count = 1, .reusable = true}}, SHIFT(145), - [3008] = {.entry = {.count = 1, .reusable = true}}, SHIFT(766), - [3010] = {.entry = {.count = 1, .reusable = true}}, SHIFT(767), - [3012] = {.entry = {.count = 1, .reusable = true}}, SHIFT(127), - [3014] = {.entry = {.count = 1, .reusable = true}}, SHIFT(138), - [3016] = {.entry = {.count = 1, .reusable = true}}, SHIFT(643), - [3018] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1051), - [3020] = {.entry = {.count = 1, .reusable = true}}, SHIFT(644), - [3022] = {.entry = {.count = 1, .reusable = true}}, SHIFT(133), - [3024] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_item, 1, 0, 0), - [3026] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1351), - [3028] = {.entry = {.count = 1, .reusable = true}}, SHIFT(753), - [3030] = {.entry = {.count = 1, .reusable = true}}, SHIFT(773), - [3032] = {.entry = {.count = 1, .reusable = true}}, SHIFT(774), - [3034] = {.entry = {.count = 1, .reusable = true}}, SHIFT(148), - [3036] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat2, 2, 0, 0), - [3038] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat2, 2, 0, 0), SHIFT_REPEAT(1184), - [3041] = {.entry = {.count = 1, .reusable = true}}, SHIFT(128), - [3043] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1548), - [3045] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1099), - [3047] = {.entry = {.count = 1, .reusable = true}}, SHIFT(903), - [3049] = {.entry = {.count = 1, .reusable = true}}, SHIFT(798), - [3051] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1116), - [3053] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_import_statement_repeat1, 2, 0, 0), - [3055] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_import_statement_repeat1, 2, 0, 0), SHIFT_REPEAT(1177), - [3058] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1118), - [3060] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1102), - [3062] = {.entry = {.count = 1, .reusable = true}}, SHIFT(627), - [3064] = {.entry = {.count = 1, .reusable = true}}, SHIFT(623), - [3066] = {.entry = {.count = 1, .reusable = true}}, SHIFT(624), - [3068] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1065), - [3070] = {.entry = {.count = 1, .reusable = true}}, SHIFT(147), - [3072] = {.entry = {.count = 1, .reusable = true}}, SHIFT(135), - [3074] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1088), - [3076] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1101), - [3078] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 7, 0, 0), - [3080] = {.entry = {.count = 1, .reusable = true}}, SHIFT(450), - [3082] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1125), - [3084] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1055), - [3086] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_named_params_block_repeat1, 2, 0, 0), - [3088] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_named_params_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1196), - [3091] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1287), - [3093] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parameter, 1, 0, 2), - [3095] = {.entry = {.count = 1, .reusable = true}}, SHIFT(846), - [3097] = {.entry = {.count = 1, .reusable = true}}, SHIFT(628), - [3099] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1054), - [3101] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_trait_method_params_repeat1, 2, 0, 0), - [3103] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_trait_method_params_repeat1, 2, 0, 0), SHIFT_REPEAT(1198), - [3106] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat2, 2, 0, 0), - [3108] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat2, 2, 0, 0), SHIFT_REPEAT(908), - [3111] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1292), - [3113] = {.entry = {.count = 1, .reusable = true}}, SHIFT(844), - [3115] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1127), - [3117] = {.entry = {.count = 1, .reusable = true}}, SHIFT(908), - [3119] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1075), - [3121] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1071), - [3123] = {.entry = {.count = 1, .reusable = true}}, SHIFT(937), - [3125] = {.entry = {.count = 1, .reusable = true}}, SHIFT(853), - [3127] = {.entry = {.count = 1, .reusable = true}}, SHIFT(151), - [3129] = {.entry = {.count = 1, .reusable = true}}, SHIFT(840), - [3131] = {.entry = {.count = 1, .reusable = true}}, SHIFT(483), - [3133] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1117), - [3135] = {.entry = {.count = 1, .reusable = true}}, SHIFT(484), - [3137] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_macro_export_repeat1, 2, 0, 0), - [3139] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_macro_export_repeat1, 2, 0, 0), SHIFT_REPEAT(1179), - [3142] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1128), - [3144] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export_item, 1, 0, 0), - [3146] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1316), - [3148] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field_init, 1, 0, 2), - [3150] = {.entry = {.count = 1, .reusable = true}}, SHIFT(189), - [3152] = {.entry = {.count = 1, .reusable = true}}, SHIFT(117), - [3154] = {.entry = {.count = 1, .reusable = true}}, SHIFT(88), - [3156] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1048), - [3158] = {.entry = {.count = 1, .reusable = true}}, SHIFT(841), - [3160] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 1, 0, 0), - [3162] = {.entry = {.count = 1, .reusable = true}}, SHIFT(116), - [3164] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1044), - [3166] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1086), - [3168] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1063), - [3170] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__parameter_list, 1, 0, 0), - [3172] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1045), - [3174] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_struct_definition_repeat1, 2, 0, 0), - [3176] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_struct_definition_repeat1, 2, 0, 0), SHIFT_REPEAT(1151), - [3179] = {.entry = {.count = 1, .reusable = true}}, SHIFT(463), - [3181] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1049), - [3183] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_map_expression_repeat1, 2, 0, 0), - [3185] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_map_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(139), - [3188] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parameter, 3, 0, 11), SHIFT(860), - [3191] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1093), - [3193] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym__parameter_list_repeat1, 2, 0, 0), - [3195] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__parameter_list_repeat1, 2, 0, 0), SHIFT_REPEAT(1271), - [3198] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__parameter_list_repeat1, 2, 0, 0), SHIFT_REPEAT(1203), - [3201] = {.entry = {.count = 1, .reusable = true}}, SHIFT(236), - [3203] = {.entry = {.count = 1, .reusable = true}}, SHIFT(152), - [3205] = {.entry = {.count = 1, .reusable = true}}, SHIFT(730), - [3207] = {.entry = {.count = 1, .reusable = true}}, SHIFT(237), - [3209] = {.entry = {.count = 1, .reusable = true}}, SHIFT(356), - [3211] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_struct_literal_repeat1, 2, 0, 0), - [3213] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_struct_literal_repeat1, 2, 0, 0), SHIFT_REPEAT(1239), - [3216] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1124), - [3218] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1077), - [3220] = {.entry = {.count = 1, .reusable = true}}, SHIFT(74), - [3222] = {.entry = {.count = 1, .reusable = true}}, SHIFT(140), - [3224] = {.entry = {.count = 1, .reusable = true}}, SHIFT(598), - [3226] = {.entry = {.count = 1, .reusable = true}}, SHIFT(599), - [3228] = {.entry = {.count = 1, .reusable = true}}, SHIFT(119), - [3230] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(730), - [3233] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(356), - [3236] = {.entry = {.count = 1, .reusable = true}}, SHIFT(907), - [3238] = {.entry = {.count = 1, .reusable = true}}, SHIFT(141), - [3240] = {.entry = {.count = 1, .reusable = true}}, SHIFT(65), - [3242] = {.entry = {.count = 1, .reusable = true}}, SHIFT(837), - [3244] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 1, 0, 0), - [3246] = {.entry = {.count = 1, .reusable = true}}, SHIFT(934), - [3248] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1485), - [3250] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1129), - [3252] = {.entry = {.count = 1, .reusable = true}}, SHIFT(574), - [3254] = {.entry = {.count = 1, .reusable = true}}, SHIFT(608), - [3256] = {.entry = {.count = 1, .reusable = true}}, SHIFT(609), - [3258] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1130), - [3260] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 5, 0, 0), - [3262] = {.entry = {.count = 1, .reusable = true}}, SHIFT(509), - [3264] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1303), - [3266] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern_entry, 3, 0, 0), - [3268] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 6, 0, 17), - [3270] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_argument, 3, 0, 19), - [3272] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 7, 0, 17), - [3274] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_param, 5, 0, 2), - [3276] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 8, 0, 17), - [3278] = {.entry = {.count = 1, .reusable = true}}, SHIFT(794), - [3280] = {.entry = {.count = 1, .reusable = true}}, SHIFT(266), - [3282] = {.entry = {.count = 1, .reusable = true}}, SHIFT(457), - [3284] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1289), - [3286] = {.entry = {.count = 1, .reusable = true}}, SHIFT(883), - [3288] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1324), - [3290] = {.entry = {.count = 1, .reusable = true}}, SHIFT(835), - [3292] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_entry, 3, 0, 10), - [3294] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field_init, 3, 0, 19), - [3296] = {.entry = {.count = 1, .reusable = true}}, SHIFT(155), - [3298] = {.entry = {.count = 1, .reusable = true}}, SHIFT(517), - [3300] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 5, 0, 17), - [3302] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1193), - [3304] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1473), - [3306] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1282), - [3308] = {.entry = {.count = 1, .reusable = true}}, SHIFT(838), - [3310] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_item, 3, 0, 0), - [3312] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export_item, 3, 0, 0), - [3314] = {.entry = {.count = 1, .reusable = true}}, SHIFT(241), - [3316] = {.entry = {.count = 1, .reusable = true}}, SHIFT(242), - [3318] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1178), - [3320] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1493), - [3322] = {.entry = {.count = 1, .reusable = true}}, SHIFT(635), - [3324] = {.entry = {.count = 1, .reusable = true}}, SHIFT(234), - [3326] = {.entry = {.count = 1, .reusable = true}}, SHIFT(83), - [3328] = {.entry = {.count = 1, .reusable = true}}, SHIFT(176), - [3330] = {.entry = {.count = 1, .reusable = true}}, SHIFT(566), - [3332] = {.entry = {.count = 1, .reusable = true}}, SHIFT(255), - [3334] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1205), - [3336] = {.entry = {.count = 1, .reusable = true}}, SHIFT(738), - [3338] = {.entry = {.count = 1, .reusable = true}}, SHIFT(201), - [3340] = {.entry = {.count = 1, .reusable = true}}, SHIFT(212), - [3342] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1108), - [3344] = {.entry = {.count = 1, .reusable = true}}, SHIFT(575), - [3346] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1109), - [3348] = {.entry = {.count = 1, .reusable = true}}, SHIFT(171), - [3350] = {.entry = {.count = 1, .reusable = true}}, SHIFT(742), - [3352] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1112), - [3354] = {.entry = {.count = 1, .reusable = true}}, SHIFT(501), - [3356] = {.entry = {.count = 1, .reusable = true}}, SHIFT(550), - [3358] = {.entry = {.count = 1, .reusable = true}}, SHIFT(459), - [3360] = {.entry = {.count = 1, .reusable = true}}, SHIFT(548), - [3362] = {.entry = {.count = 1, .reusable = true}}, SHIFT(649), - [3364] = {.entry = {.count = 1, .reusable = true}}, SHIFT(512), - [3366] = {.entry = {.count = 1, .reusable = true}}, SHIFT(529), - [3368] = {.entry = {.count = 1, .reusable = true}}, SHIFT(535), - [3370] = {.entry = {.count = 1, .reusable = true}}, SHIFT(650), - [3372] = {.entry = {.count = 1, .reusable = true}}, SHIFT(179), - [3374] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_params_block, 5, 0, 0), - [3376] = {.entry = {.count = 1, .reusable = true}}, SHIFT(460), - [3378] = {.entry = {.count = 1, .reusable = true}}, SHIFT(538), - [3380] = {.entry = {.count = 1, .reusable = true}}, SHIFT(472), - [3382] = {.entry = {.count = 1, .reusable = true}}, SHIFT(927), - [3384] = {.entry = {.count = 1, .reusable = true}}, SHIFT(473), - [3386] = {.entry = {.count = 1, .reusable = true}}, SHIFT(195), - [3388] = {.entry = {.count = 1, .reusable = true}}, SHIFT(744), - [3390] = {.entry = {.count = 1, .reusable = true}}, SHIFT(745), - [3392] = {.entry = {.count = 1, .reusable = true}}, SHIFT(89), - [3394] = {.entry = {.count = 1, .reusable = true}}, SHIFT(199), - [3396] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1017), - [3398] = {.entry = {.count = 1, .reusable = true}}, SHIFT(90), - [3400] = {.entry = {.count = 1, .reusable = true}}, SHIFT(920), - [3402] = {.entry = {.count = 1, .reusable = true}}, SHIFT(91), - [3404] = {.entry = {.count = 1, .reusable = true}}, SHIFT(181), - [3406] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1047), - [3408] = {.entry = {.count = 1, .reusable = true}}, SHIFT(843), - [3410] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1053), - [3412] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1011), - [3414] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_params_block, 3, 0, 0), - [3416] = {.entry = {.count = 1, .reusable = true}}, SHIFT(134), - [3418] = {.entry = {.count = 1, .reusable = true}}, SHIFT(207), - [3420] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1058), - [3422] = {.entry = {.count = 1, .reusable = true}}, SHIFT(184), - [3424] = {.entry = {.count = 1, .reusable = true}}, SHIFT(4), - [3426] = {.entry = {.count = 1, .reusable = true}}, SHIFT(493), - [3428] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1255), - [3430] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1262), - [3432] = {.entry = {.count = 1, .reusable = true}}, SHIFT(495), - [3434] = {.entry = {.count = 1, .reusable = true}}, SHIFT(845), - [3436] = {.entry = {.count = 1, .reusable = true}}, SHIFT(9), - [3438] = {.entry = {.count = 1, .reusable = true}}, SHIFT(523), - [3440] = {.entry = {.count = 1, .reusable = true}}, SHIFT(5), - [3442] = {.entry = {.count = 1, .reusable = true}}, SHIFT(208), - [3444] = {.entry = {.count = 1, .reusable = true}}, SHIFT(928), - [3446] = {.entry = {.count = 1, .reusable = true}}, SHIFT(190), - [3448] = {.entry = {.count = 1, .reusable = true}}, SHIFT(125), - [3450] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1147), - [3452] = {.entry = {.count = 1, .reusable = true}}, SHIFT(82), - [3454] = {.entry = {.count = 1, .reusable = true}}, SHIFT(468), - [3456] = {.entry = {.count = 1, .reusable = true}}, SHIFT(487), - [3458] = {.entry = {.count = 1, .reusable = true}}, SHIFT(279), - [3460] = {.entry = {.count = 1, .reusable = true}}, SHIFT(856), - [3462] = {.entry = {.count = 1, .reusable = true}}, SHIFT(166), - [3464] = {.entry = {.count = 1, .reusable = true}}, SHIFT(499), - [3466] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1199), - [3468] = {.entry = {.count = 1, .reusable = true}}, SHIFT(977), - [3470] = {.entry = {.count = 1, .reusable = true}}, SHIFT(132), - [3472] = {.entry = {.count = 1, .reusable = true}}, SHIFT(469), - [3474] = {.entry = {.count = 1, .reusable = true}}, SHIFT(124), - [3476] = {.entry = {.count = 1, .reusable = true}}, SHIFT(456), - [3478] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1098), - [3480] = {.entry = {.count = 1, .reusable = true}}, SHIFT(204), - [3482] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6), - [3484] = {.entry = {.count = 1, .reusable = true}}, SHIFT(527), - [3486] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), - [3488] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 4, 0, 0), - [3490] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1332), - [3492] = {.entry = {.count = 1, .reusable = true}}, SHIFT(280), - [3494] = {.entry = {.count = 1, .reusable = true}}, SHIFT(200), - [3496] = {.entry = {.count = 1, .reusable = true}}, SHIFT(7), - [3498] = {.entry = {.count = 1, .reusable = true}}, SHIFT(100), - [3500] = {.entry = {.count = 1, .reusable = true}}, SHIFT(210), - [3502] = {.entry = {.count = 1, .reusable = true}}, SHIFT(178), - [3504] = {.entry = {.count = 1, .reusable = true}}, SHIFT(847), - [3506] = {.entry = {.count = 1, .reusable = true}}, SHIFT(854), - [3508] = {.entry = {.count = 1, .reusable = true}}, SHIFT(213), - [3510] = {.entry = {.count = 1, .reusable = true}}, SHIFT(470), - [3512] = {.entry = {.count = 1, .reusable = true}}, SHIFT(214), - [3514] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 5, 0, 0), - [3516] = {.entry = {.count = 1, .reusable = true}}, SHIFT(215), - [3518] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1308), - [3520] = {.entry = {.count = 1, .reusable = true}}, SHIFT(921), - [3522] = {.entry = {.count = 1, .reusable = true}}, SHIFT(164), - [3524] = {.entry = {.count = 1, .reusable = true}}, SHIFT(160), - [3526] = {.entry = {.count = 1, .reusable = true}}, SHIFT(595), - [3528] = {.entry = {.count = 1, .reusable = true}}, SHIFT(222), - [3530] = {.entry = {.count = 1, .reusable = true}}, SHIFT(223), - [3532] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1091), - [3534] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1082), - [3536] = {.entry = {.count = 1, .reusable = true}}, SHIFT(800), - [3538] = {.entry = {.count = 1, .reusable = true}}, SHIFT(596), - [3540] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1066), - [3542] = {.entry = {.count = 1, .reusable = true}}, SHIFT(194), - [3544] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1226), - [3546] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1290), - [3548] = {.entry = {.count = 1, .reusable = true}}, SHIFT(925), - [3550] = {.entry = {.count = 1, .reusable = true}}, SHIFT(790), - [3552] = {.entry = {.count = 1, .reusable = true}}, SHIFT(232), - [3554] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1009), - [3556] = {.entry = {.count = 1, .reusable = true}}, SHIFT(839), - [3558] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1293), - [3560] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1094), - [3562] = {.entry = {.count = 1, .reusable = true}}, SHIFT(8), - [3564] = {.entry = {.count = 1, .reusable = true}}, SHIFT(235), - [3566] = {.entry = {.count = 1, .reusable = true}}, SHIFT(962), - [3568] = {.entry = {.count = 1, .reusable = true}}, SHIFT(84), - [3570] = {.entry = {.count = 1, .reusable = true}}, SHIFT(912), - [3572] = {.entry = {.count = 1, .reusable = true}}, SHIFT(922), - [3574] = {.entry = {.count = 1, .reusable = true}}, SHIFT(913), - [3576] = {.entry = {.count = 1, .reusable = true}}, SHIFT(914), - [3578] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1211), - [3580] = {.entry = {.count = 1, .reusable = true}}, SHIFT(206), - [3582] = {.entry = {.count = 1, .reusable = true}}, SHIFT(229), - [3584] = {.entry = {.count = 1, .reusable = true}}, SHIFT(567), - [3586] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1322), - [3588] = {.entry = {.count = 1, .reusable = true}}, SHIFT(192), - [3590] = {.entry = {.count = 1, .reusable = true}}, SHIFT(852), - [3592] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1056), - [3594] = {.entry = {.count = 1, .reusable = true}}, SHIFT(360), - [3596] = {.entry = {.count = 1, .reusable = true}}, SHIFT(710), - [3598] = {.entry = {.count = 1, .reusable = true}}, SHIFT(95), - [3600] = {.entry = {.count = 1, .reusable = true}}, SHIFT(639), - [3602] = {.entry = {.count = 1, .reusable = true}}, SHIFT(861), - [3604] = {.entry = {.count = 1, .reusable = true}}, SHIFT(593), - [3606] = {.entry = {.count = 1, .reusable = true}}, SHIFT(764), - [3608] = {.entry = {.count = 1, .reusable = true}}, SHIFT(629), - [3610] = {.entry = {.count = 1, .reusable = true}}, SHIFT(613), - [3612] = {.entry = {.count = 1, .reusable = true}}, SHIFT(170), - [3614] = {.entry = {.count = 1, .reusable = true}}, SHIFT(630), - [3616] = {.entry = {.count = 1, .reusable = true}}, SHIFT(616), - [3618] = {.entry = {.count = 1, .reusable = true}}, SHIFT(795), - [3620] = {.entry = {.count = 1, .reusable = true}}, SHIFT(476), - [3622] = {.entry = {.count = 1, .reusable = true}}, SHIFT(248), - [3624] = {.entry = {.count = 1, .reusable = true}}, SHIFT(249), - [3626] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1079), - [3628] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1309), - [3630] = {.entry = {.count = 1, .reusable = true}}, SHIFT(64), - [3632] = {.entry = {.count = 1, .reusable = true}}, SHIFT(254), - [3634] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1023), - [3636] = {.entry = {.count = 1, .reusable = true}}, SHIFT(187), - [3638] = {.entry = {.count = 1, .reusable = true}}, SHIFT(256), - [3640] = {.entry = {.count = 1, .reusable = true}}, SHIFT(942), - [3642] = {.entry = {.count = 1, .reusable = true}}, SHIFT(259), - [3644] = {.entry = {.count = 1, .reusable = true}}, SHIFT(260), - [3646] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1095), - [3648] = {.entry = {.count = 1, .reusable = true}}, SHIFT(526), - [3650] = {.entry = {.count = 1, .reusable = true}}, SHIFT(265), - [3652] = {.entry = {.count = 1, .reusable = true}}, SHIFT(163), - [3654] = {.entry = {.count = 1, .reusable = true}}, SHIFT(153), - [3656] = {.entry = {.count = 1, .reusable = true}}, SHIFT(565), - [3658] = {.entry = {.count = 1, .reusable = true}}, SHIFT(592), - [3660] = {.entry = {.count = 1, .reusable = true}}, SHIFT(182), - [3662] = {.entry = {.count = 1, .reusable = true}}, SHIFT(2), - [3664] = {.entry = {.count = 1, .reusable = true}}, SHIFT(167), - [3666] = {.entry = {.count = 1, .reusable = true}}, SHIFT(175), - [3668] = {.entry = {.count = 1, .reusable = true}}, SHIFT(239), - [3670] = {.entry = {.count = 1, .reusable = true}}, SHIFT(3), - [3672] = {.entry = {.count = 1, .reusable = true}}, SHIFT(99), - [3674] = {.entry = {.count = 1, .reusable = true}}, SHIFT(519), - [3676] = {.entry = {.count = 1, .reusable = true}}, SHIFT(833), - [3678] = {.entry = {.count = 1, .reusable = true}}, SHIFT(570), - [3680] = {.entry = {.count = 1, .reusable = true}}, SHIFT(631), - [3682] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_params_block, 4, 0, 0), - [3684] = {.entry = {.count = 1, .reusable = true}}, SHIFT(157), - [3686] = {.entry = {.count = 1, .reusable = true}}, SHIFT(642), - [3688] = {.entry = {.count = 1, .reusable = true}}, SHIFT(571), - [3690] = {.entry = {.count = 1, .reusable = true}}, SHIFT(545), - [3692] = {.entry = {.count = 1, .reusable = true}}, SHIFT(479), - [3694] = {.entry = {.count = 1, .reusable = true}}, SHIFT(911), - [3696] = {.entry = {.count = 1, .reusable = true}}, SHIFT(552), - [3698] = {.entry = {.count = 1, .reusable = true}}, SHIFT(168), - [3700] = {.entry = {.count = 1, .reusable = true}}, SHIFT(291), - [3702] = {.entry = {.count = 1, .reusable = true}}, SHIFT(799), - [3704] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1315), - [3706] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1353), - [3708] = {.entry = {.count = 1, .reusable = true}}, SHIFT(292), - [3710] = {.entry = {.count = 1, .reusable = true}}, SHIFT(267), - [3712] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1285), + [1827] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(429), + [1830] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(430), + [1833] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(431), + [1836] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(432), + [1839] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(424), + [1842] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(47), + [1845] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(433), + [1848] = {.entry = {.count = 1, .reusable = false}}, SHIFT(272), + [1850] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(276), + [1853] = {.entry = {.count = 1, .reusable = true}}, SHIFT(126), + [1855] = {.entry = {.count = 1, .reusable = true}}, SHIFT(324), + [1857] = {.entry = {.count = 1, .reusable = false}}, SHIFT(229), + [1859] = {.entry = {.count = 1, .reusable = false}}, SHIFT(970), + [1861] = {.entry = {.count = 1, .reusable = true}}, SHIFT(748), + [1863] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1572), + [1865] = {.entry = {.count = 1, .reusable = false}}, SHIFT(992), + [1867] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1482), + [1869] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1484), + [1871] = {.entry = {.count = 1, .reusable = true}}, SHIFT(422), + [1873] = {.entry = {.count = 1, .reusable = false}}, SHIFT(422), + [1875] = {.entry = {.count = 1, .reusable = true}}, SHIFT(423), + [1877] = {.entry = {.count = 1, .reusable = false}}, SHIFT(47), + [1879] = {.entry = {.count = 1, .reusable = true}}, SHIFT(433), + [1881] = {.entry = {.count = 1, .reusable = true}}, SHIFT(384), + [1883] = {.entry = {.count = 1, .reusable = true}}, SHIFT(385), + [1885] = {.entry = {.count = 1, .reusable = false}}, SHIFT(386), + [1887] = {.entry = {.count = 1, .reusable = true}}, SHIFT(387), + [1889] = {.entry = {.count = 1, .reusable = false}}, SHIFT(388), + [1891] = {.entry = {.count = 1, .reusable = true}}, SHIFT(372), + [1893] = {.entry = {.count = 1, .reusable = false}}, SHIFT(372), + [1895] = {.entry = {.count = 1, .reusable = true}}, SHIFT(373), + [1897] = {.entry = {.count = 1, .reusable = true}}, SHIFT(393), + [1899] = {.entry = {.count = 1, .reusable = false}}, SHIFT(393), + [1901] = {.entry = {.count = 1, .reusable = true}}, SHIFT(394), + [1903] = {.entry = {.count = 1, .reusable = true}}, SHIFT(395), + [1905] = {.entry = {.count = 1, .reusable = false}}, SHIFT(396), + [1907] = {.entry = {.count = 1, .reusable = true}}, SHIFT(397), + [1909] = {.entry = {.count = 1, .reusable = false}}, SHIFT(398), + [1911] = {.entry = {.count = 1, .reusable = true}}, SHIFT(399), + [1913] = {.entry = {.count = 1, .reusable = false}}, SHIFT(48), + [1915] = {.entry = {.count = 1, .reusable = true}}, SHIFT(400), + [1917] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_guarded_pattern, 3, 0, 0), + [1919] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_guarded_pattern, 3, 0, 0), SHIFT(396), + [1922] = {.entry = {.count = 1, .reusable = true}}, SHIFT(374), + [1924] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_guarded_pattern, 3, 0, 0), SHIFT(48), + [1927] = {.entry = {.count = 1, .reusable = true}}, SHIFT(383), + [1929] = {.entry = {.count = 1, .reusable = false}}, SHIFT(383), + [1931] = {.entry = {.count = 1, .reusable = true}}, SHIFT(389), + [1933] = {.entry = {.count = 1, .reusable = true}}, SHIFT(382), + [1935] = {.entry = {.count = 1, .reusable = false}}, SHIFT(49), + [1937] = {.entry = {.count = 1, .reusable = true}}, SHIFT(391), + [1939] = {.entry = {.count = 1, .reusable = true}}, SHIFT(381), + [1941] = {.entry = {.count = 1, .reusable = false}}, SHIFT(381), + [1943] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(381), + [1946] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(381), + [1949] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(382), + [1952] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(383), + [1955] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(383), + [1958] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(384), + [1961] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(385), + [1964] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(386), + [1967] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(387), + [1970] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(388), + [1973] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(389), + [1976] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(390), + [1979] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(49), + [1982] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(391), + [1985] = {.entry = {.count = 1, .reusable = false}}, SHIFT(266), + [1987] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(275), + [1990] = {.entry = {.count = 1, .reusable = true}}, SHIFT(209), + [1992] = {.entry = {.count = 1, .reusable = true}}, SHIFT(304), + [1994] = {.entry = {.count = 1, .reusable = true}}, SHIFT(305), + [1996] = {.entry = {.count = 1, .reusable = false}}, SHIFT(306), + [1998] = {.entry = {.count = 1, .reusable = true}}, SHIFT(307), + [2000] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1346), + [2002] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1097), + [2004] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1097), + [2006] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1111), + [2008] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1103), + [2010] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1083), + [2012] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1085), + [2014] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1112), + [2016] = {.entry = {.count = 1, .reusable = true}}, SHIFT(856), + [2018] = {.entry = {.count = 1, .reusable = true}}, SHIFT(929), + [2020] = {.entry = {.count = 1, .reusable = true}}, SHIFT(980), + [2022] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1281), + [2024] = {.entry = {.count = 1, .reusable = true}}, SHIFT(303), + [2026] = {.entry = {.count = 1, .reusable = false}}, SHIFT(303), + [2028] = {.entry = {.count = 1, .reusable = false}}, SHIFT(308), + [2030] = {.entry = {.count = 1, .reusable = true}}, SHIFT(309), + [2032] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(137), + [2035] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1307), + [2037] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1311), + [2039] = {.entry = {.count = 1, .reusable = true}}, SHIFT(264), + [2041] = {.entry = {.count = 1, .reusable = true}}, SHIFT(265), + [2043] = {.entry = {.count = 1, .reusable = false}}, SHIFT(831), + [2045] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(402), + [2048] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(402), + [2051] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(403), + [2054] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(404), + [2057] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(404), + [2060] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(405), + [2063] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(406), + [2066] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(407), + [2069] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(408), + [2072] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(409), + [2075] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(410), + [2078] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(411), + [2081] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(51), + [2084] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(412), + [2087] = {.entry = {.count = 1, .reusable = false}}, SHIFT(274), + [2089] = {.entry = {.count = 1, .reusable = true}}, SHIFT(877), + [2091] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1346), + [2094] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1097), + [2097] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1097), + [2100] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1111), + [2103] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1103), + [2106] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1083), + [2109] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1085), + [2112] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1112), + [2115] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), + [2117] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(929), + [2120] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(980), + [2123] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_match_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1281), + [2126] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(277), + [2129] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(1159), + [2132] = {.entry = {.count = 1, .reusable = true}}, SHIFT(301), + [2134] = {.entry = {.count = 1, .reusable = false}}, SHIFT(301), + [2136] = {.entry = {.count = 1, .reusable = true}}, SHIFT(302), + [2138] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_guarded_pattern, 3, 0, 0), SHIFT(306), + [2141] = {.entry = {.count = 1, .reusable = true}}, SHIFT(310), + [2143] = {.entry = {.count = 1, .reusable = false}}, SHIFT(50), + [2145] = {.entry = {.count = 1, .reusable = true}}, SHIFT(311), + [2147] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_guarded_pattern, 3, 0, 0), + [2149] = {.entry = {.count = 1, .reusable = true}}, SHIFT(695), + [2151] = {.entry = {.count = 1, .reusable = true}}, SHIFT(676), + [2153] = {.entry = {.count = 1, .reusable = true}}, SHIFT(405), + [2155] = {.entry = {.count = 1, .reusable = true}}, SHIFT(406), + [2157] = {.entry = {.count = 1, .reusable = false}}, SHIFT(407), + [2159] = {.entry = {.count = 1, .reusable = true}}, SHIFT(408), + [2161] = {.entry = {.count = 1, .reusable = false}}, SHIFT(409), + [2163] = {.entry = {.count = 1, .reusable = true}}, SHIFT(404), + [2165] = {.entry = {.count = 1, .reusable = false}}, SHIFT(404), + [2167] = {.entry = {.count = 1, .reusable = true}}, SHIFT(410), + [2169] = {.entry = {.count = 1, .reusable = true}}, SHIFT(403), + [2171] = {.entry = {.count = 1, .reusable = false}}, SHIFT(51), + [2173] = {.entry = {.count = 1, .reusable = true}}, SHIFT(412), + [2175] = {.entry = {.count = 1, .reusable = true}}, SHIFT(402), + [2177] = {.entry = {.count = 1, .reusable = false}}, SHIFT(402), + [2179] = {.entry = {.count = 1, .reusable = true}}, SHIFT(83), + [2181] = {.entry = {.count = 1, .reusable = true}}, SHIFT(98), + [2183] = {.entry = {.count = 1, .reusable = true}}, SHIFT(559), + [2185] = {.entry = {.count = 1, .reusable = true}}, SHIFT(589), + [2187] = {.entry = {.count = 1, .reusable = true}}, SHIFT(350), + [2189] = {.entry = {.count = 1, .reusable = true}}, SHIFT(351), + [2191] = {.entry = {.count = 1, .reusable = false}}, SHIFT(352), + [2193] = {.entry = {.count = 1, .reusable = true}}, SHIFT(353), + [2195] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(436), + [2198] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(436), + [2201] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(437), + [2204] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(438), + [2207] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(438), + [2210] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(439), + [2213] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(440), + [2216] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(441), + [2219] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(442), + [2222] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(443), + [2225] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(444), + [2228] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(445), + [2231] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(53), + [2234] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(446), + [2237] = {.entry = {.count = 1, .reusable = false}}, SHIFT(287), + [2239] = {.entry = {.count = 1, .reusable = true}}, SHIFT(347), + [2241] = {.entry = {.count = 1, .reusable = false}}, SHIFT(347), + [2243] = {.entry = {.count = 1, .reusable = true}}, SHIFT(348), + [2245] = {.entry = {.count = 1, .reusable = true}}, SHIFT(349), + [2247] = {.entry = {.count = 1, .reusable = false}}, SHIFT(349), + [2249] = {.entry = {.count = 1, .reusable = false}}, SHIFT(354), + [2251] = {.entry = {.count = 1, .reusable = true}}, SHIFT(355), + [2253] = {.entry = {.count = 1, .reusable = false}}, SHIFT(52), + [2255] = {.entry = {.count = 1, .reusable = true}}, SHIFT(357), + [2257] = {.entry = {.count = 1, .reusable = false}}, SHIFT(962), + [2259] = {.entry = {.count = 1, .reusable = false}}, SHIFT(991), + [2261] = {.entry = {.count = 1, .reusable = true}}, SHIFT(991), + [2263] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1002), + [2265] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1003), + [2267] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1063), + [2269] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1064), + [2271] = {.entry = {.count = 1, .reusable = true}}, SHIFT(979), + [2273] = {.entry = {.count = 1, .reusable = true}}, SHIFT(933), + [2275] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1327), + [2277] = {.entry = {.count = 1, .reusable = true}}, SHIFT(984), + [2279] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1379), + [2281] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1009), + [2283] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1363), + [2285] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1383), + [2287] = {.entry = {.count = 1, .reusable = true}}, SHIFT(137), + [2289] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_guarded_pattern, 3, 0, 0), SHIFT(352), + [2292] = {.entry = {.count = 1, .reusable = true}}, SHIFT(356), + [2294] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_primary_expression, 1, 0, 0), SHIFT(201), + [2297] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym__full_expression, 1, 0, 0), SHIFT(290), + [2300] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1024), + [2302] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1402), + [2304] = {.entry = {.count = 1, .reusable = true}}, SHIFT(439), + [2306] = {.entry = {.count = 1, .reusable = true}}, SHIFT(440), + [2308] = {.entry = {.count = 1, .reusable = false}}, SHIFT(441), + [2310] = {.entry = {.count = 1, .reusable = true}}, SHIFT(442), + [2312] = {.entry = {.count = 1, .reusable = false}}, SHIFT(443), + [2314] = {.entry = {.count = 1, .reusable = true}}, SHIFT(438), + [2316] = {.entry = {.count = 1, .reusable = false}}, SHIFT(438), + [2318] = {.entry = {.count = 1, .reusable = true}}, SHIFT(444), + [2320] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1015), + [2322] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1417), + [2324] = {.entry = {.count = 1, .reusable = true}}, SHIFT(437), + [2326] = {.entry = {.count = 1, .reusable = false}}, SHIFT(53), + [2328] = {.entry = {.count = 1, .reusable = true}}, SHIFT(446), + [2330] = {.entry = {.count = 1, .reusable = true}}, SHIFT(436), + [2332] = {.entry = {.count = 1, .reusable = false}}, SHIFT(436), + [2334] = {.entry = {.count = 1, .reusable = false}}, SHIFT(289), + [2336] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1325), + [2338] = {.entry = {.count = 1, .reusable = true}}, SHIFT(360), + [2340] = {.entry = {.count = 1, .reusable = false}}, SHIFT(360), + [2342] = {.entry = {.count = 1, .reusable = true}}, SHIFT(361), + [2344] = {.entry = {.count = 1, .reusable = true}}, SHIFT(362), + [2346] = {.entry = {.count = 1, .reusable = false}}, SHIFT(362), + [2348] = {.entry = {.count = 1, .reusable = true}}, SHIFT(363), + [2350] = {.entry = {.count = 1, .reusable = true}}, SHIFT(364), + [2352] = {.entry = {.count = 1, .reusable = false}}, SHIFT(365), + [2354] = {.entry = {.count = 1, .reusable = true}}, SHIFT(366), + [2356] = {.entry = {.count = 1, .reusable = false}}, SHIFT(367), + [2358] = {.entry = {.count = 1, .reusable = true}}, SHIFT(368), + [2360] = {.entry = {.count = 1, .reusable = false}}, SHIFT(54), + [2362] = {.entry = {.count = 1, .reusable = true}}, SHIFT(370), + [2364] = {.entry = {.count = 1, .reusable = true}}, SHIFT(821), + [2366] = {.entry = {.count = 1, .reusable = true}}, SHIFT(369), + [2368] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1020), + [2370] = {.entry = {.count = 1, .reusable = true}}, SHIFT(878), + [2372] = {.entry = {.count = 1, .reusable = true}}, SHIFT(891), + [2374] = {.entry = {.count = 1, .reusable = true}}, SHIFT(840), + [2376] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1046), + [2378] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1046), + [2380] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1075), + [2382] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1076), + [2384] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1056), + [2386] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1057), + [2388] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1066), + [2390] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_arm, 3, 0, 21), + [2392] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_arm, 3, 0, 21), + [2394] = {.entry = {.count = 1, .reusable = true}}, SHIFT(969), + [2396] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1002), + [2398] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1003), + [2400] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1075), + [2402] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1076), + [2404] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1111), + [2406] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1103), + [2408] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1442), + [2410] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1429), + [2412] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1443), + [2414] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1430), + [2416] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1419), + [2418] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1420), + [2420] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1421), + [2422] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1412), + [2424] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1425), + [2426] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1407), + [2428] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_match_arm, 4, 0, 21), + [2430] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_match_arm, 4, 0, 21), + [2432] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1027), + [2434] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1404), + [2436] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1364), + [2438] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1384), + [2440] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1328), + [2442] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1380), + [2444] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1023), + [2446] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1451), + [2448] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1326), + [2450] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), + [2452] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), SHIFT_REPEAT(794), + [2455] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_union_type, 3, 0, 0), + [2457] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_union_type, 3, 0, 0), SHIFT(771), + [2460] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_union_type, 3, 0, 0), SHIFT(989), + [2463] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), REDUCE(sym_union_type, 3, 0, 0), + [2466] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), REDUCE(sym_union_type, 3, 0, 0), SHIFT(771), + [2470] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), REDUCE(sym_union_type, 3, 0, 0), SHIFT(989), + [2474] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1026), + [2476] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym__type, 1, 0, 0), REDUCE(sym_named_type, 1, 0, 0), + [2479] = {.entry = {.count = 1, .reusable = true}}, SHIFT(803), + [2481] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1330), + [2483] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1533), + [2485] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_union_type, 4, 0, 0), + [2487] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_union_type, 4, 0, 0), SHIFT(794), + [2490] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1274), + [2492] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1650), + [2494] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_optional_type, 2, 0, 0), + [2496] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_literal_pattern, 1, 0, 0), + [2498] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_literal_pattern, 1, 0, 0), + [2500] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_primitive_type, 1, 0, 0), + [2502] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_type, 5, 0, 0), + [2504] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 5, 0, 0), SHIFT(767), + [2507] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 5, 0, 0), SHIFT(989), + [2510] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_type, 4, 0, 0), + [2512] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 4, 0, 0), SHIFT(767), + [2515] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 4, 0, 0), SHIFT(989), + [2518] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_type, 4, 0, 0), + [2520] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_type, 6, 0, 0), + [2522] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 6, 0, 0), SHIFT(767), + [2525] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_function_type, 6, 0, 0), SHIFT(989), + [2528] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_type, 6, 0, 0), + [2530] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_type, 4, 0, 0), + [2532] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), SHIFT(767), + [2535] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_union_type_repeat1, 2, 0, 0), SHIFT(989), + [2538] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_type, 5, 0, 0), + [2540] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 3, 0, 0), + [2542] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_range_pattern, 3, 0, 9), + [2544] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_wildcard_pattern, 1, 0, 0), + [2546] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 7, 0, 0), + [2548] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_pattern, 1, 0, 0), + [2550] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 7, 0, 0), + [2552] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 4, 0, 0), + [2554] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 6, 0, 0), + [2556] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_identifier_pattern, 1, 0, 0), + [2558] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 6, 0, 0), + [2560] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 2, 0, 0), + [2562] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_attributed_item_repeat1, 2, 0, 0), SHIFT_REPEAT(1627), + [2565] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_attributed_item_repeat1, 2, 0, 0), + [2567] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 4, 0, 0), + [2569] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 5, 0, 0), + [2571] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_list_pattern, 3, 0, 0), + [2573] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 2, 0, 0), + [2575] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern, 5, 0, 0), + [2577] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1033), + [2579] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1038), + [2581] = {.entry = {.count = 1, .reusable = true}}, SHIFT(990), + [2583] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1048), + [2585] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), + [2587] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(956), + [2590] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), + [2593] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(952), + [2597] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(371), + [2601] = {.entry = {.count = 2, .reusable = false}}, REDUCE(sym_pattern, 1, 0, 0), SHIFT(965), + [2604] = {.entry = {.count = 1, .reusable = true}}, SHIFT(960), + [2606] = {.entry = {.count = 1, .reusable = true}}, SHIFT(962), + [2608] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 2, 0, 0), + [2610] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1418), + [2612] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1324), + [2614] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), + [2616] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), SHIFT(956), + [2619] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 3, 0, 0), + [2621] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1432), + [2623] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), + [2625] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(952), + [2628] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(371), + [2631] = {.entry = {.count = 1, .reusable = false}}, SHIFT_EXTRA(), + [2633] = {.entry = {.count = 1, .reusable = false}}, SHIFT(889), + [2635] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1089), + [2637] = {.entry = {.count = 1, .reusable = false}}, SHIFT(220), + [2639] = {.entry = {.count = 1, .reusable = false}}, SHIFT(66), + [2641] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1045), + [2643] = {.entry = {.count = 1, .reusable = false}}, SHIFT(207), + [2645] = {.entry = {.count = 1, .reusable = false}}, REDUCE(aux_sym_double_string_repeat1, 2, 0, 0), + [2647] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_double_string_repeat1, 2, 0, 0), SHIFT_REPEAT(1045), + [2650] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_double_string_repeat1, 2, 0, 0), SHIFT_REPEAT(207), + [2653] = {.entry = {.count = 1, .reusable = false}}, SHIFT(67), + [2655] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1079), + [2657] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 1, 0, 0), + [2659] = {.entry = {.count = 1, .reusable = false}}, SHIFT(704), + [2661] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1051), + [2663] = {.entry = {.count = 1, .reusable = false}}, SHIFT(684), + [2665] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1052), + [2667] = {.entry = {.count = 1, .reusable = false}}, SHIFT(705), + [2669] = {.entry = {.count = 1, .reusable = false}}, SHIFT(707), + [2671] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(950), + [2674] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(371), + [2677] = {.entry = {.count = 1, .reusable = false}}, SHIFT(974), + [2679] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 3, 0, 0), + [2681] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1067), + [2683] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1060), + [2685] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1068), + [2687] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1061), + [2689] = {.entry = {.count = 1, .reusable = false}}, SHIFT(971), + [2691] = {.entry = {.count = 1, .reusable = true}}, SHIFT(922), + [2693] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1025), + [2695] = {.entry = {.count = 1, .reusable = true}}, SHIFT(950), + [2697] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1445), + [2699] = {.entry = {.count = 1, .reusable = true}}, SHIFT(371), + [2701] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1086), + [2703] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1087), + [2705] = {.entry = {.count = 1, .reusable = false}}, SHIFT(964), + [2707] = {.entry = {.count = 1, .reusable = true}}, SHIFT(959), + [2709] = {.entry = {.count = 1, .reusable = false}}, SHIFT(976), + [2711] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1058), + [2713] = {.entry = {.count = 1, .reusable = false}}, SHIFT(978), + [2715] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1054), + [2717] = {.entry = {.count = 1, .reusable = false}}, SHIFT(91), + [2719] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1047), + [2721] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 5, 0, 0), + [2723] = {.entry = {.count = 1, .reusable = false}}, SHIFT(528), + [2725] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1072), + [2727] = {.entry = {.count = 1, .reusable = false}}, SHIFT(529), + [2729] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1073), + [2731] = {.entry = {.count = 1, .reusable = false}}, SHIFT(530), + [2733] = {.entry = {.count = 1, .reusable = false}}, SHIFT(531), + [2735] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 4, 0, 0), + [2737] = {.entry = {.count = 1, .reusable = false}}, SHIFT(501), + [2739] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1092), + [2741] = {.entry = {.count = 1, .reusable = false}}, SHIFT(497), + [2743] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1093), + [2745] = {.entry = {.count = 1, .reusable = false}}, REDUCE(aux_sym_single_string_repeat1, 2, 0, 0), + [2747] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_single_string_repeat1, 2, 0, 0), SHIFT_REPEAT(1079), + [2750] = {.entry = {.count = 2, .reusable = false}}, REDUCE(aux_sym_single_string_repeat1, 2, 0, 0), SHIFT_REPEAT(220), + [2753] = {.entry = {.count = 1, .reusable = false}}, SHIFT(90), + [2755] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1044), + [2757] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_attribute, 3, 0, 0), + [2759] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern, 6, 0, 0), + [2761] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1096), + [2763] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1090), + [2765] = {.entry = {.count = 1, .reusable = false}}, SHIFT(888), + [2767] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1088), + [2769] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1130), + [2771] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1091), + [2773] = {.entry = {.count = 1, .reusable = false}}, SHIFT(890), + [2775] = {.entry = {.count = 1, .reusable = false}}, SHIFT(893), + [2777] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1104), + [2779] = {.entry = {.count = 1, .reusable = false}}, SHIFT(1102), + [2781] = {.entry = {.count = 1, .reusable = false}}, SHIFT(494), + [2783] = {.entry = {.count = 1, .reusable = false}}, SHIFT(499), + [2785] = {.entry = {.count = 1, .reusable = true}}, SHIFT(902), + [2787] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1350), + [2789] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1376), + [2791] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_attribute, 4, 0, 0), + [2793] = {.entry = {.count = 1, .reusable = true}}, SHIFT(244), + [2795] = {.entry = {.count = 1, .reusable = true}}, SHIFT(177), + [2797] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1381), + [2799] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_list_pattern_repeat1, 2, 0, 0), + [2801] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1242), + [2803] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_pattern_entry, 3, 0, 0), + [2805] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(951), + [2809] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(379), + [2813] = {.entry = {.count = 1, .reusable = true}}, SHIFT(563), + [2815] = {.entry = {.count = 1, .reusable = true}}, SHIFT(189), + [2817] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1626), + [2819] = {.entry = {.count = 1, .reusable = false}}, SHIFT(966), + [2821] = {.entry = {.count = 1, .reusable = true}}, SHIFT(961), + [2823] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), SHIFT(957), + [2826] = {.entry = {.count = 1, .reusable = true}}, SHIFT(700), + [2828] = {.entry = {.count = 1, .reusable = true}}, SHIFT(198), + [2830] = {.entry = {.count = 1, .reusable = true}}, SHIFT(197), + [2832] = {.entry = {.count = 1, .reusable = true}}, SHIFT(163), + [2834] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat1, 2, 0, 0), + [2836] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(1042), + [2839] = {.entry = {.count = 1, .reusable = true}}, SHIFT(193), + [2841] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_select_expression_repeat1, 2, 0, 0), + [2843] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_select_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(189), + [2846] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_select_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(1626), + [2849] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(957), + [2852] = {.entry = {.count = 1, .reusable = true}}, SHIFT(89), + [2854] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1347), + [2856] = {.entry = {.count = 1, .reusable = true}}, SHIFT(170), + [2858] = {.entry = {.count = 1, .reusable = true}}, SHIFT(293), + [2860] = {.entry = {.count = 1, .reusable = true}}, SHIFT(195), + [2862] = {.entry = {.count = 1, .reusable = true}}, SHIFT(166), + [2864] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_function_type_repeat1, 2, 0, 0), + [2866] = {.entry = {.count = 1, .reusable = true}}, SHIFT(767), + [2868] = {.entry = {.count = 1, .reusable = true}}, SHIFT(989), + [2870] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(951), + [2873] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(379), + [2876] = {.entry = {.count = 1, .reusable = true}}, SHIFT(239), + [2878] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1373), + [2880] = {.entry = {.count = 1, .reusable = true}}, SHIFT(173), + [2882] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_param, 3, 0, 2), + [2884] = {.entry = {.count = 1, .reusable = true}}, SHIFT(233), + [2886] = {.entry = {.count = 1, .reusable = true}}, SHIFT(870), + [2888] = {.entry = {.count = 1, .reusable = true}}, SHIFT(796), + [2890] = {.entry = {.count = 1, .reusable = true}}, SHIFT(998), + [2892] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1472), + [2894] = {.entry = {.count = 1, .reusable = true}}, SHIFT(591), + [2896] = {.entry = {.count = 1, .reusable = true}}, SHIFT(20), + [2898] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 4, 0, 0), + [2900] = {.entry = {.count = 1, .reusable = false}}, REDUCE(sym_string_interpolation, 3, 0, 0), + [2902] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_function_type_repeat1, 2, 0, 0), SHIFT_REPEAT(796), + [2905] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parameter, 3, 0, 11), + [2907] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_map_pattern_repeat1, 2, 0, 0), + [2909] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_map_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(999), + [2912] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1042), + [2914] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1074), + [2916] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1454), + [2918] = {.entry = {.count = 1, .reusable = true}}, SHIFT(477), + [2920] = {.entry = {.count = 1, .reusable = true}}, SHIFT(478), + [2922] = {.entry = {.count = 1, .reusable = true}}, SHIFT(479), + [2924] = {.entry = {.count = 1, .reusable = true}}, SHIFT(551), + [2926] = {.entry = {.count = 1, .reusable = true}}, SHIFT(652), + [2928] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1389), + [2930] = {.entry = {.count = 1, .reusable = true}}, SHIFT(461), + [2932] = {.entry = {.count = 1, .reusable = true}}, SHIFT(462), + [2934] = {.entry = {.count = 1, .reusable = true}}, SHIFT(463), + [2936] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(958), + [2939] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(378), + [2942] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1006), + [2944] = {.entry = {.count = 1, .reusable = true}}, SHIFT(977), + [2946] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1440), + [2948] = {.entry = {.count = 1, .reusable = true}}, SHIFT(602), + [2950] = {.entry = {.count = 1, .reusable = true}}, SHIFT(608), + [2952] = {.entry = {.count = 1, .reusable = true}}, SHIFT(17), + [2954] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(949), + [2957] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(379), + [2960] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_param, 1, 0, 2), + [2962] = {.entry = {.count = 1, .reusable = true}}, SHIFT(795), + [2964] = {.entry = {.count = 1, .reusable = true}}, SHIFT(221), + [2966] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1055), + [2968] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1411), + [2970] = {.entry = {.count = 1, .reusable = true}}, SHIFT(483), + [2972] = {.entry = {.count = 1, .reusable = true}}, SHIFT(484), + [2974] = {.entry = {.count = 1, .reusable = true}}, SHIFT(485), + [2976] = {.entry = {.count = 1, .reusable = true}}, SHIFT(843), + [2978] = {.entry = {.count = 1, .reusable = true}}, SHIFT(655), + [2980] = {.entry = {.count = 1, .reusable = true}}, SHIFT(16), + [2982] = {.entry = {.count = 1, .reusable = true}}, SHIFT(615), + [2984] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_or_pattern, 4, 0, 0), SHIFT(953), + [2987] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_impl_definition_repeat1, 2, 0, 0), + [2989] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_impl_definition_repeat1, 2, 0, 0), SHIFT_REPEAT(1453), + [2992] = {.entry = {.count = 1, .reusable = true}}, SHIFT(881), + [2994] = {.entry = {.count = 1, .reusable = true}}, SHIFT(884), + [2996] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 5, 0, 0), + [2998] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(958), + [3002] = {.entry = {.count = 3, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), REDUCE(sym_or_pattern, 3, 0, 0), SHIFT(378), + [3006] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(953), + [3009] = {.entry = {.count = 1, .reusable = true}}, SHIFT(647), + [3011] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 6, 0, 0), + [3013] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1255), + [3015] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1638), + [3017] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_trait_definition_repeat1, 2, 0, 0), + [3019] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_trait_definition_repeat1, 2, 0, 0), SHIFT_REPEAT(1389), + [3022] = {.entry = {.count = 1, .reusable = true}}, SHIFT(648), + [3024] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1453), + [3026] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_params, 2, 0, 0), + [3028] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 6, 0, 0), + [3030] = {.entry = {.count = 1, .reusable = true}}, SHIFT(55), + [3032] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1546), + [3034] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_param, 3, 0, 0), + [3036] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_params, 3, 0, 0), + [3038] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 7, 0, 0), + [3040] = {.entry = {.count = 1, .reusable = true}}, SHIFT(451), + [3042] = {.entry = {.count = 1, .reusable = true}}, SHIFT(452), + [3044] = {.entry = {.count = 1, .reusable = true}}, SHIFT(453), + [3046] = {.entry = {.count = 1, .reusable = true}}, SHIFT(599), + [3048] = {.entry = {.count = 1, .reusable = true}}, SHIFT(100), + [3050] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 3, 0, 0), + [3052] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1290), + [3054] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1632), + [3056] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 4, 0, 0), + [3058] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1370), + [3060] = {.entry = {.count = 1, .reusable = true}}, SHIFT(449), + [3062] = {.entry = {.count = 1, .reusable = true}}, SHIFT(472), + [3064] = {.entry = {.count = 1, .reusable = true}}, SHIFT(473), + [3066] = {.entry = {.count = 1, .reusable = true}}, SHIFT(698), + [3068] = {.entry = {.count = 1, .reusable = true}}, SHIFT(763), + [3070] = {.entry = {.count = 1, .reusable = true}}, SHIFT(949), + [3072] = {.entry = {.count = 1, .reusable = true}}, SHIFT(379), + [3074] = {.entry = {.count = 1, .reusable = true}}, SHIFT(191), + [3076] = {.entry = {.count = 1, .reusable = true}}, SHIFT(75), + [3078] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat1, 2, 0, 0), + [3080] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat1, 2, 0, 0), SHIFT_REPEAT(162), + [3083] = {.entry = {.count = 1, .reusable = true}}, SHIFT(540), + [3085] = {.entry = {.count = 1, .reusable = true}}, SHIFT(467), + [3087] = {.entry = {.count = 1, .reusable = true}}, SHIFT(468), + [3089] = {.entry = {.count = 1, .reusable = true}}, SHIFT(469), + [3091] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__parameter_list, 3, 0, 0), + [3093] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1351), + [3095] = {.entry = {.count = 1, .reusable = true}}, SHIFT(975), + [3097] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1377), + [3099] = {.entry = {.count = 1, .reusable = true}}, SHIFT(570), + [3101] = {.entry = {.count = 1, .reusable = true}}, SHIFT(571), + [3103] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__parameter_list, 2, 0, 0), + [3105] = {.entry = {.count = 1, .reusable = true}}, SHIFT(502), + [3107] = {.entry = {.count = 1, .reusable = true}}, SHIFT(777), + [3109] = {.entry = {.count = 1, .reusable = true}}, SHIFT(231), + [3111] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1504), + [3113] = {.entry = {.count = 1, .reusable = true}}, SHIFT(903), + [3115] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field, 3, 0, 0), + [3117] = {.entry = {.count = 1, .reusable = true}}, SHIFT(973), + [3119] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_list_pattern_repeat1, 2, 0, 0), SHIFT_REPEAT(947), + [3122] = {.entry = {.count = 1, .reusable = true}}, SHIFT(586), + [3124] = {.entry = {.count = 1, .reusable = true}}, SHIFT(685), + [3126] = {.entry = {.count = 1, .reusable = true}}, SHIFT(504), + [3128] = {.entry = {.count = 1, .reusable = true}}, SHIFT(658), + [3130] = {.entry = {.count = 1, .reusable = true}}, SHIFT(521), + [3132] = {.entry = {.count = 1, .reusable = true}}, SHIFT(592), + [3134] = {.entry = {.count = 1, .reusable = true}}, SHIFT(689), + [3136] = {.entry = {.count = 1, .reusable = true}}, SHIFT(913), + [3138] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1575), + [3140] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1576), + [3142] = {.entry = {.count = 1, .reusable = true}}, SHIFT(623), + [3144] = {.entry = {.count = 1, .reusable = true}}, SHIFT(598), + [3146] = {.entry = {.count = 1, .reusable = true}}, SHIFT(972), + [3148] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1139), + [3150] = {.entry = {.count = 1, .reusable = true}}, SHIFT(564), + [3152] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1152), + [3154] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1218), + [3156] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(954), + [3159] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_or_pattern_repeat1, 2, 0, 0), SHIFT(378), + [3162] = {.entry = {.count = 1, .reusable = true}}, SHIFT(188), + [3164] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1180), + [3166] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_item, 1, 0, 0), + [3168] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1427), + [3170] = {.entry = {.count = 1, .reusable = true}}, SHIFT(798), + [3172] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field_init, 1, 0, 2), + [3174] = {.entry = {.count = 1, .reusable = true}}, SHIFT(186), + [3176] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1560), + [3178] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1209), + [3180] = {.entry = {.count = 1, .reusable = true}}, SHIFT(92), + [3182] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1186), + [3184] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1182), + [3186] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1153), + [3188] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__argument_list, 1, 0, 0), + [3190] = {.entry = {.count = 1, .reusable = true}}, SHIFT(120), + [3192] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parameter, 1, 0, 2), + [3194] = {.entry = {.count = 1, .reusable = true}}, SHIFT(804), + [3196] = {.entry = {.count = 1, .reusable = true}}, SHIFT(805), + [3198] = {.entry = {.count = 1, .reusable = true}}, SHIFT(799), + [3200] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1183), + [3202] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 7, 0, 0), + [3204] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_struct_literal_repeat1, 2, 0, 0), + [3206] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_struct_literal_repeat1, 2, 0, 0), SHIFT_REPEAT(1310), + [3209] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export_item, 1, 0, 0), + [3211] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1439), + [3213] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_named_params_block_repeat1, 2, 0, 0), + [3215] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_named_params_block_repeat1, 2, 0, 0), SHIFT_REPEAT(1269), + [3218] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1385), + [3220] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1221), + [3222] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1168), + [3224] = {.entry = {.count = 1, .reusable = true}}, SHIFT(64), + [3226] = {.entry = {.count = 1, .reusable = true}}, SHIFT(77), + [3228] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_trait_method_params_repeat1, 2, 0, 0), + [3230] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_trait_method_params_repeat1, 2, 0, 0), SHIFT_REPEAT(1271), + [3233] = {.entry = {.count = 1, .reusable = true}}, SHIFT(147), + [3235] = {.entry = {.count = 1, .reusable = true}}, SHIFT(70), + [3237] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat2, 2, 0, 0), + [3239] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_for_pattern_repeat2, 2, 0, 0), SHIFT_REPEAT(1004), + [3242] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1387), + [3244] = {.entry = {.count = 1, .reusable = true}}, SHIFT(612), + [3246] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1219), + [3248] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1495), + [3250] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1173), + [3252] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1179), + [3254] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1629), + [3256] = {.entry = {.count = 1, .reusable = true}}, SHIFT(995), + [3258] = {.entry = {.count = 1, .reusable = true}}, SHIFT(769), + [3260] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 1, 0, 0), + [3262] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1035), + [3264] = {.entry = {.count = 1, .reusable = true}}, SHIFT(153), + [3266] = {.entry = {.count = 1, .reusable = true}}, SHIFT(678), + [3268] = {.entry = {.count = 1, .reusable = true}}, SHIFT(680), + [3270] = {.entry = {.count = 1, .reusable = true}}, SHIFT(127), + [3272] = {.entry = {.count = 1, .reusable = true}}, SHIFT(150), + [3274] = {.entry = {.count = 1, .reusable = true}}, SHIFT(837), + [3276] = {.entry = {.count = 1, .reusable = true}}, SHIFT(838), + [3278] = {.entry = {.count = 1, .reusable = true}}, SHIFT(138), + [3280] = {.entry = {.count = 1, .reusable = true}}, SHIFT(155), + [3282] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_import_statement_repeat1, 2, 0, 0), + [3284] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_import_statement_repeat1, 2, 0, 0), SHIFT_REPEAT(1297), + [3287] = {.entry = {.count = 1, .reusable = true}}, SHIFT(524), + [3289] = {.entry = {.count = 1, .reusable = true}}, SHIFT(553), + [3291] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1004), + [3293] = {.entry = {.count = 1, .reusable = true}}, SHIFT(123), + [3295] = {.entry = {.count = 1, .reusable = true}}, SHIFT(703), + [3297] = {.entry = {.count = 1, .reusable = true}}, SHIFT(702), + [3299] = {.entry = {.count = 1, .reusable = true}}, SHIFT(706), + [3301] = {.entry = {.count = 1, .reusable = true}}, SHIFT(121), + [3303] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1206), + [3305] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__parameter_list, 1, 0, 0), + [3307] = {.entry = {.count = 1, .reusable = true}}, SHIFT(554), + [3309] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1001), + [3311] = {.entry = {.count = 1, .reusable = true}}, SHIFT(143), + [3313] = {.entry = {.count = 1, .reusable = true}}, SHIFT(128), + [3315] = {.entry = {.count = 1, .reusable = true}}, SHIFT(71), + [3317] = {.entry = {.count = 1, .reusable = true}}, SHIFT(129), + [3319] = {.entry = {.count = 1, .reusable = true}}, SHIFT(839), + [3321] = {.entry = {.count = 1, .reusable = true}}, SHIFT(845), + [3323] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_map_expression_repeat1, 2, 0, 0), + [3325] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_map_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(151), + [3328] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_macro_export_repeat1, 2, 0, 0), + [3330] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_macro_export_repeat1, 2, 0, 0), SHIFT_REPEAT(1299), + [3333] = {.entry = {.count = 1, .reusable = true}}, SHIFT(846), + [3335] = {.entry = {.count = 2, .reusable = true}}, REDUCE(sym_parameter, 3, 0, 11), SHIFT(767), + [3338] = {.entry = {.count = 1, .reusable = true}}, SHIFT(78), + [3340] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym__parameter_list_repeat1, 2, 0, 0), + [3342] = {.entry = {.count = 1, .reusable = true}}, SHIFT(152), + [3344] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__parameter_list_repeat1, 2, 0, 0), SHIFT_REPEAT(1272), + [3347] = {.entry = {.count = 1, .reusable = true}}, SHIFT(954), + [3349] = {.entry = {.count = 1, .reusable = true}}, SHIFT(175), + [3351] = {.entry = {.count = 1, .reusable = true}}, SHIFT(378), + [3353] = {.entry = {.count = 1, .reusable = true}}, SHIFT(139), + [3355] = {.entry = {.count = 1, .reusable = true}}, SHIFT(144), + [3357] = {.entry = {.count = 1, .reusable = true}}, SHIFT(715), + [3359] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1216), + [3361] = {.entry = {.count = 1, .reusable = true}}, SHIFT(871), + [3363] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1166), + [3365] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1199), + [3367] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1217), + [3369] = {.entry = {.count = 1, .reusable = true}}, SHIFT(134), + [3371] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_param, 1, 0, 0), + [3373] = {.entry = {.count = 1, .reusable = true}}, SHIFT(801), + [3375] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1188), + [3377] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method_params, 1, 0, 0), + [3379] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1176), + [3381] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__parameter_list_repeat1, 2, 0, 0), SHIFT_REPEAT(1262), + [3384] = {.entry = {.count = 1, .reusable = true}}, SHIFT(236), + [3386] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field, 1, 0, 0), + [3388] = {.entry = {.count = 1, .reusable = true}}, SHIFT(806), + [3390] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1175), + [3392] = {.entry = {.count = 1, .reusable = true}}, SHIFT(156), + [3394] = {.entry = {.count = 1, .reusable = true}}, SHIFT(544), + [3396] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1167), + [3398] = {.entry = {.count = 1, .reusable = true}}, SHIFT(774), + [3400] = {.entry = {.count = 1, .reusable = true}}, SHIFT(584), + [3402] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1137), + [3404] = {.entry = {.count = 1, .reusable = true}}, SHIFT(585), + [3406] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1222), + [3408] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1177), + [3410] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat2, 2, 0, 0), + [3412] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym__argument_list_repeat2, 2, 0, 0), SHIFT_REPEAT(1250), + [3415] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_struct_definition_repeat1, 2, 0, 0), + [3417] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_struct_definition_repeat1, 2, 0, 0), SHIFT_REPEAT(1323), + [3420] = {.entry = {.count = 1, .reusable = true}}, SHIFT(622), + [3422] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1171), + [3424] = {.entry = {.count = 1, .reusable = true}}, SHIFT(775), + [3426] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1228), + [3428] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1196), + [3430] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1039), + [3432] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1573), + [3434] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1224), + [3436] = {.entry = {.count = 1, .reusable = true}}, SHIFT(545), + [3438] = {.entry = {.count = 1, .reusable = true}}, SHIFT(133), + [3440] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1225), + [3442] = {.entry = {.count = 1, .reusable = true}}, SHIFT(997), + [3444] = {.entry = {.count = 1, .reusable = true}}, SHIFT(614), + [3446] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_select_case, 5, 0, 0), + [3448] = {.entry = {.count = 1, .reusable = true}}, SHIFT(762), + [3450] = {.entry = {.count = 1, .reusable = true}}, SHIFT(785), + [3452] = {.entry = {.count = 1, .reusable = true}}, SHIFT(503), + [3454] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1207), + [3456] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_param, 5, 0, 2), + [3458] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 6, 0, 17), + [3460] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1455), + [3462] = {.entry = {.count = 1, .reusable = true}}, SHIFT(800), + [3464] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 7, 0, 17), + [3466] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_for_pattern_entry, 3, 0, 0), + [3468] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 8, 0, 17), + [3470] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_import_item, 3, 0, 0), + [3472] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_argument, 3, 0, 19), + [3474] = {.entry = {.count = 1, .reusable = true}}, SHIFT(201), + [3476] = {.entry = {.count = 1, .reusable = true}}, SHIFT(637), + [3478] = {.entry = {.count = 1, .reusable = true}}, SHIFT(538), + [3480] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1446), + [3482] = {.entry = {.count = 1, .reusable = true}}, SHIFT(87), + [3484] = {.entry = {.count = 1, .reusable = true}}, SHIFT(185), + [3486] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_macro_export_item, 3, 0, 0), + [3488] = {.entry = {.count = 1, .reusable = true}}, SHIFT(613), + [3490] = {.entry = {.count = 1, .reusable = true}}, SHIFT(258), + [3492] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_map_entry, 3, 0, 10), + [3494] = {.entry = {.count = 1, .reusable = true}}, SHIFT(970), + [3496] = {.entry = {.count = 1, .reusable = true}}, SHIFT(697), + [3498] = {.entry = {.count = 1, .reusable = true}}, SHIFT(234), + [3500] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1378), + [3502] = {.entry = {.count = 1, .reusable = true}}, SHIFT(813), + [3504] = {.entry = {.count = 1, .reusable = true}}, SHIFT(656), + [3506] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1414), + [3508] = {.entry = {.count = 1, .reusable = true}}, SHIFT(180), + [3510] = {.entry = {.count = 1, .reusable = true}}, SHIFT(187), + [3512] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1253), + [3514] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1561), + [3516] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1331), + [3518] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1539), + [3520] = {.entry = {.count = 1, .reusable = true}}, SHIFT(868), + [3522] = {.entry = {.count = 1, .reusable = true}}, SHIFT(269), + [3524] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_struct_field_init, 3, 0, 19), + [3526] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_trait_method, 5, 0, 17), + [3528] = {.entry = {.count = 1, .reusable = true}}, SHIFT(565), + [3530] = {.entry = {.count = 1, .reusable = true}}, SHIFT(607), + [3532] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1371), + [3534] = {.entry = {.count = 1, .reusable = true}}, SHIFT(562), + [3536] = {.entry = {.count = 1, .reusable = true}}, SHIFT(836), + [3538] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1019), + [3540] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1140), + [3542] = {.entry = {.count = 1, .reusable = true}}, SHIFT(766), + [3544] = {.entry = {.count = 1, .reusable = true}}, SHIFT(192), + [3546] = {.entry = {.count = 1, .reusable = true}}, SHIFT(176), + [3548] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1189), + [3550] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1161), + [3552] = {.entry = {.count = 1, .reusable = true}}, SHIFT(69), + [3554] = {.entry = {.count = 1, .reusable = true}}, SHIFT(632), + [3556] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1010), + [3558] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1426), + [3560] = {.entry = {.count = 1, .reusable = true}}, SHIFT(768), + [3562] = {.entry = {.count = 1, .reusable = true}}, SHIFT(882), + [3564] = {.entry = {.count = 1, .reusable = true}}, SHIFT(644), + [3566] = {.entry = {.count = 1, .reusable = true}}, SHIFT(696), + [3568] = {.entry = {.count = 1, .reusable = true}}, SHIFT(883), + [3570] = {.entry = {.count = 1, .reusable = true}}, SHIFT(699), + [3572] = {.entry = {.count = 1, .reusable = true}}, SHIFT(874), + [3574] = {.entry = {.count = 1, .reusable = true}}, SHIFT(628), + [3576] = {.entry = {.count = 1, .reusable = true}}, SHIFT(765), + [3578] = {.entry = {.count = 1, .reusable = true}}, SHIFT(205), + [3580] = {.entry = {.count = 1, .reusable = true}}, SHIFT(797), + [3582] = {.entry = {.count = 1, .reusable = true}}, SHIFT(131), + [3584] = {.entry = {.count = 1, .reusable = true}}, SHIFT(802), + [3586] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1146), + [3588] = {.entry = {.count = 1, .reusable = true}}, SHIFT(184), + [3590] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1131), + [3592] = {.entry = {.count = 1, .reusable = true}}, SHIFT(232), + [3594] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1100), + [3596] = {.entry = {.count = 1, .reusable = true}}, SHIFT(57), + [3598] = {.entry = {.count = 1, .reusable = true}}, SHIFT(642), + [3600] = {.entry = {.count = 1, .reusable = true}}, SHIFT(164), + [3602] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1388), + [3604] = {.entry = {.count = 1, .reusable = true}}, SHIFT(9), + [3606] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_params_block, 3, 0, 0), + [3608] = {.entry = {.count = 1, .reusable = true}}, SHIFT(7), + [3610] = {.entry = {.count = 1, .reusable = true}}, SHIFT(167), + [3612] = {.entry = {.count = 1, .reusable = true}}, SHIFT(639), + [3614] = {.entry = {.count = 1, .reusable = true}}, SHIFT(190), + [3616] = {.entry = {.count = 1, .reusable = true}}, SHIFT(168), + [3618] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1034), + [3620] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1252), + [3622] = {.entry = {.count = 1, .reusable = true}}, SHIFT(199), + [3624] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1012), + [3626] = {.entry = {.count = 1, .reusable = true}}, SHIFT(572), + [3628] = {.entry = {.count = 1, .reusable = true}}, SHIFT(573), + [3630] = {.entry = {.count = 1, .reusable = true}}, SHIFT(574), + [3632] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), + [3634] = {.entry = {.count = 1, .reusable = true}}, SHIFT(671), + [3636] = {.entry = {.count = 1, .reusable = true}}, SHIFT(672), + [3638] = {.entry = {.count = 1, .reusable = true}}, SHIFT(673), + [3640] = {.entry = {.count = 1, .reusable = true}}, SHIFT(663), + [3642] = {.entry = {.count = 1, .reusable = true}}, SHIFT(617), + [3644] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1082), + [3646] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 5, 0, 0), + [3648] = {.entry = {.count = 1, .reusable = true}}, SHIFT(507), + [3650] = {.entry = {.count = 1, .reusable = true}}, SHIFT(243), + [3652] = {.entry = {.count = 1, .reusable = true}}, SHIFT(519), + [3654] = {.entry = {.count = 1, .reusable = true}}, SHIFT(520), + [3656] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1204), + [3658] = {.entry = {.count = 1, .reusable = true}}, SHIFT(136), + [3660] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1013), + [3662] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1160), + [3664] = {.entry = {.count = 1, .reusable = true}}, SHIFT(204), + [3666] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1238), + [3668] = {.entry = {.count = 1, .reusable = true}}, SHIFT(58), + [3670] = {.entry = {.count = 1, .reusable = true}}, SHIFT(99), + [3672] = {.entry = {.count = 1, .reusable = true}}, SHIFT(194), + [3674] = {.entry = {.count = 1, .reusable = true}}, SHIFT(158), + [3676] = {.entry = {.count = 1, .reusable = true}}, SHIFT(879), + [3678] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1410), + [3680] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1422), + [3682] = {.entry = {.count = 1, .reusable = true}}, SHIFT(4), + [3684] = {.entry = {.count = 1, .reusable = true}}, SHIFT(616), + [3686] = {.entry = {.count = 1, .reusable = true}}, SHIFT(210), + [3688] = {.entry = {.count = 1, .reusable = true}}, SHIFT(171), + [3690] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1395), + [3692] = {.entry = {.count = 1, .reusable = true}}, SHIFT(169), + [3694] = {.entry = {.count = 1, .reusable = true}}, SHIFT(213), + [3696] = {.entry = {.count = 1, .reusable = true}}, SHIFT(679), + [3698] = {.entry = {.count = 1, .reusable = true}}, SHIFT(214), + [3700] = {.entry = {.count = 1, .reusable = true}}, SHIFT(215), + [3702] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_params_block, 5, 0, 0), + [3704] = {.entry = {.count = 1, .reusable = true}}, SHIFT(576), + [3706] = {.entry = {.count = 1, .reusable = true}}, SHIFT(714), + [3708] = {.entry = {.count = 1, .reusable = true}}, SHIFT(212), + [3710] = {.entry = {.count = 1, .reusable = true}}, SHIFT(605), + [3712] = {.entry = {.count = 1, .reusable = true}}, SHIFT(222), + [3714] = {.entry = {.count = 1, .reusable = true}}, SHIFT(223), + [3716] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1178), + [3718] = {.entry = {.count = 1, .reusable = true}}, SHIFT(203), + [3720] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1436), + [3722] = {.entry = {.count = 1, .reusable = true}}, SHIFT(202), + [3724] = {.entry = {.count = 1, .reusable = true}}, SHIFT(505), + [3726] = {.entry = {.count = 1, .reusable = true}}, SHIFT(172), + [3728] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1016), + [3730] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1447), + [3732] = {.entry = {.count = 1, .reusable = true}}, SHIFT(566), + [3734] = {.entry = {.count = 1, .reusable = true}}, SHIFT(567), + [3736] = {.entry = {.count = 1, .reusable = true}}, SHIFT(873), + [3738] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1122), + [3740] = {.entry = {.count = 1, .reusable = true}}, SHIFT(773), + [3742] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1452), + [3744] = {.entry = {.count = 1, .reusable = true}}, SHIFT(183), + [3746] = {.entry = {.count = 1, .reusable = true}}, SHIFT(3), + [3748] = {.entry = {.count = 1, .reusable = true}}, SHIFT(235), + [3750] = {.entry = {.count = 1, .reusable = true}}, SHIFT(125), + [3752] = {.entry = {.count = 1, .reusable = true}}, SHIFT(772), + [3754] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1005), + [3756] = {.entry = {.count = 1, .reusable = true}}, SHIFT(211), + [3758] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1008), + [3760] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1014), + [3762] = {.entry = {.count = 1, .reusable = true}}, SHIFT(624), + [3764] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1192), + [3766] = {.entry = {.count = 1, .reusable = true}}, SHIFT(659), + [3768] = {.entry = {.count = 1, .reusable = true}}, SHIFT(93), + [3770] = {.entry = {.count = 1, .reusable = true}}, SHIFT(159), + [3772] = {.entry = {.count = 1, .reusable = true}}, SHIFT(242), + [3774] = {.entry = {.count = 1, .reusable = true}}, SHIFT(580), + [3776] = {.entry = {.count = 1, .reusable = true}}, SHIFT(946), + [3778] = {.entry = {.count = 1, .reusable = true}}, SHIFT(2), + [3780] = {.entry = {.count = 1, .reusable = true}}, SHIFT(241), + [3782] = {.entry = {.count = 1, .reusable = true}}, SHIFT(867), + [3784] = {.entry = {.count = 1, .reusable = true}}, SHIFT(94), + [3786] = {.entry = {.count = 1, .reusable = true}}, SHIFT(196), + [3788] = {.entry = {.count = 1, .reusable = true}}, SHIFT(130), + [3790] = {.entry = {.count = 1, .reusable = true}}, SHIFT(869), + [3792] = {.entry = {.count = 1, .reusable = true}}, SHIFT(88), + [3794] = {.entry = {.count = 1, .reusable = true}}, SHIFT(165), + [3796] = {.entry = {.count = 1, .reusable = true}}, SHIFT(686), + [3798] = {.entry = {.count = 1, .reusable = true}}, SHIFT(687), + [3800] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1201), + [3802] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1202), + [3804] = {.entry = {.count = 1, .reusable = true}}, SHIFT(95), + [3806] = {.entry = {.count = 1, .reusable = true}}, SHIFT(633), + [3808] = {.entry = {.count = 1, .reusable = true}}, SHIFT(619), + [3810] = {.entry = {.count = 1, .reusable = true}}, SHIFT(251), + [3812] = {.entry = {.count = 1, .reusable = true}}, SHIFT(252), + [3814] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1198), + [3816] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1194), + [3818] = {.entry = {.count = 1, .reusable = true}}, SHIFT(541), + [3820] = {.entry = {.count = 1, .reusable = true}}, SHIFT(257), + [3822] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1105), + [3824] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1163), + [3826] = {.entry = {.count = 1, .reusable = true}}, SHIFT(259), + [3828] = {.entry = {.count = 1, .reusable = true}}, SHIFT(542), + [3830] = {.entry = {.count = 1, .reusable = true}}, SHIFT(262), + [3832] = {.entry = {.count = 1, .reusable = true}}, SHIFT(263), + [3834] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1205), + [3836] = {.entry = {.count = 1, .reusable = true}}, SHIFT(174), + [3838] = {.entry = {.count = 1, .reusable = true}}, SHIFT(268), + [3840] = {.entry = {.count = 1, .reusable = true}}, SHIFT(5), + [3842] = {.entry = {.count = 1, .reusable = true}}, SHIFT(270), + [3844] = {.entry = {.count = 1, .reusable = true}}, SHIFT(601), + [3846] = {.entry = {.count = 1, .reusable = true}}, SHIFT(86), + [3848] = {.entry = {.count = 1, .reusable = true}}, SHIFT(594), + [3850] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1234), + [3852] = {.entry = {.count = 1, .reusable = true}}, SHIFT(160), + [3854] = {.entry = {.count = 1, .reusable = true}}, SHIFT(246), + [3856] = {.entry = {.count = 1, .reusable = true}}, SHIFT(489), + [3858] = {.entry = {.count = 1, .reusable = true}}, SHIFT(640), + [3860] = {.entry = {.count = 1, .reusable = true}}, SHIFT(776), + [3862] = {.entry = {.count = 1, .reusable = true}}, SHIFT(808), + [3864] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6), + [3866] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1021), + [3868] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1235), + [3870] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1069), + [3872] = {.entry = {.count = 1, .reusable = true}}, SHIFT(8), + [3874] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_params, 4, 0, 0), + [3876] = {.entry = {.count = 1, .reusable = true}}, SHIFT(596), + [3878] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_named_params_block, 4, 0, 0), + [3880] = {.entry = {.count = 1, .reusable = true}}, SHIFT(560), + [3882] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1357), + [3884] = {.entry = {.count = 1, .reusable = true}}, SHIFT(603), + [3886] = {.entry = {.count = 1, .reusable = true}}, SHIFT(161), + [3888] = {.entry = {.count = 1, .reusable = true}}, SHIFT(282), + [3890] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1017), + [3892] = {.entry = {.count = 1, .reusable = true}}, SHIFT(600), + [3894] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1237), + [3896] = {.entry = {.count = 1, .reusable = true}}, SHIFT(292), + [3898] = {.entry = {.count = 1, .reusable = true}}, SHIFT(872), + [3900] = {.entry = {.count = 1, .reusable = true}}, SHIFT(1448), + [3902] = {.entry = {.count = 1, .reusable = true}}, SHIFT(294), + [3904] = {.entry = {.count = 1, .reusable = true}}, SHIFT(295), + [3906] = {.entry = {.count = 1, .reusable = true}}, SHIFT(296), }; #ifdef __cplusplus diff --git a/ecosystem/vsc-ext/lsp/README.md b/ecosystem/vsc-ext/lsp/README.md index 9c210ac6..3e5412dd 100644 --- a/ecosystem/vsc-ext/lsp/README.md +++ b/ecosystem/vsc-ext/lsp/README.md @@ -93,14 +93,15 @@ The previous standalone `lk-highlight` extension has been merged into this packa 2. Install dependencies: `npm --prefix ecosystem/vsc-ext/lsp install` 3. Compile the extension: `npm --prefix ecosystem/vsc-ext/lsp run compile` 4. Build the LK LSP server: `cargo build -p lk-lsp` -5. Run `make install` to install the LK CLI, `lk-lsp`, and the packaged VSIX into VS Code. Run `make debug-lsp-ext` to open an Extension Development Host, or run `make vsix` to only build the single VSIX package. In an interactive shell, `make vsix` asks whether to install the generated VSIX with VS Code's CLI; use `make vsix INSTALL_VSIX=1` to install without prompting, or `make vsix INSTALL_VSIX=1 VSCODE_CLI=/path/to/code` when the CLI is not on `PATH`. If VS Code refuses to reinstall an active extension, restart VS Code and rerun the printed install command. +5. Run `make install` to install the LK CLI, `lk-lsp`, and the packaged VSIX. The VSIX step installs into every VS Code-family editor it finds — VS Code, Insiders, VSCodium, Cursor, Windsurf — on macOS, Linux and Windows, and prefers the remote server's own CLI under WSL / SSH remote / devcontainers so the extension lands on the same side as `lk-lsp` (it falls back to the server's offline `code-server` CLI when no window is attached). Use `make vsix` to only build the package, `make install-vsix` to build and install it, `make install-vsix VSCODE_CLI=/path/to/code` to target one specific editor, and `make debug-lsp-ext` to open an Extension Development Host. ## Development - `npm run compile`: Compile the TypeScript source - `npm run watch`: Compile in watch mode -- `make install`: Install the LK CLI, `lk-lsp`, and the VS Code extension package -- `make vsix`: Build the merged VS Code extension package from `ecosystem/vsc-ext/lsp` and prompt to install the generated VSIX +- `make install`: Install the LK CLI, `lk-lsp`, the VS Code extension, and print the Zed dev-extension step (`install-cli` / `install-lsp` / `install-vsix` / `install-zed` run the steps individually) +- `make vsix`: Build the merged VS Code extension package from `ecosystem/vsc-ext/lsp` +- `make install-vsix`: Build the package and install it into every VS Code-family editor found - `make debug-lsp-ext`: Launch VS Code with the merged extension and a repo-local `lk-lsp` ## LK Language Features diff --git a/ecosystem/vsc-ext/lsp/package.json b/ecosystem/vsc-ext/lsp/package.json index 7f9e66b6..aba70630 100644 --- a/ecosystem/vsc-ext/lsp/package.json +++ b/ecosystem/vsc-ext/lsp/package.json @@ -217,29 +217,12 @@ "minimum": 1, "description": "Max concurrent heavy computations on the server (semantic tokens, inlay hints). Lower to reduce CPU spikes when scrolling." }, - "lk.lsp.performance.rangeTokenCacheLimit": { - "type": "number", - "default": 64, - "minimum": 1, - "description": "Per-document capacity for semantic tokens range cache (version+range)." - }, - "lk.lsp.performance.inlayHintCacheLimit": { - "type": "number", - "default": 64, - "minimum": 1, - "description": "Per-document capacity for inlay hints cache (version+range+settings)." - }, "lk.lsp.performance.inlayScanMarginLines": { "type": "number", "default": 3, "minimum": 0, "description": "Additional lines to scan above/below the visible range when computing parameter inlay hints (captures multi-line calls)." }, - "lk.lsp.performance.enableCaching": { - "type": "boolean", - "default": true, - "description": "Enable extension-side caching for semantic tokens (range) and inlay hints keyed by document version and range to reduce redundant requests." - }, "lk.lsp.performance.skipStaleResults": { "type": "boolean", "default": true, diff --git a/ecosystem/vsc-ext/lsp/src/extension.ts b/ecosystem/vsc-ext/lsp/src/extension.ts index 074efb07..5e835b3a 100644 --- a/ecosystem/vsc-ext/lsp/src/extension.ts +++ b/ecosystem/vsc-ext/lsp/src/extension.ts @@ -99,39 +99,6 @@ async function openLkLocation(target: any) { await vscode.window.showTextDocument(uri, { selection }); } -// Simple LRU cache for perf-sensitive middleware -class LRU { - private map = new Map(); - constructor(public capacity: number) {} - get(key: K): V | undefined { - if (!this.map.has(key)) return undefined; - const val = this.map.get(key)!; - this.map.delete(key); - this.map.set(key, val); - return val; - } - set(key: K, value: V) { - if (this.map.has(key)) this.map.delete(key); - this.map.set(key, value); - if (this.map.size > this.capacity) { - const iter = this.map.keys().next(); - if (!iter.done) { - this.map.delete(iter.value as K); - } - } - } - has(key: K): boolean { return this.map.has(key); } - clear() { this.map.clear(); } - setCapacity(n: number) { - this.capacity = Math.max(1, Math.floor(n || 1)); - while (this.map.size > this.capacity) { - const iter = this.map.keys().next(); - if (iter.done) break; - this.map.delete(iter.value as K); - } - } -} - // Runtime settings snapshot (kept in sync with workspace configuration) const runtime = { semanticTokensEnabled: true, @@ -388,12 +355,12 @@ export function activate(context: vscode.ExtensionContext) { // Keyed in-flight promises to dedupe identical requests const dedupeTokenRange = new Map>(); const dedupeInlay = new Map>(); - // Caches (LRU) - const rangeTokenCacheLimit = Math.max(1, Number(config.get('performance.rangeTokenCacheLimit', 64)) || 64); - const inlayHintCacheLimit = Math.max(1, Number(config.get('performance.inlayHintCacheLimit', 64)) || 64); - let enableCaching = config.get('performance.enableCaching', true); - const tokensCache = new LRU(rangeTokenCacheLimit); - const inlayCache = new LRU(inlayHintCacheLimit); + // No result cache here: the server keeps one per document, and a second + // copy keyed on this document's version cannot see a *dependency* change — + // editing an imported file leaves this version untouched, so a cached hint + // would keep showing a type read out of the old file. The server tells us + // when that happens (`workspace/inlayHint/refresh`), and that signal is + // exactly what a cache down here would swallow. let skipStaleResults = config.get('performance.skipStaleResults', true); const settings = { semanticTokensEnabled, throttleMs }; @@ -443,15 +410,6 @@ export function activate(context: vscode.ExtensionContext) { } lastTokenReqAt.set(key, now); } - // Cache key for full tokens (by version) - const cacheKey = `${key}#v${reqVersion}#FULL`; - if (enableCaching) { - const cached = tokensCache.get(cacheKey); - if (cached !== undefined) { - endChecking(); - return cached as any; - } - } tokenInFlight.add(key); const result = withTiming('middleware.semanticTokens(full)', () => next(document, token)); if (result && typeof (result as any).then === 'function') { @@ -459,13 +417,11 @@ export function activate(context: vscode.ExtensionContext) { .then(res => { if (token?.isCancellationRequested) return null as any; if (skipStaleResults && document.version !== reqVersion) return null as any; - if (enableCaching) tokensCache.set(cacheKey, res as any); return res; }) .finally(() => { tokenInFlight.delete(key); endChecking(); }); } else { try { - if (enableCaching) tokensCache.set(cacheKey, result as any); return result as any; } finally { tokenInFlight.delete(key); @@ -504,13 +460,6 @@ export function activate(context: vscode.ExtensionContext) { } const rKey = `${range.start.line}:${range.start.character}-${range.end.line}:${range.end.character}`; const cacheKey = `${key}#v${reqVersion}#R#${rKey}`; - if (enableCaching) { - const cached = tokensCache.get(cacheKey); - if (cached !== undefined) { - endChecking(); - return cached as any; - } - } if (dedupeTokenRange.has(cacheKey)) { return dedupeTokenRange.get(cacheKey)! as any; } @@ -519,7 +468,6 @@ export function activate(context: vscode.ExtensionContext) { const wrapped = p.then(res => { if (token?.isCancellationRequested) return null as any; if (skipStaleResults && document.version !== reqVersion) return null as any; - if (enableCaching) tokensCache.set(cacheKey, res as any); return res; }).finally(() => { tokenInFlight.delete(key); dedupeTokenRange.delete(cacheKey); endChecking(); }); dedupeTokenRange.set(cacheKey, wrapped); @@ -536,12 +484,6 @@ export function activate(context: vscode.ExtensionContext) { const rKey = `${range.start.line}:${range.start.character}-${range.end.line}:${range.end.character}`; const settingsKey = `${Number(runtime.inlayHintsShowParameters)}:${Number(runtime.inlayHintsShowTypes)}`; const cacheKey = `${key}#v${reqVersion}#I#${rKey}#${settingsKey}`; - if (enableCaching) { - const cached = inlayCache.get(cacheKey); - if (cached !== undefined) { - return cached as any; - } - } if (dedupeInlay.has(cacheKey)) { return dedupeInlay.get(cacheKey)! as any; } @@ -563,18 +505,14 @@ export function activate(context: vscode.ExtensionContext) { .then(v => { if (token?.isCancellationRequested) return null; if (skipStaleResults && document.version !== reqVersion) return null; - const filtered = filter(v); - if (enableCaching) inlayCache.set(cacheKey, filtered as any); - return filtered; + return filter(v); }) .finally(() => { dedupeInlay.delete(cacheKey); endChecking(); }); dedupeInlay.set(cacheKey, p as any); return p as any; } else { try { - const filtered = filter(res as any); - if (enableCaching) inlayCache.set(cacheKey, filtered as any); - return filtered; + return filter(res as any); } finally { endChecking(); } @@ -611,13 +549,9 @@ export function activate(context: vscode.ExtensionContext) { // Perf tracing settings perfTraceSteps = cfg.get('performance.traceSteps', false); perfThresholdMs = Math.max(0, Number(cfg.get('performance.traceThresholdMs', 0)) || 0); - // Cache and stale handling settings - enableCaching = cfg.get('performance.enableCaching', true); + // Stale-response handling. There is no result cache to configure: the + // server owns that. skipStaleResults = cfg.get('performance.skipStaleResults', true); - const newTokenCap = Math.max(1, Number(cfg.get('performance.rangeTokenCacheLimit', 64)) || 64); - const newInlayCap = Math.max(1, Number(cfg.get('performance.inlayHintCacheLimit', 64)) || 64); - tokensCache.setCapacity(newTokenCap); - inlayCache.setCapacity(newInlayCap); runtime.checkingDelayMs = Math.max(0, Number(cfg.get('ui.checkingDelayMs', 120)) || 120); // Soft nudge so users see status change quickly nudgeChecking(); diff --git a/ecosystem/vsc-ext/lsp/syntaxes/lk.tmLanguage.json b/ecosystem/vsc-ext/lsp/syntaxes/lk.tmLanguage.json index e818ceee..ac09b70f 100644 --- a/ecosystem/vsc-ext/lsp/syntaxes/lk.tmLanguage.json +++ b/ecosystem/vsc-ext/lsp/syntaxes/lk.tmLanguage.json @@ -501,7 +501,11 @@ "patterns": [ { "name": "support.type.primitive.lk", - "match": "\\b(Int|Float|String|Bool|Nil|Any|List|Map|Task|Channel)\\b" + "match": "\\b(Int|Float|Number|String|Bool|Nil|Any|List|Map|Set|Tuple|Task|Channel|Box|Boxed)\\b" + }, + { + "name": "support.type.primitive.lk", + "match": "\\b(i8|i16|i32|i64|u8|u16|u32|u64|isize|usize|f64)\\b" } ] }, diff --git a/ecosystem/zed-ext/src/lib.rs b/ecosystem/zed-ext/src/lib.rs index f7722b65..0ef7d434 100644 --- a/ecosystem/zed-ext/src/lib.rs +++ b/ecosystem/zed-ext/src/lib.rs @@ -51,7 +51,7 @@ fn find_lk_lsp(worktree: &zed::Worktree) -> Result { let executable = if cfg!(windows) { "lk-lsp.exe" } else { "lk-lsp" }; for path in repo_candidate_paths(worktree, executable) { - if is_executable_file(&path) { + if is_regular_file(&path) { return Ok(path.display().to_string()); } } @@ -65,6 +65,16 @@ fn find_lk_lsp(worktree: &zed::Worktree) -> Result { )) } +/// Where to look for the server, in order. +/// +/// A build inside the worktree wins over an installed one on purpose: the +/// people opening this repository in Zed are the ones changing the server, and +/// they expect the binary they just built. `debug` before `release` for the +/// same reason — `cargo build` is what an edit-test loop runs. +/// +/// The cost is that someone who only *uses* LK, but happens to have a stale +/// `target/debug/lk-lsp` lying around, gets that instead of what they +/// installed. `lsp.lk-lsp.binary.path` in Zed settings overrides all of this. fn repo_candidate_paths(worktree: &zed::Worktree, executable: &str) -> Vec { let root = PathBuf::from(worktree.root_path()); let mut paths = Vec::new(); @@ -83,7 +93,14 @@ fn repo_candidate_paths(worktree: &zed::Worktree, executable: &str) -> Vec bool { +/// Whether `path` is a regular file — *not* whether it can be executed. +/// +/// The extension is compiled to `wasm32-wasip1`, where `target_family` is +/// `wasm` rather than `unix`, so `PermissionsExt` and the executable bit are +/// out of reach. A non-executable file with the right name is therefore +/// selected here and fails when Zed tries to spawn it; the previous name for +/// this function claimed a check it never made. +fn is_regular_file(path: &Path) -> bool { std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file()) } diff --git a/examples/general/concurrency_demo.lk b/examples/general/concurrency_demo.lk index f473c8d7..f11a82e3 100644 --- a/examples/general/concurrency_demo.lk +++ b/examples/general/concurrency_demo.lk @@ -51,4 +51,27 @@ assert(task.await(iso) == 1); assert(counter == 0); println("Isolation confirmed: counter still {}", counter); +// The module spelling. `use chan;` would shadow the `chan(…)` global this file +// opened with, so it is aliased — but the members are the same operations under +// a prefix, blocking pair included. +use chan as chans; +let mod_ch = chans.new(2); +chans.send(mod_ch, 41); +assert(chans.try_send(mod_ch, 42)); +assert(!chans.try_send(mod_ch, 43)); // full is `false`, not an error +assert(chans.recv(mod_ch) == 41); +assert(chans.try_recv(mod_ch)! == 42); +assert(chans.len(mod_ch) == 0); +assert(chans.capacity(mod_ch) == 2); +assert(!chans.is_closed(mod_ch)); +chans.close(mod_ch); +assert(chans.is_closed(mod_ch)); + +// `0` is unbuffered, not unbounded: one value fits, the next does not. +let tight = chans.new(0); +assert(chans.try_send(tight, 1)); +assert(!chans.try_send(tight, 2)); +assert(chans.capacity(tight) == 0); // what was asked for, not the bound +println("Module spelling confirmed"); + println("concurrency_demo: completed"); diff --git a/examples/general/config_parser.lk b/examples/general/config_parser.lk index 9fe62881..6fff666d 100644 --- a/examples/general/config_parser.lk +++ b/examples/general/config_parser.lk @@ -26,15 +26,15 @@ assert(t.ssl.cert == "/etc/ssl/cert.pem"); // 4. Merge configs into a unified structure fn build_dsn(db_cfg) { - return "${db_cfg.host}:${db_cfg.port}"; + return "${db_cfg.host}:${db_cfg.port}"; } let dsn = build_dsn(j); assert(dsn == "db.example.com:5432"); // 5. Feature flag check from TOML fn is_feature_enabled(cfg, feature) { - if (!cfg.ssl.has(feature)) { return false; } - return cfg.ssl[feature] == true; + if (!cfg.ssl.has(feature)) { return false; } + return cfg.ssl[feature] == true; } assert(is_feature_enabled(t, "enabled")); diff --git a/examples/general/higher_order.lk b/examples/general/higher_order.lk index cea0d3fa..cf7f36bf 100644 --- a/examples/general/higher_order.lk +++ b/examples/general/higher_order.lk @@ -19,9 +19,9 @@ assert(product == 120); // 4. Chaining: filter → map → reduce let pipeline = nums - .filter(|x| x % 2 != 0) // [1,3,5,7,9] - .map(|x| x * x) // [1,9,25,49,81] - .reduce(0, |a, b| a + b); // 165 + .filter(|x| x % 2 != 0) // [1,3,5,7,9] + .map(|x| x * x) // [1,9,25,49,81] + .reduce(0, |a, b| a + b); // 165 assert(pipeline == 165); // 5. take / skip @@ -56,10 +56,10 @@ assert(zipped == [[1, "x"], [2, "y"], [3, "z"]]); // 12. Custom reduce — find max via helper function fn my_max(a, b) { - if (a > b) { return a; } - return b; + if (a > b) { return a; } + return b; } let max_val = nums.reduce(0, |acc, x| my_max(acc, x)); assert(max_val == 10); -println("higher_order: all assertions passed"); \ No newline at end of file +println("higher_order: all assertions passed"); diff --git a/examples/general/point.lk b/examples/general/point.lk new file mode 100644 index 00000000..58536af1 --- /dev/null +++ b/examples/general/point.lk @@ -0,0 +1,14 @@ +// A module that declares a type is the one that builds it: `point.Pt { … }` is +// sugar for a call to the constructor sitting beside the declaration, so the +// value carries this module's type identity (see docs/semantics.md). +struct Pt { x: Int, y: Int } + +trait Norm { + fn norm(self) -> Int; +} + +impl Norm for Pt { + fn norm(self) -> Int { return self.x + self.y; } +} + +fn manhattan(p: Pt) -> Int { return p.x + p.y; } diff --git a/examples/general/raising.lk b/examples/general/raising.lk new file mode 100644 index 00000000..7b0ac4d7 --- /dev/null +++ b/examples/general/raising.lk @@ -0,0 +1,28 @@ +// A module whose functions raise, for the cross-module half of the error model +// (see examples/syntax/cross_module_raise.lk). Definitions only, like fib.lk. +// +// The payloads here are deliberately the *heap* ones — a long string, a list, a +// map, a struct. An Int or a short string is stored inline in the value, so a +// raise carrying one crosses a module boundary whatever the runtime does with +// handles; those cases stayed correct while these did not. + +struct Failure { code: Int, detail: List } + +fn raise_text() { error("a sufficiently long heap-allocated error payload"); return 0; } + +fn raise_list() { error([7, 8, 9]); return 0; } + +fn raise_map() { error({"code": 42, "rows": [[1, 2], [3]]}); return 0; } + +fn raise_struct() { error(Failure { code: 7, detail: ["a", "b"] }); return 0; } + +// Raised two frames down, so the value crosses the module boundary after it has +// already crossed a call inside this module. +fn raise_nested() { return raise_list(); } + +// Caught here and raised again: the payload leaves on a second raise, from a +// handler rather than from the code that built it. +fn raise_rethrown() { + try { error([1, 2]); } catch caught { error(caught); } + return 0; +} diff --git a/examples/general/recursive.lk b/examples/general/recursive.lk index c7d5e27c..67426bb5 100644 --- a/examples/general/recursive.lk +++ b/examples/general/recursive.lk @@ -2,8 +2,8 @@ // 1. Classic recursive factorial fn factorial(n) { - if (n <= 1) { return 1; } - return n * factorial(n - 1); + if (n <= 1) { return 1; } + return n * factorial(n - 1); } assert(factorial(0) == 1); assert(factorial(1) == 1); @@ -12,9 +12,9 @@ assert(factorial(10) == 3628800); // 2. Recursive Fibonacci (slow but illustrative) fn fib(n) { - if (n <= 0) { return 0; } - if (n == 1) { return 1; } - return fib(n - 1) + fib(n - 2); + if (n <= 0) { return 0; } + if (n == 1) { return 1; } + return fib(n - 1) + fib(n - 2); } assert(fib(0) == 0); assert(fib(1) == 1); @@ -23,16 +23,16 @@ assert(fib(10) == 55); // 3. Recursive list sum fn list_sum(xs) { - if (xs.len() == 0) { return 0; } - return xs[0] + list_sum(xs.skip(1)); + if (xs.len() == 0) { return 0; } + return xs[0] + list_sum(xs.skip(1)); } assert(list_sum([]) == 0); assert(list_sum([1, 2, 3, 4]) == 10); // 4. Recursive GCD (Euclidean algorithm) fn gcd(a, b) { - if (b == 0) { return a; } - return gcd(b, a % b); + if (b == 0) { return a; } + return gcd(b, a % b); } assert(gcd(12, 8) == 4); assert(gcd(100, 75) == 25); @@ -40,8 +40,8 @@ assert(gcd(17, 13) == 1); // 5. Recursive power (simple linear recursion) fn power(base, exp) { - if (exp == 0) { return 1; } - return base * power(base, exp - 1); + if (exp == 0) { return 1; } + return base * power(base, exp - 1); } assert(power(2, 0) == 1); assert(power(2, 10) == 1024); @@ -49,17 +49,17 @@ assert(power(3, 4) == 81); // 6. Recursive list contains fn contains(xs, target) { - if (xs.len() == 0) { return false; } - if (xs[0] == target) { return true; } - return contains(xs.skip(1), target); + if (xs.len() == 0) { return false; } + if (xs[0] == target) { return true; } + return contains(xs.skip(1), target); } assert(contains([1, 3, 5, 7], 5)); assert(!contains([1, 3, 5, 7], 4)); // 7. Recursive length fn length(xs) { - if (xs.len() == 0) { return 0; } - return 1 + length(xs.skip(1)); + if (xs.len() == 0) { return 0; } + return 1 + length(xs.skip(1)); } assert(length([]) == 0); assert(length([1, 2, 3]) == 3); diff --git a/examples/general/sort_search.lk b/examples/general/sort_search.lk index 7a2a91f2..842eecaa 100644 --- a/examples/general/sort_search.lk +++ b/examples/general/sort_search.lk @@ -2,15 +2,15 @@ // 1. Insertion sort (using reduce to avoid if-block scoping issues) fn insert_sorted(sorted, item) { - let i = 0; - while (i < sorted.len() && sorted[i] < item) { - i += 1; - } - return sorted.take(i).concat([item]).concat(sorted.skip(i)); + let i = 0; + while (i < sorted.len() && sorted[i] < item) { + i += 1; + } + return sorted.take(i).concat([item]).concat(sorted.skip(i)); } fn insertion_sort(xs) { - return xs.reduce([], |sorted, item| insert_sorted(sorted, item)); + return xs.reduce([], |sorted, item| insert_sorted(sorted, item)); } let unsorted = [5, 3, 8, 1, 9, 2, 7, 4, 6]; @@ -19,7 +19,7 @@ assert(sorted == [1, 2, 3, 4, 5, 6, 7, 8, 9]); // 2. Sort descending — reverse the result fn reverse_list(xs) { - return xs.reduce([], |acc, x| [x].concat(acc)); + return xs.reduce([], |acc, x| [x].concat(acc)); } let desc = reverse_list(sorted); assert(desc == [9, 8, 7, 6, 5, 4, 3, 2, 1]); @@ -31,24 +31,24 @@ assert(sorted_words == ["apple", "banana", "cherry", "date"]); // 4. Linear search fn linear_search(xs, target) { - let i = 0; - while (i < xs.len()) { - if (xs[i] == target) { return i; } - i += 1; - } - return -1; + let i = 0; + while (i < xs.len()) { + if (xs[i] == target) { return i; } + i += 1; + } + return -1; } assert(linear_search(sorted, 5) == 4); assert(linear_search(sorted, 99) == -1); // 5. Min/max finder using reduce fn my_min(a, b) { - if (a < b) { return a; } - return b; + if (a < b) { return a; } + return b; } fn my_max(a, b) { - if (a > b) { return a; } - return b; + if (a > b) { return a; } + return b; } let min_val = unsorted.reduce(unsorted[0], |a, b| my_min(a, b)); let max_val = unsorted.reduce(unsorted[0], |a, b| my_max(a, b)); @@ -62,30 +62,30 @@ assert(sorted_unique == [1, 2, 3, 4, 5, 6, 9]); // 7. Merge sort style: merge two sorted lists fn merge(a, b) { - let result = []; - let i = 0; - let j = 0; - while (i < a.len() && j < b.len()) { - if (a[i] <= b[j]) { - result.push(a[i]); - i += 1; - } else { - result.push(b[j]); - j += 1; + let result = []; + let i = 0; + let j = 0; + while (i < a.len() && j < b.len()) { + if (a[i] <= b[j]) { + result.push(a[i]); + i += 1; + } else { + result.push(b[j]); + j += 1; + } + } + // Append remaining + while (i < a.len()) { + result.push(a[i]); + i += 1; + } + while (j < b.len()) { + result.push(b[j]); + j += 1; } - } - // Append remaining - while (i < a.len()) { - result.push(a[i]); - i += 1; - } - while (j < b.len()) { - result.push(b[j]); - j += 1; - } - return result; + return result; } let merged = merge([1, 3, 5], [2, 4, 6]); assert(merged == [1, 2, 3, 4, 5, 6]); -println("sort_search: all assertions passed"); \ No newline at end of file +println("sort_search: all assertions passed"); diff --git a/examples/general/word_count.lk b/examples/general/word_count.lk index 6fab9ade..f1f5e4ae 100644 --- a/examples/general/word_count.lk +++ b/examples/general/word_count.lk @@ -13,12 +13,12 @@ let lower_words = words.map(|w| w.lower()); // 3. Count word frequencies using a map let freq = {}; for word in lower_words { - let current = freq.get(word); - if (current == nil) { - freq.set(word, 1); - } else { - freq.set(word, current + 1); - } + let current = freq.get(word); + if (current == nil) { + freq.set(word, 1); + } else { + freq.set(word, current + 1); + } } // 4. Verify counts @@ -31,12 +31,12 @@ assert(freq.get("brown") == 1); let max_word = ""; let max_count = 0; for pair in freq { - let word = pair[0]; - let count = pair[1]; - if (count > max_count) { - max_count = count; - max_word = word; - } + let word = pair[0]; + let count = pair[1]; + if (count > max_count) { + max_count = count; + max_word = word; + } } assert(max_word == "the"); assert(max_count == 3); @@ -47,23 +47,23 @@ assert(unique_words.len() == 9); // 7. Sort words alphabetically (simple selection sort for small lists) fn sort_words(xs) { - let result = []; - let remaining = xs; - while (remaining.len() > 0) { - // find min - let min_idx = 0; - let i = 1; - while (i < remaining.len()) { - if (remaining[i] < remaining[min_idx]) { - min_idx = i; - } - i += 1; + let result = []; + let remaining = xs; + while (remaining.len() > 0) { + // find min + let min_idx = 0; + let i = 1; + while (i < remaining.len()) { + if (remaining[i] < remaining[min_idx]) { + min_idx = i; + } + i += 1; + } + result.push(remaining[min_idx]); + // remove element at min_idx + remaining = remaining.take(min_idx).concat(remaining.skip(min_idx + 1)); } - result.push(remaining[min_idx]); - // remove element at min_idx - remaining = remaining.take(min_idx).concat(remaining.skip(min_idx + 1)); - } - return result; + return result; } let sorted = sort_words(unique_words); assert(sorted[0] == "brown"); diff --git a/examples/stdlib/bytes_codec.lk b/examples/stdlib/bytes_codec.lk new file mode 100644 index 00000000..3e4ea137 --- /dev/null +++ b/examples/stdlib/bytes_codec.lk @@ -0,0 +1,61 @@ +// `Bytes` as a value, and the codecs that produce it. +// Run from project root: lk examples/stdlib/bytes_codec.lk + +use bytes; +use encoding; + +// 1. A Bytes is an ordinary value: read it as many times as you like. +let hi = bytes.from_string("hi"); +assert(bytes.len(hi) == 2); +assert(bytes.to_string_utf8(hi) == "hi"); +assert(bytes.len(hi) == 2); +assert(!bytes.is_empty(hi)); +assert(bytes.is_empty(bytes.from_string(""))); + +// 2. It displays as its byte values, and compares by *content*. +assert("${hi}" != ""); +assert(hi == bytes.from_string("hi")); +assert(hi != bytes.from_string("ho")); + +// 3. A negative position counts from the end, and out of range is nil — the +// same rule every other container reads by, in both spellings. +let abc = bytes.from_string("abcde"); +assert(bytes.get(abc, 0)! == 97); +assert(bytes.get(abc, -1)! == 101); +assert(bytes.get(abc, 9) == nil); +assert(abc.get(-1)! == 101); +assert(bytes.slice(abc, 1, -1) == bytes.from_string("bcd")); +assert(abc.slice(1, -1) == bytes.from_string("bcd")); + +// 4. The string method spelling. +assert("hi".bytes() == hi); +assert("hi".bytes().len() == 2); + +// 5. base64 and hex round-trip through Bytes. +assert(encoding.base64.encode("hi") == "aGk="); +assert(encoding.base64.decode("aGk=") == hi); +assert(encoding.hex.encode("hi") == "6869"); +assert(encoding.hex.decode("6869") == hi); +assert(bytes.concat(hi, hi) == bytes.from_string("hihi")); + +// A malformed encoding raises, catchably. +let bad = try { encoding.base64.decode("!!!"); "no" } catch e { "caught" }; +assert(bad == "caught"); + +// 6. A URI component round-trips too: a space is `%20`, and `+` stays the +// literal `+` it is (form encoding is what `url.query_stringify` is for). +let raw = "a b&c=d"; +let escaped = encoding.url.encode_component(raw); +assert(escaped == "a%20b%26c%3Dd"); +assert(encoding.url.decode_component(escaped) == raw); +assert(encoding.url.decode_component("a+b") == "a+b"); + +// 7. List interop, both directions. +assert(bytes.from_list([104, 105]) == hi); +assert(bytes.to_list(hi) == [104, 105]); +assert(bytes.to_list(bytes.from_string("")) == []); +// A value that is not a byte is a mistake, not something to truncate. +let out_of_range = try { bytes.from_list([300]); "no" } catch e { "caught" }; +assert(out_of_range == "caught"); + +println("bytes_codec: all assertions passed"); diff --git a/examples/stdlib/comprehensive.lk b/examples/stdlib/comprehensive.lk index ed5268b4..79bf011d 100644 --- a/examples/stdlib/comprehensive.lk +++ b/examples/stdlib/comprehensive.lk @@ -31,22 +31,81 @@ assert(string.capitalize("hello world") == "Hello world"); assert(string.title("hello world") == "Hello World"); // OS new features -assert(fs.exists(fs.temp_dir())); +// `temp_dir()` is declared `String?`; `??` supplies the String it is +// declared to maybe not be. +assert(fs.exists(fs.temp_dir() ?? "/tmp")); assert(path.sep() != ""); // Builtin Set let my_set = Set(["a", "b", "a"]); -assert(my_set.has("a")); +assert(my_set.contains("a")); assert(my_set.contains("b")); assert_eq(my_set.len(), 2); my_set.add("c"); assert_eq(my_set.len(), 3); my_set.delete("a"); -assert(!my_set.has("a")); + +// A member a set cannot hold says *what it is*, and says it the same way from +// either end. The type checker turns most of these away — `Set([[1, 2]])` never +// runs — so the ones that reach the runtime are the types it types as `Any`: +// `Bytes`, and a function. The compiled build used to answer "Float" for both. +let refused_bytes = ""; +try { Set(["ab".bytes()]); } catch e { refused_bytes = "{}".format(e); } +assert(refused_bytes == "Set() item: Bytes cannot be a map key or set member: only nil, Bool, Int and String can"); + +let refused_fn = ""; +try { Set([|x| x + 1]); } catch e { refused_fn = "{}".format(e); } +assert(refused_fn == "Set() item: Function cannot be a map key or set member: only nil, Bool, Int and String can"); + +// The type checker turns `my_set.add("zz".bytes())` away outright, whatever the +// element type — so the add path's own wording is only reachable through a +// parameter typed `Any`. +fn add_member(s, v) { s.add(v); } +let refused_add = ""; +try { add_member(my_set, "zz".bytes()); } catch e { refused_add = "{}".format(e); } +assert( + refused_add == "set.add() value: Bytes cannot be a map key or set member: only nil, Bool, Int and String can" +); +assert(!my_set.contains("a")); // List methods remain available without importing a list module. assert_eq(xs.first(), 3); assert_eq(xs.last(), 6); -assert_eq(xs.slice(1, 4), [1, 4, 1]); +// `slice` is a window over `xs` rather than a copy of part of it, so it is +// compared element by element. `to_list()` would say the same thing in one +// line, but it has no native lowering yet and this file is in the AOT +// coverage gate. +let window = xs.slice(1, 4); +assert_eq(window.len(), 3); +assert_eq(window[0], 1); +assert_eq(window[1], 4); +assert_eq(window[2], 1); println("stdlib comprehensive test: all assertions passed"); + +// `clear()` hands the receiver back, empty — the same handle, for a map as for +// a list. The compiled build answered `nil` for the map, so +// `println(m.clear())` printed two different things. +let cleared_map = {"a": 1, "b": 2}; +assert(cleared_map.clear() == {}); +assert(cleared_map.len() == 0); +let cleared_list = [1, 2]; +assert(cleared_list.clear() == []); +let cleared_set = Set([1, 2]); +assert(cleared_set.clear() == Set([])); +assert(cleared_set.len() == 0); + +// A map has `delete`, and does not have `remove`. The compiled build accepted +// `remove` as a second spelling — so a program the interpreter refuses to run +// ran, on one backend, and answered. +let with_key = {"a": 1}; +assert(with_key.delete("a") == 1); +assert(with_key.len() == 0); + +// A refusal's message *is* the error, not something printed on the way to a +// different one. `chunk(0)` used to print "size must be positive" on stderr and +// then raise `runtime type error`, so the explanation and the catchable +// sentence were two different strings. +let chunk_error = ""; +try { [1, 2, 3].chunk(0); } catch e { chunk_error = "{}".format(e); } +assert(chunk_error == "list.chunk() size must be positive"); diff --git a/examples/stdlib/datetime_demo.lk b/examples/stdlib/datetime_demo.lk index 84990e96..f0eedee5 100644 --- a/examples/stdlib/datetime_demo.lk +++ b/examples/stdlib/datetime_demo.lk @@ -37,4 +37,4 @@ assert(doy <= 366); let weekend = datetime.is_weekend(now_secs); assert(weekend == true || weekend == false); -println("datetime_demo: all assertions passed"); \ No newline at end of file +println("datetime_demo: all assertions passed"); diff --git a/examples/stdlib/json_process.lk b/examples/stdlib/json_process.lk index fdfded2f..a59e5eed 100644 --- a/examples/stdlib/json_process.lk +++ b/examples/stdlib/json_process.lk @@ -15,7 +15,7 @@ assert(names == ["Alice", "Bob", "Carol"]); // 2. Compute average score per user fn avg_score(user) { - return user.scores.reduce(0, |a, b| a + b) / user.scores.len(); + return user.scores.reduce(0, |a, b| a + b) / user.scores.len(); } let averages = data.users.map(|u| avg_score(u)); assert(averages[0] > 90); @@ -27,23 +27,23 @@ assert(top_users == ["Alice", "Carol"]); // 4. Find the highest single score across all users fn my_max(a, b) { - if (a > b) { return a; } - return b; + if (a > b) { return a; } + return b; } fn max_score(users) { - let all_scores = []; - for u in users { - for s in u.scores { - all_scores.push(s); + let all_scores = []; + for u in users { + for s in u.scores { + all_scores.push(s); + } } - } - return all_scores.reduce(0, |a, b| my_max(a, b)); + return all_scores.reduce(0, |a, b| my_max(a, b)); } assert(max_score(data.users) == 100); // 5. Compute total scores per user as pairs fn user_total(u) { - return u.scores.reduce(0, |a, b| a + b); + return u.scores.reduce(0, |a, b| a + b); } let summaries = data.users.map(|u| [u.name, user_total(u)]); assert(summaries[0] == ["Alice", 274]); diff --git a/examples/stdlib/list_ops.lk b/examples/stdlib/list_ops.lk index 7ece2878..9ce16220 100644 --- a/examples/stdlib/list_ops.lk +++ b/examples/stdlib/list_ops.lk @@ -36,8 +36,8 @@ assert(mixed[4] == [4, 5]); // 8. List comprehension via map/filter let result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - .filter(|x| x % 3 == 0) - .map(|x| x * x); + .filter(|x| x % 3 == 0) + .map(|x| x * x); assert(result == [9, 36, 81]); // 9. Chunk — split into groups @@ -58,4 +58,4 @@ assert([1, 2, 1, 3, 2, 4].unique() == [1, 2, 3, 4]); // 12. Flatten assert([[1, 2], [3], [4, 5, 6]].flatten() == [1, 2, 3, 4, 5, 6]); -println("list_ops: all assertions passed"); \ No newline at end of file +println("list_ops: all assertions passed"); diff --git a/examples/stdlib/map_demo.lk b/examples/stdlib/map_demo.lk index 29f43080..2cd17014 100644 --- a/examples/stdlib/map_demo.lk +++ b/examples/stdlib/map_demo.lk @@ -18,8 +18,8 @@ assert(vals.len() == 3); // 4. Nested maps let config = { - "db": { "host": "localhost", "port": 5432 }, - "cache": { "ttl": 300 }, + "db": { "host": "localhost", "port": 5432 }, + "cache": { "ttl": 300 }, }; assert(config.db.host == "localhost"); assert(config.db["port"] == 5432); @@ -42,7 +42,7 @@ assert(m.len() == 2); let inventory = { "apples": 10, "bananas": 5, "cherries": 20 }; let total_items = 0; for pair in inventory { - total_items += pair[1]; + total_items += pair[1]; } assert(total_items == 35); diff --git a/examples/stdlib/math_demo.lk b/examples/stdlib/math_demo.lk index b9687cc5..ec470435 100644 --- a/examples/stdlib/math_demo.lk +++ b/examples/stdlib/math_demo.lk @@ -28,9 +28,9 @@ assert(math.cos(0) == 1); // Distance between two 2D points fn distance(x1, y1, x2, y2) { - let dx = x2 - x1; - let dy = y2 - y1; - return math.sqrt(dx * dx * 1.0 + dy * dy * 1.0); + let dx = x2 - x1; + let dy = y2 - y1; + return math.sqrt(dx * dx * 1.0 + dy * dy * 1.0); } let d = distance(0, 0, 3, 4); assert(d > 4.99); @@ -48,4 +48,4 @@ let area = circle_area(5); assert(area > 78.0); assert(area < 79.0); -println("math_demo: all assertions passed"); \ No newline at end of file +println("math_demo: all assertions passed"); diff --git a/examples/stdlib/method_surface.lk b/examples/stdlib/method_surface.lk new file mode 100644 index 00000000..c37ab975 --- /dev/null +++ b/examples/stdlib/method_surface.lk @@ -0,0 +1,117 @@ +// Every method the native lowering claims, printed on both backends. +// +// The AOT lowering has a table of methods it can lower, and its answer has to +// be the interpreter's — not only the value, but what a mutating method hands +// back and what a refusal says. Four defects came out of comparing it entry by +// entry: `m.clear()` answered nil, `s.clear()` answered nil, a map accepted a +// `remove` the language does not have, and `chunk(0)` printed its explanation +// instead of raising it. Each was one arm of a table nobody read as a whole. +// +// So the table is read as a whole here. The VM/native differential gate runs +// every example on both backends, which is exactly the comparison that found +// them — this file is that comparison, kept. + +// ── String ────────────────────────────────────────────────── +let v1 = "hello world"; println("String.len()"); println(v1.len()); println(v1); +let v2 = "hello world"; println("String.upper()"); println(v2.upper()); println(v2); +let v3 = "hello world"; println("String.lower()"); println(v3.lower()); println(v3); +let v4 = "hello world"; println("String.trim()"); println(v4.trim()); println(v4); +let v5 = "hello world"; println("String.chars()"); println(v5.chars()); println(v5); +let v6 = "hello world"; println("String.bytes()"); println(v6.bytes()); println(v6); +let v7 = "hello world"; println("String.repeat(2)"); println(v7.repeat(2)); println(v7); +let v8 = "hello world"; println("String.reverse()"); println(v8.reverse()); println(v8); +let v9 = "hello world"; println("String.slice(1,4)"); println(v9.slice(1,4)); println(v9); +let v10 = "hello world"; println("String.byte_at(0)"); println(v10.byte_at(0)); println(v10); +let v11 = "hello world"; println("String.is_empty()"); println(v11.is_empty()); println(v11); +let v12 = "hello world"; println("String.capitalize()"); println(v12.capitalize()); println(v12); +let v13 = "hello world"; println("String.title()"); println(v13.title()); println(v13); + +// ── List ──────────────────────────────────────────────────── +let v14 = [3,1,2]; println("List.len()"); println(v14.len()); println(v14); +let v15 = [3,1,2]; println("List.first()"); println(v15.first()); println(v15); +let v16 = [3,1,2]; println("List.last()"); println(v16.last()); println(v16); +let v17 = [3,1,2]; println("List.sum()"); println(v17.sum()); println(v17); +let v18 = [3,1,2]; println("List.max()"); println(v18.max()); println(v18); +let v19 = [3,1,2]; println("List.min()"); println(v19.min()); println(v19); +let v20 = [3,1,2]; println("List.sort()"); println(v20.sort()); println(v20); +let v21 = [3,1,2]; println("List.reverse()"); println(v21.reverse()); println(v21); +let v22 = [3,1,2]; println("List.contains(2)"); println(v22.contains(2)); println(v22); +let v23 = [3,1,2]; println("List.index_of(2)"); println(v23.index_of(2)); println(v23); +let v24 = [3,1,2]; println("List.slice(0,2)"); println(v24.slice(0,2)); println(v24); +let v25 = [3,1,2]; println("List.take(2)"); println(v25.take(2)); println(v25); +let v26 = [3,1,2]; println("List.skip(1)"); println(v26.skip(1)); println(v26); +let v27 = [3,1,2]; println("List.concat([4])"); println(v27.concat([4])); println(v27); +let v28 = [3,1,2]; println("List.unique()"); println(v28.unique()); println(v28); +let v29 = [3,1,2]; println("List.enumerate()"); println(v29.enumerate()); println(v29); +let v30 = [3,1,2]; println("List.zip([7,8,9])"); println(v30.zip([7,8,9])); println(v30); +let v31 = [3,1,2]; println("List.chunk(2)"); println(v31.chunk(2)); println(v31); +let v32 = [3,1,2]; println("List.is_empty()"); println(v32.is_empty()); println(v32); +let v33 = [3,1,2]; println("List.count(1)"); println(v33.count(1)); println(v33); +let v34 = [3,1,2]; println("List.get(0)"); println(v34.get(0)); println(v34); +let v35 = [3,1,2]; println("List.remove_at(0)"); println(v35.remove_at(0)); println(v35); +let v36 = [3,1,2]; println("List.clear()"); println(v36.clear()); println(v36); + +// ── Map ───────────────────────────────────────────────────── +let v37 = {"a":1,"b":2}; println("Map.len()"); println(v37.len()); println(v37); +let v38 = {"a":1,"b":2}; println("Map.keys()"); println(v38.keys()); println(v38); +let v39 = {"a":1,"b":2}; println("Map.values()"); println(v39.values()); println(v39); +let v40 = {"a":1,"b":2}; println("Map.is_empty()"); println(v40.is_empty()); println(v40); +let v41 = {"a":1,"b":2}; println("Map.clear()"); println(v41.clear()); println(v41); + +// ── Set ───────────────────────────────────────────────────── +let v42 = Set([1,2,3]); println("Set.len()"); println(v42.len()); println(v42); +let v43 = Set([1,2,3]); println("Set.contains(2)"); println(v43.contains(2)); println(v43); +let v44 = Set([1,2,3]); println("Set.add(4)"); println(v44.add(4)); println(v44); +let v45 = Set([1,2,3]); println("Set.delete(1)"); println(v45.delete(1)); println(v45); +let v46 = Set([1,2,3]); println("Set.union(Set([9]))"); println(v46.union(Set([9]))); println(v46); +let v47 = Set([1,2,3]); println("Set.intersection(Set([1]))"); println(v47.intersection(Set([1]))); println(v47); +let v48 = Set([1,2,3]); println("Set.difference(Set([1]))"); println(v48.difference(Set([1]))); println(v48); +let v49 = Set([1,2,3]); println("Set.symmetric_difference(Set([1]))"); println(v49.symmetric_difference(Set([1]))); println(v49); +let v50 = Set([1,2,3]); println("Set.is_subset(Set([1,2,3,4]))"); println(v50.is_subset(Set([1,2,3,4]))); println(v50); +let v51 = Set([1,2,3]); println("Set.is_superset(Set([1]))"); println(v51.is_superset(Set([1]))); println(v51); +let v52 = Set([1,2,3]); println("Set.is_disjoint(Set([9]))"); println(v52.is_disjoint(Set([9]))); println(v52); +let v53 = Set([1,2,3]); println("Set.is_empty()"); println(v53.is_empty()); println(v53); + +// ── Bytes ─────────────────────────────────────────────────── +let v54 = "ab".bytes(); println("Bytes.len()"); println(v54.len()); println(v54); +let v55 = "ab".bytes(); println("Bytes.to_string_utf8()"); println(v55.to_string_utf8()); println(v55); +let v56 = "ab".bytes(); println("Bytes.to_string_lossy()"); println(v56.to_string_lossy()); println(v56); +let v57 = "ab".bytes(); println("Bytes.to_list()"); println(v57.to_list()); println(v57); +let v58 = "ab".bytes(); println("Bytes.get(0)"); println(v58.get(0)); println(v58); + +// ── Empty list ────────────────────────────────────────────── +let v59 = []; println("Empty list.len()"); println(v59.len()); println(v59); +let v60 = []; println("Empty list.is_empty()"); println(v60.is_empty()); println(v60); +let v61 = []; println("Empty list.first()"); println(v61.first()); println(v61); +let v62 = []; println("Empty list.last()"); println(v62.last()); println(v62); +let v63 = []; println("Empty list.sum()"); println(v63.sum()); println(v63); +let v64 = []; println("Empty list.clear()"); println(v64.clear()); println(v64); +let v65 = []; println("Empty list.unique()"); println(v65.unique()); println(v65); +let v66 = []; println("Empty list.flatten()"); println(v66.flatten()); println(v66); +let v67 = []; println("Empty list.take(2)"); println(v67.take(2)); println(v67); +let v68 = []; println("Empty list.skip(2)"); println(v68.skip(2)); println(v68); + +// ── Empty map ─────────────────────────────────────────────── +let v69 = {}; println("Empty map.len()"); println(v69.len()); println(v69); +let v70 = {}; println("Empty map.keys()"); println(v70.keys()); println(v70); +let v71 = {}; println("Empty map.values()"); println(v71.values()); println(v71); +let v72 = {}; println("Empty map.is_empty()"); println(v72.is_empty()); println(v72); +let v73 = {}; println("Empty map.clear()"); println(v73.clear()); println(v73); + +// ── Empty string ──────────────────────────────────────────── +let v74 = ""; println("Empty string.len()"); println(v74.len()); println(v74); +let v75 = ""; println("Empty string.upper()"); println(v75.upper()); println(v75); +let v76 = ""; println("Empty string.trim()"); println(v76.trim()); println(v76); +let v77 = ""; println("Empty string.split(",")"); println(v77.split(",")); println(v77); +let v78 = ""; println("Empty string.reverse()"); println(v78.reverse()); println(v78); +let v79 = ""; println("Empty string.is_empty()"); println(v79.is_empty()); println(v79); + +// ── Edge index ────────────────────────────────────────────── +let v80 = [1,2,3]; println("Edge index.slice(0-2, 3)"); println(v80.slice(0-2, 3)); println(v80); +let v81 = [1,2,3]; println("Edge index.slice(1, 99)"); println(v81.slice(1, 99)); println(v81); +let v82 = [1,2,3]; println("Edge index.get(0-1)"); println(v82.get(0-1)); println(v82); +let v83 = [1,2,3]; println("Edge index.get(99)"); println(v83.get(99)); println(v83); +let v84 = [1,2,3]; println("Edge index.take(99)"); println(v84.take(99)); println(v84); +let v85 = [1,2,3]; println("Edge index.skip(99)"); println(v85.skip(99)); println(v85); + +println("method surface: ok"); diff --git a/examples/stdlib/os_demo.lk b/examples/stdlib/os_demo.lk index 0a32f1bf..cf964a6b 100644 --- a/examples/stdlib/os_demo.lk +++ b/examples/stdlib/os_demo.lk @@ -19,7 +19,9 @@ assert(os_name != nil); let path_val = env.get_or("PATH", ""); assert(path_val != ""); -// env.set/unset disabled in VM runtime - skip +// The language has no env.set/env.unset: an environment variable is read-only +// from LK. (Rust 2024 makes mutating it process-wide unsafe, and a script that +// changed its own environment could not be run twice for the same answer.) // 3. Clock / time let t1 = os.clock(); @@ -35,8 +37,10 @@ assert(epoch > 0); let cwd = process.cwd(); assert(cwd != nil); -let tmp = fs.temp_dir(); -assert(tmp != nil); +// `temp_dir()` is declared `String?`, and `read_dir` wants a `String`. `??` +// is what bridges them — the checker does not narrow a type from a `!= nil` +// assertion, so asserting it is not enough. +let tmp = fs.temp_dir() ?? "/tmp"; let listing = fs.read_dir(tmp); assert(listing != nil); diff --git a/examples/stdlib/path_normalize.lk b/examples/stdlib/path_normalize.lk new file mode 100644 index 00000000..cc6bf4e7 --- /dev/null +++ b/examples/stdlib/path_normalize.lk @@ -0,0 +1,41 @@ +// `path.normalize` is the one path member the native runtime *copies* rather +// than shares: lkrt walks `std::path::Component` itself, with the same two +// rules the module states — `..` cancels only a named component, and above a +// root it is dropped, because `/..` is `/` on every filesystem. +// +// Two copies of a rule drift in silence, so this walks the cases the loop +// distinguishes. `scripts/vm_native_sweep.sh` compares this output between the +// interpreter and the native build, which is what makes the drift loud. + +use path; + +fn show(p: String) { + println("${p} -> ${path.normalize(p)}"); +} + +// A `.` component disappears; a named component is kept. +show("a/./b"); +show("./a"); + +// `..` cancels the named component before it, and only that one. +show("a/b/../c"); +show("a/../b"); + +// Nothing named to cancel: a relative path keeps the `..`, so going up twice +// really is two levels up. +show("../.."); +show("a/../../b"); +show(".."); + +// Above a root there is nothing to go up to, so the `..` is dropped rather +// than kept. +show("/../a"); +show("/a/../../b"); + +// Trailing separators and repeated ones are not components at all. +show("a//b/"); +show("a/b/"); + +// The empty path and a bare root normalize to themselves. +show(""); +show("/"); diff --git a/examples/stdlib/stream_identity.lk b/examples/stdlib/stream_identity.lk new file mode 100644 index 00000000..b54ee45d --- /dev/null +++ b/examples/stdlib/stream_identity.lk @@ -0,0 +1,46 @@ +// A stream is a value of its own, not the list it is materialized into. +// +// Natively a finite pipeline is built eagerly as a list, which is what makes it +// cheap. That is sound only while nothing can tell the difference, and four +// things can: `typeof`, display, `==`, and a trait dispatching on it. The +// materialized list used to answer all four, so `typeof(s)` was `List` and +// printing one wrote its elements. +// +// Marking the value instead of tagging it could not work here: `stream.from_list(xs)` +// answers a stream *over the caller's own list*, so a mark on the result was a +// mark on `xs`. A box is a value of its own. + +use stream; + +let xs = [1, 2, 3]; +let s = stream.from_list(xs); + +println(typeof(s)); +println(s); +println("s=" + s); + +// The list it was built from is still a list. +println(typeof(xs)); +println(xs); + +// A stream is not the list it collects to. +let collected = stream.collect(s); +println(typeof(collected)); +println(collected); + +// Every operation answers a stream again. +let doubled = stream.map(stream.from_list([1, 2]), fn(x) => x * 2); +println(typeof(doubled)); +println(stream.collect(doubled)); + +let kept = stream.filter(stream.range(0, 6), fn(x) => x % 2 == 0); +println(typeof(kept)); +println(stream.collect(kept)); + +// Including the one that takes a second stream. +let joined = stream.chain(stream.from_list([1]), stream.from_list([2])); +println(typeof(joined)); +println(stream.collect(joined)); + +// In a container, where a mark would have been lost. +println([stream.range(0, 2)]); diff --git a/examples/stdlib/string_methods.lk b/examples/stdlib/string_methods.lk index dc561956..574d0420 100644 --- a/examples/stdlib/string_methods.lk +++ b/examples/stdlib/string_methods.lk @@ -17,7 +17,7 @@ let url = "https://example.com"; assert(url.starts_with("https")); assert(url.ends_with(".com")); assert(url.contains("example")); -assert("the quick brown fox".find("quick") == 4); +assert("the quick brown fox".index_of("quick") == 4); // 5. Replace assert("hello world".replace("world", "LK") == "hello LK"); @@ -28,7 +28,7 @@ assert(parts == ["red", "green", "blue"]); assert(parts.join(" | ") == "red | green | blue"); // 7. Substring -assert("abcdef".substring(1, 3) == "bcd"); +assert("abcdef".slice(1, 4) == "bcd"); // 8. Reverse and repeat assert("stressed".reverse() == "desserts"); @@ -54,4 +54,4 @@ assert(raw == "No \\n escapes here"); let json_like = r#"{ "key": "value" }"#; assert(json_like == "{ \"key\": \"value\" }"); -println("string_methods: all assertions passed"); \ No newline at end of file +println("string_methods: all assertions passed"); diff --git a/examples/stdlib/time_demo.lk b/examples/stdlib/time_demo.lk index 07c1fc48..7ec99255 100644 --- a/examples/stdlib/time_demo.lk +++ b/examples/stdlib/time_demo.lk @@ -23,4 +23,4 @@ let end_time = time.now(); let duration = time.since(start, end_time); assert(duration >= 0); -println("time_demo: all assertions passed"); \ No newline at end of file +println("time_demo: all assertions passed"); diff --git a/examples/stdlib/yaml_toml.lk b/examples/stdlib/yaml_toml.lk index 62c78698..ddde653d 100644 --- a/examples/stdlib/yaml_toml.lk +++ b/examples/stdlib/yaml_toml.lk @@ -29,4 +29,25 @@ let cfg = toml.parse(tml); let db_url = "${cfg.database.host}:${cfg.database.port}"; assert(db_url == "localhost:5432"); + +// A parsed document keeps the order it was written in — every format. +// +// An LK map's iteration order is the order a key was first written, and for a +// document that is the order it appears. JSON and TOML both sorted instead, not +// by decision but because the value type each was parsed through is a +// `BTreeMap`. Writing is deliberately the other way: `stringify` sorts, so a +// config written twice from the same data is byte-identical. +use encoding; +let from_json = encoding.json.parse("{\"beta\": 1, \"alpha\": 2, \"gamma\": 3}"); +assert(from_json.keys().join(",") == "beta,alpha,gamma"); +let from_yaml = encoding.yaml.parse("beta: 1\nalpha: 2\ngamma: 3\n"); +assert(from_yaml.keys().join(",") == "beta,alpha,gamma"); +let from_toml = encoding.toml.parse("beta = 1\nalpha = 2\ngamma = 3\n"); +assert(from_toml.keys().join(",") == "beta,alpha,gamma"); + +// Nested objects too, and the sorted *output* is what it always was. +let nested = encoding.json.parse("{\"z\": {\"y\": 1, \"x\": 2}}"); +assert(nested["z"].keys().join(",") == "y,x"); +assert(encoding.json.stringify({"b": 1, "a": 2}) == "{\"a\":2,\"b\":1}"); + println("yaml_toml: all assertions passed"); diff --git a/examples/syntax/closure.lk b/examples/syntax/closure.lk index 99164748..6fa67dfa 100644 --- a/examples/syntax/closure.lk +++ b/examples/syntax/closure.lk @@ -22,7 +22,7 @@ assert(apply(square, 5) == 25); // 5. Returning closures from functions fn multiplier(n) { - return |x| x * n; + return |x| x * n; } let triple = multiplier(3); let quintuple = multiplier(5); @@ -40,10 +40,121 @@ assert(total == 55); // 7. Composing operations with a helper function fn process_list(xs) { - let filtered = xs.filter(|x| x > 3); - let mapped = filtered.map(|x| x * x); - return mapped.reduce(0, |a, b| a + b); + let filtered = xs.filter(|x| x > 3); + let mapped = filtered.map(|x| x * x); + return mapped.reduce(0, |a, b| a + b); } assert(process_list([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) == 371); -println("closure: all assertions passed"); \ No newline at end of file +// 8. Closures may *assign* to what they captured — the capture is a shared +// mutable cell, so every call and the enclosing scope see the same variable. +let hits = 0; +let bump = |n| { hits = hits + n; return hits; }; +assert(bump(1) == 1); +assert(bump(2) == 3); +assert(hits == 3); + +// Two closures over one variable share it. +let count = 0; +let inc = || { count = count + 1; }; +let dec = || { count = count - 1; }; +for i in 0..5 { inc(); } +dec(); +assert(count == 4); + +// A written capture and a read-only one side by side. +let base = 10; +let total = 0; +let accumulate = |v| { total = total + v + base; }; +accumulate(1); +accumulate(2); +assert(total == 23); +assert(base == 10); + +// 9. A lambda may write its own types — parameters, return, or both. +let typed = |x: Int, y: Int| -> Int { return x * y; }; +assert(typed(3, 4) == 12); +let only_params = |s: String| { return s.len(); }; +assert(only_params("abcd") == 4); +let only_return = |x| -> Int { return x + 1; }; +assert(only_return(1) == 2); +let container_param = |xs: List| -> Int { return xs.len(); }; +assert(container_param([1, 2, 3]) == 3); + +// 10. A closure nested in a closure writes what its parent captured — one +// variable, one cell, however many levels down. +let ledger = 0; +let record = |amount| { + let apply = |delta| { ledger = ledger + delta; }; + apply(amount); + apply(amount); +}; +record(5); +assert(ledger == 10); + +// 11. A declared function type reaches the lambda written for it, wherever it +// is declared: a parameter, a return type, an aggregate's element. +fn call_with(g: (Int) -> Int, v: Int) -> Int { return g(v); } +assert(call_with(|x| { return x + 1; }, 5) == 6); + +fn adder() -> (Int) -> Int { return |x| { return x + 100; }; } +assert(adder()(1) == 101); + +// (A closure *inside* a container is checked the same way, but does not lower +// natively yet, so it stays out of this file — the AOT coverage gate reads it.) + +// 12. A closure may call another closure. The callee lives in a cell (that is +// what being captured means), and what goes into that cell is a reference to a +// function rather than a value — so composing two lambdas is a different shape +// from capturing a number. +let add_one = |x| x + 1; +let double_after = |x| add_one(x) * 2; +assert(double_after(1) == 4); + +fn times_three(x: Int) -> Int { return x * 3; } +let times_three_plus_one = |x| times_three(x) + 1; +assert(times_three_plus_one(2) == 7); + +// Calling one and writing a capture at the same time. +let seen = 0; +let record = |x| { seen = seen + add_one(x); }; +record(1); +record(2); +assert(seen == 5); + +// A captured lambda inside a callback: the closure's whole environment is a +// static reference, so it carries nothing at run time and reaches the same +// fast path a bare `|x| x + 1` would. +assert([1, 2, 3].map(|x| add_one(x)) == [2, 3, 4]); +assert([1, 2, 3].filter(|x| add_one(x) > 2) == [2, 3]); + +// 13. The same onward capture, but handed to somebody else instead of called +// where it was built. The inner lambda names `ledger` and `amount`, neither of +// which is its own — the *outer* lambda holds them, so resolving them means +// reading the enclosing closure's environment rather than a local cell. +fn call_it(g: () -> Int) -> Int { return g(); } + +let running = 0; +let post = |amount| { + let entry = || { running = running + amount; return running; }; + return call_it(entry); +}; +assert(post(4) == 4); +assert(post(6) == 10); +assert(running == 10); + +// And across an isolate boundary, where the capture is copied rather than +// shared: the goroutine reads what `spawn` snapshotted and its writes stay +// its own. +use task; + +let readings = 7; +let sample = |offset| { + let take = || { return readings + offset; }; + return task.await(spawn(take)); +}; +assert(sample(1) == 8); +assert(sample(2) == 9); +assert(readings == 7); + +println("closure: all assertions passed"); diff --git a/examples/syntax/closure_value.lk b/examples/syntax/closure_value.lk new file mode 100644 index 00000000..d6b2de01 --- /dev/null +++ b/examples/syntax/closure_value.lk @@ -0,0 +1,148 @@ +// A closure used as a *value* rather than called where it is built. +// +// Every other closure the compiler sees is a compile-time fact: it knows which +// function a register names, so the call devirtualizes and the captures become +// hidden arguments. That covers a closure that is built and called, and nothing +// else — putting one in a list has no compile-time answer, because what comes +// back out of the list is decided at run time. +// +// So it becomes a value: a code address, the arity, and the captured +// environment, called back through the runtime. The lambda gets a second copy +// of its body with an all-`Dyn` signature for that path, so the original keeps +// the typed signature its ordinary call sites resolved and pays nothing. + +// In a list, called through the element. +let ops = [|x| x + 1, |x| x * 2]; +assert(ops[0](3) == 4); +assert(ops[1](3) == 6); +assert(ops.len() == 2); + +// Pushed rather than written in a literal. This is the shape that used to +// compile to the list pushing *itself*: the bytecode reuses registers, and the +// lambda landed on the slot the `[]` had just been in. +let built = []; +built.push(|x| x + 10); +assert(built.len() == 1); +assert(typeof(built[0]) == "Function"); +assert(built[0](5) == 15); + +// One built per iteration, each capturing that iteration's value. The capture +// is copied into the closure, so each one keeps its own. +let steps = []; +for i in 0..3 { steps.push(|| i * 100); } +assert(steps.len() == 3); +assert(steps[0]() == 0); +assert(steps[2]() == 200); + +// Iterated and called, which reads the closure back out as an ordinary value. +let total = 0; +for op in ops { total = total + op(10); } +assert(total == 31); + +// A capture-free lambda and a capturing one side by side. +let factor = 4; +let mixed = [|x| x, |x| x * factor]; +assert(mixed[0](7) == 7); +assert(mixed[1](7) == 28); + +// As a map's value, and as a struct's field. Same rule, two more places a +// value can be written: what comes back out is decided at run time, so what +// goes in has to be a value. +let handlers = {"inc": |x| x + 1, "double": |x| x * 2}; +let inc = handlers["inc"]; +let double = handlers["double"]; +assert(inc(4) == 5); +assert(double(4) == 8); +assert(handlers.len() == 2); + +struct Rule { apply: (Int) -> Int, weight: Int } +let rule = Rule { apply: |x| x + 100, weight: 3 }; +let apply = rule.apply; +assert(apply(1) == 101); +assert(rule.weight == 3); + +// Called where it sits, without naming it first. One meaning, two spellings — +// and for a while only the spelling with a local in the middle compiled. +assert(handlers["inc"](4) == 5); +assert(rule.apply(1) == 101); + +// The miss says what the interpreter says. A map is the one receiver where the +// question has two halves — a method *and* an entry holding a function — and +// the answer names both. +let missed = ""; +try { handlers.nosuch(); } catch e { missed = "{}".format(e); } +assert(missed == "a Map has no method `nosuch`, and this map has no key `nosuch` holding a function either"); + +// Returned from a branch, and called on the spot. A function whose *only* +// return is a closure over its parameters is summarized instead — the call site +// builds the closure and the body is never emitted — but two returns cannot be, +// so this one comes back as a value. +fn pick(up: Bool) -> (Int) -> Int { + if up { return |x| x + 1; } + return |x| x * 2; +} +assert(pick(true)(5) == 6); +assert(pick(false)(5) == 10); + +let chosen = pick(true); +assert(chosen(9) == 10); + +// A closure capturing *another closure*. Its environment is entirely static +// references, which the compiler erases — correct for a call that resolves them +// where it stands, and not enough for a value, whose environment has to hold +// something at run time. +let base = |x| x + 1; +let wrapped = [|y| base(y) * 10, |y| base(y) - 1]; +assert(wrapped[0](2) == 30); +assert(wrapped[1](2) == 2); + +println("closure value: all assertions passed"); + +// A closure is one object with however many names. `==` and `in` both compare +// by reference, so two lambdas written the same way are two closures and an +// alias is not. Compiled, that holds because a lambda the program passes around +// is built *once*, where it is written — building one per use answered `false` +// to every line here. +let f = |x| x + 1; +let g = f; +let h = |x| x + 1; +assert(f == g); +assert(!(f == h)); + +let fs = [f, f, h]; +assert(fs[0] == fs[1]); +assert(fs.contains(f)); +assert(fs.contains(h)); + +// And a capturing one, whose environment is copied into the closure: still one +// object, so the copy does not make a second identity. +let n = 2; +let scaled = |x| x * n; +let alias = scaled; +assert(scaled == alias); +assert([scaled].contains(alias)); + +// A callable is not a map key. The refusal names the type, which is the whole +// point of the sentence — "runtime type error" was the compiled answer. +let refused = ""; +let m = {}; +try { m[f] = 1; } catch e { refused = "{}".format(e); } +assert(refused == "Function cannot be a map key or set member: only nil, Bool, Int and String can"); +assert(m.len() == 0); + +// A list HOF whose callback is a *value* rather than a lambda written at the +// call. The typed fold takes a compiled address, which only exists when the +// lowering can say which lambda the register names; a callback read out of a +// container or handed in as a parameter is a closure, and the same three folds +// run through it. +let ops = [|x| x + 1, |x| x * 10]; +assert([1, 2, 3].map(ops[0]) == [2, 3, 4]); +assert([1, 2, 3].map(ops[1]) == [10, 20, 30]); +assert([1, 2, 3, 4].filter(ops[0]) == [1, 2, 3, 4]); +assert(["ab", "cd"].map(ops[0]) == ["ab1", "cd1"]); + +fn apply_all(xs, f) { return xs.map(f); } +assert(apply_all([5, 6], ops[1]) == [50, 60]); + +fn fold_with(xs, f) { return xs.reduce(0, f); } +assert(fold_with([1, 2, 3], |a, b| a + b) == 6); diff --git a/examples/syntax/control_flow.lk b/examples/syntax/control_flow.lk index c4939128..6e72ce22 100644 --- a/examples/syntax/control_flow.lk +++ b/examples/syntax/control_flow.lk @@ -3,32 +3,32 @@ // 1. If / else let x = 10; if (x > 5) { - assert(true); + assert(true); } else { - panic("wrong branch"); + panic("wrong branch"); } // 2. If without parentheses let y = 3; if y < 5 { - assert(true); + assert(true); } else { - panic("wrong branch"); + panic("wrong branch"); } // 3. While loop let n = 0; let total = 0; while (n < 5) { - total += n; - n += 1; + total += n; + n += 1; } assert(total == 10); // 4. For loop over range let sum = 0; for i in 1..=10 { - sum += i; + sum += i; } assert(sum == 55); @@ -36,24 +36,24 @@ assert(sum == 55); let fruits = ["apple", "banana", "cherry"]; let count = 0; for fruit in fruits { - count += 1; + count += 1; } assert(count == 3); // 6. For loop over string (chars) let chars = ""; for ch in "abc" { - chars += ch; + chars += ch; } assert(chars == "abc"); // 7. Break let found = 0; for i in 1..=100 { - if (i * i > 50) { - found = i; - break; - } + if (i * i > 50) { + found = i; + break; + } } // 7*7=49 ≤50, 8*8=64 >50 assert(found == 8); @@ -61,8 +61,8 @@ assert(found == 8); // 8. Continue let odd_sum = 0; for i in 1..=10 { - if (i % 2 == 0) { continue; } - odd_sum += i; + if (i % 2 == 0) { continue; } + odd_sum += i; } // 1+3+5+7+9 = 25 assert(odd_sum == 25); @@ -71,31 +71,31 @@ assert(odd_sum == 25); let matrix = [[1, 2], [3, 4]]; let flat = []; for row in matrix { - for cell in row { - flat.push(cell); - } + for cell in row { + flat.push(cell); + } } assert(flat == [1, 2, 3, 4]); // 10. Block scoping let outer = 1; { - let inner = 2; - outer += inner; + let inner = 2; + outer += inner; } assert(outer == 3); // 11. Conditional logic with function return values // Functions that return values avoid the if-block scoping issues fn classify(score) { - if (score >= 90) { return "A"; } - if (score >= 80) { return "B"; } - if (score >= 70) { return "C"; } - return "F"; + if (score >= 90) { return "A"; } + if (score >= 80) { return "B"; } + if (score >= 70) { return "C"; } + return "F"; } assert(classify(95) == "A"); assert(classify(85) == "B"); assert(classify(75) == "C"); assert(classify(50) == "F"); -println("control_flow: all assertions passed"); \ No newline at end of file +println("control_flow: all assertions passed"); diff --git a/examples/syntax/cross_module_raise.lk b/examples/syntax/cross_module_raise.lk new file mode 100644 index 00000000..14424714 --- /dev/null +++ b/examples/syntax/cross_module_raise.lk @@ -0,0 +1,60 @@ +// A raise crossing a module boundary keeps its payload. +// +// error_model_edges.lk covers first-class heap error values within one module. +// This is the same question across two, which is a different mechanism: each +// module has its own heap, and a heap value is a *handle* into one of them. The +// call's return path copies the value between the two heaps; the raise path did +// not, so `error([7, 8, 9])` arrived as a handle the catch could not read and +// the VM reported `heap object 88 out of bounds` — an internal invariant, +// printed at the user. The native build printed the list, so this is also a +// VM/AOT divergence, which is why it is an example: the sweep compares them. +// +// Int and short-string payloads were unaffected (stored inline, no handle), +// which is why the gap survived — the obvious probe passes. +// +// Run from project root: lk examples/syntax/cross_module_raise.lk + +use { raise_text, raise_list, raise_map, raise_struct, raise_nested, raise_rethrown } from "../general/raising"; + +// --- a heap string --- +try { raise_text(); } catch texted { + assert(texted == "a sufficiently long heap-allocated error payload"); +} + +// --- a list, read by index on the far side --- +try { raise_list(); } catch listed { + assert(listed[0] == 7); + assert(listed[2] == 9); +} + +// --- a map, including a nested list inside it --- +try { raise_map(); } catch mapped { + assert(mapped["code"] == 42); + assert(mapped["rows"][0][1] == 2); +} + +// --- a struct, whose field is itself a heap value --- +try { raise_struct(); } catch failure { + assert(failure.code == 7); + assert(failure.detail == ["a", "b"]); +} + +// --- raised a frame deeper than the imported function --- +try { raise_nested(); } catch nested { + assert(nested == [7, 8, 9]); +} + +// --- caught and re-raised inside the other module --- +try { raise_rethrown(); } catch again { + assert(again == [1, 2]); +} + +// --- an Int payload, the case that always worked; kept so a regression that +// breaks the inline path is told apart from one that breaks the heap path --- +try { raise_list(); } catch _ { + let scalar = 0; + try { error(11); } catch small { scalar = small; } + assert(scalar == 11); +} + +println("cross-module raise: ok"); diff --git a/examples/syntax/cross_task_raise.lk b/examples/syntax/cross_task_raise.lk new file mode 100644 index 00000000..e54321cd --- /dev/null +++ b/examples/syntax/cross_task_raise.lk @@ -0,0 +1,68 @@ +// A raise crossing a task boundary keeps its payload, and every map +// representation crosses a channel. +// +// Same question as cross_module_raise.lk, one boundary further out. A task runs +// against a heap of its own and its *result* travels as a value plus the heap it +// lives in, so the awaiting side can copy it. A raise had no such carrier: the +// error propagated with a bare handle and the task's heap was gone by the time +// anyone read it, so `error([1, 2, 3])` inside `spawn` came back as whatever +// object now sat at that index — ``, with no error +// reported at all. +// +// The native build failed differently and for a second reason: a map whose +// values are all Int is a typed carrier rather than a boxed map, and the +// channel copy only knew the boxed one. `send(c, {"code": 7})` answered "value +// cannot cross a channel" while the interpreter sent it. +// +// Run from project root: lk examples/syntax/cross_task_raise.lk + +use task; + +// --- a raise out of a task, by payload representation --- +let int_task = spawn(|| { error(42); return 0; }); +try { task.await(int_task); } catch e { assert(e == 42); } + +let short_task = spawn(|| { error("ab"); return 0; }); +try { task.await(short_task); } catch e { assert(e == "ab"); } + +let text_task = spawn(|| { error("a sufficiently long heap-allocated payload"); return 0; }); +try { task.await(text_task); } catch e { assert(e == "a sufficiently long heap-allocated payload"); } + +let list_task = spawn(|| { error([1, 2, 3]); return 0; }); +try { task.await(list_task); } catch e { assert(e == [1, 2, 3]); } + +// All-Int values: the typed map carrier, which is the case that failed twice. +let map_task = spawn(|| { error({"code": 7, "line": 9}); return 0; }); +try { task.await(map_task); } catch e { assert(e["code"] == 7); } + +let mixed_task = spawn(|| { error({"code": 7, "what": "bad"}); return 0; }); +try { task.await(mixed_task); } catch e { assert(e["what"] == "bad"); } + +let nested_task = spawn(|| { error({"rows": [[1, 2], [3]]}); return 0; }); +try { task.await(nested_task); } catch e { assert(e["rows"][0][1] == 2); } + +// --- a value of every map representation, over a channel --- +struct Point { p: Int } +let c = chan(4); + +send(c, {"code": 7}); +assert(recv(c)["code"] == 7); + +send(c, {"name": "x"}); +assert(recv(c)["name"] == "x"); + +send(c, {"mixed": 1, "other": "s"}); +let mixed = recv(c); +assert(mixed["mixed"] == 1); +assert(mixed["other"] == "s"); + +// A struct is a map with an identity, and it has to arrive as one. +send(c, Point { p: 3 }); +let point = recv(c); +assert(point.p == 3); +assert(typeof(point) == "Point"); + +send(c, [[1, 2], [3]]); +assert(recv(c) == [[1, 2], [3]]); + +println("cross-task raise: ok"); diff --git a/examples/syntax/defer.lk b/examples/syntax/defer.lk new file mode 100644 index 00000000..bd747715 --- /dev/null +++ b/examples/syntax/defer.lk @@ -0,0 +1,117 @@ +// `defer` — a release that happens on every path, written once. +// +// The statement runs when the function *returns* — by any `return`, or by +// falling off the end — and the deferred statements run in reverse: the second +// thing taken is the first thing given back. +// +// It does **not** run when a raise unwinds past it. `core/src/stmt/defer.rs` +// records the two attempts at closing that gap and what each one cost. Until it +// closes: a resource that must survive a raise needs `try`/`catch`, not +// `defer`. +// +// It exists because a kernel is where release-on-every-path fails, and it fails +// quietly. Writing this language's bare-metal demonstration produced, in one +// sitting, an allocator that leaked the pages it had already taken when a run +// turned out not to be contiguous, a task-stack allocator with the same bug, and +// one function that had to be *split in two* so five pages could be released on +// the seven paths that gave up. None of those was a wrong answer. Each was a +// machine that ran out of memory later, on a path nothing exercised. +// +// It is a rewrite, not a mechanism: the compiler moves the statements to just +// before every `return` and to the end of the body, and nothing downstream knows +// `defer` existed. See `core/src/stmt/defer.rs` for why that matters — a runtime +// version would need an implementation in each backend, which is how an +// interpreter and a compiled build come to disagree about a program. + +// The trace is passed in rather than kept in a global, because what this +// example is about is *when* things run, and a list handed to a function is the +// shape both backends agree on down to the order. +fn note(events: List, n: Int) -> Int { + events.push(n); + return n; +} + +// Three ways out, one release each, written once. +fn three_exits(events: List, which: Int) -> Int { + note(events, 1); + defer note(events, 91); + note(events, 2); + defer note(events, 92); + + if (which == 1) { + return 0 - 1; + } + if (which == 2) { + return 22; + } + return 33; +} + +let events: List = []; +println(three_exits(events, 1)); +println(three_exits(events, 2)); +println(three_exits(events, 0)); + +// The order, and the one detail that is easy to get wrong: the value being +// returned is computed *before* the releases run. `return note(3)` with a +// release above it has to answer 3 and then release — a rewrite that put the +// releases first would compute the return value out of whatever the release had +// just given away. +fn value_first(events: List) -> Int { + note(events, 1); + defer note(events, 91); + note(events, 2); + defer note(events, 92); + return note(events, 3); +} + +println(value_first(events)); + +// Every event, in order. The two functions above should have left: +// 1 2 92 91 1 2 92 91 1 2 92 91 1 2 3 92 91 +for index in 0..events.len() { + println(events[index]); +} + +// A function with no `defer` is untouched, and a `defer` with nothing after it +// still runs at the end of the body. +fn only_at_the_end(events: List) -> Int { + defer note(events, 80); + return 8; +} + +println(only_at_the_end(events)); +println(events.len()); + +// A closure defers on the way out of the *closure*, and the value it returns is +// computed first — the same rule a named function follows. +// +// It did not. `descend` in the desugar walked into `Stmt::Function` and a lambda +// is an *expression*, so a closure body reached neither the rewrite nor the +// check that rejects a misplaced `defer`: the statement was simply compiled +// where it stood and ran before the return expression was evaluated. The same +// three lines answered 0 in a function and 1 in a closure. +fn closure_defer_runs_last() -> Int { + let trace = []; + let body = || { + defer trace.push(1); + return trace.len(); + }; + let during = body(); + return during * 10 + trace.len(); +} +assert(closure_defer_runs_last() == 1); + +// And the same three lines written as a named function agree. +fn body_as_function(t: List) -> Int { + defer t.push(1); + return t.len(); +} +fn named_defer_runs_last() -> Int { + let trace = []; + let during = body_as_function(trace); + return during * 10 + trace.len(); +} +assert(named_defer_runs_last() == 1); + +println("defer: ok"); diff --git a/examples/syntax/error_handling.lk b/examples/syntax/error_handling.lk index c34ad060..c8cb613d 100644 --- a/examples/syntax/error_handling.lk +++ b/examples/syntax/error_handling.lk @@ -3,8 +3,8 @@ // 1. Return nil on failure fn safe_divide(a, b) { - if (b == 0) { return nil; } - return a / b; + if (b == 0) { return nil; } + return a / b; } assert(safe_divide(10, 2) > 4.9); assert(safe_divide(10, 0) == nil); @@ -15,9 +15,9 @@ assert(result == 0); // 3. Chain of operations with nil propagation fn get_nested(data, key1, key2) { - let level1 = data.get(key1); - if (level1 == nil) { return nil; } - return level1.get(key2); + let level1 = data.get(key1); + if (level1 == nil) { return nil; } + return level1.get(key2); } let obj = { "user": { "email": "alice@example.com" } }; assert(get_nested(obj, "user", "email") == "alice@example.com"); @@ -27,16 +27,16 @@ assert(email == "no-email"); // 4. Validation using ?? with defaults fn validate_name(name) { - return name ?? "name required"; + return name ?? "name required"; } assert(validate_name("Alice") == "Alice"); assert(validate_name(nil) == "name required"); // 5. Result list — [ok, value_or_error] pair fn safe_lookup(map, key) { - let val = map.get(key); - if (val == nil) { return [false, "key not found"]; } - return [true, val]; + let val = map.get(key); + if (val == nil) { return [false, "key not found"]; } + return [true, val]; } let config = { "host": "localhost", "port": 8080 }; let [ok1, val1] = safe_lookup(config, "host"); @@ -48,9 +48,9 @@ assert(val2 == "key not found"); // 6. Range check — returns error string or nil fn check_range(n, lo, hi, label) { - if (n < lo) { return "${label} too low (min ${lo})"; } - if (n > hi) { return "${label} too high (max ${hi})"; } - return nil; + if (n < lo) { return "${label} too low (min ${lo})"; } + if (n > hi) { return "${label} too high (max ${hi})"; } + return nil; } assert(check_range(5, 0, 10, "age") == nil); assert(check_range(-1, 0, 10, "age") == "age too low (min 0)"); diff --git a/examples/syntax/error_model_edges.lk b/examples/syntax/error_model_edges.lk index 6f4119ce..d3d10505 100644 --- a/examples/syntax/error_model_edges.lk +++ b/examples/syntax/error_model_edges.lk @@ -51,7 +51,7 @@ try { deep1(); } catch deep { } // --- runtime errors (not error()) are recoverable too: division by zero --- -fn div_zero() { return 1 / 0; } +fn div_zero() { return 1 % 0; } let div_caught = false; try { div_zero(); } catch e { div_caught = true; } assert(div_caught); diff --git a/examples/syntax/error_unwrap.lk b/examples/syntax/error_unwrap.lk index ef9b7b8b..c688d1ed 100644 --- a/examples/syntax/error_unwrap.lk +++ b/examples/syntax/error_unwrap.lk @@ -5,7 +5,7 @@ fn double(x) { return x * 2; } fn boom() { error("kaboom"); return 0; } -fn div_zero() { return 1 / 0; } +fn div_zero() { return 1 % 0; } // Success path: no raise, catch skipped. let ok = 0; diff --git a/examples/syntax/for_loop_patterns.lk b/examples/syntax/for_loop_patterns.lk index f2d43446..25c6a49e 100644 --- a/examples/syntax/for_loop_patterns.lk +++ b/examples/syntax/for_loop_patterns.lk @@ -15,8 +15,8 @@ let pairs = [[1, "a"], [2, "b"], [3, "c"]]; let nums = []; let labels = []; for (n, l) in pairs { - nums.push(n); - labels.push(l); + nums.push(n); + labels.push(l); } assert(nums == [1, 2, 3]); assert(labels == ["a", "b", "c"]); @@ -25,14 +25,14 @@ assert(labels == ["a", "b", "c"]); let matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; let firsts = []; for [head, ..tail] in matrix { - firsts.push(head); + firsts.push(head); } assert(firsts == [1, 4, 7]); // 5. String iteration (chars) let chars = []; for ch in "abc" { - chars.push(ch); + chars.push(ch); } assert(chars == ["a", "b", "c"]); @@ -40,7 +40,7 @@ assert(chars == ["a", "b", "c"]); let scores = { "alice": 95, "bob": 87, "carol": 92 }; let total = 0; for pair in scores { - total += pair[1]; + total += pair[1]; } assert(total == 274); @@ -48,8 +48,8 @@ assert(total == 274); let data = [[1, 2], [3, 4], [5, 6]]; let flattened = []; for [a, b] in data { - flattened.push(a); - flattened.push(b); + flattened.push(a); + flattened.push(b); } assert(flattened == [1, 2, 3, 4, 5, 6]); @@ -57,8 +57,29 @@ assert(flattened == [1, 2, 3, 4, 5, 6]); let items = ["x", "y", "z"]; let indices = []; for pair in items.enumerate() { - indices.push(pair[0]); + indices.push(pair[0]); } assert(indices == [0, 1, 2]); -println("for_loop_patterns: all assertions passed"); \ No newline at end of file +// A loop variable is asked a question, not just read. The element it holds +// came out of a bounds-checked read, so it is a `Maybe` — and asking it +// anything unwraps first, which is what happens outside a loop too: a method +// on an absent value raises, catchably. +let widths = []; +for word in ["ab", "cde", "f"] { widths.push(word.len()); } +assert(widths == [2, 3, 1]); + +let cased = []; +for word in ["ab", "cd"] { cased.push(word.upper()); } +assert(cased == ["AB", "CD"]); + +fn width_of(table: Map, key: String) -> Int { + let asked = 0; + try { asked = table[key].len(); } catch e { asked = 0 - 1; } + return asked; +} +let table = {"a": "xy"}; +assert(width_of(table, "missing") == 0 - 1); +assert(width_of(table, "a") == 2); + +println("for_loop_patterns: all assertions passed"); diff --git a/examples/syntax/handle_identity.lk b/examples/syntax/handle_identity.lk new file mode 100644 index 00000000..ecd5b55e --- /dev/null +++ b/examples/syntax/handle_identity.lk @@ -0,0 +1,51 @@ +// A channel and a task are values, not the numbers the runtime keys them by. +// +// Natively a handle is an `i64` id. It used to travel as that bare id, and the +// id is an `Int`: `typeof` answered `Int`, printing one wrote the number, and +// `chan(1) == 1` was *true* compiled and false interpreted. Tracking which +// integers were really handles caught the direct cases and lost the fact +// wherever the value escaped — into a list, into a function's parameter — which +// is most of what a program does with a channel. So the tag travels with the +// value instead. + +use task; + +let c = chan(2); +let t = spawn(|| 7); + +// What it is, and how it reads. +println(typeof(c)); +println(typeof(t)); +println(c); +println(t); +println("c=" + c); + +// Inside containers, where the mark used to be lost. +println([c, t]); +println({ "k": c }); + +// Identity, not the number behind it. +println(c == 1); +println(c == c); +println(c == chan(1)); + +// And it still does what a channel is for. +send(c, 1); +send(c, 2); +println(recv(c)); +println(recv(c)); +println(task.await(t)); + +// Through a function, where a typed parameter used to erase it. +fn describe(h) -> String { + return typeof(h) + " " + h; +} + +println(describe(c)); + +// And through a channel, which copies by identity: the same channel comes out. +let post = chan(1); +send(post, c); +let back = recv(post); +println(typeof(back)); +println(back == c); diff --git a/examples/syntax/impl_builtin.lk b/examples/syntax/impl_builtin.lk new file mode 100644 index 00000000..296a8ed9 --- /dev/null +++ b/examples/syntax/impl_builtin.lk @@ -0,0 +1,90 @@ +// A user `impl` block can name a built-in type, and the method it adds is an +// ordinary method call on an ordinary value. +// +// An impl target cannot name an *element* type — `impl List` is refused, +// because two element types are not distinguishable at run time — so each +// constructor has at most one impl block. + +impl Int { + fn doubled(self) -> Int { + return self * 2; + } +} + +impl Float { + fn halved(self) -> Float { + return self / 2.0; + } +} + +impl String { + fn shout(self) -> String { + return self.upper() + "!"; + } +} + +impl Bool { + fn flipped(self) -> Bool { + return !self; + } +} + +impl List { + fn second(self) -> Any { + return self[1]; + } +} + +impl Map { + fn size(self) -> Int { + return self.len(); + } +} + +impl Set { + fn is_any(self) -> Bool { + return self.len() > 0; + } +} + +impl Nil { + fn describe(self) -> String { + return "nothing"; + } +} + +impl Slice { + fn window_len(self) -> Int { + return self.len(); + } +} + +let nothing = nil; +println(nothing.describe()); +println((5).doubled()); +println((3.0).halved()); +println("hi".shout()); +println(true.flipped()); +println([1, 2, 3].second()); +println(["a", "b"].second()); +println([1.5, 2.5].second()); +println([1, 2, 3].slice(0, 2).window_len()); +println({ "k": 1 }.size()); +println(Set([1, 2]).is_any()); + +// Through a parameter, where the receiver's type comes from the call site. +fn twice(v: Int) -> Int { + return v.doubled(); +} + +println(twice(7)); + +// A built-in method wins over an impl that names it: `len` is the language's, +// not this one's. +impl List { + fn len(self) -> Int { + return 99; + } +} + +println([1, 2].len()); diff --git a/examples/syntax/impl_inherent.lk b/examples/syntax/impl_inherent.lk new file mode 100644 index 00000000..ade598c5 --- /dev/null +++ b/examples/syntax/impl_inherent.lk @@ -0,0 +1,31 @@ +// `impl Type { … }` — methods that belong to the type itself. +// +// A method used to need a trait to hang off, and there is no UFCS either, so +// giving a struct a plain method meant declaring a trait that said nothing: +// +// trait Methods { } +// impl Methods for Point { fn norm2(self) -> Int { … } } +// +// Everything else was already in place — dispatch keys on the target type, not +// on the trait — so only this spelling was missing. + +struct Point { x: Int, y: Int } + +impl Point { + fn norm2(self) -> Int { return self.x * self.x + self.y * self.y; } + fn scaled(self, by: Int) -> Point { return Point { x: self.x * by, y: self.y * by }; } +} + +let p = Point { x: 3, y: 4 }; +assert(p.norm2() == 25); +assert(p.scaled(2).x == 6); + +// An inherent impl and a trait impl coexist on the same type: the trait states +// what the type promises, the inherent block holds what is simply its own. +trait Area { fn area(self) -> Int; } +impl Area for Point { fn area(self) -> Int { return self.x * self.y; } } + +assert(p.area() == 12); +assert(p.norm2() == 25); + +println("impl_inherent: all assertions passed"); diff --git a/examples/syntax/index_by_variable.lk b/examples/syntax/index_by_variable.lk new file mode 100644 index 00000000..5fb76d76 --- /dev/null +++ b/examples/syntax/index_by_variable.lk @@ -0,0 +1,43 @@ +// `a[i]` is an index; `a.i` is a member. They parse to the same node shape — +// an access with a key — and the key is what tells them apart: a member is +// always a string *literal*, an index is any expression. +// +// The compiler read a bare variable as a member name too, so `fs[i]()` meant +// `fs.i()`: calling a closure out of a list by a variable index raised +// `List has no method 'i'`. `fs[0]()` worked, because `0` is not an +// identifier — which is why nothing noticed. + +let fs = [|| 7, || 8]; +let i = 1; +assert(fs[0]() == 7); +assert(fs[i]() == 8); + +// The same shape through a map, and through a chain. +let handlers = {"start": || "go", "stop": || "halt"}; +let which = "start"; +assert(handlers[which]() == "go"); + +let nested = {"outer": {"inner": || 42}}; +let k1 = "outer"; +let k2 = "inner"; +assert(nested[k1][k2]() == 42); + +// An index whose variable happens to be named like a method is still an index. +let xs = [10, 20, 30]; +let len = 1; +assert(xs[len] == 20); +assert(xs.len() == 3); + +let m = {"get": "a value", "keys": "another"}; +let get = "get"; +assert(m[get] == "a value"); +assert(m.get("keys") == "another"); + +// And an index computed by a call. +fn first() -> Int { + return 0; +} + +assert(fs[first()]() == 7); + +println("index by variable: ok"); diff --git a/examples/syntax/internal.lk b/examples/syntax/internal.lk index d288b216..3342856f 100644 --- a/examples/syntax/internal.lk +++ b/examples/syntax/internal.lk @@ -1,3 +1,3 @@ fn f(x: Int, {y: Int = x + 1}) { return y; } println(f(11)); -return f(10); \ No newline at end of file +return f(10); diff --git a/examples/syntax/macro_internal_rules.lk b/examples/syntax/macro_internal_rules.lk new file mode 100644 index 00000000..3237e34a --- /dev/null +++ b/examples/syntax/macro_internal_rules.lk @@ -0,0 +1,78 @@ +// Internal macro rules, marked with `@` the way Rust's are. +// +// A declarative macro cannot add numbers up: there is no accumulator, only +// patterns. The way that is done is a recursive rule that carries the running +// total in its own argument list — and to keep a caller from reaching that rule +// by accident it is marked with a token nobody writes by hand. +// +// Rust uses `@`. LK does too, and for the same reason: it is legal in a token +// stream and illegal in every position a person would write one, so +// `layout!(@from …)` cannot be confused with a real invocation, and a rule that +// begins with it cannot be matched by `layout! { A: 8, … }`. +// +// The example is the one that motivated it. A structure in memory is a list of +// widths, and what the code needs is the offsets — which is the widths added up. +// Written by hand that is a column of numbers where inserting a field changes +// every line below it, and where the size at the end is the one number nothing +// checks. + +macro_rules! layout { + // The end of the run: the accumulated offset *is* the size, so it cannot + // disagree with the fields above it. + (@from $prev:expr, => $size:ident) => { + const $size = $prev; + }; + // One field starts where the run has got to, and the run advances by its + // width. + (@from $prev:expr, $name:ident : $width:expr, $($rest:tt)*) => { + const $name = $prev; + layout!(@from ($prev) + ($width), $($rest)*); + }; + // What a caller writes. A layout begins at zero: an offset is measured from + // whatever base the caller supplies. + ($($body:tt)*) => { + layout!(@from 0, $($body)*); + }; +} + +// An Ethernet header, which is the case worth checking by hand: two six-byte +// addresses and a two-byte type. A reader who assumed machine words would get +// every one of these wrong. +layout! { + ETH_DEST: 6, + ETH_SOURCE: 6, + ETH_TYPE: 2, + => ETH_HEADER_SIZE +} + +// And a mixed one, where the odd widths are the point: the two single bytes in +// the middle make every offset after them look wrong and be right. +layout! { + ARP_HTYPE: 2, + ARP_PTYPE: 2, + ARP_HLEN: 1, + ARP_PLEN: 1, + ARP_OPER: 2, + ARP_SENDER_MAC: 6, + => ARP_SENDER_IP_OFFSET +} + +println(ETH_DEST); +println(ETH_SOURCE); +println(ETH_TYPE); +println(ETH_HEADER_SIZE); + +println(ARP_HLEN); +println(ARP_PLEN); +println(ARP_OPER); +println(ARP_SENDER_MAC); +println(ARP_SENDER_IP_OFFSET); + +// The offsets are constants by the time anything runs, so they can be used the +// way any other constant is. +fn field_at(base: Int, offset: Int) -> Int { + return base + offset; +} + +println(field_at(0x1000, ETH_TYPE)); +println(field_at(0x1000, ARP_SENDER_MAC)); diff --git a/examples/syntax/macros.lk b/examples/syntax/macros.lk index 5b5ea2e5..3a6b9523 100644 --- a/examples/syntax/macros.lk +++ b/examples/syntax/macros.lk @@ -34,4 +34,36 @@ fn selected_value() { } assert_eq!(selected_value(), 5); + +// An expansion that lands where an *expression* is expected is one expression, +// not loose tokens. Spliced raw, the pieces bound to whatever surrounded the +// call instead of to each other: `twice!(n) * 2` computed `n + n * 2`, and +// `"v=" + twice!(n)` concatenated both terms. +macro_rules! twice { + ($e:expr) => { ($e) + ($e) }; +} + +let n = 3; +assert_eq!(twice!(n), 6); +assert_eq!(twice!(n) * 2, 12); +assert_eq!(2 * twice!(n), 12); +assert_eq!("v=" + twice!(n), "v=6"); +assert_eq!([twice!(n), 9], [6, 9]); + +// A multi-statement expansion stays multiple statements — the grouping is for +// expression position only, and a top-level `;` says this is not one. +macro_rules! swap_two { + ($a:ident, $b:ident) => { + let held = $a; + $a = $b; + $b = held; + }; +} + +let left = 1; +let right = 2; +swap_two!(left, right); +assert_eq!(left, 2); +assert_eq!(right, 1); + return values.1; diff --git a/examples/syntax/map_order.lk b/examples/syntax/map_order.lk new file mode 100644 index 00000000..20b4b4a0 --- /dev/null +++ b/examples/syntax/map_order.lk @@ -0,0 +1,45 @@ +// A map iterates, and prints, in the order its keys were first written. +// +// Not the hash table's order, which is a property of *how the map was built* +// rather than of what it holds: the two maps below are `==`, and used to render +// their fields differently. It is also what makes the interpreter and the +// compiled build agree structurally — both append to a vector — instead of by +// both landing on the same hash layout. + +// Written as a literal, and built one key at a time: same content, same order. +let literal = {"zebra": 1, "apple": 2, "mango": 3, "kiwi": 4, "pear": 5, "fig": 6}; +let built = {}; +built["zebra"] = 1; +built["apple"] = 2; +built["mango"] = 3; +built["kiwi"] = 4; +built["pear"] = 5; +built["fig"] = 6; + +assert(literal == built); +let order = ["zebra", "apple", "mango", "kiwi", "pear", "fig"]; +assert(literal.keys() == order); +assert(built.keys() == order); +assert("{}".format(literal) == "{}".format(built)); + +// Rewriting a key updates it in place and keeps its position. +built["mango"] = 30; +assert(built.keys() == order); +assert(built["mango"] == 30); + +// A new key goes to the end. +built["quince"] = 7; +assert(built.keys() == ["zebra", "apple", "mango", "kiwi", "pear", "fig", "quince"]); + +// Integer keys are ordered the same way. +let ints = {}; +ints[30] = 3; +ints[10] = 1; +ints[20] = 2; +assert(ints.keys() == [30, 10, 20]); +assert(ints.values() == [3, 1, 2]); + +// Values follow the keys. +assert(literal.values() == [1, 2, 3, 4, 5, 6]); + +println("map order: all assertions passed"); diff --git a/examples/syntax/match.lk b/examples/syntax/match.lk index f43162a5..29b074ec 100644 --- a/examples/syntax/match.lk +++ b/examples/syntax/match.lk @@ -3,53 +3,154 @@ // 1. Literal patterns let grade = match "A" { - "A" => "Excellent", - "B" => "Good", - "C" => "Average", - _ => "Below average", + "A" => "Excellent", + "B" => "Good", + "C" => "Average", + _ => "Below average", }; assert(grade == "Excellent"); // 2. Integer literal + wildcard let label = match 404 { - 200 => "OK", - 301 => "Moved", - 404 => "Not Found", - 500 => "Server Error", - _ => "Unknown", + 200 => "OK", + 301 => "Moved", + 404 => "Not Found", + 500 => "Server Error", + _ => "Unknown", }; assert(label == "Not Found"); // 3. Or-pattern — combine multiple values in one arm let day_type = match 6 { - 1 | 2 | 3 | 4 | 5 => "weekday", - 6 | 7 => "weekend", - _ => "invalid", + 1 | 2 | 3 | 4 | 5 => "weekday", + 6 | 7 => "weekend", + _ => "invalid", }; assert(day_type == "weekend"); // 4. Bool literal match let bool_result = match true { - true => "yes", - false => "no", + true => "yes", + false => "no", }; assert(bool_result == "yes"); // 5. String literal match let lang = match "lk" { - "rs" => "Rust", - "lk" => "LK", - "py" => "Python", - _ => "Unknown", + "rs" => "Rust", + "lk" => "LK", + "py" => "Python", + _ => "Unknown", }; assert(lang == "LK"); // 6. Match returns a value — use it inline let size_label = match 42 { - 1 => "one", - _ => "many", + 1 => "one", + _ => "many", }; assert(size_label == "many"); -println("match: all assertions passed"); \ No newline at end of file +// A list pattern says *how many*, and `..rest` is how you say "at least". +// +// Without that distinction `[]` matched a list of any length — so an arm +// written first swallowed every arm after it, and `[a]` matched a pair. Every +// example here writes `..rest` when it means a prefix, which is the reading the +// length test now has: exact without a rest pattern, a minimum with one. +fn count_of(v: List) -> String { + match v { + [] => { return "none"; } + [a] => { return "one:{}".format(a); } + [a, b] => { return "two:{}".format(a + b); } + [a, ..rest] => { return "many:{}".format(rest.len()); } + _ => { return "other"; } + } +} +assert(count_of([]) == "none"); +assert(count_of([7]) == "one:7"); +assert(count_of([2, 3]) == "two:5"); +assert(count_of([1, 2, 3, 4]) == "many:3"); + +// The same rule where the arm order cannot rescue it: the empty pattern is +// written *after* a longer one, so only an actually-empty list may reach it. +fn empty_last(v: List) -> Int { + match v { + [a] => { return 1; } + [] => { return 0; } + _ => { return 9; } + } +} +assert(empty_last([]) == 0); +assert(empty_last([5]) == 1); +assert(empty_last([5, 6]) == 9); + +println("match: all assertions passed"); + +// A sub-pattern inside a list or a map is a pattern like any other. It used to +// be refused outright — "Compiler does not support nested refutable pattern +// yet" — so a list pattern could check its *shape* and nothing about its +// contents, which is most of what pattern matching is for. +fn quadrant(p) -> String { + match p { + [0, 0] => { return "origin"; } + [0, y] => { return "on-y"; } + [x, 0] => { return "on-x"; } + [x, y] => { return "point"; } + _ => { return "not a pair"; } + } +} +assert(quadrant([0, 0]) == "origin"); +assert(quadrant([0, 5]) == "on-y"); +assert(quadrant([3, 0]) == "on-x"); +assert(quadrant([3, 4]) == "point"); +assert(quadrant([1]) == "not a pair"); + +// A tagged map, which is what most protocol handling looks like. +fn apply(msg) -> Int { + match msg { + {"op": "add", "n": n} => { return n + 1; } + {"op": "double", "n": n} => { return n * 2; } + _ => { return 0; } + } +} +assert(apply({"op": "add", "n": 4}) == 5); +assert(apply({"op": "double", "n": 4}) == 8); +assert(apply({"op": "nope", "n": 4}) == 0); + +// One function, six shapes. An unannotated parameter matched against several +// patterns gives the scrutinee an open type, and a `match` arm asks a question +// rather than stating a requirement — constraining the scrutinee to each arm's +// shape reported the arms as a conflict with each other. +fn shape(v) -> String { + match v { + [1, [a, 9]] => { return "nine"; } + [1, [a, b]] => { return "inner"; } + ["a", n] => { return "tagged"; } + [x, ..rest] => { return "head and {}".format(rest.len()); } + {"op": "add", "n": n} => { return "add"; } + _ => { return "other"; } + } +} +assert(shape([1, [2, 9]]) == "nine"); +assert(shape([1, [2, 3]]) == "inner"); +assert(shape(["a", 2]) == "tagged"); +assert(shape([0, 1, 2, 3]) == "head and 3"); +assert(shape({"op": "add", "n": 1}) == "add"); +assert(shape(5) == "other"); + +// The `..rest` arm above is the one that pins where the binding is built: a map +// reaches it, and running `SliceFrom` on a map raises "not sliceable" from an +// arm that does not match. It is built inside the shape guard, and the slot is +// seeded with an empty container of its own kind rather than with `nil` so that +// both paths give it one type. +fn guarded(v: List) -> String { + match v { + [n, _] if n > 3 => { return "big"; } + [x, ..rest] => { return "head and {}".format(rest.len()); } + _ => { return "other"; } + } +} +assert(guarded([5, 1]) == "big"); +assert(guarded([0, 1, 2, 3]) == "head and 3"); +assert(guarded([]) == "other"); diff --git a/examples/syntax/named_default_scope.lk b/examples/syntax/named_default_scope.lk new file mode 100644 index 00000000..1f813c64 --- /dev/null +++ b/examples/syntax/named_default_scope.lk @@ -0,0 +1,61 @@ +// A named parameter's default is read in the scope that *declared* it. +// +// A default is lowered at the call site, because it may refer to the call's own +// earlier arguments — `fn f(x: Int, {y: Int = x + 1})` needs `x`, and only the +// caller has it. The parameters are bound for that, and everything else used to +// fall through to whatever the *caller* had in scope. So a caller that happened +// to have a binding of the same name captured it: +// +// const LIMIT: Int = 7; +// fn f({n: Int = LIMIT}) -> Int { return n; } +// fn g() -> Int { let LIMIT = 99; return f(); } // answered 99 +// +// A silent wrong answer, in one module, with no import and no concurrency +// involved. `lk check` had nothing to say about it either, because the default +// is written in one function and read in another. +// +// Run from project root: lk examples/syntax/named_default_scope.lk + +const LIMIT: Int = 7; + +fn describe(x: Int, {y: Int = x + 1, z: Int = LIMIT}) -> String { + return "${x} ${y} ${z}"; +} + +// A caller that shadows *both* names a default mentions: the parameter `x` and +// the module-level `LIMIT`. Neither may be visible to the default. +fn from_a_shadowing_scope() -> String { + let x = 900; + let LIMIT = 99; + return describe(1); +} + +// The same call from a scope that shadows nothing, so the two answers can be +// compared rather than only asserted. +fn from_a_plain_scope() -> String { + return describe(1); +} + +assert(describe(1) == "1 2 7"); +assert(from_a_shadowing_scope() == "1 2 7"); +assert(from_a_plain_scope() == "1 2 7"); + +// Supplying a named argument still wins over the default. +assert(describe(1, y: 50) == "1 50 7"); + +// A default reading an earlier parameter reads the *argument*, not a leftover: +// two calls in the same shadowing scope must not see each other. +fn twice() -> String { + let x = 900; + return "${describe(1)} ${describe(10)}"; +} +assert(twice() == "1 2 7 10 11 7"); + +// A nested call: the inner one's default must not see the outer one's binding +// of the same parameter name. +fn nested() -> String { + return describe(describe(2).len()); +} +assert(nested() == "5 6 7"); + +println("named default scope: ok"); diff --git a/examples/syntax/nested_assignment.lk b/examples/syntax/nested_assignment.lk new file mode 100644 index 00000000..50e0d5a7 --- /dev/null +++ b/examples/syntax/nested_assignment.lk @@ -0,0 +1,73 @@ +// An assignment target is a *chain*, and the store belongs to its last step. +// +// The parser used to read the chain's *first* step and discard the rest, so +// `p.m["b"] = 2` became `p.m = 2`: it replaced the map with the value, on both +// engines, and `lk check` had nothing to object to whenever the field's type +// left room for it. `p.q.n = 5` did the same to a nested struct, and the two +// spellings that are all index (`m["a"]["b"] = 2`) were a syntax error. + +struct Inner { + n: Int, +} + +struct Outer { + inner: Inner, + counts: Map, + items: List, +} + +let o = Outer { + inner: Inner { n: 1 }, + counts: { "a": 1 }, + items: [10, 20], +}; + +// A field of a field. +o.inner.n = 5; +println(o.inner.n); + +// A key of a field, and an index of a field. +o.counts["b"] = 2; +o.items[0] = 99; +println(o.counts); +println(o.items); + +// Compound assignment reads the same chain it writes. +o.counts["a"] += 10; +o.items[1] *= 3; +println(o.counts); +println(o.items); + +// All the way down, with no struct in sight. +let grid = [[1, 2], [3, 4]]; +grid[1][0] = 30; +println(grid); + +let nested = { "x": { "y": 1 } }; +nested["x"]["y"] = 2; +println(nested); + +// The store lands on the container the chain names, so anything else holding +// that container sees it. +let alias = o.inner; +o.inner.n = 42; +println(alias.n); + +// A map pattern destructures a map, and a struct is not one — even though a +// struct instance rides the map carrier natively, where this used to match. +let pairs = { "left": 1, "right": 2 }; +let { left: l, right: r } = pairs; +println(l + r); + +struct Corner { + left: Int, + right: Int, +} + +let corner = Corner { left: 3, right: 4 }; +try { + let { left: bad, right: worse } = corner; + println(bad + worse); +} catch e { + println("a struct is not a map"); +} diff --git a/examples/syntax/null_coalescing.lk b/examples/syntax/null_coalescing.lk index 495602a1..ab0d69ba 100644 --- a/examples/syntax/null_coalescing.lk +++ b/examples/syntax/null_coalescing.lk @@ -24,9 +24,9 @@ assert(timeout == 1000); let user = { "name": "Alice", "address": nil }; let city = nil; if (user.address != nil) { - city = user.address.city; + city = user.address.city; } else { - city = nil; + city = nil; } assert(city == nil); @@ -34,17 +34,17 @@ assert(city == nil); let data = { "a": { "b": nil } }; let deep_result = nil; if (data.a != nil) { - if (data.a.b != nil) { - deep_result = data.a.b.c; - } + if (data.a.b != nil) { + deep_result = data.a.b.c; + } } assert(deep_result == nil); // 6. ?? in function defaults fn get_config({host: String? = nil, port: Int? = nil}) { - let h = host ?? "localhost"; - let p = port ?? 8080; - return "${h}:${p}"; + let h = host ?? "localhost"; + let p = port ?? 8080; + return "${h}:${p}"; } assert(get_config(host: "prod.io") == "prod.io:8080"); assert(get_config() == "localhost:8080"); @@ -57,7 +57,175 @@ assert(items.len() == 0); // 8. Truthiness in if/while — nil is falsy, only false and nil are falsy let maybe_empty = 0; if (maybe_empty) { - assert(true); + assert(true); } -println("null_coalescing: all assertions passed"); \ No newline at end of file +// 9. An out-of-range read *is* nil, everywhere it lands +// +// Reading past the end of a container gives nil — and nil is a value, so it +// goes into a list, into a map, compares unequal to everything, and renders as +// `nil` in a string. All four of those raised on a compiled build while the +// interpreter ran them: the native lowering was narrowing "possibly nil" to +// "definitely present" by asserting, which is right where a *number* is +// required and wrong everywhere else. A typed container is the one place that +// genuinely cannot hold nil, and there the list is built to hold anything. +fn past_the_end(xs: List, upto: Int) -> String { + let seen = []; + let index = {}; + let text = ""; + let equal = 0; + let i = 0; + while i < upto { + seen.push(xs[i]); + index["at" + i] = xs[i]; + text = text + "|" + xs[i]; + if xs[i] == 7 { equal = equal + 1; } + if xs[i] != 7 { equal = equal + 10; } + i = i + 1; + } + return text + " " + seen.len() + " " + index.len() + " " + equal; +} +assert(past_the_end([7, 8], 2) == "|7|8 2 2 11"); +assert(past_the_end([7, 8], 4) == "|7|8|nil|nil 4 4 31"); +assert(past_the_end([], 2) == "|nil|nil 2 2 20"); + +// Arithmetic is the exception, and it is an exception in the interpreter too: +// using nil as a number is an error there, so it is one here. +fn sums_or_fails(xs: List, upto: Int) -> Int { + let total = 0; + let i = 0; + try { + while i < upto { + total = total + xs[i]; + i = i + 1; + } + } catch e { + return 0 - total; + } + return total; +} +assert(sums_or_fails([1, 2], 2) == 3); +assert(sums_or_fails([1, 2], 3) == 0 - 3); + +// 10. And the sentence that error carries is the same sentence either way +// +// A caught error is a *value*, so which words it contains is part of what the +// program computes — not a diagnostic detail. The compiled build used to answer +// `runtime error` for all of these while the interpreter named the operator and +// both operands, so this comparison failed on one backend and passed on the +// other. Both sides of each operator, because the message says which one was +// nil. +fn message_for(pick: Int) -> String { + let xs = [1]; + let ys = [2]; + return try { + if pick == 0 { xs[9] + 1 } else + if pick == 1 { 1 + xs[9] } else + if pick == 2 { xs[9] - 1 } else + if pick == 3 { xs[9] * 1 } else + if pick == 4 { xs[9] / 1 } else + if pick == 5 { xs[9] % 1 } else + if pick == 6 { xs[9] < 1 } else + if pick == 7 { 1 >= xs[9] } else + { xs[9] + ys[9] } + } catch e { e }; +} +assert(message_for(0) == "Add expected numbers or strings, got Nil and Int"); +assert(message_for(1) == "Add expected numbers or strings, got Int and Nil"); +assert(message_for(2) == "Sub expected numbers or list/map lhs, got Nil and Int"); +assert(message_for(3) == "* expects Int or Float, got Nil and Int"); +assert(message_for(4) == "/ expects Int or Float, got Nil and Int"); +assert(message_for(5) == "% expects Int or Float, got Nil and Int"); +assert(message_for(6) == "< expected Int, Float, or String, got Nil and Int"); +assert(message_for(7) == ">= expected Int, Float, or String, got Int and Nil"); +assert(message_for(8) == "Add expected numbers or strings, got Nil and Nil"); + +// 11. And nil travels through the *constructors* too +// +// A list literal, `insert`, and `join` each read a value and had their own idea +// of what a value could be. `[xs[9], 1]` is `[nil, 1]` in the interpreter; the +// compiled build refused to lower it at all, because the literal's element-type +// list simply did not mention the nullable kinds. A typed list is the one +// receiver that cannot take nil, and there the answer is to build the list to +// hold anything — which is what the fixpoint does once the store says so. +fn shapes(xs: List, upto: Int) -> String { + let built = []; + let joined = ""; + let i = 0; + while i < upto { + built.push([xs[i], i]); + joined = joined + [xs[i]].join("") + ";"; + i = i + 1; + } + let ordered = [9]; + ordered.insert(0, xs[upto]); + return joined + " " + built.len() + " " + ordered.len(); +} +assert(shapes([4, 5], 2) == "4;5; 2 2"); +assert(shapes([4], 2) == "4;nil; 2 2"); + +// 12. One parameter, a list at one call and a number at another +// +// An unannotated parameter accepts both — every other pair already did, and an +// *empty* list was the exception: `f([]); f(5)` was rejected with `Cannot unify +// Int with List<'T2>` while `f([1]); f(5)` was accepted, the same program with +// one element in it. The empty literal's element type is still a variable, and +// the widening that turns two observations into a union refused to widen +// anything containing one. +fn describe(v) -> String { + if v == nil { return "nil"; } + return typeof(v); +} +assert(describe([]) == "List"); +assert(describe(5) == "Int"); +assert(describe("s") == "String"); +assert(describe({}) == "Map"); + +// 13. Looking for nil in a container that cannot hold it +// +// `List` has no nil in it, so an out-of-range needle is simply not found: +// `contains` false, `count` zero, `index_of` nil. The typed lowering matched the +// needle's type exactly, so a possibly-absent one matched nothing and the module +// fell back — for a question whose answer was already known. It is answered by +// asking with the payload and picking, not by rebuilding the list to hold +// something it never receives. +fn searches(xs: List, upto: Int) -> String { + let table = [7, 8, 7]; + let found = 0; + let counted = 0; + let placed = 0; + let i = 0; + while i < upto { + if table.contains(xs[i]) { found = found + 1; } + counted = counted + table.count(xs[i]); + placed = placed + (table.index_of(xs[i]) ?? 50); + i = i + 1; + } + return found + "/" + counted + "/" + placed; +} +assert(searches([7, 8], 2) == "2/3/1"); +assert(searches([7, 8], 4) == "2/3/101"); +assert(searches([], 2) == "0/0/100"); + +// `?.` — the other half of the pair, and the half that had no native lowering +// at all. It lowers to a nil test on the receiver, and the nil test had no case +// for a *container*: `m?.k` on a plain map and `p?.field` on a struct both +// dropped the whole program to the interpreter, including the receivers that +// cannot be nil in the first place. + +struct Node { + value: Int, + next: Any, +} + +let node = Node { value: 1, next: nil }; +let table = { "k": 5 }; +let nothing: Any = nil; + +assert((node?.value ?? -1) == 1); +assert((table?.k ?? -1) == 5); +assert((table?.missing ?? -1) == -1); +assert((nothing?.value ?? -1) == -1); +assert((node?.next?.value ?? -1) == -1); + +println("null_coalescing: all assertions passed"); diff --git a/examples/syntax/numeric_auto_promotion.lk b/examples/syntax/numeric_auto_promotion.lk index 25d4c335..3448e52a 100644 --- a/examples/syntax/numeric_auto_promotion.lk +++ b/examples/syntax/numeric_auto_promotion.lk @@ -4,4 +4,29 @@ let precise = answer + 2.5; let maybe_float = precise / 2; // Expect 21.25 after promotion. + +// A float rendered into a string reads the same however it got there. +// +// Two things used to make that false. The constant folder formatted with `ryu` +// — a shortest-round-trip formatter — while everything else uses Rust's +// `Display`, which is the rule `docs/semantics.md` states and which lkrt is +// aligned to byte for byte. So `"" + 1.0e300` was `1e300` folded and three +// hundred digits when the same value came from a variable. +// +// And the float opcodes had no dynamic fallback while their int twins did: the +// compiler picks `AddFloat` from *one* operand's type, so `"" + (1.0 + 2.0)` +// folded its parenthesised half to a float and then added a string to it — +// `AddInt` dispatches in that situation, `AddFloat` raised, and `lk check` said +// nothing on the way past. +fn rendered(from_literal: Bool) -> String { + let computed = 1.0 + 2.0; + if from_literal { return "" + 3.0; } + return "" + computed; +} +assert(rendered(true) == rendered(false)); +assert(rendered(true) == "3"); +assert("v=" + (1.0 + 2.0) == "v=3"); +assert((1.0 + 2.0) + "!" == "3!"); +assert(("" + 0.5) == "0.5"); + return maybe_float; diff --git a/examples/syntax/operators.lk b/examples/syntax/operators.lk index 84ddc0f5..588f05a4 100644 --- a/examples/syntax/operators.lk +++ b/examples/syntax/operators.lk @@ -66,11 +66,23 @@ assert(float_val > 42.0); assert(float_val < 43.0); // 11. Bitwise operators and shifts -// `&`, `|` and `~` are Int-only; `<<` and `>>` are two adjacent comparison +// `&`, `|`, `^` and `~` are Int-only; `<<` and `>>` are two adjacent comparison // tokens rather than tokens of their own, so that a generic annotation like // `Map>` still ends with two separate `>`. assert((0b1100 & 0b1010) == 0b1000); assert((0b1100 | 0b1010) == 0b1110); +assert((0b1100 ^ 0b1010) == 0b0110); +// `^` binds tighter than `|` and looser than `&`, as in C and Rust. It was the +// one bitwise operator with no spelling — `__lk_bit_xor` was already named in +// the type checker's arity table and the VM compiler's builtin list, and +// nothing could produce it. +assert((0b1000 | 0b0011 ^ 0b0001) == 0b1010); +assert((0b0110 & 0b0011 ^ 0b0001) == 0b0011); +assert((5 ^ 3) == 6); +// `&`, `^` and `|` sit *below* comparison here, so an equality on either side +// needs its own parentheses — the same rule `&` already followed. +assert(((0 - 1) ^ 0) == 0 - 1); +assert(~0b1010 == (0b1010 ^ (0 - 1))); assert((1 << 8) == 256); // Arithmetic, not logical: Int is signed, so the sign bit is replicated. assert((0 - 16) >> 2 == 0 - 4); @@ -80,4 +92,41 @@ assert(8 >> 1 == 4); // A shift amount outside 0..63 is an error rather than a mask or a wrap — // the hardware would mask it to 63 and return a number nobody asked for. -println("operators: all assertions passed"); \ No newline at end of file +// Each of them has a compound assignment, on a name, an index and a field. +// `&=`, `|=` and `^=` desugar to the same `__lk_bit_*` call `a = a & b` builds, +// rather than becoming `BinOp` variants with a second lowering of their own. +// `<<=` and `>>=` are three adjacent tokens, since the lexer never emits a +// shift: `<<` is two `<`, so `<<=` is `<` then `<=`, and adjacency is what +// separates them from `a < (b <= c)`. +let bits = 0b1100; +bits &= 0b1010; +assert(bits == 0b1000); +bits |= 0b0011; +assert(bits == 0b1011); +bits ^= 0b0001; +assert(bits == 0b1010); +bits <<= 2; +assert(bits == 0b101000); +bits >>= 3; +assert(bits == 0b101); + +let counters = {"hits": 0b110}; +counters["hits"] ^= 0b011; +assert(counters["hits"] == 0b101); +counters["hits"] <<= 1; +assert(counters["hits"] == 0b1010); + +struct Flags { mask: Int } +let flags = Flags { mask: 0b1001 }; +flags.mask |= 0b0110; +assert(flags.mask == 0b1111); +flags.mask >>= 2; +assert(flags.mask == 0b11); + +// And a comparison next to one, which is the shape adjacency has to keep apart. +let lo = 1; +let hi = 4; +assert(lo < hi); +assert(lo <= hi); + +println("operators: all assertions passed"); diff --git a/examples/syntax/pattern_matching.lk b/examples/syntax/pattern_matching.lk index 1d149efb..34eec7f6 100644 --- a/examples/syntax/pattern_matching.lk +++ b/examples/syntax/pattern_matching.lk @@ -26,26 +26,26 @@ assert(remaining.has("city")); // 5. if let — conditional destructuring let maybe_num = [1, 2, 3]; if let [head, ..tail] = maybe_num { - assert(head == 1); - assert(tail == [2, 3]); + assert(head == 1); + assert(tail == [2, 3]); } else { - panic("if let should match"); + panic("if let should match"); } // 6. if let with literal — only matches that literal let val = 42; if let 42 = val { - assert(true); + assert(true); } else { - panic("if let 42 should match"); + panic("if let 42 should match"); } // 7. while let — loop until pattern fails let stack = [1, 2, 3]; let collected = []; while let [top, ..rest] = stack { - collected.push(top); - stack = rest; + collected.push(top); + stack = rest; } assert(collected == [1, 2, 3]); @@ -59,4 +59,4 @@ assert(py == 20); let [_, second_item, .._] = [100, 200, 300, 400]; assert(second_item == 200); -println("pattern_matching: all assertions passed"); \ No newline at end of file +println("pattern_matching: all assertions passed"); diff --git a/examples/syntax/raise_interrupt.lk b/examples/syntax/raise_interrupt.lk new file mode 100644 index 00000000..42c83daa --- /dev/null +++ b/examples/syntax/raise_interrupt.lk @@ -0,0 +1,74 @@ +// `cpu_raise_interrupt` — the one x86 instruction whose operand a program +// cannot supply. +// +// `int` takes its vector as an *immediate*. There is no register form, so a +// program that wants to raise a vector it computed has nowhere to put it, and +// every kernel written in a language without inline assembly ends up with the +// number written down twice: once where the gate is installed, and once inside a +// hand-written stub in whatever language could say `int 0x30`. +// +// That is not a small gap. A kernel that can *handle* an interrupt but not raise +// one can answer a syscall and not define one, and cannot reschedule itself. +// +// The answer is the same one the entry side already uses: 256 stubs, each one +// `int n` and a return, and the vector becomes an index. See `lkrt/src/isr.rs`, +// which holds both directions of it. +// +// This example runs hosted, where there are no interrupts to raise — so what it +// checks is the part that is the same everywhere: the call type-checks, takes +// one argument, and is a statement rather than a value. What it does on a +// machine with an interrupt table is in `bare-metal-x86/program.lk`, where +// `task_yield` is now one line naming the same constant its gate was installed +// with. + +// A vector a kernel would give a name to. The point of the intrinsic is that +// this is an ordinary constant — computed, passed around, and used in both +// places — rather than a literal baked into an instruction. +const VECTOR_YIELD = 0x30; +const VECTOR_SYSCALL = 0x80; + +// Both directions of the same number: what a kernel installs a gate for, and +// what it raises. One spelling. +fn vector_of(name: String) -> Int { + if (name.byte_at(0) == 121) { // 'y' + return VECTOR_YIELD; + } + return VECTOR_SYSCALL; +} + +// Hosted, this is *refused*, and the refusal is the interesting part. +// +// There is no interrupt table under a process, and a runtime that went ahead and +// raised a real `int 0x80` would be making a Linux system call with whatever +// happened to be in the registers. Doing nothing would be worse than either: it +// is the answer that lets a program look like it worked. +// +// Both backends refuse, with the same message, which is the property that +// matters — an intrinsic the interpreter rejects and the compiled build ignores +// is two languages wearing one name. +// The answer comes back in a variable rather than out of a `return` inside the +// `try`, because a region whose body returns from the enclosing function cannot +// be outlined — the body becomes a function of its own, and a `return` in it +// would return from *that*. The lowering says so; this is what it looks like to +// write for it. +fn raise_or_say_why(vector: Int) -> String { + let outcome = "raised"; + try { + unsafe { cpu_raise_interrupt(vector); }; + } catch e { + outcome = "refused"; + } + return outcome; +} + +println(raise_or_say_why(vector_of("yield"))); +println(raise_or_say_why(vector_of("syscall"))); + +// The vectors are what a reader should be able to check, so print them: a table +// installed at one number and raised at another is the failure this intrinsic +// exists to make impossible, and it is invisible at run time. +println(VECTOR_YIELD); +println(VECTOR_SYSCALL); +println(vector_of("yield")); +println(vector_of("syscall")); +println("raise-interrupt: ok"); diff --git a/examples/syntax/ranges.lk b/examples/syntax/ranges.lk index 36847700..4f74eec7 100644 --- a/examples/syntax/ranges.lk +++ b/examples/syntax/ranges.lk @@ -38,4 +38,4 @@ assert(chained == [1, 2, 3, 10, 11, 12]); let range_list = 1..=5; assert(3 in range_list); -println("ranges: all assertions passed"); \ No newline at end of file +println("ranges: all assertions passed"); diff --git a/examples/syntax/select.lk b/examples/syntax/select.lk index 6ff2ab5e..c5bf23c0 100644 --- a/examples/syntax/select.lk +++ b/examples/syntax/select.lk @@ -94,4 +94,46 @@ let nested = select { }; assert(nested == 3); +// --- the blocking form that actually blocks --- +// Every `select` above is answered by an arm that is *already* ready, so none +// of them ever parks — and parking is where a `select` has to be woken by +// another thread. That matters to more than `select`: a sender only broadcasts +// when something is parked (`BLOCKED_SELECTS` in `lkrt/src/chan.rs`), because +// doing it unconditionally was 58% of a channel program with no `select` in it. +// If the hand-off is ever wrong in the direction that skips a wake, this hangs +// rather than printing a wrong number, which the corpus timeout reports. +// +// Unbuffered channels, so each producer waits for a taker: over a hundred +// rounds the loop below parks many times. The totals are deterministic even +// though the interleaving is not. +use task; +fn feed(c: Channel, n: Int) -> Int { + let i = 0; + while i < n { + send(c, i); + i = i + 1; + } + return n; +} +let left = chan(0); +let right = chan(0); +let rounds = 50; +let lt = spawn(|| feed(left, rounds)); +let rt = spawn(|| feed(right, rounds)); +let from_left = 0; +let from_right = 0; +let taken = 0; +while taken < rounds * 2 { + let which = select { + case v <- recv(left) => 0; + case v <- recv(right) => 1; + }; + if which == 0 { from_left = from_left + 1; } else { from_right = from_right + 1; } + taken = taken + 1; +} +assert(task.await(lt) == rounds); +assert(task.await(rt) == rounds); +assert(from_left == rounds); +assert(from_right == rounds); + println("select: ok"); diff --git a/examples/syntax/shadowing.lk b/examples/syntax/shadowing.lk new file mode 100644 index 00000000..0dfb51af --- /dev/null +++ b/examples/syntax/shadowing.lk @@ -0,0 +1,36 @@ +// A name a program binds is that program's name. + +// The standard library's modules and the built-in functions live in the same +// namespace a top-level `let` writes into, so a program may reuse any of their +// names. The binding wins from then on — which is what the interpreter does, +// and what the compiled build had to be taught: it resolved the *name* before +// asking whether the slot had been written, so `let time = …` read by any +// function dropped the whole program off the native path. +let time = [30, 45, 60]; +let env = "production"; +let hash = 7; + +fn first_reading() -> Int { + return time[0]; +} +fn where_it_runs() -> String { + return env; +} +assert(first_reading() == 30); +assert(where_it_runs() == "production"); +assert(hash == 7); + +// A built-in function's name is the same kind of name. +let len = 42; +fn stored_len() -> Int { + return len; +} +assert(stored_len() == 42); +// The method spelling is unaffected — it was never the global. +assert([1, 2, 3].len() == 3); + +// An unshadowed module is still the module, in the same file. +use math; +assert(math.abs(0 - 3) == 3); + +println("shadowing: ok"); diff --git a/examples/syntax/struct.lk b/examples/syntax/struct.lk index f4f4d818..d1bb58b3 100644 --- a/examples/syntax/struct.lk +++ b/examples/syntax/struct.lk @@ -14,4 +14,87 @@ println("u2.id={}, u2.name={}, u2.active={}", u2.id, u2.name, u2.active); let u3 = User { id: 3, name: "Bob", active: true }; println("u3.id={}, u3.name={}, u3.active={}", u3.id, u3.name, u3.active); +// Struct update (`{ ..base, field: value }`) keeps the *declared* field order, +// like every other way of building the type. It printed the hash map's own +// order instead, so the same struct rendered two ways depending on which syntax +// built it — and the interpreter and the compiled build disagreed about which. +// Six fields, deliberately: with fewer, the two orders can coincide. +struct Reading { zebra: Int, apple: Int, mango: Int, kiwi: Int, pear: Int, fig: Int } +let base = Reading { zebra: 1, apple: 2, mango: 3, kiwi: 4, pear: 5, fig: 6 }; +let shown = "{}".format(base); +assert(shown == "Reading{zebra:1,apple:2,mango:3,kiwi:4,pear:5,fig:6}"); +let bumped = Reading { ..base, apple: 99 }; +assert("{}".format(bumped) == "Reading{zebra:1,apple:99,mango:3,kiwi:4,pear:5,fig:6}"); +assert(bumped.apple == 99); +assert(bumped.zebra == 1); + return; + +// A declared struct's fields are an ordered list, and a read of one is a read +// at a known position. +// +// The storage is a string-keyed map, so a field read used to hash the name on +// every access — measured at ~107ns each, which was the entire cost of a loop +// that reads a field. The declaration order travels with the module now, so +// the read is an index; the name still travels along and is compared, because +// an instance built somewhere the lowering did not see may store its fields in +// another order. +struct Sample { at: Int, value: Float, sensor: String } + +fn newer(a: Sample, b: Sample) -> Int { + if a.at > b.at { return a.at; } + return b.at; +} +let first = Sample { at: 10, value: 1.5, sensor: "a" }; +let second = Sample { at: 4, value: 2.5, sensor: "b" }; +assert(newer(first, second) == 10); +assert(first.value + second.value == 4.0); +assert(first.sensor + second.sensor == "ab"); + +// Through a list of them, where the struct identity has to survive the element +// read for the position to be known. +let readings = [first, second]; +assert(readings[0].at == 10); +assert(readings[1].at + 1 == 5); +let total = 0; +for r in readings { total = total + r.at; } +assert(total == 14); + +// A declared field type is enforced, not decorative. +// +// The type checker catches every store it can type. It cannot type a store +// through an untyped binding — `fn poison(p) { p["v"] = "s"; }` — and without +// a runtime check a `struct Sample { at: Int }` could hold a String, which +// makes the declaration a comment. Every way of writing a field is checked: +// the two store spellings, the two construction spellings. +struct Counter { n: Int, label: String?, ratio: Float } + +fn store_by_index(c, v) { c["n"] = v; } +fn store_by_dot(c, v) { c.n = v; } +fn build(v) { let made = Counter { n: v, label: nil, ratio: 1.5 }; } +fn spread(fields) { let made = Counter { ..fields }; } + +let counter = Counter { n: 1, label: nil, ratio: 0.5 }; + +// An `Int` satisfies a `Float` field and stays an `Int`: this language never +// coerces at a typed boundary, so the value keeps its own type. +store_by_dot(counter, 7); +assert(counter.n == 7); +counter.ratio = 2; +assert(counter.ratio == 2); +assert(typeof(counter.ratio) == "Int"); + +// A nullable field takes nil; a non-nullable one does not. +counter.label = nil; +assert(counter.label == nil); + +let refused = []; +try { store_by_index(counter, "x"); } catch e { refused.push("{}".format(e)); } +try { store_by_dot(counter, "x"); } catch e { refused.push("{}".format(e)); } +try { build("x"); } catch e { refused.push("{}".format(e)); } +try { spread({"n": "x", "label": nil, "ratio": 1.0}); } catch e { refused.push("{}".format(e)); } +assert(refused.len() == 4); +for message in refused { + assert(message == "field `n` of Counter is declared Int, and a String cannot be stored in it"); +} +assert(counter.n == 7); diff --git a/examples/syntax/struct_trait.lk b/examples/syntax/struct_trait.lk index 57433f3f..85a15ed9 100644 --- a/examples/syntax/struct_trait.lk +++ b/examples/syntax/struct_trait.lk @@ -26,34 +26,34 @@ assert(u2.active == false); // 4. Trait definition and implementation trait Area { - fn area(self) -> Int; + fn area(self) -> Int; } impl Area for Rect { - fn area(self) -> Int { return self.w * self.h; } + fn area(self) -> Int { return self.w * self.h; } } assert(r.area() == 200); // 5. Multiple traits trait Describe { - fn describe(self) -> String; + fn describe(self) -> String; } impl Describe for Rect { - fn describe(self) -> String { return "Rect(${self.w}x${self.h})"; } + fn describe(self) -> String { return "Rect(${self.w}x${self.h})"; } } assert(r.describe() == "Rect(10x20)"); // 6. Same trait, different types impl Area for Circle { - fn area(self) -> Int { return 3 * self.r * self.r; } // approximate + fn area(self) -> Int { return 3 * self.r * self.r; } // approximate } let c_area = c.area(); assert(c_area == 75); impl Describe for Circle { - fn describe(self) -> String { return "Circle(r=${self.r})"; } + fn describe(self) -> String { return "Circle(r=${self.r})"; } } assert(c.describe() == "Circle(r=5)"); @@ -62,4 +62,23 @@ let shapes = [Rect { w: 3, h: 4 }, Circle { r: 10 }]; let areas = shapes.map(|s| s.area()); assert(areas == [12, 300]); -println("struct_trait: all assertions passed"); \ No newline at end of file +// 7. Trait method with a default body: implementors that do not write it get it +// The default body is written in terms of the trait's other methods, which is +// what a default is for. +trait Scaled { + fn base(self) -> Int; + fn doubled(self) -> Int { return self.base() * 2; } +} +impl Scaled for Rect { fn base(self) -> Int { return self.w; } } +assert(r.doubled() == 20); + +// A keyword names a member fine: a member is reached through `.` or declared +// inside a struct/impl/trait body, and none of those positions starts a +// statement. (A top-level `fn` keeps the restriction.) +struct Row { type: String, select: Int } +impl Row { fn match(self) -> Int { return self.select * 2; } } +let row = Row { type: "t", select: 21 }; +assert(row.match() == 42); +assert(row.type == "t"); + +println("struct_trait: all assertions passed"); diff --git a/examples/syntax/template_infer.lk b/examples/syntax/template_infer.lk new file mode 100644 index 00000000..7271ba1f --- /dev/null +++ b/examples/syntax/template_infer.lk @@ -0,0 +1,50 @@ +// Interpolation renders its operand; it does not decide what its operand is. +// +// `"${x}"` turns whatever `x` is into text, so a parameter that has not been +// annotated must not be *pinned* to `String` by appearing in one. It used to be, +// and the effect reached a long way: +// +// fn key_of(p) { table["k${p}"] = 1; return p; } +// +// the map's key type made the whole interpolation a `String`, the constraint +// travelled back through it onto `p`, and the function was inferred to return a +// String — so an `Int` caller was rejected for a program that is fine. A +// generative fuzz run on a fresh seed produced it at case 651. +// +// String `+` is a different question and still constrains: `+` is overloaded, so +// which one it is has to be decided. + +let table: Map = {}; + +// `p` is unannotated. The only things that should decide its type are what is +// done *to* it — here, being stored as the map's value, which makes it an Int. +fn remember(p) { + table["k${p}"] = p; + return p; +} + +let a = 0; +a = remember(3); +a = remember(a + 4); +println(a); +println(table.len()); + +// The same shape one level further out, which is what a driver looks like: the +// value travels through a function that never says what it is. +fn remember_twice(p) { + remember(p); + remember(p + 1); + return p * 2; +} + +let b = 0; +b = remember_twice(10); +println(b); +println(table.len()); + +// And interpolation still renders the scalar it is given, which is the property +// that made the constraint wrong in the first place. A container is not one of +// them — `"${[1, 2]}"` is refused, by both backends, which is a separate and +// deliberate rule. +println("int ${1} float ${2.5} bool ${true}"); +println("template-infer: ok"); diff --git a/examples/syntax/template_strings.lk b/examples/syntax/template_strings.lk index e4d523ab..768cd8c4 100644 --- a/examples/syntax/template_strings.lk +++ b/examples/syntax/template_strings.lk @@ -48,4 +48,35 @@ println("{} + {} = {}", a, b, a + b); let s10 = "Price: \$100"; assert(s10 == "Price: $100"); -println("template_strings: all assertions passed"); \ No newline at end of file +// 11. A template inside a loop. +// +// `format` expands at compile time, so the template has to be a constant — and +// the bytecode compiler hoists a loop-invariant literal out of the loop body, +// which means inside the loop the template is a *phi parameter* rather than the +// literal itself. Looking the constant up by SSA value found nothing there, so +// every `"{}".format(x)` written in a loop fell off the native path. +let joined = ""; +for i in 0..4 { + joined = joined + "[{}]".format(i); +} +assert(joined == "[0][1][2][3]"); + +let rows = []; +let table = {"a": 1, "b": 2}; +for k in table.keys() { + rows.push("{}={}".format(k, table[k])); +} +assert(rows == ["a=1", "b=2"]); + +fn render(n: Int) -> String { + let out = ""; + let i = 0; + while i < n { + out = out + "{} of {};".format(i, n); + i = i + 1; + } + return out; +} +assert(render(2) == "0 of 2;1 of 2;"); + +println("template_strings: all assertions passed"); diff --git a/examples/syntax/trait_as_type.lk b/examples/syntax/trait_as_type.lk new file mode 100644 index 00000000..2833b1a6 --- /dev/null +++ b/examples/syntax/trait_as_type.lk @@ -0,0 +1,59 @@ +// A trait is a type. +// +// The trait system had three parts and shipped two: a `trait` declares a +// method set, an `impl` provides it for a type, and dispatch finds the right +// body at the call. The third part is writing the trait's *name* where a type +// goes — which is what makes "any shape" a thing a signature can say. It was +// rejected outright: the checker knew `Show` was a declared name and had no +// rule saying `P` satisfies it, so the only way to write this was to leave the +// parameter untyped and check nothing. + +trait Shape { + fn area(self) -> Int; + fn name(self) -> String; +} + +struct Rect { w: Int, h: Int } +struct Square { side: Int } + +impl Shape for Rect { + fn area(self) -> Int { return self.w * self.h; } + fn name(self) -> String { return "rect"; } +} + +impl Shape for Square { + fn area(self) -> Int { return self.side * self.side; } + fn name(self) -> String { return "square"; } +} + +// 1. As a parameter type: one function, either implementor. +fn describe(s: Shape) -> String { + return "{} {}".format(s.name(), s.area()); +} +assert(describe(Rect { w: 3, h: 4 }) == "rect 12"); +assert(describe(Square { side: 5 }) == "square 25"); + +// 2. As a return type. Checked by *unification* rather than assignability — +// two answers to one question, and only one of them had the rule. +fn pick(big: Bool) -> Shape { + if big { return Rect { w: 10, h: 10 }; } + return Square { side: 2 }; +} +assert(pick(true).area() == 100); +assert(pick(false).area() == 4); + +// 3. As a binding's type. +let one: Shape = Rect { w: 2, h: 2 }; +assert(one.area() == 4); + +// 4. As a container's element type. A heterogeneous literal infers as a tuple, +// which is the precision that made the annotation written for it fail. +let all: List = [Rect { w: 1, h: 6 }, Square { side: 3 }]; +let total = all.reduce(0, |sum, s| sum + s.area()); +assert(total == 15); +assert(all[0].name() == "rect"); + +// A type that does not implement it is refused, and so is a method the trait +// does not declare — the surface of a `Shape` is what `Shape` says it is. + +println("trait as type: ok"); diff --git a/examples/syntax/trait_builtin.lk b/examples/syntax/trait_builtin.lk new file mode 100644 index 00000000..ee8c6dd9 --- /dev/null +++ b/examples/syntax/trait_builtin.lk @@ -0,0 +1,108 @@ +// A trait can be implemented for a *built-in* type, and dispatch through a +// trait-typed parameter has to reach those impls as well as a struct's. +// +// A struct instance carries a type mark the runtime can read; a value of a +// built-in type does not. So dispatch asks for a type id that answers the mark +// when there is one and a fixed code for the value's language type otherwise — +// and this file is what checks the two sides agree on those codes, because a +// disagreement matches no arm and raises where the interpreter answers. + +trait Describe { + fn describe(self) -> String; +} + +impl Describe for Nil { + fn describe(self) -> String { + return "nothing"; + } +} + +impl Describe for Bool { + fn describe(self) -> String { + return "bool " + self; + } +} + +impl Describe for Int { + fn describe(self) -> String { + return "int " + self; + } +} + +impl Describe for Float { + fn describe(self) -> String { + return "float " + self; + } +} + +impl Describe for String { + fn describe(self) -> String { + return "string " + self; + } +} + +impl Describe for List { + fn describe(self) -> String { + return "list of " + self.len(); + } +} + +impl Describe for Map { + fn describe(self) -> String { + return "map of " + self.len(); + } +} + +impl Describe for Set { + fn describe(self) -> String { + return "set of " + self.len(); + } +} + +// A window is its own type here, not a list: `[1, 2, 3].slice(0, 2)` picks +// this impl and not the `List` one. +impl Describe for Slice { + fn describe(self) -> String { + return "window of " + self.len(); + } +} + +impl Describe for Bytes { + fn describe(self) -> String { + return "bytes of " + self.len(); + } +} + +struct Point { + x: Int, +} + +impl Describe for Point { + fn describe(self) -> String { + return "point " + self.x; + } +} + +fn tell(v: Describe) -> String { + return v.describe(); +} + +let nothing = nil; +println(tell(nothing)); +println(tell(true)); +println(tell(7)); +println(tell(2.5)); +println(tell("hi")); +println(tell([1, 2, 3])); +println(tell({ "a": 1, "b": 2 })); +println(tell(Set([1]))); +println(tell([1, 2, 3].slice(0, 2))); +println(tell("ab".bytes())); +println(tell(Point { x: 4 })); + +// The same call reached with several kinds in turn, so one call site has to +// dispatch rather than specialize. +let mixed = [1, "two", 3.0, [4], true]; +for v in mixed { + println(tell(v)); +} diff --git a/examples/syntax/try_catch.lk b/examples/syntax/try_catch.lk index 6b4092a2..6500dc43 100644 --- a/examples/syntax/try_catch.lk +++ b/examples/syntax/try_catch.lk @@ -38,4 +38,321 @@ try { } assert(log == ["ok", "caught", "code-404"]); + +// A closure written outside the region and called inside it. The body runs in +// a frame of its own, so what crosses the boundary is the closure's *identity* +// plus whatever it captured — one value per capture, and nothing at all for a +// capture-free lambda. +fn scale(v: Int) -> Int { + let factor = 3; + let apply = || -> Int { return v * factor; }; + let plain = || -> Int { return 7; }; + let out = 0; + try { out = apply() + plain(); } catch e { out = -1; } + return out; +} +assert(scale(2) == 13); + +// Two of them across one region, and a region the closure raises out of. +fn probe(bad: Bool) -> Int { + let base = 10; + let ok = || -> Int { return base + 1; }; + let boom = || -> Int { error("nope"); return 0; }; + let out = 0; + try { out = if bad { boom() } else { ok() }; } catch e { out = -2; } + return out; +} +assert(probe(false) == 11); +assert(probe(true) == -2); + +// A `try` inside a `try`. Each region's body becomes a function of its own, so +// nesting is that same step taken twice — and what the inner body writes has to +// travel out through *both* frames, including on a path where the outer one +// then raises. +fn layered(k: Int) -> String { + let trace = ""; + try { + trace = trace + "a"; + try { + if k == 1 { error("inner"); } + trace = trace + "b"; + } catch e { + trace = trace + "B"; + } + if k == 2 { error("outer"); } + trace = trace + "c"; + } catch e { + trace = trace + "A"; + } + return trace; +} +assert(layered(0) == "abc"); +assert(layered(1) == "aBc"); +assert(layered(2) == "abA"); + +// What an inner region writes has to reach the frame that reads it, however +// many frames away that is, and whatever the reader looks like. Both halves of +// that sentence were wrong once: a write two regions deep was reported to +// nobody, and a read spelled `xs.push(v)` rather than `let t = v;` swallowed the +// report that would have fixed it. +fn relay(n: Int) -> Int { + let seen = []; + let value = 0; + let bump = || -> Int { return n + 1; }; + try { + try { value = bump(); } catch inner { value = 0 - 1; } + } catch outer { + value = 0 - 2; + } + seen.push(value); + return value + seen.len(); +} +assert(relay(0) == 2); +assert(relay(4) == 6); + +// The same shape with the captured variable *itself* crossing the boundary and +// being written on the far side: one cell, three frames, and the closure called +// after the write sees it. +fn accumulate(step: Int) -> Int { + let total = 1; + let add = || -> Int { return total + step; }; + try { total = total * 10; total = add(); } catch e { total = 0 - 1; } + return total; +} +assert(accumulate(0) == 10); +assert(accumulate(3) == 13); + +// A `try` whose region reads a value that occupies *two* machine registers: +// a `Maybe` (a list's loop variable is one — the element read is bounds +// checked) and a boxed `Dyn`. The region's inputs travel through a buffer of +// machine words, so those cross as two words and are put back together inside. +// Unwrapping at the boundary would have been wrong, which the absent case here +// is the witness for: it aborts, where the body only asked `?? default`. +fn tally(values: List) -> Int { + let total = 0; + for v in values { + try { + if v < 0 { error("negative"); } + total = total + v; + } catch e { + total = total + 1; + } + } + return total; +} +assert(tally([10, 20, 30]) == 60); +assert(tally([10, 0 - 5, 30]) == 41); + +fn defaulted(present: Bool) -> Int { + let lookup = {"a": 7}; + let found = if present { lookup["a"] } else { lookup["missing"] }; + let out = 0; + try { out = found ?? 42; } catch e { out = 0 - 1; } + return out; +} +assert(defaulted(true) == 7); +assert(defaulted(false) == 42); + +// A region in the module does not change what the rest of it compiles to. +// It did once: outlining a `try` body appends a function, and the tables that +// answer "what does parameter *k* of function *n* hold" are parallel arrays +// indexed by function. Outlining pushed to some of them and not the others, so +// every later entry was recorded under the previous function's index — and a +// lambda handed to a function stopped being erased for the whole module. +fn apply_to(xs: List, f: (Int) -> Int) -> List { + return xs.map(f); +} +fn fold_with(xs: List, f: (Int, Int) -> Int) -> Int { + return xs.reduce(0, f); +} +assert(apply_to([1, 2, 3], |x| x + 1) == [2, 3, 4]); +assert(fold_with([1, 2, 3], |a, b| a + b) == 6); + +// A loop and a region, both ways round. The body of a `try` becomes a function +// of its own when it is compiled natively, so a `break` written in it belongs to +// a loop that function does not have. Both directions are here because they +// lower by different means and only running them says so: a loop *written +// inside* the `try` keeps its own jumps, and one that *encloses* the `try` +// reports which way it left through the same channel a `return` uses +// (`docs/aot/aot-gaps-and-lkrt.md` §47). +fn scan(values: List) -> Int { + let seen = 0; + try { + for v in values { + if v < 0 { break; } + if v == 0 { continue; } + seen = seen + v; + } + } catch e { + seen = 0 - 1; + } + return seen; +} +assert(scan([1, 2, 3]) == 6); +assert(scan([1, 0, 2]) == 3); +assert(scan([1, 0 - 5, 9]) == 1); + +// The region inside the loop, with the jump belonging to the region's own body. +fn tally_each(values: List) -> Int { + let total = 0; + for v in values { + try { + if v < 0 { error("negative"); } + total = total + v; + } catch e { + total = total + 100; + } + } + return total; +} +assert(tally_each([1, 2]) == 3); +assert(tally_each([1, 0 - 1, 2]) == 103); + +// The jump belonging to the loop *outside* the region: one `break`, one +// `continue`, and a raise in between, so the handler runs on the way past. Each +// distinct destination is one more outcome the body reports, so the two here +// and the ordinary fall-through are three answers on one flag. +fn until_negative(values: List) -> Int { + let total = 0; + for v in values { + try { + if v < 0 { break; } + if v == 0 { continue; } + if v == 13 { error("unlucky"); } + total = total + v; + } catch e { + total = total + 100; + } + } + return total; +} +assert(until_negative([1, 2, 3]) == 6); +assert(until_negative([1, 0, 2]) == 3); +assert(until_negative([1, 0 - 5, 9]) == 1); +assert(until_negative([1, 13, 2]) == 103); + +// A `while` loop, where `continue` jumps *backwards* to the condition rather +// than forwards to a latch — the same channel, a different destination. +fn skip_evens(limit: Int) -> Int { + let total = 0; + let i = 0; + while i < limit { + i = i + 1; + try { + if i % 2 == 0 { continue; } + if i > 7 { break; } + total = total + i; + } catch e { + total = 0 - 1; + } + } + return total; +} +assert(skip_evens(10) == 16); +assert(skip_evens(3) == 4); + +// And with a `return` in the same body: the flag carries four answers now, and +// the one that returns also carries a value. +fn first_over(values: List, bound: Int) -> Int { + let seen = 0; + for v in values { + try { + if v == 0 { continue; } + if v < 0 { break; } + if v > bound { return v; } + seen = seen + 1; + } catch e { + seen = 0; + } + } + return 0 - seen; +} +assert(first_over([1, 2, 9], 5) == 9); +assert(first_over([1, 0, 2], 5) == 0 - 2); +assert(first_over([1, 0 - 3, 9], 5) == 0 - 1); + +// A region that crosses a lot: eight inputs of five different kinds, five +// variables the body assigns and the function reads afterwards, and an outcome +// channel carrying `continue`, `break` and `return`. The body of this one is a +// function of twenty-nine parameters when it is compiled natively, which is only +// interesting because the count used to be capped at eight. +fn crowded(a: Int, b: Int, c: Int, d: Float, flag: Bool, tag: String) -> Int { + let p = 0; + let q = 0; + let r = 0; + let s = 0; + let t = 0; + for i in 0..5 { + try { + p = p + a + i; + q = q + b; + r = r + c; + s = s + tag.len(); + if flag { t = t + 1; } + if d > 1.5 { t = t + 10; } + if i == 2 { continue; } + if i == 4 { break; } + if p > 900 { return 0 - 1; } + } catch e { + p = 0 - 99; + } + } + return p + q + r + s + t; +} +assert(crowded(1, 2, 3, 2.5, true, "abcd") == 115); +assert(crowded(9, 8, 7, 0.5, false, "xy") == 140); + +// A region inside a *closure*, reading what the closure captured. The body is +// outlined into a function of its own, and that function has no captures of its +// own — so a capture read inside it has to be handed the enclosing closure's, +// positionally, or index 0 means something else entirely. Three kinds here, +// because they cross differently: a number, a string, and a container. +fn scaled_report(count: Int, factor: Int, label: String) -> String { + let bounds = [2, 20]; + let out = ""; + let describe = |v| { + let text = ""; + try { + let scaled = v * factor; + if scaled < bounds[0] { error("under"); } + if scaled > bounds[1] { error("over"); } + text = label + ":" + scaled; + } catch e { + text = label + ":-"; + } + return text; + }; + let i = 0; + while i < count { + out = out + describe(i) + " "; + i = i + 1; + } + return out; +} +assert(scaled_report(3, 4, "x") == "x:- x:4 x:8 "); +assert(scaled_report(2, 30, "y") == "y:- y:- "); + +// A handler with nothing in it, and a body that returns on one path only. +// +// The compiler emits no jump over an empty handler, because there is nothing to +// jump over — and "the region's fallthrough is its handler" is *also* what a +// body that returns on every path looks like. Reading the second from the first +// is a mistake this shape is the witness for: the path that did *not* return +// went straight to the return block and read a value nobody had parked. Every +// other `try` above has a statement in its handler, which is why none of them +// caught it. +fn maybe_early(values: List, stop: Int) -> Int { + let seen = 0; + for v in values { + try { + if v == stop { return 0 - v; } + seen = seen + v; + } catch e { } + } + return seen; +} +assert(maybe_early([1, 2, 3], 9) == 6); +assert(maybe_early([1, 2, 3], 2) == 0 - 2); +assert(maybe_early([], 1) == 0); + println("try/catch: ok"); diff --git a/examples/syntax/try_expression.lk b/examples/syntax/try_expression.lk new file mode 100644 index 00000000..1642363d --- /dev/null +++ b/examples/syntax/try_expression.lk @@ -0,0 +1,23 @@ +// `try` is an *expression*, like `if` and `match`. +// +// The value is the body's trailing expression, or the handler's when the body +// raised. A half that ends in a statement has no value, exactly as an `if` +// branch that ends in one yields nil. Statement position is unchanged: the +// value is simply discarded, the same way an `if` in statement position is. +// +// Before this, `let r = try { … } catch e { … };` was a syntax error, and the +// way to get a value out was to declare a `nil` first and assign into it from +// both halves — or to wrap the whole thing in a function and `return` twice. + +fn checked_div(a: Int, b: Int) -> Float { + if (b == 0) { error("division by zero"); } + return a / b; +} + +let recovered = try { checked_div(1, 0) } catch e { -1.0 }; +assert(recovered == -1.0); + +let straight = try { checked_div(10, 2) } catch e { -1.0 }; +assert(straight == 5.0); + +println("try_expression: all assertions passed"); diff --git a/examples/syntax/unsupported.lk b/examples/syntax/unsupported.lk index 7bd47b87..2d33350d 100644 --- a/examples/syntax/unsupported.lk +++ b/examples/syntax/unsupported.lk @@ -3,7 +3,9 @@ // Run: lk examples/syntax/unsupported.lk // ── 1. Match variable binding ── -let r = match 99 { n => n, _ => 0 }; +// A binding pattern already matches every value, so a `_` arm after it would +// be dead code — and is now refused. +let r = match 99 { n => n }; println("1. match var = {} (expected: 99)", r); // ── 2. Match destructuring ── @@ -19,11 +21,11 @@ println("2b. match map destruct = {} (expected: Alice)", r3); let score = 75; let g = ""; if (score >= 90) { - g = "A"; + g = "A"; } else if (score >= 70) { - g = "C"; + g = "C"; } else { - g = "F"; + g = "F"; } println("3. g = {} (prints C)", g); println("3. g == \"C\" = {} (expected: true)", g == "C"); @@ -79,7 +81,7 @@ println("10b. pow(2,10) == 1024.0 = {} (Float == Float)", p == 1024.0); // ── 11. for comma pattern destructures iterable pair items ── let pair_total = 0; for i, item in [[0, 10], [1, 20], [2, 30]] { - pair_total += i + item; + pair_total += i + item; } println("11. for i, item total = {} (expected: 63)", pair_total); @@ -88,8 +90,11 @@ let spread_a = [1, 2]; let spread_b = [0, ..spread_a, 3]; println("12. [0, ..[1,2], 3] = {} (expected: [0, 1, 2, 3])", spread_b); -// ── 13. String * count ── -println("13. \"ha\" * 3 = {} (expected: hahaha)", "ha" * 3); +// ── 13. String repetition ── +// `*` does **not** repeat a string — the checker rejects it and names the +// operation that exists. It read `"ha" * 3` here until the parse-time constant +// folder (the only thing that still implemented `*`) was fixed to agree. +println("13. \"ha\".repeat(3) = {} (expected: hahaha)", "ha".repeat(3)); // ── 14. Map literal bare keys are string keys ── let bare_user = {name: "Alice", age: 30}; @@ -110,7 +115,7 @@ println("16b. assign_p.x = {} (expected: 10)", assign_p.x); // ── 17. Default positional parameters ── fn greet_default(name, greeting = "hello") { - return greeting + ", " + name; + return greeting + ", " + name; } println("17. default positional = {} (expected: hello, Bob)", greet_default("Bob")); println("17b. default positional override = {} (expected: hi, Bob)", greet_default("Bob", "hi")); diff --git a/examples/syntax/use.lk b/examples/syntax/use.lk index 361aa451..d0947c1b 100644 --- a/examples/syntax/use.lk +++ b/examples/syntax/use.lk @@ -8,7 +8,7 @@ const n = 30; let acc = 0; for _ in 0..iters { - acc = fib.iterative(n); + acc = fib.iterative(n); } println("fib(${n}) = ${acc}, iters=${iters}"); diff --git a/examples/syntax/use_forms.lk b/examples/syntax/use_forms.lk index 5f0e1321..98011ea9 100644 --- a/examples/syntax/use_forms.lk +++ b/examples/syntax/use_forms.lk @@ -34,4 +34,20 @@ assert(f30 == 832040); use { iterative } from "../general/fib"; assert(iterative(10) == 55); +// 8. Construct a type the imported module declares — the module builds it, so +// the value carries that module's type identity. +use "../general/point"; +let pt = point.Pt { x: 3, y: 4 }; +assert(pt.x == 3); +assert(point.manhattan(pt) == 7); +// A trait the imported module implements dispatches on it too. +assert(pt.norm() == 7); + +// 9. A submodule through its parent. Both spellings reach the same member: +// `encoding.json` is a module object, so the chain continues through it. +use encoding; +assert(encoding.json.parse("[1,2]") == [1, 2]); +use { json } from encoding; +assert(json.parse("[1,2]") == [1, 2]); + println("use_forms: all assertions passed"); diff --git a/lkrt/Cargo.toml b/lkrt/Cargo.toml index 4829473f..6a7ac54c 100644 --- a/lkrt/Cargo.toml +++ b/lkrt/Cargo.toml @@ -18,18 +18,44 @@ default = ["std"] # representation, arithmetic, MMIO and CPU control. Files, processes, threads, # sockets and the clock drop out — there is nothing on bare metal for them to # call. -std = ["lk-aot-abi/std", "serde_json/std", "dep:serde_yaml", "dep:toml", "dep:chrono"] +std = ["lk-aot-abi/std", "serde_json/std", "dep:serde_yaml", "dep:toml", "dep:chrono", "dep:uuid", "dep:regex", "dep:rand"] [dependencies] lk-aot-abi = { path = "../aot/abi", default-features = false } rustc-hash = { version = "2", default-features = false } hashbrown = { version = "0.15", default-features = false, features = ["default-hasher", "inline-more"] } +# The value-map carrier, mirroring `lk-core`'s `util::value_map`: a program's +# `Map` iterates in insertion order, so both back ends agree by *construction* +# rather than by both landing on the same hash layout. +indexmap = { version = "2", default-features = false } # Bare metal has no thread-local storage; the arena lives behind a spin lock # there instead. Uncontended on a single core, so the hot path stays cheap. spin = { version = "0.9", default-features = false, features = ["mutex", "spin_mutex"] } serde_json = { version = "1", default-features = false, features = ["alloc"] } +# Named directly for the ordered-object visitor in `encoding.rs`; serde_json +# already pulls it in, this only makes the path available. +serde = { version = "1", default-features = false, features = ["alloc", "derive"] } +# Same crates the stdlib `hash` module uses, so a digest and its hex +# rendering are byte-identical to the VM's. +sha1 = { version = "0.10", default-features = false } +sha2 = { version = "0.10", default-features = false } +crc32fast = { version = "1", default-features = false } +# Same crates the stdlib `encoding` module uses, so base64/hex text is +# byte-identical to the VM's. +base64 = { version = "0.22", default-features = false, features = ["alloc"] } +hex = { version = "0.4", default-features = false, features = ["alloc"] } +# Same crate the stdlib `random` module uses, so ranges, the shuffle and the +# byte cap behave identically. `std`-only: the entropy source is the OS's. +rand = { workspace = true, optional = true } +# Same crate the stdlib `regex` module uses, so pattern syntax, match +# semantics and the parse-error text are one rule. `std`-only. +regex = { workspace = true, optional = true } +# Same crate the stdlib `uuid` module uses, so the text and the parse-error +# wording match. `std`-only: v4 needs the OS entropy source. +uuid = { workspace = true, optional = true } serde_yaml = { version = "0.9", optional = true } -toml = { version = "1", optional = true } +# `preserve_order`, mirroring the VM: a decoded table keeps document order. +toml = { version = "1", optional = true, features = ["preserve_order"] } # Same crate the stdlib datetime module uses, so native formatting/weekday # results are byte-identical to the VM. chrono = { workspace = true, optional = true } diff --git a/lkrt/build.rs b/lkrt/build.rs index cf06a49c..0af0e0f1 100644 --- a/lkrt/build.rs +++ b/lkrt/build.rs @@ -1,11 +1,13 @@ -//! Compiles the native protected-call trampoline (`src/try_trampoline.c`) into -//! the crate's object set. It hoists `setjmp` into a C frame for the Cranelift +//! Compiles the C parts of the runtime into the crate's object set: the native +//! protected-call trampoline (`src/try_trampoline.c`) and the stack-exhaustion +//! guard (`src/stack_guard.c`). It hoists `setjmp` into a C frame for the Cranelift //! backend, which cannot emit the `returns_twice` call itself (see the C file). -//! Bundled into `liblkrt.a`/the rlib, so the `lkrt_rt_try_call` symbol links +//! Bundled into `liblkrt.a`/the rlib, so the `lkrt_rt_try_region` symbol links //! wherever the runtime does. fn main() { println!("cargo:rerun-if-changed=src/try_trampoline.c"); + println!("cargo:rerun-if-changed=src/stack_guard.c"); // Bare-metal targets are skipped, for two reasons that agree. // @@ -27,7 +29,11 @@ fn main() { return; } + // `stack_guard.c` rides along for the same reason and with the same + // exclusion: it needs `sigaltstack`/`sigaction`, and a bare-metal target has + // no signals for them to install. cc::Build::new() .file("src/try_trampoline.c") + .file("src/stack_guard.c") .compile("lk_try_trampoline"); } diff --git a/lkrt/src/abi.rs b/lkrt/src/abi.rs index 28e6eff8..dab8ad81 100644 --- a/lkrt/src/abi.rs +++ b/lkrt/src/abi.rs @@ -40,6 +40,26 @@ pub(crate) fn flush_and_abort() -> ! { } } +/// The exit of a program whose own error nobody caught. +/// +/// Distinct from [`flush_and_abort`] on purpose: an uncaught raise is the +/// *program* failing, not the runtime, and the VM reports it as exit status 1. +/// Aborting instead made the same program die with SIGABRT (status 134), print +/// `Aborted` from the shell, and — where core dumps are enabled — write one for +/// a script that merely forgot a `catch`. +pub(crate) fn flush_and_exit_failure() -> ! { + flush_c_stdio(); + #[cfg(feature = "std")] + { + std::process::exit(1) + } + // Bare metal has no process to exit; the panic handler is the stop. + #[cfg(not(feature = "std"))] + { + panic!("Error: uncaught") + } +} + /// Flushes every C stdio stream (`fflush(NULL)`). Rust-side writers that share /// a stream with generated `printf` output call this first so the two buffers /// cannot interleave out of order. @@ -53,11 +73,11 @@ pub(crate) fn flush_c_stdio() { } } -/// FFI surface of [`flush_and_abort`] for generated code (`Term::Abort`). +/// The generated-code guard exit (`Term::Abort`), kept under its ABI name. +/// It does not abort: those guards mirror *catchable* VM errors, so this +/// raises to the nearest `try` frame and, uncaught, exits 1 like the VM. #[unsafe(no_mangle)] pub extern "C" fn lkrt_abort() { - // Generated-code guards (`Term::Abort`) mirror catchable VM errors: - // raise first, abort only without a handler. crate::panic::raise_str("runtime error"); } @@ -94,17 +114,40 @@ pub extern "C" fn lkrt_abi_version() -> i64 { ABI_VERSION } -/// Called at the start of a native binary's `main` with the ABI version the code -/// was generated against. If the linked `lkrt` reports a different version the -/// binary and runtime disagree on the calling/representation contract, so we -/// abort with a clear message rather than execute with a mismatched ABI (this is -/// a link/configuration error, never a reason to fall back to the VM). +// `sigaltstack`/`sigaction` in C, because lkrt has no `libc` dependency to spell +// their platform structs with — the same reason `try_trampoline.c` exists. Absent +// on bare metal, where `build.rs` skips the C files and there are no signals. +#[cfg(feature = "std")] +unsafe extern "C" { + fn lk_install_stack_guard(); +} + +/// The program's start, called from a native binary's `main` before any user +/// code, with the ABI version the code was generated against. +/// +/// Two things happen here, which is why this is `rt_begin` and not `abi_check` +/// (its name until the second one arrived): +/// +/// * The ABI version is checked. A linked `lkrt` reporting a different version +/// disagrees with the binary about the calling/representation contract, so this +/// aborts with a clear message rather than executing under a mismatched ABI — +/// a link/configuration error, never a reason to fall back to the VM. +/// * The stack-exhaustion handler is installed (`stack_guard.c`). Runaway +/// recursion used to die on SIGSEGV with exit 139 and no output at all, while +/// the VM raised a catchable `call depth limit exceeded`. It costs nothing on +/// the hot path: this runs once, and the handler only ever runs on a fault. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_abi_check(expected: i64) { +pub extern "C" fn lkrt_rt_begin(expected: i64) { if expected != ABI_VERSION { crate::rt_eprintln!("lkrt ABI mismatch: binary built for ABI v{expected}, linked lkrt is v{ABI_VERSION}"); flush_and_abort(); } + #[cfg(feature = "std")] + // SAFETY: installs a signal handler and an alternate stack; both are + // process-wide, idempotent, and this runs once before any user code. + unsafe { + lk_install_stack_guard() + }; } #[unsafe(no_mangle)] @@ -145,9 +188,10 @@ pub unsafe extern "C" fn lkrt_string_free(ptr: *mut c_char) { } } -/// Runtime `panic(message)` lowered from AOT builtin calls: always fatal, -/// matching the VM's loud panic halt (the message text goes to stderr; the -/// VM additionally prints a backtrace, which stderr comparisons don't cover). +/// Runtime `panic(message)` lowered from AOT builtin calls: always fatal and +/// uncatchable, matching the VM's loud panic halt down to the exit status +/// (the message goes to stderr; the VM additionally prints a backtrace, +/// which stderr comparisons don't cover). /// /// # Safety /// `message` must be null or a NUL-terminated string pointer. @@ -160,7 +204,10 @@ pub unsafe extern "C" fn lkrt_panic(message: *const c_char) { unsafe { CStr::from_ptr(message) }.to_string_lossy().into_owned() }; crate::rt_eprintln!("{text}"); - flush_and_abort(); + // Uncatchable in both backends, but the *status* has to agree: the VM's + // panic halt exits 1, so aborting here made the same program die with + // SIGABRT (134) once compiled. + flush_and_exit_failure(); } /// Runtime `assert(cond)` lowered from AOT builtin calls: a false (zero) @@ -169,8 +216,8 @@ pub unsafe extern "C" fn lkrt_panic(message: *const c_char) { pub extern "C" fn lkrt_assert(cond: i64) { if cond == 0 { // Catchable in the VM (a try around a failing assert recovers): - // raise to the nearest frame, abort when uncaught (same as before). - crate::panic::raise_str("Assertion failed"); + // raise to the nearest frame, exit 1 when uncaught. + crate::panic::raise_str("assertion failed"); } } @@ -189,7 +236,7 @@ pub unsafe extern "C" fn lkrt_assert_msg(cond: i64, message: *const c_char) { // SAFETY: non-null message pointers are NUL-terminated per the ABI. unsafe { CStr::from_ptr(message) }.to_string_lossy().into_owned() }; - crate::panic::raise_str(&format!("Assertion failed: {text}")); + crate::panic::raise_str(&format!("assertion failed: {text}")); } } @@ -214,13 +261,23 @@ pub(crate) fn owned_c_string(value: impl AsRef) -> Result<*mut c_char, Stri Ok(ptr) } -pub(crate) fn aborting(f: impl FnOnce() -> Result) -> T { +/// Runs a host operation whose failure is a *language* error — a missing file, +/// an unreadable directory, a bad address — and raises it to the nearest `try` +/// frame, exactly as the VM does. +/// +/// It used to abort the process. That made the same `fs.read_dir("/nope")` +/// catchable in the VM and fatal natively, with SIGABRT (status 134) instead of +/// the VM's exit 1 — a backend disagreement about whether a program can handle +/// its own IO failure. `set_last_error` still records the text for the ABI +/// entries that report a status instead of raising. +pub(crate) fn raising(f: impl FnOnce() -> Result) -> T { match f() { Ok(value) => value, Err(error) => { + // Both borrows are dropped before the raise: `raise_str` longjmps + // past Rust drops, so a live `RefCell` borrow would stay flagged. set_last_error(error.clone()); - crate::rt_eprintln!("lkrt error: {error}"); - flush_and_abort(); + crate::panic::raise_str(&error) } } } diff --git a/lkrt/src/abi_conformance_test.rs b/lkrt/src/abi_conformance_test.rs index 65e94364..8b97defa 100644 --- a/lkrt/src/abi_conformance_test.rs +++ b/lkrt/src/abi_conformance_test.rs @@ -13,6 +13,14 @@ //! LLVM `ptr` and are calling-convention-identical; the distinction in the //! schema is documentation, not ABI. +// `alloc`, not the std prelude: this crate builds without an OS, and this test +// had never been compiled in that configuration — `cargo clippy --all-targets` +// in CI runs with `--all-features`, so the no_std side of the *test* code was +// never checked at all. That is a hole in the gate, not a detail: this file is +// what guarantees the ABI schema names the symbols lkrt actually exports. +use alloc::vec; +use alloc::vec::Vec; + use lk_aot_abi::{ABI_FUNCTIONS, AbiType, for_each_abi_fn}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/lkrt/src/arith.rs b/lkrt/src/arith.rs index 7bf00211..94a60931 100644 --- a/lkrt/src/arith.rs +++ b/lkrt/src/arith.rs @@ -27,21 +27,26 @@ use alloc::{ #[unsafe(no_mangle)] pub extern "C" fn lkrt_i64_div_checked(lhs: i64, rhs: i64) -> i64 { if rhs == 0 { - crate::panic::raise_str("Division by zero"); + crate::panic::raise_str("division by zero"); } lhs.wrapping_div(rhs) } /// `lhs << rhs`, raising when the shift amount is not in `0..=63`. /// -/// The message is the VM's, word for word — including the offending amount — -/// because the two back ends have to fail the same way and a differential test -/// compares the text. The hardware would mask the amount to 63 and produce a -/// number; that number is not what the program asked for. +/// The message is the VM's, word for word — including the offending amount. +/// Cross-backend error text is not guaranteed identical in general (see +/// `docs/semantics.md`), but a *catchable* arithmetic failure is one a program +/// can branch on, so these few are aligned by hand. `%` by zero was not: the VM +/// said `ModInt divisor is zero` and this side said `Division by zero` — two +/// different strings, both wrong about which operator failed. +/// +/// The hardware would mask the amount to 63 and produce a number; that number +/// is not what the program asked for. #[unsafe(no_mangle)] pub extern "C" fn lkrt_i64_shl_checked(lhs: i64, rhs: i64) -> i64 { if !(0..64).contains(&rhs) { - crate::panic::raise_str(&format!("__lk_shl shift amount {rhs} is out of range 0..63")); + crate::panic::raise_str(&format!("shift amount {rhs} is out of range 0..63")); } lhs.wrapping_shl(rhs as u32) } @@ -50,40 +55,129 @@ pub extern "C" fn lkrt_i64_shl_checked(lhs: i64, rhs: i64) -> i64 { #[unsafe(no_mangle)] pub extern "C" fn lkrt_i64_shr_checked(lhs: i64, rhs: i64) -> i64 { if !(0..64).contains(&rhs) { - crate::panic::raise_str(&format!("__lk_shr shift amount {rhs} is out of range 0..63")); + crate::panic::raise_str(&format!("shift amount {rhs} is out of range 0..63")); } lhs.wrapping_shr(rhs as u32) } +/// `lhs >> rhs`, *logical* — zeros come in at the top — with the same range rule. +/// +/// The one shift `>>` cannot always be. Every value in this language rides an +/// `i64` carrier, so for a `u8`, `u16` or `u32` the high bits are zero and an +/// arithmetic shift happens to give the right answer. A `u64` fills the carrier: +/// bit 63 *is* the sign bit, and shifting `1u64 << 63` right by 63 answered -1 +/// instead of 1 — silently, on both backends, which is what a physical address +/// or a page-table entry is made of. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_u64_shr_checked(lhs: i64, rhs: i64) -> i64 { + if !(0..64).contains(&rhs) { + crate::panic::raise_str(&format!("shift amount {rhs} is out of range 0..63")); + } + ((lhs as u64).wrapping_shr(rhs as u32)) as i64 +} + +/// `lhs < rhs`, unsigned. Answers 1 or 0. +/// +/// The one comparison a `u64` cannot borrow from `Int`. Every value rides an +/// `i64` carrier, so a `u64` with bit 63 set *is* a negative carrier and a +/// signed compare puts it below 1. One primitive rather than four: `a > b` is +/// `b < a`, and the two inclusive forms are those negated. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_u64_lt(lhs: i64, rhs: i64) -> i64 { + i64::from((lhs as u64) < (rhs as u64)) +} + +/// `lhs / rhs`, unsigned, aborting on a zero divisor. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_u64_div(lhs: i64, rhs: i64) -> i64 { + if rhs == 0 { + crate::panic::raise_str("division by zero"); + } + ((lhs as u64) / (rhs as u64)) as i64 +} + +/// `lhs % rhs`, unsigned, aborting on a zero divisor. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_u64_rem(lhs: i64, rhs: i64) -> i64 { + if rhs == 0 { + crate::panic::raise_str("modulo by zero"); + } + ((lhs as u64) % (rhs as u64)) as i64 +} + +/// `value as Float`, reading the carrier as unsigned. +/// +/// The last place a `u64` is read as an `i64`. A value with bit 63 set is a +/// negative carrier, so the ordinary conversion answers a negative float — and +/// unlike a comparison or a divide, nothing about the result *looks* wrong until +/// it is compared with zero. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_u64_to_f64(value: i64) -> f64 { + (value as u64) as f64 +} + /// `lhs % rhs` for integers, aborting on a zero divisor. `i64::MIN % -1` wraps to /// `0` instead of overflowing. #[unsafe(no_mangle)] pub extern "C" fn lkrt_i64_mod_checked(lhs: i64, rhs: i64) -> i64 { if rhs == 0 { - crate::panic::raise_str("Division by zero"); + crate::panic::raise_str("modulo by zero"); } lhs.wrapping_rem(rhs) } -/// `lhs / rhs` for floats, aborting on a zero divisor to match the VM (which -/// errors on float division by zero rather than producing infinity). +/// `lhs / rhs` for floats — IEEE, so a zero divisor gives an infinity or a +/// NaN rather than raising. +/// +/// The name keeps `_checked` because it is the ABI symbol both backends were +/// built against; there is nothing left to check. It used to raise, to match a +/// VM that raised — and both were wrong about `Float`, which *is* `f64`. +// TODO: rename to `lkrt_f64_div` once an ABI version bump is due anyway. #[unsafe(no_mangle)] pub extern "C" fn lkrt_f64_div_checked(lhs: f64, rhs: f64) -> f64 { - if rhs == 0.0 { - crate::panic::raise_str("Division by zero"); - } lhs / rhs } -/// `lhs % rhs` for floats, aborting on a zero divisor to match the VM. +/// `lhs % rhs` for floats — IEEE, so a zero divisor gives a NaN. +// TODO: rename to `lkrt_f64_mod` alongside `lkrt_f64_div_checked`. #[unsafe(no_mangle)] pub extern "C" fn lkrt_f64_mod_checked(lhs: f64, rhs: f64) -> f64 { - if rhs == 0.0 { - crate::panic::raise_str("Division by zero"); - } lhs % rhs } +/// `value as ` — a float narrowed to a fixed width. +/// +/// Saturating to the *target's* range, which is what `as` means from a float. +/// Both engines used to saturate to `i64` first and then mask, so a value out +/// of range came back as an arbitrary bit pattern: at `i32`, `1 / 0` (which is +/// `inf`, since `/` is float division) answered `-1` and `-1 / 0` answered `0`. +/// +/// One implementation for both ends, called from the native lowering and +/// mirrored by `cast_to_machine_int` in the VM — a cast is not hot enough to be +/// worth two copies of a rule this easy to get subtly different. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_f64_to_machine_int(value: f64, bits: i64, signed: i64) -> i64 { + let signed = signed != 0; + if bits >= 64 { + return if signed { value as i64 } else { value as u64 as i64 }; + } + if value.is_nan() { + return 0; + } + let (low, high) = if signed { + (-(1i64 << (bits - 1)), (1i64 << (bits - 1)) - 1) + } else { + (0, (1i64 << bits) - 1) + }; + if value <= low as f64 { + low + } else if value >= high as f64 { + high + } else { + value as i64 + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lkrt/src/chan.rs b/lkrt/src/chan.rs index 56a2dba9..b590f573 100644 --- a/lkrt/src/chan.rs +++ b/lkrt/src/chan.rs @@ -16,10 +16,12 @@ use alloc::ffi::CString; use core::ffi::{CStr, c_char, c_void}; use std::collections::{HashMap, VecDeque}; -use std::sync::atomic::{AtomicI64, Ordering}; -use std::sync::{Arc, Condvar, Mutex, OnceLock}; +use std::sync::atomic::{AtomicI64, AtomicUsize, Ordering}; +use std::sync::{Arc, Condvar, Mutex, OnceLock, RwLock}; -use crate::lkdyn::{DYN_BOOL, DYN_F64, DYN_I64, DYN_LIST, DYN_MAP, DYN_NIL, DYN_STR, LkDyn}; +use crate::lkdyn::{ + DYN_BOOL, DYN_F64, DYN_I64, DYN_LIST, DYN_MAP, DYN_NIL, DYN_STR, LkDyn, is_list_tag, is_map_tag, map_entries, +}; use crate::lkmap::StrDynMap; use crate::lkstr::arena_c_string; use crate::state::arena_handle; @@ -27,17 +29,30 @@ use crate::state::arena_handle; /// A value that crossed an isolate boundary: fully owned, `Send`. Maps keep /// their iteration order (entries captured in order, replayed on rebuild — /// same keys + same insertion order = the same Fx layout on the other side). -enum OwnedVal { +#[derive(Clone)] +pub(crate) enum OwnedVal { Nil, Bool(bool), Int(i64), Float(f64), Str(String), List(Vec), - Map(Vec<(String, OwnedVal)>), + /// A map's entries, and the declared-struct id if the map is a struct + /// instance. Copying the entries alone dropped the identity at the channel: + /// the receiver got a plain map, so `typeof` answered `Map` and `println` + /// printed `{"p":1}` where the interpreter had `P{p:1}`. + Map(Vec<(String, OwnedVal)>, i64), + /// A closure: its code address, its visible arity, its module function + /// index (for `display`), and its own captures owned the same way. The + /// address is shared rather than copied — it is code. + Closure(usize, i64, i64, Vec), + /// A channel or a task, as its tag and its id. Copied by *identity*: the + /// registry is process-wide, so a channel that crosses a channel is the + /// same channel — which is what the interpreter's handle copy means too. + Handle(i64, i64), } -fn own(v: LkDyn) -> OwnedVal { +pub(crate) fn own(v: LkDyn) -> OwnedVal { match v.tag { DYN_NIL => OwnedVal::Nil, DYN_BOOL => OwnedVal::Bool(v.payload != 0), @@ -53,33 +68,48 @@ fn own(v: LkDyn) -> OwnedVal { }; OwnedVal::Str(text) } - DYN_LIST => { - let handle = v.payload as *mut c_void; - let items: &[LkDyn] = if handle.is_null() { - &[] - } else { - // SAFETY: DYN_LIST payloads are live dyn-list handles. - unsafe { &*(handle as *mut Vec) } - }; - OwnedVal::List(items.iter().map(|&item| own(item)).collect()) + // A channel copies by value, so every list representation deep-copies + // the same way — the typed carriers box in place now and would + // otherwise fall through to the unsupported arm. + tag if is_list_tag(tag) => { + OwnedVal::List(crate::lkdyn::dyn_list_values(v).iter().map(|&item| own(item)).collect()) } - DYN_MAP => { - let handle = v.payload as *mut c_void; - if handle.is_null() { - return OwnedVal::Map(Vec::new()); + // Every map representation, for the same reason the list arm covers + // every list one: a map whose values are all Int is a `MapStrI64` + // carrier, not a boxed `DYN_MAP`, and it used to fall through to the + // unsupported arm below. `send(c, {"code": 7})` answered "value cannot + // cross a channel" natively while the interpreter sent it — and a raise + // out of a task travels this same path, so `error({"code": 7})` inside + // `spawn` reported the same thing. + tag if is_map_tag(tag) => { + if v.payload == 0 { + return OwnedVal::Map(Vec::new(), 0); } - // SAFETY: DYN_MAP payloads are live `StrDynMap` handles. - let map = unsafe { &*(handle as *mut StrDynMap) }; - OwnedVal::Map(map.iter().map(|(k, &val)| (k.clone(), own(val))).collect()) + let type_id = crate::lkdyn::lkrt_dyn_obj_type_id(v); + OwnedVal::Map( + map_entries(v) + .into_iter() + .map(|(key, val)| (String::from(crate::vm_mirror::key_str(&key)), own(val))) + .collect(), + type_id, + ) } - // Channels/tasks/functions do not cross as *values* in the native - // subset (channels travel as their i64 ids). + // A closure copies its captures the same way and shares its code. + crate::lkdyn::DYN_CLOSURE => crate::lkclosure::own_closure(v), + // A channel or a task crosses as itself. They used to travel as bare + // `i64` ids, which is why this arm did not exist — and why `typeof` on + // one answered `Int`. + crate::lkdyn::DYN_CHAN | crate::lkdyn::DYN_TASK => OwnedVal::Handle(v.tag, v.payload), _ => crate::panic::raise_str("value cannot cross a channel"), } } -fn materialize(v: &OwnedVal) -> LkDyn { +pub(crate) fn materialize(v: &OwnedVal) -> LkDyn { match v { + OwnedVal::Handle(tag, id) => LkDyn { + tag: *tag, + payload: *id, + }, OwnedVal::Nil => LkDyn::NIL, OwnedVal::Bool(b) => LkDyn { tag: DYN_BOOL, @@ -107,22 +137,40 @@ fn materialize(v: &OwnedVal) -> LkDyn { payload: arena_handle(list) as i64, } } - OwnedVal::Map(entries) => { + OwnedVal::Map(entries, type_id) => { let mut map = StrDynMap::default(); for (k, val) in entries { - map.insert(k.clone(), materialize(val)); + map.insert(crate::lkmap::StrKey::Owned(k.clone()), materialize(val)); } + map.type_id = *type_id; LkDyn { tag: DYN_MAP, payload: arena_handle(map) as i64, } } + OwnedVal::Closure(code, params, fn_index, env) => { + crate::lkclosure::materialize_closure(*code, *params, *fn_index, env) + } } } struct ChanState { queue: VecDeque, closed: bool, + /// How many threads are inside `recv_cv.wait` / `send_cv.wait` on this + /// channel. + /// + /// Kept in the state rather than in an atomic because it is only ever read + /// and written under `state`, which makes it exact for free: a waiter + /// increments it before `wait` releases the lock, so a notifier holding the + /// lock and seeing zero knows nobody is waiting *and* nobody can start + /// without going through it. + /// + /// The point is the syscall. `Condvar::notify_one` on Linux issues a + /// `futex_wake` whether or not anything is parked, and a send/receive loop + /// spent a third of its time in that syscall waking nobody. + recv_waiters: usize, + send_waiters: usize, } struct ChanInner { @@ -132,11 +180,31 @@ struct ChanInner { /// Signals bounded senders (space available / closed). send_cv: Condvar, cap: Option, + /// What the program asked for, which is not `cap`: `chan.new(0)` is + /// unbuffered and reports `0` while the queue's bound is 1. The VM keeps + /// the same two numbers apart (`ChannelValue::capacity`). + requested: i64, } -fn registry() -> &'static Mutex>> { - static REGISTRY: OnceLock>>> = OnceLock::new(); - REGISTRY.get_or_init(|| Mutex::new(HashMap::new())) +/// Every channel ever created, indexed by `id - 1`. +/// +/// A `Vec` rather than a map because ids come from one `fetch_add` and nothing +/// is ever removed — a channel outlives the program, which is the same arena +/// model the rest of lkrt uses. Lookup is then a bounds check and an `Arc` +/// clone, and it takes a *read* lock, so two threads on two channels do not +/// serialize on each other. +/// +/// It was a `Mutex>` with the default hasher. Every send and +/// every receive resolves its channel through here, so each one paid a +/// process-global mutex plus a SipHash of an `i64` — 28% of a send/receive loop +/// between them, to look up a dense integer. +/// +/// `Option` because ids are handed out before the insert takes the lock, so two +/// threads creating channels can arrive out of order and leave a hole for the +/// slower one to fill. +fn registry() -> &'static RwLock>>> { + static REGISTRY: OnceLock>>>> = OnceLock::new(); + REGISTRY.get_or_init(|| RwLock::new(Vec::new())) } /// Process-global select wake-up: a generation counter bumped (and @@ -150,6 +218,32 @@ struct SelectGen { cv: Condvar, } +/// How many `select`s are parked on [`SelectGen`] right now. +/// +/// Read by every send and receive so that a program with no `select` in it pays +/// one relaxed load instead of a process-global mutex and a `notify_all`. That +/// was not a rounding error: on a send/receive loop with no `select` anywhere, +/// `notify_selects` and the futex calls its broadcast made were **58% of the +/// program** — a global serialization point on the hot path of a feature the +/// program did not use. +static BLOCKED_SELECTS: AtomicUsize = AtomicUsize::new(0); + +/// A `select` counted in [`BLOCKED_SELECTS`] for as long as this is alive. +struct SelectParked; + +impl SelectParked { + fn enter() -> Self { + BLOCKED_SELECTS.fetch_add(1, Ordering::SeqCst); + Self + } +} + +impl Drop for SelectParked { + fn drop(&mut self) { + BLOCKED_SELECTS.fetch_sub(1, Ordering::SeqCst); + } +} + fn select_gen() -> &'static SelectGen { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| SelectGen { @@ -159,7 +253,19 @@ fn select_gen() -> &'static SelectGen { } /// Signals every blocked `select` that some channel changed state. +/// +/// Skipping the broadcast when nothing is parked is safe, and the ordering is +/// what makes it so. A notifier reaches here having *already* released its +/// channel's `state` guard, so its change is published; a `select` increments +/// [`BLOCKED_SELECTS`] *before* reading the generation and polling. So if the +/// load below sees zero, the increment that would have made it one comes later +/// in the `SeqCst` total order — and the poll that follows that increment takes +/// the channel lock, and therefore sees the change this notifier just made. +/// Either the notifier wakes the select, or the select was never going to sleep. fn notify_selects() { + if BLOCKED_SELECTS.load(Ordering::SeqCst) == 0 { + return; + } let wake = select_gen(); { let mut generation = wake.lock.lock().expect("select generation poisoned"); @@ -173,7 +279,14 @@ fn channel(id: i64) -> Arc { // raise longjmps to the nearest handler, skipping Rust drops — a live // guard would leave the *global* registry locked forever, deadlocking // every later channel operation once the raise is caught. - let found = registry().lock().expect("channel registry poisoned").get(&id).cloned(); + let found = usize::try_from(id).ok().and_then(|id| { + registry() + .read() + .expect("channel registry poisoned") + .get(id.checked_sub(1)?) + .cloned() + .flatten() + }); match found { Some(inner) => inner, None => crate::panic::raise_str("Channel not found"), @@ -182,21 +295,42 @@ fn channel(id: i64) -> Arc { static NEXT_CHANNEL_ID: AtomicI64 = AtomicI64::new(1); -/// `chan(capacity)` — `capacity <= 0` is unbounded (the VM's rule). The -/// channel travels as its `i64` id. +/// `chan(capacity)`. The channel travels as its `i64` id. +/// +/// `0` is **unbuffered**, not unbounded — the same ruling the VM's +/// `create_channel_value` records, and this had been left behind on the older +/// one (`capacity <= 0` meant unbounded here). It was observable: `chan.new(0)` +/// then two `try_send`s answered `true, false` on the VM and `true, true` +/// natively. As there, unbuffered takes the smallest bound the queue offers. +/// +/// A negative capacity raises, likewise matching the VM instead of silently +/// handing back an unbounded channel. #[unsafe(no_mangle)] pub extern "C" fn lkrt_chan_new(capacity: i64) -> i64 { + if capacity < 0 { + crate::panic::raise_str(&format!("chan() capacity cannot be negative, got {capacity}")); + } let id = NEXT_CHANNEL_ID.fetch_add(1, Ordering::Relaxed); let inner = Arc::new(ChanInner { state: Mutex::new(ChanState { queue: VecDeque::new(), closed: false, + recv_waiters: 0, + send_waiters: 0, }), recv_cv: Condvar::new(), send_cv: Condvar::new(), - cap: if capacity <= 0 { None } else { Some(capacity as usize) }, + cap: Some((capacity as usize).max(1)), + requested: capacity, }); - registry().lock().expect("channel registry poisoned").insert(id, inner); + { + let mut table = registry().write().expect("channel registry poisoned"); + let slot = id as usize - 1; + if table.len() <= slot { + table.resize(slot + 1, None); + } + table[slot] = Some(inner); + } id } @@ -215,12 +349,17 @@ pub extern "C" fn lkrt_chan_send(id: i64, value: LkDyn) { } if inner.cap.is_none_or(|cap| state.queue.len() < cap) { state.queue.push_back(owned); + let wake = state.recv_waiters > 0; drop(state); - inner.recv_cv.notify_one(); + if wake { + inner.recv_cv.notify_one(); + } notify_selects(); return; } + state.send_waiters += 1; state = inner.send_cv.wait(state).expect("channel poisoned"); + state.send_waiters -= 1; } } @@ -232,8 +371,11 @@ pub extern "C" fn lkrt_chan_recv(id: i64) -> LkDyn { let mut state = inner.state.lock().expect("channel poisoned"); loop { if let Some(value) = state.queue.pop_front() { + let wake = state.send_waiters > 0; drop(state); - inner.send_cv.notify_one(); + if wake { + inner.send_cv.notify_one(); + } notify_selects(); return materialize(&value); } @@ -241,7 +383,9 @@ pub extern "C" fn lkrt_chan_recv(id: i64) -> LkDyn { drop(state); crate::panic::raise_str("receive on closed channel"); } + state.recv_waiters += 1; state = inner.recv_cv.wait(state).expect("channel poisoned"); + state.recv_waiters -= 1; } } @@ -250,12 +394,75 @@ pub extern "C" fn lkrt_chan_recv(id: i64) -> LkDyn { #[unsafe(no_mangle)] pub extern "C" fn lkrt_chan_close(id: i64) { let inner = channel(id); - inner.state.lock().expect("channel poisoned").closed = true; - inner.recv_cv.notify_all(); - inner.send_cv.notify_all(); + let (wake_recv, wake_send) = { + let mut state = inner.state.lock().expect("channel poisoned"); + state.closed = true; + (state.recv_waiters > 0, state.send_waiters > 0) + }; + if wake_recv { + inner.recv_cv.notify_all(); + } + if wake_send { + inner.send_cv.notify_all(); + } notify_selects(); } +/// `time.timeout(ms)` / `time.after(ms)` — a capacity-1 channel that receives +/// one value once the duration is up. +/// +/// The stdlib module builds this out of a tokio timer plus its async runtime's +/// channel; here it is a thread that sleeps and then sends, because lkrt's +/// channels are already thread-backed. What matters is the observable part, and +/// it is the same on both: capacity 1, exactly one value, and the value itself +/// — `timeout` sends nil, `after` sends the epoch milliseconds **read when the +/// timer fires**, not when it was armed. +/// +/// The send is a `try_send` whose result is dropped, matching the module: if +/// nobody ever receives, the timer must not keep a thread parked forever, and a +/// closed channel is not the timer's error to report. +fn spawn_timer(duration_ms: i64, after: bool) -> i64 { + let id = lkrt_chan_new(1); + let delay = core::time::Duration::from_millis(duration_ms.max(0) as u64); + register_task(std::thread::spawn(move || { + std::thread::sleep(delay); + let value = if after { + crate::lkdyn::lkrt_dyn_from_i64(crate::host::lkrt_time_now_ms()) + } else { + crate::lkdyn::lkrt_dyn_from_nil() + }; + let inner = channel(id); + let mut state = inner.state.lock().expect("channel poisoned"); + // Not `lkrt_chan_try_send`: that raises on a closed channel, and a + // raise `longjmp`s — out of a spawned thread, past this lock guard, + // with nobody to catch it. A closed channel means the receiver is gone, + // which is the timer's cue to do nothing. + if !state.closed && inner.cap.is_none_or(|cap| state.queue.len() < cap) { + state.queue.push_back(own(value)); + if state.recv_waiters > 0 { + inner.recv_cv.notify_all(); + } + } + drop(state); + // The timer body raises nothing — the comment above says why — so it + // reports the outcome directly rather than going through a try frame. + TaskOutcome::Returned(own(crate::lkdyn::lkrt_dyn_from_nil())) + })); + id +} + +/// `time.timeout(ms)` — fires with nil. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_time_timeout(duration_ms: i64) -> i64 { + spawn_timer(duration_ms, false) +} + +/// `time.after(ms)` — fires with the epoch milliseconds at that moment. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_time_after(duration_ms: i64) -> i64 { + spawn_timer(duration_ms, true) +} + /// Non-blocking send: 1 delivered, 0 full; closed raises. #[unsafe(no_mangle)] pub extern "C" fn lkrt_chan_try_send(id: i64, value: LkDyn) -> i64 { @@ -268,8 +475,11 @@ pub extern "C" fn lkrt_chan_try_send(id: i64, value: LkDyn) -> i64 { } if inner.cap.is_none_or(|cap| state.queue.len() < cap) { state.queue.push_back(owned); + let wake = state.recv_waiters > 0; drop(state); - inner.recv_cv.notify_one(); + if wake { + inner.recv_cv.notify_one(); + } notify_selects(); 1 } else { @@ -284,8 +494,11 @@ pub extern "C" fn lkrt_chan_try_recv(id: i64) -> LkDyn { let inner = channel(id); let mut state = inner.state.lock().expect("channel poisoned"); if let Some(value) = state.queue.pop_front() { + let wake = state.send_waiters > 0; drop(state); - inner.send_cv.notify_one(); + if wake { + inner.send_cv.notify_one(); + } notify_selects(); return materialize(&value); } @@ -302,6 +515,12 @@ pub extern "C" fn lkrt_chan_len(id: i64) -> i64 { channel(id).state.lock().expect("channel poisoned").queue.len() as i64 } +/// `chan.capacity(c)` — the capacity as asked for, so `chan.new(0)` reports 0. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_chan_capacity(id: i64) -> i64 { + channel(id).requested +} + /// `chan.is_closed(c)`. #[unsafe(no_mangle)] pub extern "C" fn lkrt_chan_is_closed(id: i64) -> i64 { @@ -357,45 +576,72 @@ pub unsafe extern "C" fn lkrt_chan_select( ]; arena_handle(list) }; - // Pre-validate arm kinds and deep-copy the armed send payloads *before* - // any channel lock is taken: `own` and the shape guards raise, and a - // longjmp past a live `MutexGuard` would leave that channel locked - // forever (the blocking send/recv paths follow the same - // drop-before-raise discipline). A send arm's copy is taken exactly - // once, up front; the retry loop consumes it on delivery. - let mut kinds = Vec::with_capacity(len); + // Everything that can raise, and everything that does not change between + // polls, happens here — before any channel lock is taken and before this + // call registers itself as a parked select. + // + // Raising is the original reason: `own` and the shape guards raise, and a + // longjmp past a live `MutexGuard` would leave that channel locked forever + // (the blocking send/recv paths follow the same drop-before-raise + // discipline). A send arm's copy is taken exactly once, up front; the retry + // loop consumes it on delivery. + // + // Resolving the channels here as well does two more things. It keeps the + // *global registry* mutex out of the poll loop, which used to take it once + // per armed arm per round. And it leaves the loop below with only one raise + // in it, which matters because a raise skips the parked-select bookkeeping + // (see `SelectParked`) — one site is a thing that can be got right by + // reading, a site per arm is not. + // + // A *disarmed* arm is not resolved and not shape-checked, because it was + // not before: `select { c1 <- v if false, … }` naming a channel that does + // not exist is a program the VM runs. + let mut arms: Vec)>> = Vec::with_capacity(len); let mut owned_sends: Vec> = Vec::with_capacity(len); for index in 0..len { let kind = match types[index].tag { DYN_I64 if matches!(types[index].payload, 0 | 1) => types[index].payload, _ => crate::panic::raise_str("select$block: invalid arm entry types"), }; + // Guard must be exactly `true` (the VM normalizes to Bool). let armed = guards[index].tag == DYN_BOOL && guards[index].payload != 0; owned_sends.push((kind == 1 && armed).then(|| own(values[index]))); - kinds.push(kind); + arms.push(armed.then(|| { + let id = match channels[index].tag { + // A channel travels boxed under its own tag; the bare id is + // still accepted, which is what a `chan::…` spelling that has + // not been through the boxing path hands over. + crate::lkdyn::DYN_CHAN | DYN_I64 => channels[index].payload, + _ => crate::panic::raise_str("select$block: invalid channel arm"), + }; + (kind, channel(id)) + })); } + let any_armed = arms.iter().any(Option::is_some); loop { + // Registered *before* the poll, which is what lets a notifier skip its + // broadcast when this counter reads zero — see `notify_selects` for the + // ordering argument. Dropped on every way out of this loop body, + // including the `return`s inside the poll. + let parked = SelectParked::enter(); // Read the wake-up generation *before* polling: a channel op that // lands mid-poll bumps it, so the wait below returns immediately // instead of missing the change. let round_gen = *select_gen().lock.lock().expect("select generation poisoned"); for index in 0..len { - // Guard must be exactly `true` (the VM normalizes to Bool). - if !(guards[index].tag == DYN_BOOL && guards[index].payload != 0) { + let Some((kind, inner)) = &arms[index] else { continue; - } - let id = match channels[index].tag { - DYN_I64 => channels[index].payload, - _ => crate::panic::raise_str("select$block: invalid channel arm"), }; - let kind = kinds[index]; - let inner = channel(id); + let (kind, inner) = (*kind, inner.clone()); let mut state = inner.state.lock().expect("channel poisoned"); match kind { 0 => { if let Some(value) = state.queue.pop_front() { + let wake = state.send_waiters > 0; drop(state); - inner.send_cv.notify_one(); + if wake { + inner.send_cv.notify_one(); + } notify_selects(); let payload = arena_handle(vec![ LkDyn { @@ -436,13 +682,22 @@ pub unsafe extern "C" fn lkrt_chan_select( 1 => { if state.closed { drop(state); + // The one raise left inside the poll. A raise longjmps + // past Rust drops, so the registration has to come off + // by hand — otherwise this select stays counted as + // parked forever and every channel operation in the + // process goes back to broadcasting. + drop(parked); crate::panic::raise_str("send on closed channel"); } if inner.cap.is_none_or(|cap| state.queue.len() < cap) { let owned = owned_sends[index].take().expect("armed send payload pre-owned"); state.queue.push_back(owned); + let wake = state.recv_waiters > 0; drop(state); - inner.recv_cv.notify_one(); + if wake { + inner.recv_cv.notify_one(); + } notify_selects(); return result(false, index as i64, LkDyn::NIL); } @@ -455,7 +710,7 @@ pub unsafe extern "C" fn lkrt_chan_select( if has_default != 0 { return result(true, -1, LkDyn::NIL); } - if len == 0 || !guards.iter().any(|g| g.tag == DYN_BOOL && g.payload != 0) { + if !any_armed { // Every arm disabled and no default: the VM yields nil-ish; // mirror its documented "all guards off → nil" rule by // reporting the default shape. @@ -481,8 +736,19 @@ pub unsafe extern "C" fn lkrt_chan_select( // ── Goroutine threads + task registry (H2) ───────────────────────────── +/// How a task finished. +/// +/// A raise inside a task is the task's *result*, not the process's: the +/// interpreter hands it to whoever awaits, and a task nobody awaits fails +/// silently. Natively the raise had no handler on that thread and took the +/// uncaught path — print and exit — so one failing task killed the program. +enum TaskOutcome { + Returned(OwnedVal), + Raised(OwnedVal), +} + struct TaskSlot { - handle: Option>, + handle: Option>, } fn tasks() -> &'static Mutex> { @@ -525,7 +791,7 @@ pub unsafe extern "C" fn lkrt_spawn_arg(block: *mut c_void, index: i64) -> LkDyn } } -fn register_task(handle: std::thread::JoinHandle) -> i64 { +fn register_task(handle: std::thread::JoinHandle) -> i64 { let id = NEXT_TASK_ID.fetch_add(1, Ordering::Relaxed); tasks() .lock() @@ -552,21 +818,72 @@ macro_rules! spawn_arity { let block = block_addr as *mut c_void; // SAFETY: ownership of the block moved into this thread. let args = unsafe { Box::from_raw(block as *mut Vec) }; - let result = f($(materialize(&args[$idx])),*); - own(result) + // Materialised before the protected call: a raise jumps past + // every drop in between, and `args` must not be one of them. + let ready: Vec = alloc::vec![$(materialize(&args[$idx])),*]; + drop(args); + // `ready` is the closure's; a raise jumps past its drop, so a + // failing task leaks one small `Vec`. Bounded by the number of + // tasks that fail, which is why it is left rather than worked + // around with a thread-local. + run_protected(move || store_task_result(own(f($(ready[$idx]),*)))) })) } }; (@ty $idx:literal) => { LkDyn }; } +unsafe extern "C" { + /// See `try_trampoline.c`. Cranelift and Rust both refuse `setjmp`, so the + /// frame that survives the jump has to be a C one. + fn lkrt_rt_try_thunk(thunk: extern "C" fn(*mut c_void), state: *mut c_void) -> i64; +} + +extern "C" fn call_boxed_thunk(state: *mut c_void) { + // SAFETY: `state` is the `Box` `run_protected` handed over. + let body = unsafe { &mut *(state as *mut Box) }; + body(); +} + +/// Runs a task body under its own `try` frame and reports how it finished. +/// +/// The body must own nothing that needs dropping: a raise `longjmp`s past every +/// Rust drop between here and the C frame. That is why the arguments are +/// materialised *before* the call and only the plain call happens inside. +fn run_protected(body: impl FnMut() + 'static) -> TaskOutcome { + let mut boxed: Box = Box::new(body); + let state = (&raw mut boxed).cast::(); + // SAFETY: `call_boxed_thunk` reads exactly the pointer passed here, and + // `boxed` outlives the call. + if unsafe { lkrt_rt_try_thunk(call_boxed_thunk, state) } == 0 { + return TaskOutcome::Raised(own(crate::panic::lkrt_rt_current_error())); + } + TaskOutcome::Returned( + TASK_RESULT + .with(|slot| slot.borrow_mut().take()) + .unwrap_or(OwnedVal::Nil), + ) +} + +std::thread_local! { + /// Where a protected task body leaves its result. A value cannot be + /// returned *through* the C trampoline, which speaks only `long long`. + static TASK_RESULT: core::cell::RefCell> = const { core::cell::RefCell::new(None) }; +} + +fn store_task_result(value: OwnedVal) { + TASK_RESULT.with(|slot| *slot.borrow_mut() = Some(value)); +} + /// Zero-capture spawn (no argument block). /// /// # Safety /// `f` must be a compiled zero-argument function returning a boxed value. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_spawn0(f: extern "C" fn() -> LkDyn) -> i64 { - register_task(std::thread::spawn(move || own(f()))) + register_task(std::thread::spawn(move || { + run_protected(move || store_task_result(own(f()))) + })) } spawn_arity!(lkrt_spawn1, 0); @@ -585,10 +902,22 @@ pub extern "C" fn lkrt_task_await(id: i64) -> LkDyn { }; match handle { Some(handle) => match handle.join() { - Ok(owned) => materialize(&owned), + Ok(TaskOutcome::Returned(owned)) => materialize(&owned), + // The task's raise, delivered here — the interpreter's rule, and + // the reason it is caught rather than fatal. Materialised into this + // thread's arena first: the value was built in the task's. + Ok(TaskOutcome::Raised(owned)) => { + let value = materialize(&owned); + crate::panic::lkrt_rt_raise_dyn(value); + unreachable!("a raise does not return") + } Err(_) => crate::panic::raise_str("task failed"), }, - None => crate::panic::raise_str("Task not found"), + // Same wording as the VM: awaiting takes the task, so a second + // await finds nothing. + None => { + crate::panic::raise_str("this task has already been awaited — its result was handed to the first `await`") + } } } diff --git a/lkrt/src/encoding.rs b/lkrt/src/encoding.rs index 23cab555..d69dc476 100644 --- a/lkrt/src/encoding.rs +++ b/lkrt/src/encoding.rs @@ -2,13 +2,18 @@ //! crates and conversion rules of the VM's `core/src/val/de.rs`, so values — //! numbers, nesting, and **map iteration order** — match byte-for-byte. //! -//! Order argument: the VM inserts each decoded object's entries, in the -//! serde iteration order (serde_json `Value::Object` is a BTreeMap → sorted; -//! serde_yaml `Mapping` and `toml::Table` preserve/sort per their own -//! defaults — the same crates at the same lockfile versions produce the same -//! sequence), into a fresh `FastHashMap` and rebuilds the typed map from -//! *its* iteration (`typed_map_from_entries`). [`str_dyn_map_mirrored`] -//! replays both stages. +//! Order argument: the VM inserts each decoded object's entries, **in document +//! order**, into a fresh `FastHashMap` and rebuilds the typed map from *its* +//! iteration (`typed_map_from_entries`). [`str_dyn_map_mirrored`] replays both +//! stages. +//! +//! Document order, and not the intermediate's: `serde_json::Value::Object` is a +//! `BTreeMap`, so both sides used to hand back a document alphabetised — which +//! contradicts the language's own rule that a map iterates in the order a key +//! was first written. The VM stopped going through that value type +//! ([`lk_core::val::de`]'s `OrderedJson`); this does the same, with the same +//! visitor, because the two orders have to be the same order. TOML takes its +//! crate's `preserve_order`; YAML's `Mapping` was already ordered. //! //! Arrays decode to dyn lists (the VM shapes uniform scalars into typed //! lists — indexing/len/eq agree; display quoting of a uniform *string* @@ -29,7 +34,7 @@ use alloc::{ use alloc::ffi::CString; use core::ffi::{CStr, c_char}; -use crate::lkdyn::{DYN_BOOL, DYN_F64, DYN_I64, DYN_LIST, DYN_MAP, LkDyn}; +use crate::lkdyn::{DYN_BOOL, DYN_F64, DYN_I64, DYN_LIST, DYN_MAP, DYN_SLICE, LkDyn, is_list_tag}; use crate::lkstr::arena_c_string; use crate::state::arena_handle; use crate::vm_mirror::str_dyn_map_mirrored; @@ -95,14 +100,82 @@ fn dyn_number(int_value: Option, float_value: Option) -> LkDyn { } } -fn json_to_dyn(value: serde_json::Value) -> LkDyn { +/// A JSON document with its objects still in document order — the mirror of +/// `lk_core::val::de`'s type of the same shape. See this module's note. +enum OrderedJson { + Null, + Bool(bool), + Int(i64), + Float(f64), + Str(String), + Array(Vec), + Object(Vec<(String, OrderedJson)>), +} + +impl<'de> serde::Deserialize<'de> for OrderedJson { + fn deserialize>(deserializer: D) -> Result { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = OrderedJson; + + fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.write_str("any JSON value") + } + + fn visit_unit(self) -> Result { + Ok(OrderedJson::Null) + } + fn visit_none(self) -> Result { + Ok(OrderedJson::Null) + } + fn visit_bool(self, v: bool) -> Result { + Ok(OrderedJson::Bool(v)) + } + fn visit_i64(self, v: i64) -> Result { + Ok(OrderedJson::Int(v)) + } + fn visit_u64(self, v: u64) -> Result { + Ok(i64::try_from(v).map_or(OrderedJson::Float(v as f64), OrderedJson::Int)) + } + fn visit_f64(self, v: f64) -> Result { + Ok(OrderedJson::Float(v)) + } + fn visit_str(self, v: &str) -> Result { + Ok(OrderedJson::Str(v.to_string())) + } + fn visit_string(self, v: String) -> Result { + Ok(OrderedJson::Str(v)) + } + fn visit_seq>(self, mut seq: A) -> Result { + let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(0)); + while let Some(item) = seq.next_element()? { + out.push(item); + } + Ok(OrderedJson::Array(out)) + } + fn visit_map>(self, mut map: A) -> Result { + let mut out = Vec::with_capacity(map.size_hint().unwrap_or(0)); + while let Some((key, value)) = map.next_entry::()? { + out.push((key, value)); + } + Ok(OrderedJson::Object(out)) + } + } + + deserializer.deserialize_any(Visitor) + } +} + +fn json_to_dyn(value: OrderedJson) -> LkDyn { match value { - serde_json::Value::Null => LkDyn::NIL, - serde_json::Value::Bool(value) => dyn_bool(value), - serde_json::Value::Number(value) => dyn_number(value.as_i64(), value.as_f64()), - serde_json::Value::String(value) => dyn_str_of(&value), - serde_json::Value::Array(values) => dyn_list_of(values.into_iter().map(json_to_dyn).collect()), - serde_json::Value::Object(values) => dyn_map_of(values.into_iter().map(|(k, v)| (k, json_to_dyn(v))).collect()), + OrderedJson::Null => LkDyn::NIL, + OrderedJson::Bool(value) => dyn_bool(value), + OrderedJson::Int(value) => dyn_number(Some(value), None), + OrderedJson::Float(value) => dyn_number(None, Some(value)), + OrderedJson::Str(value) => dyn_str_of(&value), + OrderedJson::Array(values) => dyn_list_of(values.into_iter().map(json_to_dyn).collect()), + OrderedJson::Object(values) => dyn_map_of(values.into_iter().map(|(k, v)| (k, json_to_dyn(v))).collect()), } } @@ -112,7 +185,7 @@ fn json_to_dyn(value: serde_json::Value) -> LkDyn { /// `text` must be a valid C string, or null. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_json_parse(text: *const c_char) -> LkDyn { - match serde_json::from_str::(input(text)) { + match serde_json::from_str::(input(text)) { Ok(value) => json_to_dyn(value), Err(_) => crate::panic::raise_str("Invalid JSON"), } @@ -181,3 +254,141 @@ pub unsafe extern "C" fn lkrt_toml_parse(text: *const c_char) -> LkDyn { Err(_) => crate::panic::raise_str("Invalid TOML"), } } + +/// The write direction: an LK value as `serde_json::Value`, by exactly the +/// rules of the VM's `core/src/val/ser.rs`. +/// +/// Object keys come out **sorted**, and that is not a choice made here: both +/// sides build a `serde_json::Map`, which is a `BTreeMap`. So `stringify` is +/// the one place where a map's iteration order does *not* show — the ordering +/// argument that governs `parse` does not apply in reverse. +mod write { + use super::*; + use crate::lkdyn::{DYN_BYTES, DYN_NIL, DYN_RAW, DYN_SET, DYN_STR, is_map_tag, map_entries}; + use crate::vm_mirror::{RtKey, key_str}; + + /// The VM's `MAX_VALUE_DEPTH`, and its refusal names the number. + const MAX_VALUE_DEPTH: u32 = 512; + + pub(super) fn to_serde(value: LkDyn, depth: u32) -> Result { + if depth >= MAX_VALUE_DEPTH { + return Err(format!( + "value nested deeper than {MAX_VALUE_DEPTH} levels; it is cyclic or too deeply nested to write" + )); + } + Ok(match value.tag { + DYN_NIL => serde_json::Value::Null, + DYN_BOOL => serde_json::Value::Bool(value.payload != 0), + DYN_I64 => serde_json::Value::from(value.payload), + DYN_F64 => { + let number = f64::from_bits(value.payload as u64); + match serde_json::Number::from_f64(number) { + Some(number) => serde_json::Value::Number(number), + None => return Err(format!("{number} has no JSON form (NaN and the infinities do not)")), + } + } + DYN_STR => serde_json::Value::String(input(value.payload as *const c_char).to_string()), + // Every list representation, not only the boxed one: a typed + // carrier boxes in place now, so `json.stringify([[1]])` sees a + // `DYN_TLIST_*` tag where it used to see a rebuilt `DYN_LIST`. + tag if is_list_tag(tag) => { + let mut out = Vec::new(); + for element in crate::lkdyn::dyn_list_values(value).iter() { + out.push(to_serde(*element, depth + 1)?); + } + serde_json::Value::Array(out) + } + // A `Bytes` and a `Set` are the VM's refusals, by their type names. + DYN_BYTES => return Err("Bytes has no JSON form".to_string()), + DYN_SET => return Err("Set has no JSON form".to_string()), + DYN_RAW => return Err("Object has no JSON form".to_string()), + // A window is a list, and the VM encodes it as one: + // `json.stringify([xs.slice(0, 2)])` is `[[1,2]]` there and was + // `value has no JSON form` here. `DYN_SLICE` was added to the tag + // space after this match was written and the catch-all swallowed + // it — the third arm in this runtime to lose a carrier that way + // (see `lkrt_dyn_contains`, and `container_ty` in the lowering). + DYN_SLICE => { + let mut out = Vec::new(); + // SAFETY: a `DYN_SLICE` payload is a live window handle — the + // tag is only ever set by `lkrt_dyn_from_slice`. + let handle = value.payload as *mut core::ffi::c_void; + let len = unsafe { crate::lkslice::lkrt_lkslice_i64_len(handle) }; + for index in 0..len { + // SAFETY: as above, and `index` is inside `len`. + let element = unsafe { crate::lkslice::lkrt_lkslice_i64_get_pair(handle, index) }; + out.push(serde_json::Value::Number(element.value.into())); + } + serde_json::Value::Array(out) + } + tag if is_map_tag(tag) => { + let mut out = serde_json::Map::new(); + for (key, element) in map_entries(value) { + out.insert(object_key(&key)?, to_serde(element, depth + 1)?); + } + serde_json::Value::Object(out) + } + _ => return Err("value has no JSON form".to_string()), + }) + } + + /// A JSON object key, or the VM's refusal — verbatim, because a caught + /// error's message is program output and this one *tells the program what + /// to write instead*. + fn object_key(key: &RtKey) -> Result { + match key { + RtKey::ShortStr(_) | RtKey::String(_) => Ok(key_str(key).to_string()), + RtKey::Int(value) => Err(format!( + "a JSON object key is a String, and `{value}` is an Int — write it as \"{value}\" if that is what you mean" + )), + RtKey::Bool(value) => Err(format!("a JSON object key is a String, and `{value}` is a Bool")), + RtKey::Nil => Err("a JSON object key is a String, and `nil` is not one".to_string()), + RtKey::Obj(_) => Err("a JSON object key is a String".to_string()), + } + } +} + +/// `encoding.json.stringify(value)` — compact, the `serde_json::Value` +/// `Display`. +/// +/// The raise carries the member's name in front of the reason, which is the +/// stdlib's `write_format` wrapper doing it there. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_json_stringify(value: LkDyn) -> *mut c_char { + match write::to_serde(value, 0).map(|value| value.to_string()) { + Ok(text) => arena_c_string(CString::new(text).unwrap_or_default()), + Err(message) => crate::panic::raise_str(&format!("encoding.json.stringify: {message}")), + } +} + +/// `encoding.yaml.stringify(value)`. +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_yaml_stringify(value: LkDyn) -> *mut c_char { + let text = write::to_serde(value, 0) + .and_then(|value| serde_yaml::to_string(&value).map_err(|error| format!("cannot write YAML: {error}"))); + match text { + Ok(text) => arena_c_string(CString::new(text).unwrap_or_default()), + Err(message) => crate::panic::raise_str(&format!("encoding.yaml.stringify: {message}")), + } +} + +/// `encoding.toml.stringify(value)`. +/// +/// A TOML document *is* a table, so a top-level scalar or array is refused +/// rather than written out as something no TOML parser reads back — the VM's +/// rule, in the VM's words. +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_toml_stringify(value: LkDyn) -> *mut c_char { + let text = write::to_serde(value, 0).and_then(|value| { + if !value.is_object() { + return Err("a TOML document is a table, so the top level must be a map".to_string()); + } + toml::to_string(&value).map_err(|error| format!("cannot write TOML: {error}")) + }); + match text { + Ok(text) => arena_c_string(CString::new(text).unwrap_or_default()), + Err(message) => crate::panic::raise_str(&format!("encoding.toml.stringify: {message}")), + } +} diff --git a/lkrt/src/hash.rs b/lkrt/src/hash.rs new file mode 100644 index 00000000..bd36774a --- /dev/null +++ b/lkrt/src/hash.rs @@ -0,0 +1,125 @@ +//! Native `hash`: the same crates the stdlib module uses, so a digest is +//! byte-identical to the VM's. +//! +//! `sha2`/`sha1`/`crc32fast` are shared rather than reimplemented — the rule +//! that keeps base64/hex text and datetime formatting identical across the two +//! back ends. The hex rendering matches too, because both sides spell it +//! `format!("{:x}", digest)` and that is the crates' own `LowerHex`. +//! +//! `fnv64` is the exception: FNV-1a is four lines with two constants and no +//! crate in the graph provides it, so the loop genuinely exists twice. What +//! keeps the pair from drifting is `hash_members_answer_the_same_on_both_ends` +//! in the CLI's `clif_differential_test`, which runs both spellings over the +//! same inputs and compares stdout — a *wrong digest* is precisely the thing a +//! differential can see. +//! +//! Every member takes `Bytes | String` in the language, which is two native +//! argument types and therefore two entry points each. The digest of a string +//! is the digest of its UTF-8 bytes, which is what the VM does as well +//! (`runtime_bytes_or_string_arg`). + +// `alloc`, not the std prelude: hashing needs no OS, so this module is part of +// the computation-only subset. +use alloc::format; +use core::ffi::{CStr, c_char, c_void}; + +use sha1::Digest as _; + +fn str_bytes(text: *const c_char) -> &'static [u8] { + if text.is_null() { + return &[]; + } + // SAFETY: LK strings reaching the ABI are NUL-terminated and live for the + // duration of the call. + unsafe { CStr::from_ptr(text) }.to_bytes() +} + +fn out(text: alloc::string::String) -> *mut c_char { + crate::lkstr::arena_c_string(alloc::ffi::CString::new(text).unwrap_or_default()) +} + +fn sha256_of(data: &[u8]) -> *mut c_char { + out(format!("{:x}", sha2::Sha256::digest(data))) +} + +fn sha1_of(data: &[u8]) -> *mut c_char { + out(format!("{:x}", sha1::Sha1::digest(data))) +} + +fn crc32_of(data: &[u8]) -> i64 { + crc32fast::hash(data) as i64 +} + +/// FNV-1a, 64-bit — the stdlib `hash` module's loop, constants included. +/// +/// Kept in sync by the differential, not by inspection. +pub(crate) fn fnv64_of(data: &[u8]) -> i64 { + const OFFSET: u64 = 0xcbf29ce484222325; + const PRIME: u64 = 0x100000001b3; + let mut hash = OFFSET; + for byte in data { + hash ^= u64::from(*byte); + hash = hash.wrapping_mul(PRIME); + } + hash as i64 +} + +/// `hash.sha256(text)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_hash_sha256_str(text: *const c_char) -> *mut c_char { + sha256_of(str_bytes(text)) +} + +/// `hash.sha1(text)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_hash_sha1_str(text: *const c_char) -> *mut c_char { + sha1_of(str_bytes(text)) +} + +/// `hash.crc32(text)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_hash_crc32_str(text: *const c_char) -> i64 { + crc32_of(str_bytes(text)) +} + +/// `hash.fnv64(text)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_hash_fnv64_str(text: *const c_char) -> i64 { + fnv64_of(str_bytes(text)) +} + +/// `hash.sha256(bytes)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_hash_sha256_bytes(handle: *mut c_void) -> *mut c_char { + sha256_of(crate::lkbytes::bytes_slice(handle)) +} + +/// `hash.sha1(bytes)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_hash_sha1_bytes(handle: *mut c_void) -> *mut c_char { + sha1_of(crate::lkbytes::bytes_slice(handle)) +} + +/// `hash.crc32(bytes)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_hash_crc32_bytes(handle: *mut c_void) -> i64 { + crc32_of(crate::lkbytes::bytes_slice(handle)) +} + +/// `hash.fnv64(bytes)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_hash_fnv64_bytes(handle: *mut c_void) -> i64 { + fnv64_of(crate::lkbytes::bytes_slice(handle)) +} diff --git a/lkrt/src/host.rs b/lkrt/src/host.rs index a6b5945e..1644799e 100644 --- a/lkrt/src/host.rs +++ b/lkrt/src/host.rs @@ -1,7 +1,4 @@ -use crate::{ - abi::{aborting, c_str, owned_c_string, status, write_out}, - state::with_runtime, -}; +use crate::abi::{c_str, owned_c_string, raising, status, write_out}; use core::ffi::c_char; use std::{ fs, @@ -35,7 +32,7 @@ pub extern "C" fn lkrt_env_get(key: *const c_char, out: *mut *mut c_char) -> i64 #[unsafe(no_mangle)] pub extern "C" fn lkrt_env_get_or(key: *const c_char, default: *const c_char) -> *mut c_char { - aborting(|| { + raising(|| { let key = c_str(key, "env.get_or key")?; let default = c_str(default, "env.get_or default")?; let value = { @@ -48,7 +45,7 @@ pub extern "C" fn lkrt_env_get_or(key: *const c_char, default: *const c_char) -> #[unsafe(no_mangle)] pub extern "C" fn lkrt_env_has(key: *const c_char) -> i64 { - aborting(|| { + raising(|| { let key = c_str(key, "env.has key")?; let _env = env_lock(); Ok(i64::from(std::env::var_os(key.as_str()).is_some())) @@ -56,112 +53,291 @@ pub extern "C" fn lkrt_env_has(key: *const c_char) -> i64 { } #[unsafe(no_mangle)] -pub extern "C" fn lkrt_env_set(key: *const c_char, value: *const c_char) -> i64 { - status(|| { - let key = c_str(key, "env.set key")?; - let value = c_str(value, "env.set value")?; - let _env = env_lock(); - // SAFETY: Rust 2024 requires process environment reads and writes to - // be serialized. Every lkrt env accessor takes this process-wide mutex - // before touching std::env, including reads and mutations. - unsafe { - std::env::set_var(key, value); - } - Ok(()) +pub extern "C" fn lkrt_fs_exists(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.exists path")?; + Ok(i64::from(Path::new(path.as_str()).exists())) }) } +/// `fs.is_file(path)` / `fs.is_dir(path)` — `Path::is_file`/`is_dir`, which +/// answer false rather than raising for a path that does not exist. +/// `fs.metadata(path)` — the stdlib module's four-key map, built through the +/// VM's own two-stage construction so it *iterates* the same way. +/// +/// The keys go in in the module's order (`len`, `is_file`, `is_dir`, +/// `readonly`) and `str_dyn_map_mirrored` replays the same rehash the VM does, +/// because a map's iteration order is what `println` prints. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_metadata_map(path: *const c_char) -> *mut core::ffi::c_void { + raising(|| { + let path = c_str(path, "fs.metadata path")?; + let meta = fs::metadata(path.as_str()).map_err(|err| format!("failed to stat '{path}': {err}"))?; + let pairs = alloc::vec![ + ( + alloc::string::String::from("len"), + crate::lkdyn::lkrt_dyn_from_i64(meta.len() as i64) + ), + ( + alloc::string::String::from("is_file"), + crate::lkdyn::lkrt_dyn_from_bool(i64::from(meta.is_file())) + ), + ( + alloc::string::String::from("is_dir"), + crate::lkdyn::lkrt_dyn_from_bool(i64::from(meta.is_dir())) + ), + ( + alloc::string::String::from("readonly"), + crate::lkdyn::lkrt_dyn_from_bool(i64::from(meta.permissions().readonly())) + ), + ]; + Ok(crate::vm_mirror::str_dyn_map_mirrored(pairs)) + }) +} + +/// `env.vars()` — every environment variable, in `std::env::vars_os` order, +/// through the same mirrored construction. +/// +/// Lossy conversion on both sides: the stdlib module calls `to_string_lossy` on +/// key and value, so a non-UTF-8 variable is U+FFFD there and here. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_env_remove(key: *const c_char) -> i64 { - status(|| { - let key = c_str(key, "env.remove key")?; - let _env = env_lock(); - // SAFETY: See lkrt_env_set; all lkrt std::env access is serialized. - unsafe { - std::env::remove_var(key); +pub extern "C" fn lkrt_env_vars_map() -> *mut core::ffi::c_void { + raising(|| { + let mut pairs = Vec::new(); + { + let _env = env_lock(); + for (key, value) in std::env::vars_os() { + let key = key.to_string_lossy().into_owned(); + let value = value.to_string_lossy().into_owned(); + let value = owned_c_string(value)?; + pairs.push((key, crate::lkdyn::lkrt_dyn_from_str(value))); + } } - Ok(()) + Ok(crate::vm_mirror::str_dyn_map_mirrored(pairs)) }) } #[unsafe(no_mangle)] -pub extern "C" fn lkrt_fs_exists(path: *const c_char) -> i64 { - aborting(|| { - let path = c_str(path, "fs.exists path")?; - Ok(i64::from(Path::new(path.as_str()).exists())) +pub extern "C" fn lkrt_fs_is_file(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.is_file path")?; + Ok(i64::from(Path::new(path.as_str()).is_file())) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_is_dir(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.is_dir path")?; + Ok(i64::from(Path::new(path.as_str()).is_dir())) + }) +} + +/// `fs.append(path, text)` — creates the file if it is absent, like the stdlib +/// module's `OpenOptions::new().create(true).append(true)`. +/// +/// Two error messages, not one: the stdlib distinguishes failing to *open* from +/// failing to *write*, and both are program-visible text. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_append_str(path: *const c_char, data: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.append path")?; + let data = c_str(data, "fs.append data")?; + append_bytes(path.as_str(), data.as_bytes()) + }) +} + +/// `fs.append(path, bytes)`. +/// +/// # Safety +/// `data` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_fs_append_bytes(path: *const c_char, data: *mut core::ffi::c_void) -> i64 { + raising(|| { + let path = c_str(path, "fs.append path")?; + let data = crate::lkbytes::bytes_slice(data).to_vec(); + append_bytes(path.as_str(), &data) + }) +} + +fn append_bytes(path: &str, data: &[u8]) -> Result { + use std::io::Write as _; + let mut file = fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + .map_err(|err| format!("failed to open file '{path}': {err}"))?; + file.write_all(data) + .map_err(|err| format!("failed to append file '{path}': {err}"))?; + Ok(1) +} + +/// `fs.create_dir(path)` — one level, and `fs.create_dir_all(path)`, the whole +/// chain. Both report with the stdlib module's single wording. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_create_dir(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.create_dir path")?; + fs::create_dir(path.as_str()).map_err(|err| format!("failed to create directory '{path}': {err}"))?; + Ok(1) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_create_dir_all(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.create_dir_all path")?; + fs::create_dir_all(path.as_str()).map_err(|err| format!("failed to create directory '{path}': {err}"))?; + Ok(1) + }) +} + +/// The three `fs.remove_*` members. +/// +/// **A missing path is `false`, not a raise** — the stdlib module's +/// `remove_path` singles out `NotFound` and every other error raises `failed to +/// remove '{path}'`. Writing the raise for all of them would have turned a +/// two-valued answer into a control-flow difference between the back ends. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_remove_file(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.remove_file path")?; + remove_result(path.as_str(), fs::remove_file(path.as_str())) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_remove_dir(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.remove_dir path")?; + remove_result(path.as_str(), fs::remove_dir(path.as_str())) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_remove_dir_all(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "fs.remove_dir_all path")?; + remove_result(path.as_str(), fs::remove_dir_all(path.as_str())) + }) +} + +fn remove_result(path: &str, result: std::io::Result<()>) -> Result { + match result { + Ok(()) => Ok(1), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(0), + Err(err) => Err(format!("failed to remove '{path}': {err}")), + } +} + +/// `fs.rename(from, to)` — the error names *from*, as the stdlib module does. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_rename(from: *const c_char, to: *const c_char) -> i64 { + raising(|| { + let from = c_str(from, "fs.rename from")?; + let to = c_str(to, "fs.rename to")?; + fs::rename(from.as_str(), to.as_str()).map_err(|err| format!("failed to rename '{from}': {err}"))?; + Ok(1) }) } +/// `fs.copy(from, to)` — answers the byte count, not a bool. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_fs_read(path: *const c_char) -> i64 { - aborting(|| { +pub extern "C" fn lkrt_fs_copy(from: *const c_char, to: *const c_char) -> i64 { + raising(|| { + let from = c_str(from, "fs.copy from")?; + let to = c_str(to, "fs.copy to")?; + let copied = fs::copy(from.as_str(), to.as_str()).map_err(|err| format!("failed to copy '{from}': {err}"))?; + Ok(copied as i64) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_fs_read(path: *const c_char) -> *mut core::ffi::c_void { + raising(|| { let path = c_str(path, "fs.read path")?; let data = fs::read(path.as_str()).map_err(|err| format!("fs.read {path}: {err}"))?; - Ok(with_runtime(|rt| rt.insert_bytes(data))) + Ok(crate::lkbytes::bytes_handle(data)) }) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_fs_read_to_string(path: *const c_char) -> *mut c_char { - aborting(|| { + raising(|| { let path = c_str(path, "fs.read_to_string path")?; - let data = fs::read_to_string(path.as_str()).map_err(|err| format!("fs.read_to_string {path}: {err}"))?; + let data = fs::read_to_string(path.as_str()).map_err(|err| format!("failed to read file '{path}': {err}"))?; owned_c_string(data) }) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_fs_write_str(path: *const c_char, data: *const c_char) -> i64 { - aborting(|| { + raising(|| { let path = c_str(path, "fs.write path")?; let data = c_str(data, "fs.write data")?; - fs::write(path.as_str(), data.as_bytes()).map_err(|err| format!("fs.write {path}: {err}"))?; + fs::write(path.as_str(), data.as_bytes()).map_err(|err| format!("failed to write file '{path}': {err}"))?; Ok(1) }) } #[unsafe(no_mangle)] -pub extern "C" fn lkrt_fs_write_bytes(path: *const c_char, data: i64) -> i64 { - aborting(|| { +pub extern "C" fn lkrt_fs_write_bytes(path: *const c_char, data: *mut core::ffi::c_void) -> i64 { + raising(|| { let path = c_str(path, "fs.write path")?; - let data = with_runtime(|rt| rt.take_bytes(data))?; - fs::write(path.as_str(), &data).map_err(|err| format!("fs.write {path}: {err}"))?; + let data = crate::lkbytes::bytes_slice(data).to_vec(); + fs::write(path.as_str(), &data).map_err(|err| format!("failed to write file '{path}': {err}"))?; Ok(1) }) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_fs_metadata_len(path: *const c_char) -> i64 { - aborting(|| fs_metadata_field(path, MetadataField::Len)) + raising(|| fs_metadata_field(path, MetadataField::Len)) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_fs_metadata_is_file(path: *const c_char) -> i64 { - aborting(|| fs_metadata_field(path, MetadataField::IsFile)) + raising(|| fs_metadata_field(path, MetadataField::IsFile)) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_fs_metadata_is_dir(path: *const c_char) -> i64 { - aborting(|| fs_metadata_field(path, MetadataField::IsDir)) + raising(|| fs_metadata_field(path, MetadataField::IsDir)) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_fs_metadata_readonly(path: *const c_char) -> i64 { - aborting(|| fs_metadata_field(path, MetadataField::Readonly)) + raising(|| fs_metadata_field(path, MetadataField::Readonly)) } +/// `fs.canonicalize(path)` — the resolved path, or **nil** when it is not +/// UTF-8. +/// +/// The nil is the language's answer (`returns = String?`), not a convenience: +/// canonicalizing follows symlinks, and a Linux path component is arbitrary +/// bytes, so a resolved path that no LK string can hold is reachable. This used +/// to hand back `to_string_lossy`, which invents U+FFFD where the VM answers +/// nil — a different value, not a different rendering. +/// +/// (`fs.temp_dir` is also `String?` and stays a plain string: its path comes +/// from the OS's own temp-directory setting, so the same nil is not reachable +/// through it in the way a user-supplied path is.) #[unsafe(no_mangle)] -pub extern "C" fn lkrt_fs_canonicalize(path: *const c_char) -> *mut c_char { - aborting(|| { +pub extern "C" fn lkrt_fs_canonicalize(path: *const c_char) -> crate::lkdyn::LkDyn { + raising(|| { let path = c_str(path, "fs.canonicalize path")?; - let path = fs::canonicalize(path.as_str()).map_err(|err| format!("fs.canonicalize {path}: {err}"))?; - owned_c_string(path.to_string_lossy()) + let resolved = + fs::canonicalize(path.as_str()).map_err(|err| format!("failed to canonicalize '{path}': {err}"))?; + Ok(match resolved.into_os_string().into_string() { + Ok(text) => crate::lkdyn::lkrt_dyn_from_str(owned_c_string(text)?), + Err(_) => crate::lkdyn::lkrt_dyn_from_nil(), + }) }) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_fs_temp_dir() -> *mut c_char { - aborting(|| owned_c_string(std::env::temp_dir().to_string_lossy())) + raising(|| owned_c_string(std::env::temp_dir().to_string_lossy())) } #[unsafe(no_mangle)] @@ -171,7 +347,7 @@ pub extern "C" fn lkrt_path_temp_dir() -> *mut c_char { #[unsafe(no_mangle)] pub extern "C" fn lkrt_process_cwd() -> *mut c_char { - aborting(|| { + raising(|| { let cwd = std::env::current_dir().map_err(|err| format!("process.cwd failed: {err}"))?; owned_c_string(cwd.to_string_lossy()) }) @@ -181,7 +357,7 @@ pub extern "C" fn lkrt_process_cwd() -> *mut c_char { /// stdlib os module's exact fallback chain. #[unsafe(no_mangle)] pub extern "C" fn lkrt_os_hostname() -> *mut c_char { - aborting(|| { + raising(|| { let hostname = std::env::var_os("HOSTNAME") .or_else(|| std::env::var_os("COMPUTERNAME")) .and_then(|value| value.into_string().ok()) @@ -194,13 +370,13 @@ pub extern "C" fn lkrt_os_hostname() -> *mut c_char { /// for the same target the interpreter runs on). #[unsafe(no_mangle)] pub extern "C" fn lkrt_os_arch() -> *mut c_char { - aborting(|| owned_c_string(std::env::consts::ARCH)) + raising(|| owned_c_string(std::env::consts::ARCH)) } /// `os.os()` — `std::env::consts::OS`. #[unsafe(no_mangle)] pub extern "C" fn lkrt_os_name() -> *mut c_char { - aborting(|| owned_c_string(std::env::consts::OS)) + raising(|| owned_c_string(std::env::consts::OS)) } /// `fs.read_dir(path)` — the sorted list of entry *names* (UTF-8 names only, @@ -212,7 +388,7 @@ pub extern "C" fn lkrt_os_name() -> *mut c_char { /// is a caller bug and aborts with a loud `lkrt error`). #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_fs_read_dir_list(path: *const c_char) -> *mut core::ffi::c_void { - aborting(|| { + raising(|| { let path = c_str(path, "fs.read_dir path")?; let mut names = Vec::new(); for entry in fs::read_dir(path.as_str()).map_err(|err| format!("failed to read directory '{path}': {err}"))? { @@ -251,12 +427,17 @@ pub extern "C" fn lkrt_math_round(value: f64) -> i64 { } /// `math.sqrt(Number)` — the stdlib module rejects negative arguments loudly, -/// so the guard aborts (matching the VM's fatal error), never returns NaN. +/// so the guard raises (matching the VM's error), never returns NaN. +/// +/// The message goes to `raise_str`, not to stderr. It used to do both, the wrong +/// way round: the real reason was printed and `"runtime error"` was raised, so +/// `try { math.sqrt(-1.0) } catch e { e }` evaluated to `"runtime error"` +/// compiled and to `"sqrt() argument must be non-negative"` interpreted — and a +/// caught error's text *is* the program's output. #[unsafe(no_mangle)] pub extern "C" fn lkrt_math_sqrt(value: f64) -> f64 { if value < 0.0 { - eprintln!("lkrt error: sqrt() argument must be non-negative"); - crate::panic::raise_str("runtime error"); + crate::panic::raise_str("sqrt() argument must be non-negative"); } value.sqrt() } @@ -273,6 +454,89 @@ pub extern "C" fn lkrt_math_cos(value: f64) -> f64 { value.cos() } +/// `math.tan(Number)` → Float. +/// +/// `sin` and `cos` were native and `tan` was not — the same class of function, +/// split for no reason a program can see. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_tan(value: f64) -> f64 { + value.tan() +} + +/// The domain guards the stdlib module states, raising *its* words. +macro_rules! math_domain { + ($name:ident, $call:ident, $ok:expr, $message:literal, $doc:literal) => { + #[doc = $doc] + #[unsafe(no_mangle)] + pub extern "C" fn $name(value: f64) -> f64 { + let ok: fn(f64) -> bool = $ok; + if !ok(value) { + crate::panic::raise_str($message); + } + value.$call() + } + }; +} + +math_domain!( + lkrt_math_asin, + asin, + |v| (-1.0..=1.0).contains(&v), + "asin() argument must be between -1 and 1", + "`math.asin(Number)` → Float; outside `-1..=1` is the module's loud error." +); +math_domain!( + lkrt_math_acos, + acos, + |v| (-1.0..=1.0).contains(&v), + "acos() argument must be between -1 and 1", + "`math.acos(Number)` → Float; outside `-1..=1` is the module's loud error." +); +math_domain!( + lkrt_math_log, + ln, + |v| v > 0.0, + "log() argument must be positive", + "`math.log(Number)` → natural log; a non-positive argument raises." +); +math_domain!( + lkrt_math_log10, + log10, + |v| v > 0.0, + "log10() argument must be positive", + "`math.log10(Number)` → Float; a non-positive argument raises." +); +math_domain!( + lkrt_math_log2, + log2, + |v| v > 0.0, + "log2() argument must be positive", + "`math.log2(Number)` → Float; a non-positive argument raises." +); + +/// `math.atan(Number)` → Float. Total, so no guard. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_atan(value: f64) -> f64 { + value.atan() +} + +/// `math.atan2(y, x)` → Float. Total, including `atan2(0, 0)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_atan2(y: f64, x: f64) -> f64 { + y.atan2(x) +} + +/// `math.clamp(value, low, high)` on `Int` — the module's only arity. +/// +/// The module rejects an inverted range loudly rather than picking a side. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_clamp_i64(value: i64, low: i64, high: i64) -> i64 { + if low > high { + crate::panic::raise_str("clamp() requires 'min' to be less than or equal to 'max'"); + } + value.clamp(low, high) +} + /// `math.exp(Number)` → Float. #[unsafe(no_mangle)] pub extern "C" fn lkrt_math_exp(value: f64) -> f64 { @@ -297,6 +561,54 @@ pub extern "C" fn lkrt_math_cbrt(x: f64) -> f64 { x.cbrt() } +/// `math.sinh(Number)` → Float. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_sinh(value: f64) -> f64 { + value.sinh() +} + +/// `math.cosh(Number)` → Float. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_cosh(value: f64) -> f64 { + value.cosh() +} + +/// `math.tanh(Number)` → Float. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_tanh(value: f64) -> f64 { + value.tanh() +} + +/// `math.trunc(Float)` → Float. The module's Int arm hands the Int back +/// unchanged, so only the Float half is a call. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_trunc_f64(value: f64) -> f64 { + value.trunc() +} + +/// `math.fract(Float)` → Float. The Int arm answers `0.0` without a call. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_fract_f64(value: f64) -> f64 { + value.fract() +} + +/// `math.to_int(Float)` → Int, the module's `value as i64`. +/// +/// A call rather than a MIR cast because Rust's `as` saturates and Cranelift's +/// `fcvt_to_sint` traps: `math.to_int(1e30)` is `i64::MAX` in the VM and would +/// have aborted the native build. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_to_int_f64(value: f64) -> i64 { + value as i64 +} + +/// `math.is_inf(x)` → 0/1. Only a Float is ever infinite in the VM; an Int +/// argument promotes to a finite `f64` and answers false, same as the module. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_math_is_inf(x: f64) -> i64 { + i64::from(x.is_infinite()) +} + /// `math.is_nan(x)` → 0/1 (only a Float NaN is true in the VM; the lowering /// promotes Int args, whose result is always false — same as the module). #[unsafe(no_mangle)] @@ -325,20 +637,150 @@ pub extern "C" fn lkrt_math_sign_f64(v: f64) -> f64 { /// `path.sep()` — the platform's main separator (the stdlib module's /// `MAIN_SEPARATOR_STR`). +/// The `path` module's `String?` parts, each one `std::path`'s own answer. +/// +/// The stdlib module calls exactly these `Path` methods, so sharing the +/// *underlying crate* is what keeps the two ends identical — the same discipline +/// the base64/hex and datetime helpers here follow, rather than a second +/// implementation of the rule. +macro_rules! path_part { + ($name:ident, $call:ident, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `path` must be a valid C string. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(path: *const c_char) -> crate::lkdyn::LkDyn { + // SAFETY: caller guarantees a valid C string. + let text = unsafe { core::ffi::CStr::from_ptr(path) }.to_str().unwrap_or(""); + match std::path::Path::new(text).$call() { + Some(part) => { + let owned = crate::lkstr::arena_c_string( + alloc::ffi::CString::new(part.to_string_lossy().as_ref()).unwrap_or_default(), + ); + crate::lkdyn::lkrt_dyn_from_str(owned) + } + None => crate::lkdyn::LkDyn::NIL, + } + } + }; +} + +/// `path.normalize(p)` → String — the module's component walk, verbatim. +/// +/// `..` cancels only a *named* component, so `normalize("../..")` keeps both; +/// above a root it means nothing and is dropped, because `/..` is `/` on every +/// filesystem and keeping it produces a path that normalizes to itself forever. +/// +/// # Safety +/// `path` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_path_normalize(path: *const c_char) -> *mut c_char { + use std::path::Component; + // SAFETY: the caller guarantees a valid C string. + let text = unsafe { core::ffi::CStr::from_ptr(path) }.to_str().unwrap_or(""); + let path = std::path::Path::new(text); + let rooted = path.has_root(); + let mut out = std::path::PathBuf::new(); + for component in path.components() { + match component { + Component::CurDir => {} + Component::ParentDir => { + if matches!(out.components().next_back(), Some(Component::Normal(_))) { + out.pop(); + } else if !rooted { + out.push(component.as_os_str()); + } + } + other => out.push(other.as_os_str()), + } + } + crate::lkstr::arena_c_string(alloc::ffi::CString::new(out.to_string_lossy().as_ref()).unwrap_or_default()) +} + +path_part!(lkrt_path_parent, parent, "`path.parent(p)` → String?"); +path_part!(lkrt_path_file_name, file_name, "`path.file_name(p)` → String?"); +path_part!(lkrt_path_file_stem, file_stem, "`path.file_stem(p)` → String?"); +path_part!(lkrt_path_extension, extension, "`path.extension(p)` → String?"); + +/// `path.with_extension(p, ext)` → String. +/// +/// # Safety +/// Both arguments must be valid C strings. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_path_with_extension(path: *const c_char, ext: *const c_char) -> *mut c_char { + // SAFETY: caller guarantees valid C strings. + let (text, ext) = unsafe { + ( + core::ffi::CStr::from_ptr(path).to_str().unwrap_or(""), + core::ffi::CStr::from_ptr(ext).to_str().unwrap_or(""), + ) + }; + let joined = std::path::Path::new(text).with_extension(ext); + crate::lkstr::arena_c_string(alloc::ffi::CString::new(joined.to_string_lossy().as_ref()).unwrap_or_default()) +} + +/// `path.is_absolute(p)` → Bool, as 1/0. +/// +/// # Safety +/// `path` must be a valid C string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_path_is_absolute(path: *const c_char) -> i64 { + // SAFETY: caller guarantees a valid C string. + let text = unsafe { core::ffi::CStr::from_ptr(path) }.to_str().unwrap_or(""); + i64::from(std::path::Path::new(text).is_absolute()) +} + +/// `path.components(p)` → List, the same `Path::components` walk. +/// +/// # Safety +/// `path` must be a valid C string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_path_components(path: *const c_char) -> *mut core::ffi::c_void { + // SAFETY: caller guarantees a valid C string. + let text = unsafe { core::ffi::CStr::from_ptr(path) }.to_str().unwrap_or(""); + let parts: Vec<*const c_char> = std::path::Path::new(text) + .components() + .map(|component| { + crate::lkstr::arena_c_string( + alloc::ffi::CString::new(component.as_os_str().to_string_lossy().as_ref()).unwrap_or_default(), + ) + .cast_const() + }) + .collect(); + crate::state::arena_handle(parts) +} + +/// The stdlib `path` module's `delimiter`: the character that separates +/// *entries* in a `PATH`-style variable, as opposed to `sep`, which separates +/// components within one path. +/// +/// `std::path` names only the latter (`MAIN_SEPARATOR_STR`), so the platform +/// answer is written out here — the same two-line `cfg!(windows)` the stdlib +/// module has. That is a rule in two places, which is why the table test in +/// `aot/lower/src/tables.rs` names it: it exists so the pair cannot silently +/// disagree the day one of them learns a third platform. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_path_delimiter() -> *mut c_char { + let delimiter = if cfg!(windows) { ";" } else { ":" }; + crate::lkstr::arena_c_string(alloc::ffi::CString::new(delimiter).unwrap_or_default()) +} + #[unsafe(no_mangle)] pub extern "C" fn lkrt_path_sep() -> *mut c_char { crate::lkstr::arena_c_string(alloc::ffi::CString::new(std::path::MAIN_SEPARATOR_STR).unwrap_or_default()) } -/// The stdlib datetime module's `utc_datetime`: aborts on an out-of-range -/// timestamp (the VM's loud `invalid timestamp` error). -fn datetime_utc(timestamp: i64, context: &str) -> chrono::DateTime { +/// The stdlib datetime module's `utc_datetime`: raises on an out-of-range +/// timestamp, with the module's own words. +/// +/// `invalid timestamp` and nothing else — the same reason the caught value has +/// to be the message: this used to print `lkrt error: {context}: invalid +/// timestamp` and raise `"runtime error"`, so a catching program saw neither the +/// reason nor the same text the VM gives it. +fn datetime_utc(timestamp: i64) -> chrono::DateTime { match chrono::DateTime::::from_timestamp(timestamp, 0) { Some(dt) => dt, - None => { - eprintln!("lkrt error: {context}: invalid timestamp"); - crate::panic::raise_str("runtime error"); - } + None => crate::panic::raise_str("invalid timestamp"), } } @@ -355,36 +797,59 @@ pub extern "C" fn lkrt_datetime_now() -> i64 { /// `format` must be a valid NUL-terminated C string, or null (empty). #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_datetime_format(timestamp: i64, format: *const c_char) -> *mut c_char { - aborting(|| { + raising(|| { let format = c_str(format, "datetime.format format")?; - let formatted = datetime_utc(timestamp, "datetime.format") - .format(format.as_str()) - .to_string(); + let formatted = datetime_utc(timestamp).format(format.as_str()).to_string(); owned_c_string(formatted) }) } -/// `datetime.parse(value, format)` — chrono naive parse anchored to UTC; -/// a parse failure aborts (the VM's loud error). +/// `datetime.parse(value, format)` — anchored to UTC; a parse failure aborts +/// (the VM's loud error). +/// +/// The three shapes `format` can write, tried in order: a full datetime, a +/// date alone (midnight, which is what the format dropped), a time alone (that +/// time on the epoch day). Only the first was here, so +/// `datetime.parse("2026-08-20", "%Y-%m-%d")` answered interpreted and failed +/// compiled — the stdlib module has said what the rule is above `parse_naive` +/// the whole time. +/// +/// The refusal names the value and the format, for the reason written there: +/// chrono's own phrasing describes its parser's internal requirement ("input +/// is not enough for unique date and time"), a sentence about a library the +/// program never mentioned. /// /// # Safety /// Both pointers must be valid NUL-terminated C strings, or null (empty). #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_datetime_parse(value: *const c_char, format: *const c_char) -> i64 { - aborting(|| { + raising(|| { let value = c_str(value, "datetime.parse value")?; let format = c_str(format, "datetime.parse format")?; - let naive = chrono::NaiveDateTime::parse_from_str(value.as_str(), format.as_str()) - .map_err(|err| format!("failed to parse datetime: {err}"))?; + let naive = parse_naive(value.as_str(), format.as_str()) + .ok_or_else(|| format!("`{value}` does not match the format `{format}`"))?; Ok(chrono::DateTime::::from_naive_utc_and_offset(naive, chrono::Utc).timestamp()) }) } +/// `stdlib/crates/datetime`'s `parse_naive`, mirrored. +fn parse_naive(value: &str, format: &str) -> Option { + if let Ok(naive) = chrono::NaiveDateTime::parse_from_str(value, format) { + return Some(naive); + } + if let Ok(date) = chrono::NaiveDate::parse_from_str(value, format) { + return Some(date.and_time(chrono::NaiveTime::MIN)); + } + let time = chrono::NaiveTime::parse_from_str(value, format).ok()?; + let epoch = chrono::DateTime::::from_timestamp(0, 0)?.date_naive(); + Some(epoch.and_time(time)) +} + /// `datetime.day_of_week(timestamp)` — the stdlib module's mapping (Sun = 0). #[unsafe(no_mangle)] pub extern "C" fn lkrt_datetime_day_of_week(timestamp: i64) -> i64 { use chrono::Datelike; - match datetime_utc(timestamp, "datetime.day_of_week").weekday() { + match datetime_utc(timestamp).weekday() { chrono::Weekday::Sun => 0, chrono::Weekday::Mon => 1, chrono::Weekday::Tue => 2, @@ -399,7 +864,7 @@ pub extern "C" fn lkrt_datetime_day_of_week(timestamp: i64) -> i64 { #[unsafe(no_mangle)] pub extern "C" fn lkrt_datetime_day_of_year(timestamp: i64) -> i64 { use chrono::Datelike; - i64::from(datetime_utc(timestamp, "datetime.day_of_year").ordinal()) + i64::from(datetime_utc(timestamp).ordinal()) } /// `datetime.is_weekend(timestamp)` — 1 for Sat/Sun, else 0 (the lowering @@ -408,7 +873,7 @@ pub extern "C" fn lkrt_datetime_day_of_year(timestamp: i64) -> i64 { pub extern "C" fn lkrt_datetime_is_weekend(timestamp: i64) -> i64 { use chrono::Datelike; i64::from(matches!( - datetime_utc(timestamp, "datetime.is_weekend").weekday(), + datetime_utc(timestamp).weekday(), chrono::Weekday::Sat | chrono::Weekday::Sun )) } @@ -424,6 +889,17 @@ pub extern "C" fn lkrt_os_epoch() -> i64 { epoch_millis() } +/// `os.time()` — Unix time in **seconds**, where `os.epoch()` is milliseconds. +/// Truncating the millisecond count would answer one second early for a +/// negative time, so this asks for seconds directly, as the module does. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_os_time() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64 +} + #[unsafe(no_mangle)] pub extern "C" fn lkrt_time_now_ms() -> i64 { epoch_millis() @@ -431,9 +907,13 @@ pub extern "C" fn lkrt_time_now_ms() -> i64 { #[unsafe(no_mangle)] pub extern "C" fn lkrt_time_sleep_ms(ms: i64) { - aborting(|| { + raising(|| { if ms < 0 { - return Err(format!("time.sleep expects non-negative milliseconds, got {ms}")); + // Same wording as the VM's `duration_millis`, because a caught + // error's message *is* the program's output. + return Err(format!( + "time.sleep() expects a non-negative duration in milliseconds, got {ms}" + )); } std::thread::sleep(Duration::from_millis(ms as u64)); Ok(()) @@ -470,7 +950,7 @@ fn env_lock() -> MutexGuard<'static, ()> { #[cfg(test)] mod tests { use super::*; - use crate::{lkrt_bytes_free, lkrt_string_free}; + use crate::lkrt_string_free; use alloc::ffi::CString; use core::ffi::CStr; @@ -500,9 +980,13 @@ mod tests { assert_eq!(lkrt_fs_metadata_is_file(file.as_ptr()), 1); assert_eq!(lkrt_fs_metadata_is_dir(file.as_ptr()), 0); + // A `Bytes` **value**: read its length, then its text, then its length + // again — the one-shot host handle this used to be could only be read + // once. let bytes = lkrt_fs_read(file.as_ptr()); - assert!(bytes > 0); - let text_ptr = crate::lkrt_bytes_to_string_utf8(bytes); + assert!(!bytes.is_null()); + assert_eq!(unsafe { crate::lkrt_lkbytes_len(bytes) }, 5); + let text_ptr = unsafe { crate::lkrt_lkbytes_utf8(bytes) }; assert!(!text_ptr.is_null()); // SAFETY: text_ptr is an lkrt-owned NUL-terminated CString pointer. let text = unsafe { CStr::from_ptr(text_ptr) }; @@ -512,11 +996,13 @@ mod tests { // provenance, so handing it to `CString::from_raw` is UB (caught by // Miri's Stacked Borrows checking). unsafe { lkrt_string_free(text_ptr) }; - assert_eq!(lkrt_bytes_free(bytes), 0); + assert_eq!(unsafe { crate::lkrt_lkbytes_len(bytes) }, 5); + // `canonicalize` answers `String?`, so the result is boxed: a resolved + // path that is not UTF-8 is nil, which is the VM's answer too. let canonical = lkrt_fs_canonicalize(file.as_ptr()); - assert!(!canonical.is_null()); - // SAFETY: the pointer came from an lkrt owned-string return. - unsafe { lkrt_string_free(canonical) }; + assert_eq!(canonical.tag, crate::lkdyn::DYN_STR); + // SAFETY: the payload came from an lkrt owned-string return. + unsafe { lkrt_string_free(canonical.payload as *mut c_char) }; } } diff --git a/lkrt/src/io.rs b/lkrt/src/io.rs index 5a8713a5..c5c23796 100644 --- a/lkrt/src/io.rs +++ b/lkrt/src/io.rs @@ -1,4 +1,4 @@ -use crate::abi::{aborting, c_str, owned_c_string}; +use crate::abi::{c_str, owned_c_string, raising}; use std::{ ffi::c_char, io::{Read, Write}, @@ -8,7 +8,7 @@ const MAX_STDIN_READ_BYTES: u64 = 1024 * 1024; #[unsafe(no_mangle)] pub extern "C" fn lkrt_io_std_write(resource: i64, data: *const c_char, newline: i64) -> i64 { - aborting(|| { + raising(|| { let data = c_str(data, "io.std.write data")?; match resource { 1 => write_std_stream(std::io::stdout().lock(), data.as_bytes(), newline != 0, "stdout"), @@ -20,7 +20,7 @@ pub extern "C" fn lkrt_io_std_write(resource: i64, data: *const c_char, newline: #[unsafe(no_mangle)] pub extern "C" fn lkrt_io_std_flush(resource: i64) -> i64 { - aborting(|| match resource { + raising(|| match resource { 0 => Err("io.std.flush unsupported for stdin".to_string()), 1 => std::io::stdout() .flush() @@ -36,7 +36,7 @@ pub extern "C" fn lkrt_io_std_flush(resource: i64) -> i64 { #[unsafe(no_mangle)] pub extern "C" fn lkrt_io_std_read_to_string(resource: i64) -> *mut c_char { - aborting(|| { + raising(|| { if resource != 0 { return Err(format!("io.std.read_to_string expects stdin handle, got {resource}")); } diff --git a/lkrt/src/io_bare.rs b/lkrt/src/io_bare.rs index fa54ba3e..c0a1e98f 100644 --- a/lkrt/src/io_bare.rs +++ b/lkrt/src/io_bare.rs @@ -29,7 +29,7 @@ pub fn set_output(sink: OutputSink) { /// /// `data` must be a valid NUL-terminated C string, as codegen guarantees. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_io_std_write(_resource: i64, data: *const c_char, newline: i64) -> i64 { +pub unsafe extern "C" fn lkrt_io_std_write(_resource: i64, data: *const c_char, newline: i64) -> i64 { // The sink is copied out and the guard dropped before calling it: a sink // that itself logs would otherwise deadlock on a spin mutex. let Some(sink) = *OUTPUT.lock() else { diff --git a/lkrt/src/isr.rs b/lkrt/src/isr.rs new file mode 100644 index 00000000..ab3a99d3 --- /dev/null +++ b/lkrt/src/isr.rs @@ -0,0 +1,230 @@ +//! Interrupt entry, once, for any vector. +//! +//! An interrupt is not a call. The code it lands in never agreed to lose its +//! caller-saved registers, so a compiled handler cannot be the thing a gate +//! points at — something has to spill them first and leave with `iretq`. That +//! something is assembly in every language, which is why it is here rather than +//! in LK. +//! +//! What *was* per-kernel is that every vector needed its own hand-written stub +//! in the board's Rust: adding a device meant editing a file the driver has +//! nothing to do with. So this is 256 stubs and a table. A kernel points a gate +//! at `lkrt_isr_stubs + vector * stride` and writes its handler's address into +//! `lkrt_isr_handlers[vector]`, and both of those are things LK can say — +//! `symbol_address` and a volatile store. +//! +//! What is deliberately *not* here: the two shapes that are not ordinary device +//! interrupts. A timer that switches tasks has to return on a *different* stack, +//! and a syscall has to return a value in `rax`; both need their own tail, and +//! both belong to the kernel that defines them. + +/// Where each vector's handler is, or 0. +/// +/// Written by the kernel, read by the tail below. A `u64` per vector rather +/// than a function pointer type, because what a kernel installs here is the +/// result of `symbol_address` — an integer, on the LK side. +/// +/// Zero means nothing is installed, and the tail checks: a vector that arrives +/// with no handler is a spurious interrupt, and answering it with a call to +/// address zero turns a diagnosable event into a fault inside a fault. +#[cfg(all(not(feature = "std"), target_arch = "x86_64"))] +#[unsafe(no_mangle)] +pub static mut lkrt_isr_handlers: [u64; 256] = [0; 256]; + +// The stubs, and the tail they share. +// +// Each stub exists only to say which vector it is — the CPU does not tell the +// handler — and then join the common path. `push imm32` rather than `push imm8` +// because the immediate is sign-extended: vector 200 pushed as a byte arrives +// as -56, which is the kind of mistake that shows up only on the vectors nobody +// tested. +// +// The register discipline is the interesting part. Every caller-saved integer +// register, and all sixteen XMM registers, because a compiled LK handler may +// use any of them and LK numbers are `f64`. Missing one corrupts a value in the +// interrupted program rather than crashing, at a moment nothing can predict. +// +// Alignment: the CPU aligns RSP to 16 before pushing its own frame, and the +// stub's vector push plus nine register pushes plus 256 bytes of XMM area come +// to 336 — the same total the hand-written trampolines reach with nine pushes +// and 264. `call` gets the alignment it expects because the arithmetic works +// out, not because it happens to. +#[cfg(all(not(feature = "std"), target_arch = "x86_64"))] +core::arch::global_asm!( + ".section .text, \"ax\"", + ".align 16", + ".globl lkrt_isr_stubs", + "lkrt_isr_stubs:", + ".set vector, 0", + ".rept 256", + " .align 16", + " .byte 0x68", // push imm32 + " .long vector", + " jmp __lkrt_isr_common", + " .set vector, vector + 1", + ".endr", + // Past the padding of the last stub, not past its last instruction: a + // caller divides `end - start` by 256 to get the stride, and without this + // the array ends ten bytes into its final slot. + ".align 16", + ".globl lkrt_isr_stubs_end", + "lkrt_isr_stubs_end:", + "__lkrt_isr_common:", + " push rax", + " push rcx", + " push rdx", + " push rsi", + " push rdi", + " push r8", + " push r9", + " push r10", + " push r11", + " sub rsp, 256", + " movups [rsp + 0], xmm0", + " movups [rsp + 16], xmm1", + " movups [rsp + 32], xmm2", + " movups [rsp + 48], xmm3", + " movups [rsp + 64], xmm4", + " movups [rsp + 80], xmm5", + " movups [rsp + 96], xmm6", + " movups [rsp + 112], xmm7", + " movups [rsp + 128], xmm8", + " movups [rsp + 144], xmm9", + " movups [rsp + 160], xmm10", + " movups [rsp + 176], xmm11", + " movups [rsp + 192], xmm12", + " movups [rsp + 208], xmm13", + " movups [rsp + 224], xmm14", + " movups [rsp + 240], xmm15", + // The vector the stub pushed, under the XMM area and the nine registers. + " mov rdi, [rsp + 328]", + " lea rax, [rip + lkrt_isr_handlers]", + " mov rax, [rax + rdi * 8]", + " test rax, rax", + " jz 2f", + // The handler is called with its own vector, so one LK function can serve + // several gates and still know which one arrived. + " call rax", + "2:", + " movups xmm0, [rsp + 0]", + " movups xmm1, [rsp + 16]", + " movups xmm2, [rsp + 32]", + " movups xmm3, [rsp + 48]", + " movups xmm4, [rsp + 64]", + " movups xmm5, [rsp + 80]", + " movups xmm6, [rsp + 96]", + " movups xmm7, [rsp + 112]", + " movups xmm8, [rsp + 128]", + " movups xmm9, [rsp + 144]", + " movups xmm10, [rsp + 160]", + " movups xmm11, [rsp + 176]", + " movups xmm12, [rsp + 192]", + " movups xmm13, [rsp + 208]", + " movups xmm14, [rsp + 224]", + " movups xmm15, [rsp + 240]", + " add rsp, 256", + " pop r11", + " pop r10", + " pop r9", + " pop r8", + " pop rdi", + " pop rsi", + " pop rdx", + " pop rcx", + " pop rax", + // The vector the stub pushed. + " add rsp, 8", + " iretq", +); + +// ---------------------------------------------------------- raising, not taking +// +// The other direction, and the same obstacle. `int` takes its vector as an +// *immediate*: there is no operand to pass one in through, so a kernel that +// wanted to raise vector `n` for a computed `n` could not say so at all. That is +// why the board's `kernel_yield` was a Rust function containing `int 0x30`, and +// why the vector was written down twice — once where the gate is installed and +// once where it is raised, in two languages, with nothing checking they agree. +// +// Two hundred and fifty-six stubs answer it the same way the entry side does. A +// stub is three bytes; the padding is what makes the stride derivable, and the +// caller divides `end - start` by 256 rather than being told. + +// One stub per vector: `int n` and return. (A plain comment, not a doc comment: +// `global_asm!` is a macro invocation, and a `///` on one documents nothing.) +// +// `.byte 0xcd` then the vector, rather than `int $n`, because the assembler +// will happily encode `int 3` as the one-byte breakpoint `0xcc` — a different +// instruction, on the one vector a debugger is most likely to be watching. +// Writing the opcode out means all 256 slots are the same two instructions. +#[cfg(all(not(feature = "std"), target_arch = "x86_64"))] +core::arch::global_asm!( + ".section .text, \"ax\"", + ".align 8", + ".globl lkrt_int_stubs", + "lkrt_int_stubs:", + ".set intvec, 0", + ".rept 256", + " .align 8", + " .byte 0xcd", + " .byte intvec", + " ret", + " .set intvec, intvec + 1", + ".endr", + // Past the padding of the last stub, not past its last instruction: a caller + // divides `end - start` by 256 to get the stride, and without this the array + // ends three bytes into its final slot. + ".align 8", + ".globl lkrt_int_stubs_end", + "lkrt_int_stubs_end:", +); + +/// Raises `vector`, whatever it is. +/// +/// The call lands in the stub, the stub raises the interrupt, and the handler's +/// `iretq` comes back to the `ret` — so from the caller this is an ordinary +/// function call that happens to have run a gate in the middle. A gate that +/// switches stacks (a task switch) simply does not come back until the caller is +/// resumed, at which point its frame is exactly as it left it. +/// +/// # Safety +/// The vector must have a gate installed. Raising one that does not is a general +/// protection fault, which is the same thing that happens when a device does it. +#[cfg(all(not(feature = "std"), target_arch = "x86_64"))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_cpu_raise_interrupt(vector: i64) { + unsafe extern "C" { + static lkrt_int_stubs: u8; + static lkrt_int_stubs_end: u8; + } + if !(0..256).contains(&vector) { + return; + } + let start = &raw const lkrt_int_stubs as usize; + let stride = ((&raw const lkrt_int_stubs_end as usize) - start) / 256; + let target = start + vector as usize * stride; + // SAFETY: `target` is inside the stub array, which is `int`+`ret` and takes + // no arguments. + let stub: extern "C" fn() = unsafe { core::mem::transmute(target) }; + stub(); +} + +/// Anywhere else, this is refused rather than ignored. +/// +/// Refused, and that is the point: under a process there is no interrupt table, +/// and a runtime that went ahead and raised a real `int 0x80` would be making a +/// Linux system call with whatever happened to be in the registers. Doing +/// nothing would be worse than either — it is the answer that lets a program +/// look like it worked, and it would put the two backends into disagreement, +/// since the interpreter refuses. +/// +/// # Safety +/// Nothing: this build raises before doing anything. The signature is `unsafe` +/// only to match the bare-metal x86-64 one, which really does execute `int`. +#[cfg(not(all(not(feature = "std"), target_arch = "x86_64")))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_cpu_raise_interrupt(_vector: i64) { + crate::panic::raise_str( + "cpu_raise_interrupt requires bare-metal execution on x86-64: no other target has this instruction", + ); +} diff --git a/lkrt/src/lib.rs b/lkrt/src/lib.rs index 5a94c9be..cfc42f48 100644 --- a/lkrt/src/lib.rs +++ b/lkrt/src/lib.rs @@ -37,145 +37,257 @@ macro_rules! rt_eprintln { } mod abi; -#[cfg(test)] +// The schema describes the **host** runtime: an AOT-compiled binary links a +// `std` lkrt. Without `std` this crate deliberately exports a subset (no +// channels, no sockets, no host handles), so asserting that every schema symbol +// exists is only a question with an answer there. +#[cfg(all(test, feature = "std"))] mod abi_conformance_test; mod arith; #[cfg(feature = "std")] mod chan; mod cpu; mod encoding; +mod hash; #[cfg(feature = "std")] mod host; #[cfg(feature = "std")] mod io; #[cfg(not(feature = "std"))] mod io_bare; +mod isr; +mod lkbytes; +#[cfg(feature = "std")] +mod lkclosure; mod lkdyn; mod lklist; mod lkmap; +#[cfg(feature = "std")] +mod lkprocess; +#[cfg(feature = "std")] +mod lkrandom; +#[cfg(feature = "std")] +mod lkregex; mod lkset; +mod lkslice; mod lkstr; -mod mmio; #[cfg(feature = "std")] mod net; mod panic; mod port; mod state; +mod system; +mod textcodec; +#[cfg(feature = "std")] +mod uuid; mod vm_mirror; pub use abi::{ - lkrt_abi_check, lkrt_abi_version, lkrt_abort, lkrt_assert, lkrt_assert_msg, lkrt_cleanup, lkrt_error_clear, - lkrt_last_error, lkrt_panic, lkrt_string_free, + lkrt_abi_version, lkrt_abort, lkrt_assert, lkrt_assert_msg, lkrt_cleanup, lkrt_error_clear, lkrt_last_error, + lkrt_panic, lkrt_rt_begin, lkrt_string_free, }; pub use arith::{ - lkrt_f64_div_checked, lkrt_f64_mod_checked, lkrt_i64_div_checked, lkrt_i64_mod_checked, lkrt_i64_shl_checked, - lkrt_i64_shr_checked, + lkrt_f64_div_checked, lkrt_f64_mod_checked, lkrt_f64_to_machine_int, lkrt_i64_div_checked, lkrt_i64_mod_checked, + lkrt_i64_shl_checked, lkrt_i64_shr_checked, lkrt_u64_div, lkrt_u64_lt, lkrt_u64_rem, lkrt_u64_shr_checked, + lkrt_u64_to_f64, }; #[cfg(feature = "std")] pub use chan::{ - lkrt_chan_close, lkrt_chan_is_closed, lkrt_chan_len, lkrt_chan_new, lkrt_chan_recv, lkrt_chan_select, - lkrt_chan_send, lkrt_chan_try_recv, lkrt_chan_try_send, lkrt_spawn_arg, lkrt_spawn_args_new, lkrt_spawn_args_push, - lkrt_spawn0, lkrt_spawn1, lkrt_spawn2, lkrt_spawn3, lkrt_spawn4, lkrt_task_await, + lkrt_chan_capacity, lkrt_chan_close, lkrt_chan_is_closed, lkrt_chan_len, lkrt_chan_new, lkrt_chan_recv, + lkrt_chan_select, lkrt_chan_send, lkrt_chan_try_recv, lkrt_chan_try_send, lkrt_spawn_arg, lkrt_spawn_args_new, + lkrt_spawn_args_push, lkrt_spawn0, lkrt_spawn1, lkrt_spawn2, lkrt_spawn3, lkrt_spawn4, lkrt_task_await, + lkrt_time_after, lkrt_time_timeout, }; pub use cpu::{ lkrt_cpu_barrier, lkrt_cpu_compiler_barrier, lkrt_cpu_irq_restore, lkrt_cpu_irq_save, lkrt_cpu_timestamp, lkrt_cpu_wait_for_interrupt, }; -pub use encoding::lkrt_json_parse; +// A `cpu_*` intrinsic that lives with the interrupt stubs rather than with the +// rest of them, because it *is* one of them: `isr.rs` holds both directions of +// the same obstacle — a vector that cannot be an operand, answered by a table. +pub use encoding::{lkrt_json_parse, lkrt_json_stringify}; +#[cfg(feature = "std")] +pub use encoding::{lkrt_toml_parse, lkrt_toml_stringify, lkrt_yaml_parse, lkrt_yaml_stringify}; +pub use hash::{ + lkrt_hash_crc32_bytes, lkrt_hash_crc32_str, lkrt_hash_fnv64_bytes, lkrt_hash_fnv64_str, lkrt_hash_sha1_bytes, + lkrt_hash_sha1_str, lkrt_hash_sha256_bytes, lkrt_hash_sha256_str, +}; +pub use isr::lkrt_cpu_raise_interrupt; +pub use lkbytes::{ + lkrt_lkbytes_concat, lkrt_lkbytes_contains, lkrt_lkbytes_count, lkrt_lkbytes_eq, lkrt_lkbytes_from_dyn_list, + lkrt_lkbytes_from_i64_list, lkrt_lkbytes_from_str, lkrt_lkbytes_get, lkrt_lkbytes_index_of, lkrt_lkbytes_is_empty, + lkrt_lkbytes_len, lkrt_lkbytes_max, lkrt_lkbytes_min, lkrt_lkbytes_reverse, lkrt_lkbytes_skip, lkrt_lkbytes_slice, + lkrt_lkbytes_sort, lkrt_lkbytes_sum, lkrt_lkbytes_take, lkrt_lkbytes_to_i64_list, lkrt_lkbytes_to_str, + lkrt_lkbytes_unique, lkrt_lkbytes_utf8, lkrt_lkbytes_utf8_lossy, +}; +#[cfg(feature = "std")] +pub use lkprocess::{ + lkrt_process_exit, lkrt_process_id, lkrt_process_output, lkrt_process_output_noargs, lkrt_process_output_string, + lkrt_process_output_string_noargs, lkrt_process_set_cwd, lkrt_process_status, lkrt_process_status_noargs, +}; +#[cfg(feature = "std")] +pub use lkrandom::{ + lkrt_random_bool, lkrt_random_bool_p, lkrt_random_bytes, lkrt_random_choice_dyn, lkrt_random_choice_f64, + lkrt_random_choice_i64, lkrt_random_choice_str, lkrt_random_float, lkrt_random_int, lkrt_random_shuffle_dyn, + lkrt_random_shuffle_f64, lkrt_random_shuffle_i64, lkrt_random_shuffle_str, +}; #[cfg(feature = "std")] -pub use encoding::{lkrt_toml_parse, lkrt_yaml_parse}; +pub use lkregex::{ + lkrt_regex_captures, lkrt_regex_find, lkrt_regex_find_all, lkrt_regex_is_match, lkrt_regex_replace, + lkrt_regex_split, +}; +pub use textcodec::{ + lkrt_base64_decode, lkrt_base64_encode, lkrt_base64_encode_bytes, lkrt_hex_decode, lkrt_hex_encode, + lkrt_hex_encode_bytes, lkrt_url_decode_component, lkrt_url_encode_component, +}; +#[cfg(feature = "std")] +pub use uuid::{lkrt_uuid_is_valid, lkrt_uuid_parse, lkrt_uuid_v4}; // Re-exported at the crate root because the ABI conformance macro checks // signatures as `crate::$symbol`. #[cfg(feature = "std")] pub use host::{ lkrt_datetime_day_of_week, lkrt_datetime_day_of_year, lkrt_datetime_format, lkrt_datetime_is_weekend, - lkrt_datetime_now, lkrt_datetime_parse, lkrt_env_get, lkrt_env_get_or, lkrt_env_has, lkrt_env_remove, lkrt_env_set, - lkrt_fs_canonicalize, lkrt_fs_exists, lkrt_fs_metadata_is_dir, lkrt_fs_metadata_is_file, lkrt_fs_metadata_len, - lkrt_fs_metadata_readonly, lkrt_fs_read, lkrt_fs_read_dir_list, lkrt_fs_read_to_string, lkrt_fs_temp_dir, - lkrt_fs_write_bytes, lkrt_fs_write_str, lkrt_math_ceil, lkrt_math_cos, lkrt_math_exp, lkrt_math_floor, - lkrt_math_pow, lkrt_math_round, lkrt_math_sin, lkrt_math_sqrt, lkrt_os_arch, lkrt_os_clock, lkrt_os_epoch, - lkrt_os_hostname, lkrt_os_name, lkrt_path_temp_dir, lkrt_process_cwd, lkrt_time_now_ms, lkrt_time_sleep_ms, + lkrt_datetime_now, lkrt_datetime_parse, lkrt_env_get, lkrt_env_get_or, lkrt_env_has, lkrt_env_vars_map, + lkrt_fs_append_bytes, lkrt_fs_append_str, lkrt_fs_canonicalize, lkrt_fs_copy, lkrt_fs_create_dir, + lkrt_fs_create_dir_all, lkrt_fs_exists, lkrt_fs_is_dir, lkrt_fs_is_file, lkrt_fs_metadata_is_dir, + lkrt_fs_metadata_is_file, lkrt_fs_metadata_len, lkrt_fs_metadata_map, lkrt_fs_metadata_readonly, lkrt_fs_read, + lkrt_fs_read_dir_list, lkrt_fs_read_to_string, lkrt_fs_remove_dir, lkrt_fs_remove_dir_all, lkrt_fs_remove_file, + lkrt_fs_rename, lkrt_fs_temp_dir, lkrt_fs_write_bytes, lkrt_fs_write_str, lkrt_math_acos, lkrt_math_asin, + lkrt_math_atan, lkrt_math_atan2, lkrt_math_ceil, lkrt_math_clamp_i64, lkrt_math_cos, lkrt_math_exp, + lkrt_math_floor, lkrt_math_log, lkrt_math_log2, lkrt_math_log10, lkrt_math_pow, lkrt_math_round, lkrt_math_sin, + lkrt_math_sqrt, lkrt_math_tan, lkrt_os_arch, lkrt_os_clock, lkrt_os_epoch, lkrt_os_hostname, lkrt_os_name, + lkrt_os_time, lkrt_path_temp_dir, lkrt_process_cwd, lkrt_time_now_ms, lkrt_time_sleep_ms, }; #[cfg(feature = "std")] pub use host::{ - lkrt_math_cbrt, lkrt_math_hypot, lkrt_math_is_nan, lkrt_math_sign_f64, lkrt_math_sign_i64, lkrt_path_sep, + lkrt_math_cbrt, lkrt_math_cosh, lkrt_math_fract_f64, lkrt_math_hypot, lkrt_math_is_inf, lkrt_math_is_nan, + lkrt_math_sign_f64, lkrt_math_sign_i64, lkrt_math_sinh, lkrt_math_tanh, lkrt_math_to_int_f64, lkrt_math_trunc_f64, + lkrt_path_components, lkrt_path_delimiter, lkrt_path_extension, lkrt_path_file_name, lkrt_path_file_stem, + lkrt_path_is_absolute, lkrt_path_normalize, lkrt_path_parent, lkrt_path_sep, lkrt_path_with_extension, }; #[cfg(feature = "std")] pub use io::{lkrt_io_std_flush, lkrt_io_std_read_to_string, lkrt_io_std_write}; #[cfg(not(feature = "std"))] pub use io_bare::{lkrt_io_std_flush, lkrt_io_std_read_to_string, lkrt_io_std_write, set_output}; +#[cfg(feature = "std")] +pub use lkclosure::{lkrt_closure_arity, lkrt_closure_call, lkrt_closure_call_property, lkrt_closure_new}; +pub use lkdyn::lkrt_dyn_from_typed_map; pub use lkdyn::{ - DYN_BOOL, DYN_F64, DYN_I64, DYN_LIST, DYN_MAP, DYN_NIL, DYN_STR, LkDyn, lkrt_dyn_add, lkrt_dyn_as_bool, - lkrt_dyn_as_f64, lkrt_dyn_as_i64, lkrt_dyn_as_list, lkrt_dyn_as_map, lkrt_dyn_as_str, lkrt_dyn_cast_to_i64, - lkrt_dyn_display, lkrt_dyn_display_quoted, lkrt_dyn_div, lkrt_dyn_eq, lkrt_dyn_field, lkrt_dyn_from_bool, - lkrt_dyn_from_f64, lkrt_dyn_from_i64, lkrt_dyn_from_list, lkrt_dyn_from_map, lkrt_dyn_from_maybe_bool, - lkrt_dyn_from_maybe_f64, lkrt_dyn_from_maybe_i64, lkrt_dyn_from_maybe_str, lkrt_dyn_from_nil, lkrt_dyn_from_str, - lkrt_dyn_ge, lkrt_dyn_get, lkrt_dyn_gt, lkrt_dyn_index, lkrt_dyn_le, lkrt_dyn_len_of, lkrt_dyn_lt, - lkrt_dyn_method_missing, lkrt_dyn_mod, lkrt_dyn_mul, lkrt_dyn_not, lkrt_dyn_obj_type_id, lkrt_dyn_sub, - lkrt_dyn_tag, lkrt_dyn_truthy, lkrt_lklist_dyn_at, lkrt_lklist_dyn_chain, lkrt_lklist_dyn_chunk, - lkrt_lklist_dyn_contains, lkrt_lklist_dyn_display, lkrt_lklist_dyn_enumerate, lkrt_lklist_dyn_eq, - lkrt_lklist_dyn_filter_fn, lkrt_lklist_dyn_flatten, lkrt_lklist_dyn_len, lkrt_lklist_dyn_map_fn, - lkrt_lklist_dyn_new, lkrt_lklist_dyn_push, lkrt_lklist_dyn_reduce_fn, lkrt_lklist_dyn_set, lkrt_lklist_dyn_skip, - lkrt_lklist_dyn_slice_from, lkrt_lklist_dyn_take, lkrt_lklist_dyn_unique, lkrt_lklist_dyn_zip, + DYN_BOOL, DYN_F64, DYN_I64, DYN_LIST, DYN_MAP, DYN_NIL, DYN_STR, LkDyn, lkrt_check_declared_field, + lkrt_check_marked_field, lkrt_check_marked_field_dyn, lkrt_dyn_add, lkrt_dyn_as_bool, lkrt_dyn_as_f64, + lkrt_dyn_as_handle, lkrt_dyn_as_i64, lkrt_dyn_as_key_i64, lkrt_dyn_as_key_str, lkrt_dyn_as_list, lkrt_dyn_as_map, + lkrt_dyn_as_slice, lkrt_dyn_as_str, lkrt_dyn_cast_to_i64, lkrt_dyn_clear, lkrt_dyn_dispatch_type_id, + lkrt_dyn_display, lkrt_dyn_display_quoted, lkrt_dyn_div, lkrt_dyn_eq, lkrt_dyn_field, lkrt_dyn_field_at, + lkrt_dyn_from_bool, lkrt_dyn_from_bytes, lkrt_dyn_from_chan, lkrt_dyn_from_f64, lkrt_dyn_from_i64, + lkrt_dyn_from_list, lkrt_dyn_from_map, lkrt_dyn_from_maybe_bool, lkrt_dyn_from_maybe_f64, lkrt_dyn_from_maybe_i64, + lkrt_dyn_from_maybe_str, lkrt_dyn_from_nil, lkrt_dyn_from_set, lkrt_dyn_from_slice, lkrt_dyn_from_str, + lkrt_dyn_from_stream, lkrt_dyn_from_task, lkrt_dyn_ge, lkrt_dyn_get, lkrt_dyn_gt, lkrt_dyn_index, + lkrt_dyn_index_set, lkrt_dyn_le, lkrt_dyn_len_of, lkrt_dyn_lt, lkrt_dyn_map_delete, lkrt_dyn_map_get_or, + lkrt_dyn_map_has, lkrt_dyn_map_keys, lkrt_dyn_map_pairs, lkrt_dyn_map_values, lkrt_dyn_method_missing, + lkrt_dyn_mod, lkrt_dyn_mul, lkrt_dyn_neg, lkrt_dyn_not, lkrt_dyn_obj_type_id, lkrt_dyn_stream_list, lkrt_dyn_sub, + lkrt_dyn_tag, lkrt_dyn_to_iter, lkrt_dyn_truthy, lkrt_dyn_type_name, lkrt_lklist_dyn_at, lkrt_lklist_dyn_chain, + lkrt_lklist_dyn_chunk, lkrt_lklist_dyn_contains, lkrt_lklist_dyn_display, lkrt_lklist_dyn_enumerate, + lkrt_lklist_dyn_eq, lkrt_lklist_dyn_filter_fn, lkrt_lklist_dyn_flatten, lkrt_lklist_dyn_join, lkrt_lklist_dyn_len, + lkrt_lklist_dyn_map_fn, lkrt_lklist_dyn_new, lkrt_lklist_dyn_push, lkrt_lklist_dyn_reduce_fn, lkrt_lklist_dyn_set, + lkrt_lklist_dyn_slice, lkrt_lklist_dyn_slice_from, lkrt_lklist_dyn_unique, lkrt_lklist_dyn_zip, lkrt_lklist_f64_to_dyn, lkrt_lklist_i64_to_dyn, lkrt_lklist_str_to_dyn, lkrt_lkmap_obj_mark, + lkrt_lkmap_obj_mark_checked, lkrt_struct_type_begin, lkrt_struct_type_field, +}; +pub use lkdyn::{ + lkrt_dyn_contains, lkrt_dyn_from_typed_list, lkrt_dyn_is_list, lkrt_dyn_is_map, lkrt_dyn_list_drop_last, + lkrt_dyn_list_insert, lkrt_dyn_list_push, lkrt_dyn_list_remove_at, lkrt_dyn_seq_contains, }; pub use lklist::{ - LkMaybeF64, LkMaybeI64, LkMaybeStr, lkrt_lklist_f64_at, lkrt_lklist_f64_chain, lkrt_lklist_f64_contains, - lkrt_lklist_f64_display, lkrt_lklist_f64_eq, lkrt_lklist_f64_get_pair, lkrt_lklist_f64_len, lkrt_lklist_f64_new, - lkrt_lklist_f64_push, lkrt_lklist_f64_set, lkrt_lklist_f64_slice_from, lkrt_lklist_i64_at, lkrt_lklist_i64_chain, - lkrt_lklist_i64_contains, lkrt_lklist_i64_display, lkrt_lklist_i64_eq, lkrt_lklist_i64_f64_eq, - lkrt_lklist_i64_filter_fn, lkrt_lklist_i64_from_range, lkrt_lklist_i64_get, lkrt_lklist_i64_get_pair, - lkrt_lklist_i64_len, lkrt_lklist_i64_map_fn, lkrt_lklist_i64_new, lkrt_lklist_i64_push, lkrt_lklist_i64_reduce_fn, + LkMaybeF64, LkMaybeI64, LkMaybeStr, lkrt_lklist_dyn_clear, lkrt_lklist_dyn_count, lkrt_lklist_dyn_drop_last, + lkrt_lklist_dyn_index_of, lkrt_lklist_dyn_insert, lkrt_lklist_dyn_max, lkrt_lklist_dyn_min, + lkrt_lklist_dyn_remove_at, lkrt_lklist_dyn_reverse, lkrt_lklist_dyn_skip, lkrt_lklist_dyn_sort, + lkrt_lklist_dyn_sum, lkrt_lklist_dyn_take, lkrt_lklist_f64_at, lkrt_lklist_f64_chain, lkrt_lklist_f64_clear, + lkrt_lklist_f64_contains, lkrt_lklist_f64_contains_i64, lkrt_lklist_f64_count, lkrt_lklist_f64_display, + lkrt_lklist_f64_drop_last, lkrt_lklist_f64_eq, lkrt_lklist_f64_get_pair, lkrt_lklist_f64_index_of, + lkrt_lklist_f64_insert, lkrt_lklist_f64_join, lkrt_lklist_f64_len, lkrt_lklist_f64_max, lkrt_lklist_f64_min, + lkrt_lklist_f64_new, lkrt_lklist_f64_push, lkrt_lklist_f64_remove_at, lkrt_lklist_f64_reverse, lkrt_lklist_f64_set, + lkrt_lklist_f64_skip, lkrt_lklist_f64_slice, lkrt_lklist_f64_slice_from, lkrt_lklist_f64_sort, lkrt_lklist_f64_sum, + lkrt_lklist_f64_take, lkrt_lklist_i64_at, lkrt_lklist_i64_chain, lkrt_lklist_i64_clear, lkrt_lklist_i64_contains, + lkrt_lklist_i64_contains_f64, lkrt_lklist_i64_count, lkrt_lklist_i64_display, lkrt_lklist_i64_drop_last, + lkrt_lklist_i64_eq, lkrt_lklist_i64_f64_eq, lkrt_lklist_i64_filter_fn, lkrt_lklist_i64_from_range, + lkrt_lklist_i64_get, lkrt_lklist_i64_get_pair, lkrt_lklist_i64_index_of, lkrt_lklist_i64_insert, + lkrt_lklist_i64_join, lkrt_lklist_i64_len, lkrt_lklist_i64_map_fn, lkrt_lklist_i64_max, lkrt_lklist_i64_min, + lkrt_lklist_i64_new, lkrt_lklist_i64_push, lkrt_lklist_i64_reduce_fn, lkrt_lklist_i64_remove_at, lkrt_lklist_i64_reverse, lkrt_lklist_i64_set, lkrt_lklist_i64_skip, lkrt_lklist_i64_slice, - lkrt_lklist_i64_slice_from, lkrt_lklist_i64_slice_method, lkrt_lklist_i64_sort, lkrt_lklist_i64_take, - lkrt_lklist_i64_unique, lkrt_lklist_str_at, lkrt_lklist_str_chain, lkrt_lklist_str_contains, - lkrt_lklist_str_display, lkrt_lklist_str_eq, lkrt_lklist_str_filter_fn, lkrt_lklist_str_get_pair, - lkrt_lklist_str_join, lkrt_lklist_str_len, lkrt_lklist_str_map_fn, lkrt_lklist_str_new, lkrt_lklist_str_push, - lkrt_lklist_str_slice_from, lkrt_maybe_f64_unwrap, lkrt_maybe_i64_unwrap, lkrt_maybe_str_unwrap, lkrt_str_split, + lkrt_lklist_i64_slice_from, lkrt_lklist_i64_sort, lkrt_lklist_i64_sum, lkrt_lklist_i64_take, + lkrt_lklist_i64_unique, lkrt_lklist_str_at, lkrt_lklist_str_chain, lkrt_lklist_str_clear, lkrt_lklist_str_contains, + lkrt_lklist_str_count, lkrt_lklist_str_display, lkrt_lklist_str_drop_last, lkrt_lklist_str_eq, + lkrt_lklist_str_filter_fn, lkrt_lklist_str_get_pair, lkrt_lklist_str_index_of, lkrt_lklist_str_insert, + lkrt_lklist_str_join, lkrt_lklist_str_len, lkrt_lklist_str_map_fn, lkrt_lklist_str_max, lkrt_lklist_str_min, + lkrt_lklist_str_new, lkrt_lklist_str_push, lkrt_lklist_str_remove_at, lkrt_lklist_str_reverse, lkrt_lklist_str_set, + lkrt_lklist_str_skip, lkrt_lklist_str_slice, lkrt_lklist_str_slice_from, lkrt_lklist_str_sort, + lkrt_lklist_str_take, lkrt_maybe_f64_unwrap, lkrt_maybe_i64_unwrap, lkrt_maybe_str_unwrap, lkrt_str_split, }; pub use lkmap::{ - lkrt_lkmap_i64_f64_get_pair, lkrt_lkmap_i64_f64_len, lkrt_lkmap_i64_f64_new, lkrt_lkmap_i64_f64_set, + lkrt_lkmap_i64_f64_clear, lkrt_lkmap_i64_f64_delete, lkrt_lkmap_i64_f64_get_pair, lkrt_lkmap_i64_f64_len, + lkrt_lkmap_i64_f64_new, lkrt_lkmap_i64_f64_set, lkrt_lkmap_i64_i64_clear, lkrt_lkmap_i64_i64_delete, lkrt_lkmap_i64_i64_get_pair, lkrt_lkmap_i64_i64_len, lkrt_lkmap_i64_i64_new, lkrt_lkmap_i64_i64_set, - lkrt_lkmap_str_dyn_get, lkrt_lkmap_str_dyn_has, lkrt_lkmap_str_dyn_len, lkrt_lkmap_str_dyn_merge, - lkrt_lkmap_str_dyn_new, lkrt_lkmap_str_dyn_rebuild, lkrt_lkmap_str_dyn_set, lkrt_lkmap_str_dyn_without, - lkrt_lkmap_str_f64_get_pair, lkrt_lkmap_str_f64_len, lkrt_lkmap_str_f64_new, lkrt_lkmap_str_f64_set, - lkrt_lkmap_str_f64_set_ik, lkrt_lkmap_str_f64_without, lkrt_lkmap_str_i64_get_pair, lkrt_lkmap_str_i64_len, - lkrt_lkmap_str_i64_new, lkrt_lkmap_str_i64_set, lkrt_lkmap_str_i64_set_ik, lkrt_lkmap_str_i64_without, + lkrt_lkmap_str_dyn_get, lkrt_lkmap_str_dyn_get_at, lkrt_lkmap_str_dyn_has, lkrt_lkmap_str_dyn_len, + lkrt_lkmap_str_dyn_merge, lkrt_lkmap_str_dyn_merge_typed, lkrt_lkmap_str_dyn_new, lkrt_lkmap_str_dyn_new_sized, + lkrt_lkmap_str_dyn_rebuild, lkrt_lkmap_str_dyn_set, lkrt_lkmap_str_dyn_set_const, lkrt_lkmap_str_dyn_without, + lkrt_lkmap_str_f64_get_pair, lkrt_lkmap_str_f64_len, lkrt_lkmap_str_f64_new, lkrt_lkmap_str_f64_new_sized, + lkrt_lkmap_str_f64_set, lkrt_lkmap_str_f64_set_const, lkrt_lkmap_str_f64_set_ik, lkrt_lkmap_str_f64_without, + lkrt_lkmap_str_i64_get_pair, lkrt_lkmap_str_i64_len, lkrt_lkmap_str_i64_new, lkrt_lkmap_str_i64_new_sized, + lkrt_lkmap_str_i64_set, lkrt_lkmap_str_i64_set_const, lkrt_lkmap_str_i64_set_ik, lkrt_lkmap_str_i64_without, }; pub use lkmap::{ - lkrt_lkmap_str_bool_delete, lkrt_lkmap_str_bool_iter_pairs, lkrt_lkmap_str_bool_keys, lkrt_lkmap_str_bool_to_dyn, - lkrt_lkmap_str_bool_values, lkrt_lkmap_str_dyn_delete, lkrt_lkmap_str_dyn_iter_pairs, lkrt_lkmap_str_dyn_keys, - lkrt_lkmap_str_dyn_values, lkrt_lkmap_str_f64_delete, lkrt_lkmap_str_f64_iter_pairs, lkrt_lkmap_str_f64_keys, - lkrt_lkmap_str_f64_to_dyn, lkrt_lkmap_str_f64_values, lkrt_lkmap_str_i64_delete, lkrt_lkmap_str_i64_iter_pairs, - lkrt_lkmap_str_i64_keys, lkrt_lkmap_str_i64_to_dyn, lkrt_lkmap_str_i64_values, + lkrt_lkmap_i64_f64_display, lkrt_lkmap_i64_f64_iter_pairs, lkrt_lkmap_i64_f64_keys, lkrt_lkmap_i64_f64_values, + lkrt_lkmap_i64_i64_display, lkrt_lkmap_i64_i64_iter_pairs, lkrt_lkmap_i64_i64_keys, lkrt_lkmap_i64_i64_values, + lkrt_lkmap_str_bool_display, lkrt_lkmap_str_f64_display, lkrt_lkmap_str_i64_display, +}; +pub use lkmap::{ + lkrt_lkmap_str_bool_delete, lkrt_lkmap_str_bool_iter_pairs, lkrt_lkmap_str_bool_keys, lkrt_lkmap_str_bool_values, + lkrt_lkmap_str_dyn_clear, lkrt_lkmap_str_dyn_delete, lkrt_lkmap_str_dyn_iter_pairs, lkrt_lkmap_str_dyn_keys, + lkrt_lkmap_str_dyn_values, lkrt_lkmap_str_f64_clear, lkrt_lkmap_str_f64_delete, lkrt_lkmap_str_f64_iter_pairs, + lkrt_lkmap_str_f64_keys, lkrt_lkmap_str_f64_values, lkrt_lkmap_str_i64_clear, lkrt_lkmap_str_i64_delete, + lkrt_lkmap_str_i64_iter_pairs, lkrt_lkmap_str_i64_keys, lkrt_lkmap_str_i64_values, }; pub use lkset::{ - lkrt_lkset_add, lkrt_lkset_clear, lkrt_lkset_delete, lkrt_lkset_from_i64_list, lkrt_lkset_from_str_list, - lkrt_lkset_has, lkrt_lkset_len, lkrt_lkset_new, + lkrt_lkset_add, lkrt_lkset_clear, lkrt_lkset_combine, lkrt_lkset_delete, lkrt_lkset_display, lkrt_lkset_eq, + lkrt_lkset_from_dyn_list, lkrt_lkset_from_i64_list, lkrt_lkset_from_str_list, lkrt_lkset_has, lkrt_lkset_iter, + lkrt_lkset_len, lkrt_lkset_new, lkrt_lkset_relate, }; -pub use lkstr::{ - lkrt_bool_to_str, lkrt_f64_to_str, lkrt_i64_to_str, lkrt_str_byte_len, lkrt_str_capitalize, lkrt_str_char_at, - lkrt_str_char_len, lkrt_str_chars, lkrt_str_cmp, lkrt_str_concat, lkrt_str_concat_i64, lkrt_str_contains, - lkrt_str_count, lkrt_str_ends_with, lkrt_str_find, lkrt_str_lower, lkrt_str_repeat, lkrt_str_replace, - lkrt_str_reverse, lkrt_str_slice_chars, lkrt_str_starts_with, lkrt_str_strip_prefix, lkrt_str_strip_suffix, - lkrt_str_substring, lkrt_str_title, lkrt_str_trim, lkrt_str_upper, +pub use lkslice::{ + lkrt_lkslice_i64_contains, lkrt_lkslice_i64_count, lkrt_lkslice_i64_display, lkrt_lkslice_i64_get_pair, + lkrt_lkslice_i64_index_of, lkrt_lkslice_i64_len, lkrt_lkslice_i64_max, lkrt_lkslice_i64_min, lkrt_lkslice_i64_new, + lkrt_lkslice_i64_skip, lkrt_lkslice_i64_sub, lkrt_lkslice_i64_sum, lkrt_lkslice_i64_take, lkrt_lkslice_i64_to_list, }; -pub use mmio::{ - lkrt_mmio_read_u8, lkrt_mmio_read_u16, lkrt_mmio_read_u32, lkrt_mmio_read_u64, lkrt_mmio_write_u8, - lkrt_mmio_write_u16, lkrt_mmio_write_u32, lkrt_mmio_write_u64, +pub use lkstr::{ + lkrt_bool_to_str, lkrt_f64_to_str, lkrt_i64_to_str, lkrt_str_byte_at, lkrt_str_byte_len, lkrt_str_capitalize, + lkrt_str_char_at, lkrt_str_char_len, lkrt_str_chars, lkrt_str_cmp, lkrt_str_concat, lkrt_str_concat_i64, + lkrt_str_contains, lkrt_str_count, lkrt_str_ends_with, lkrt_str_index_of, lkrt_str_lower, lkrt_str_pad_left, + lkrt_str_pad_right, lkrt_str_repeat, lkrt_str_replace, lkrt_str_replace_limited, lkrt_str_reverse, lkrt_str_skip, + lkrt_str_slice_chars, lkrt_str_starts_with, lkrt_str_strip, lkrt_str_strip_prefix, lkrt_str_strip_suffix, + lkrt_str_take, lkrt_str_title, lkrt_str_to_float, lkrt_str_to_int, lkrt_str_trim, lkrt_str_upper, lkrt_u64_to_str, }; #[cfg(feature = "std")] pub use net::{ - lkrt_bytes_free, lkrt_bytes_to_string_utf8, lkrt_handle_close, lkrt_socket_addr, lkrt_tcp_close, lkrt_tcp_connect, - lkrt_tcp_read, lkrt_tcp_write_bytes, lkrt_tcp_write_str, + lkrt_handle_close, lkrt_socket_addr, lkrt_tcp_close, lkrt_tcp_connect, lkrt_tcp_read, lkrt_tcp_write_bytes, + lkrt_tcp_write_str, }; +// The closure-callback entries are `std`-only, like `lkclosure` itself: a +// closure value is deep-copied through the channel model, which needs an OS. +// Listed apart rather than inside the block above, because a `cfg` cannot sit +// on one name in a `use` list. +#[cfg(feature = "std")] +pub use lkdyn::{lkrt_lklist_dyn_filter_closure, lkrt_lklist_dyn_map_closure, lkrt_lklist_dyn_reduce_closure}; pub use panic::{ - lkrt_rt_cell_get, lkrt_rt_cell_new, lkrt_rt_cell_set, lkrt_rt_current_error, lkrt_rt_handle_release, - lkrt_rt_handle_release_deep, lkrt_rt_raise_dyn, lkrt_rt_raise_msg, lkrt_rt_try_pop, lkrt_rt_try_push, + lkrt_rt_cell_get, lkrt_rt_cell_get_raw, lkrt_rt_cell_new, lkrt_rt_cell_new_raw, lkrt_rt_cell_set, + lkrt_rt_cell_set_raw, lkrt_rt_current_error, lkrt_rt_handle_release, lkrt_rt_handle_release_deep, + lkrt_rt_maybe_guard, lkrt_rt_raise_dyn, lkrt_rt_raise_msg, lkrt_rt_try_pop, lkrt_rt_try_push, }; pub use port::{ lkrt_port_in_u8, lkrt_port_in_u16, lkrt_port_in_u32, lkrt_port_out_u8, lkrt_port_out_u16, lkrt_port_out_u32, }; +pub use system::{ + lkrt_cpu_invalidate_page, lkrt_cpu_load_gdt, lkrt_cpu_load_idt, lkrt_cpu_load_task_register, lkrt_cpu_read_cr2, + lkrt_cpu_read_cr3, lkrt_cpu_reload_segments, lkrt_cpu_write_cr3, +}; pub use vm_mirror::{ lkrt_lkmap_lit_finish_i64_f64, lkrt_lkmap_lit_finish_i64_i64, lkrt_lkmap_lit_finish_str_bool, lkrt_lkmap_lit_finish_str_dyn, lkrt_lkmap_lit_finish_str_f64, lkrt_lkmap_lit_finish_str_i64, lkrt_lkmap_lit_new, diff --git a/lkrt/src/lkbytes.rs b/lkrt/src/lkbytes.rs new file mode 100644 index 00000000..498b0918 --- /dev/null +++ b/lkrt/src/lkbytes.rs @@ -0,0 +1,514 @@ +//! Native `Bytes` handles: an arena-owned `Vec`, mirroring the VM's +//! `HeapValue::Bytes`. +//! +//! There *was* already a "bytes" in this crate — the one-shot host handle the +//! `tcp` path uses (`HandleKind::Bytes`, read with `take_bytes`, which removes +//! it). That is right for "read a socket, decode it once" and wrong for a +//! *value*: `bytes.len(b)` followed by `bytes.to_string_utf8(b)` would find the +//! second call's handle already gone. A `Bytes` in the language is an ordinary +//! value you may read twice, so it gets an ordinary arena handle like +//! `List`/`Map`/`Set` do. +//! +//! Display is the VM's: `Bytes([104,105])` — the byte values, comma-separated, +//! no spaces, wrapped in `Bytes([…])`. Equality is content equality, which is +//! what the VM does too (unlike a struct, which compared by handle until it was +//! fixed). + +// `alloc`, not the std prelude: this module is part of the computation-only +// subset that builds without an OS. +#[allow(unused_imports)] +use alloc::{ + borrow::ToOwned, + boxed::Box, + format, + string::{String, ToString}, + vec, + vec::Vec, +}; + +use alloc::ffi::CString; +use core::ffi::{CStr, c_char, c_void}; + +use crate::lkdyn::LkDyn; +use crate::lkstr::arena_c_string; + +type LkBytes = Vec; + +fn bytes_ref<'a>(handle: *mut c_void) -> &'a LkBytes { + debug_assert!(!handle.is_null(), "bytes handle must be live"); + // SAFETY: handles come from `arena_handle::` and stay alive for + // the arena's lifetime. + unsafe { &*(handle as *const LkBytes) } +} + +fn view<'a>(p: *const c_char) -> &'a str { + if p.is_null() { + return ""; + } + // SAFETY: non-null pointers are NUL-terminated per the ABI. + unsafe { CStr::from_ptr(p) }.to_str().unwrap_or("") +} + +fn out(text: String) -> *mut c_char { + arena_c_string(CString::new(text).unwrap_or_default()) +} + +/// `bytes.from_string(s)` and the `s.bytes()` method — the string's UTF-8 bytes. +/// +/// # Safety +/// `s` must be a valid C string, or null (→ empty). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_from_str(s: *const c_char) -> *mut c_void { + crate::state::arena_handle(view(s).as_bytes().to_vec()) +} + +/// `bytes.len(b)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_len(handle: *mut c_void) -> i64 { + bytes_ref(handle).len() as i64 +} + +/// `bytes.is_empty(b)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_is_empty(handle: *mut c_void) -> i64 { + i64::from(bytes_ref(handle).is_empty()) +} + +/// `a == b` — content equality, the VM's rule. +/// +/// # Safety +/// Both handles must be live `Bytes` handles. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_eq(left: *mut c_void, right: *mut c_void) -> i64 { + i64::from(bytes_ref(left) == bytes_ref(right)) +} + +/// `bytes.get(b, i)` — the byte as an `Int`, or nil when out of range. +/// +/// Negative indexes count from the end, the one rule every container's *read* +/// side follows (`docs/semantics.md`). +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_get(handle: *mut c_void, index: i64) -> LkDyn { + let bytes = bytes_ref(handle); + let resolved = if index < 0 { bytes.len() as i64 + index } else { index }; + if resolved < 0 || resolved >= bytes.len() as i64 { + return LkDyn::NIL; + } + LkDyn { + tag: crate::lkdyn::DYN_I64, + payload: i64::from(bytes[resolved as usize]), + } +} + +/// `bytes.concat(a, b)`. +/// +/// # Safety +/// Both handles must be live `Bytes` handles. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_concat(left: *mut c_void, right: *mut c_void) -> *mut c_void { + let mut joined = bytes_ref(left).clone(); + joined.extend_from_slice(bytes_ref(right)); + crate::state::arena_handle(joined) +} + +/// `bytes.to_string_utf8(b)` — raises on invalid UTF-8, with the stdlib +/// module's exact message. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_utf8(handle: *mut c_void) -> *mut c_char { + match core::str::from_utf8(bytes_ref(handle)) { + Ok(text) => out(text.to_owned()), + Err(error) => crate::panic::raise_str(&alloc::format!("bytes are not valid UTF-8: {error}")), + } +} + +/// `sum()` / `min()` / `max()` on a `Bytes`. +/// +/// A `Bytes` is a sequence of numbers, so it answers the same three reductions +/// a list does — and the empty answers match: `0` for the sum, nil for the two +/// extremes. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_sum(handle: *mut c_void) -> i64 { + bytes_slice(handle) + .iter() + .fold(0i64, |total, byte| total.wrapping_add(i64::from(*byte))) +} + +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_min(handle: *mut c_void) -> crate::lkdyn::LkDyn { + match bytes_slice(handle).iter().min() { + Some(byte) => crate::lkdyn::lkrt_dyn_from_i64(i64::from(*byte)), + None => crate::lkdyn::lkrt_dyn_from_nil(), + } +} + +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_max(handle: *mut c_void) -> crate::lkdyn::LkDyn { + match bytes_slice(handle).iter().max() { + Some(byte) => crate::lkdyn::lkrt_dyn_from_i64(i64::from(*byte)), + None => crate::lkdyn::lkrt_dyn_from_nil(), + } +} + +/// `bytes.to_string_lossy(b)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_utf8_lossy(handle: *mut c_void) -> *mut c_char { + out(String::from_utf8_lossy(bytes_ref(handle)).into_owned()) +} + +/// `Bytes([104,105])` — the VM's display. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_to_str(handle: *mut c_void) -> *mut c_char { + out(bytes_text(handle)) +} + +/// `Bytes([104,105])` as text — the same rendering [`lkrt_lkbytes_to_str`] +/// returns, reachable from the boxed-value renderer without going through a +/// C string and back. +pub(crate) fn bytes_text(handle: *mut c_void) -> String { + let bytes = bytes_ref(handle); + let mut text = String::with_capacity(bytes.len() * 4 + 9); + text.push_str("Bytes(["); + for (index, byte) in bytes.iter().enumerate() { + if index > 0 { + text.push(','); + } + text.push_str(&alloc::format!("{byte}")); + } + text.push_str("])"); + text +} + +/// `bytes.slice(b, start[, end])` — a window, copied out as its own `Bytes`. +/// +/// Positions follow the read rule: negative counts from the end, out of range +/// clamps, and a *reversed* window is empty. +/// +/// It used to raise on `end < start`, which was the `bytes` module's rule — +/// while the method it shares this symbol with answered `Bytes([])`. So +/// `b.slice(2, 1)` raised compiled and answered an empty window interpreted, +/// and no corpus program had ever written a reversed window down. Every other +/// sequence clamps: `"abcde".slice(-1, -3)` and `xs.slice(-1, -3)` are empty. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_slice(handle: *mut c_void, start: i64, end: i64) -> *mut c_void { + let bytes = bytes_ref(handle); + let len = bytes.len() as i64; + let resolve = |index: i64| { + let resolved = if index < 0 { len + index } else { index }; + resolved.clamp(0, len) as usize + }; + let (from, to) = (resolve(start), resolve(end)); + crate::state::arena_handle(bytes[from..to.max(from)].to_vec()) +} + +/// `bytes.take(n)` / `bytes.skip(n)` — a prefix and the rest of one. +/// +/// A separate rule from `slice`: a *count* is not a position, so a negative one +/// is the loud error the VM gives rather than something measured from the end, +/// and a count past the end clamps. Same shape as `lklist`'s `list_window!`, +/// which is where the wording comes from. +macro_rules! bytes_window { + ($name:ident, $method:literal, $window:expr, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live `Bytes` handle. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void, n: i64) -> *mut c_void { + if n < 0 { + crate::panic::raise_str(&alloc::format!( + concat!("bytes.", $method, "() count must be non-negative, got {}"), + n + )); + } + let bytes = bytes_ref(handle); + let cut = (n as usize).min(bytes.len()); + let window: fn(&[u8], usize) -> &[u8] = $window; + crate::state::arena_handle(window(bytes, cut).to_vec()) + } + }; +} + +bytes_window!( + lkrt_lkbytes_take, + "take", + |bytes, cut| &bytes[..cut], + "The first `n` bytes, or all of them." +); +bytes_window!( + lkrt_lkbytes_skip, + "skip", + |bytes, cut| &bytes[cut..], + "Everything after the first `n` bytes." +); + +/// `bytes.index_of(v)` — the first position holding `v`, or nil. +/// +/// Nil rather than -1, and returned as a `Dyn` rather than converted by the +/// caller, because -1 is a legal position: `xs[xs.index_of(v)]` would quietly +/// answer the *last* byte instead of failing. Same shape as `lklist`'s +/// `list_index_of!`, whose arms this mirrors. +/// +/// A needle outside `0..=255` is not a byte, so it is simply absent. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_index_of(handle: *mut c_void, needle: i64) -> crate::lkdyn::LkDyn { + if !(0..=255).contains(&needle) { + return crate::lkdyn::LkDyn::NIL; + } + let needle = needle as u8; + match bytes_ref(handle).iter().position(|&b| b == needle) { + Some(index) => crate::lkdyn::lkrt_dyn_from_i64(index as i64), + None => crate::lkdyn::LkDyn::NIL, + } +} + +/// `bytes.contains(v)` — 1 when present, 0 when not. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_contains(handle: *mut c_void, needle: i64) -> i64 { + if !(0..=255).contains(&needle) { + return 0; + } + let needle = needle as u8; + i64::from(bytes_ref(handle).contains(&needle)) +} + +/// `bytes.from_list(values)` — a `List` of byte values. +/// +/// Out-of-range values raise, matching the stdlib module: a "byte" that is not +/// one is a mistake, not something to truncate silently. +/// +/// # Safety +/// `handle` must be a live `List` handle, or null (→ empty). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_from_i64_list(handle: *mut c_void) -> *mut c_void { + if handle.is_null() { + return crate::state::arena_handle(LkBytes::new()); + } + // SAFETY: the caller passes a live `List` handle. + let values = unsafe { &*(handle as *const Vec) }; + let mut bytes = LkBytes::with_capacity(values.len()); + for &value in values { + match u8::try_from(value) { + Ok(byte) => bytes.push(byte), + // The interpreter's wording, and it names `to_bytes` for both + // spellings because they are one call there: `bytes.from_list(xs)` + // *is* `xs.to_bytes()`. This said "bytes.from_list() value 300 is + // not a byte (0-255)", so a program that caught the error and + // printed it read differently compiled — on a plain `List`, + // with no boxing involved. + Err(_) => crate::panic::raise_str(&alloc::format!( + "list.to_bytes() expects byte values in 0..=255, got {value}" + )), + } + } + crate::state::arena_handle(bytes) +} + +/// `xs.to_bytes()` where the elements are boxed. +/// +/// The typed spelling hands `from_i64_list` a `Vec` and every element is +/// an Int by construction. A boxed list has to ask, which is the whole reason +/// this is a second function rather than a conversion: the interpreter has two +/// refusals here — the element is not an Int, or it is an Int outside a byte — +/// and both are caught and printed by ordinary programs. +/// +/// # Safety +/// `handle` must be a live boxed list handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_from_dyn_list(handle: *mut c_void) -> *mut c_void { + use crate::lkdyn::{DYN_I64, LkDyn}; + if handle.is_null() { + return crate::state::arena_handle(LkBytes::new()); + } + // SAFETY: the caller passes a live boxed list handle. + let values = unsafe { &*(handle as *const Vec) }; + let mut bytes = LkBytes::with_capacity(values.len()); + for &value in values { + if value.tag != DYN_I64 { + crate::panic::raise_str(&alloc::format!( + "list.to_bytes() expects Int items, got {}", + crate::lkdyn::kind_name(value) + )); + } + match u8::try_from(value.payload) { + Ok(byte) => bytes.push(byte), + Err(_) => crate::panic::raise_str(&alloc::format!( + "list.to_bytes() expects byte values in 0..=255, got {}", + value.payload + )), + } + } + crate::state::arena_handle(bytes) +} + +/// `bytes.to_list(b)` — the byte values as a `List`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_to_i64_list(handle: *mut c_void) -> *mut c_void { + let values: Vec = bytes_ref(handle).iter().map(|&byte| i64::from(byte)).collect(); + crate::state::arena_handle(values) +} + +/// The bytes behind a handle — for the host writers (`fs.write`, `tcp.write`), +/// which need the content without taking ownership of it. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +/// `b.reverse()` — the bytes in reverse order, as a new `Bytes`. +/// +/// Shape-preserving and element-type-independent, so the answer is a `Bytes` +/// and not a list: the same reading `take`, `skip`, `slice` and `concat` take. +/// +/// # Safety +/// `handle` must be a live handle from a `bytes_h` constructor, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_reverse(handle: *mut c_void) -> *mut c_void { + let mut out = bytes_slice(handle).to_vec(); + out.reverse(); + bytes_handle(out) +} + +/// `b.sort()` — the bytes in ascending order, as a new `Bytes`. +/// +/// # Safety +/// `handle` must be a live handle from a `bytes_h` constructor, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_sort(handle: *mut c_void) -> *mut c_void { + let mut out = bytes_slice(handle).to_vec(); + out.sort_unstable(); + bytes_handle(out) +} + +/// `b.unique()` — later duplicates dropped, order kept, as a new `Bytes`. +/// +/// 256 possible values, so the "seen" set is a bitmap rather than a hash set. +/// +/// # Safety +/// `handle` must be a live handle from a `bytes_h` constructor, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_unique(handle: *mut c_void) -> *mut c_void { + let bytes = bytes_slice(handle); + let mut seen = [false; 256]; + let mut out = Vec::with_capacity(bytes.len()); + for byte in bytes { + if !seen[*byte as usize] { + seen[*byte as usize] = true; + out.push(*byte); + } + } + bytes_handle(out) +} + +/// `b.count(v)` — how many bytes equal `v`. A value no byte can hold counts +/// zero, which is the answer `contains` gives it too. +/// +/// # Safety +/// `handle` must be a live handle from a `bytes_h` constructor, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkbytes_count(handle: *mut c_void, value: i64) -> i64 { + match u8::try_from(value) { + Ok(needle) => bytes_slice(handle).iter().filter(|byte| **byte == needle).count() as i64, + Err(_) => 0, + } +} + +pub(crate) fn bytes_slice<'a>(handle: *mut c_void) -> &'a [u8] { + bytes_ref(handle).as_slice() +} + +/// Builds a `Bytes` from a byte slice — the constructor the decoders use. +pub(crate) fn bytes_handle(bytes: Vec) -> *mut c_void { + crate::state::arena_handle(bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn from(text: &str) -> *mut c_void { + let c = CString::new(text).expect("no interior NUL"); + unsafe { lkrt_lkbytes_from_str(c.as_ptr()) } + } + + fn rendered(handle: *mut c_void) -> String { + let ptr = unsafe { lkrt_lkbytes_to_str(handle) }; + unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() + } + + #[test] + fn display_and_equality_match_the_vm() { + let hi = from("hi"); + assert_eq!(rendered(hi), "Bytes([104,105])"); + assert_eq!(rendered(from("")), "Bytes([])"); + // Content equality, not handle identity. + assert_eq!(unsafe { lkrt_lkbytes_eq(hi, from("hi")) }, 1); + assert_eq!(unsafe { lkrt_lkbytes_eq(hi, from("ho")) }, 0); + assert_eq!(unsafe { lkrt_lkbytes_len(hi) }, 2); + assert_eq!(unsafe { lkrt_lkbytes_is_empty(from("")) }, 1); + } + + /// A value you may read twice — the reason this is an arena handle rather + /// than the one-shot host handle the `tcp` path uses. + #[test] + fn a_handle_survives_being_read_twice() { + let hi = from("hi"); + assert_eq!(unsafe { lkrt_lkbytes_len(hi) }, 2); + let text = unsafe { lkrt_lkbytes_utf8(hi) }; + assert_eq!(unsafe { CStr::from_ptr(text) }.to_str().expect("utf-8"), "hi"); + assert_eq!(unsafe { lkrt_lkbytes_len(hi) }, 2); + } + + /// Negative indexes count from the end, and out of range is nil — the one + /// rule every container's read side follows. + #[test] + fn get_counts_from_the_end_and_answers_nil_out_of_range() { + let hi = from("hi"); + assert_eq!(unsafe { lkrt_lkbytes_get(hi, 0) }.payload, 104); + assert_eq!(unsafe { lkrt_lkbytes_get(hi, -1) }.payload, 105); + assert_eq!(unsafe { lkrt_lkbytes_get(hi, 2) }.tag, crate::lkdyn::DYN_NIL); + assert_eq!(unsafe { lkrt_lkbytes_get(hi, -3) }.tag, crate::lkdyn::DYN_NIL); + } + + #[test] + fn concat_joins_and_lossy_never_raises() { + let joined = unsafe { lkrt_lkbytes_concat(from("hi"), from("!")) }; + assert_eq!(rendered(joined), "Bytes([104,105,33])"); + let invalid = bytes_handle(vec![0xff]); + let lossy = unsafe { lkrt_lkbytes_utf8_lossy(invalid) }; + assert_eq!(unsafe { CStr::from_ptr(lossy) }.to_str().expect("utf-8"), "\u{fffd}"); + } +} diff --git a/lkrt/src/lkclosure.rs b/lkrt/src/lkclosure.rs new file mode 100644 index 00000000..48d99674 --- /dev/null +++ b/lkrt/src/lkclosure.rs @@ -0,0 +1,248 @@ +//! A closure as a **runtime value**. +//! +//! Every other closure in the native build is a compile-time fact: the lowering +//! knows which function a register names, so a call devirtualizes and the +//! captures become hidden trailing arguments. That covers a closure that is +//! built and called, which is most of them — and nothing else. Storing one in a +//! list, putting one in a struct field, or returning one from a branch has no +//! compile-time answer. +//! +//! This is the value they become. It is the shape `spawn` already used to reach +//! a lambda through a pointer: the callee is a lowered `lk_fn_N` whose +//! signature the lowering pinned to all-`LkDyn`, so one arity switch can call +//! any of them. The environment travels beside the pointer instead of as hidden +//! arguments, and the call appends it — which is exactly the argument order the +//! native signature already has (`params…`, then `captures…`). +//! +//! Owned, not borrowed: a closure outlives the frame that built it by +//! definition, so its captures are deep-copied into `OwnedVal` the way a +//! spawned goroutine's are, and re-materialized into the caller's arena on each +//! call. + +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::ffi::c_void; + +use crate::chan::{OwnedVal, materialize}; +use crate::lkdyn::{DYN_CLOSURE, LkDyn}; + +/// A callable value: where the code is, how many arguments it takes, and what +/// it captured. +pub(crate) struct LkClosure { + /// A lowered `lk_fn_N`, whose signature is `(LkDyn × (params + env)) -> LkDyn`. + pub(crate) code: *const c_void, + /// Visible parameters. The environment's length is `env.len()`, and the two + /// together are the native arity. + pub(crate) params: i64, + /// The module function index, carried only so `display` can print what the + /// interpreter prints: ``. + pub(crate) fn_index: i64, + pub(crate) env: Vec, +} + +/// Builds one from a function address and an argument block of captures. +/// +/// The block is the same `lkrt_spawn_args_new`/`push` pair a `spawn` builds, +/// and ownership of it moves here. +/// +/// # Safety +/// `env_block` must be a live handle from `lkrt_spawn_args_new`, or null for a +/// capture-free lambda. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_closure_new( + code: *const c_void, + env_block: *mut c_void, + params: i64, + fn_index: i64, +) -> LkDyn { + let env = if env_block.is_null() { + Vec::new() + } else { + // SAFETY: ownership of the block moves here, as it does into a spawn. + *unsafe { Box::from_raw(env_block as *mut Vec) } + }; + LkDyn { + tag: DYN_CLOSURE, + payload: crate::state::arena_handle(LkClosure { + code, + params, + fn_index, + env, + }) as i64, + } +} + +/// How many arguments the closure takes. +/// +/// # Safety +/// `callee` must be a `DYN_CLOSURE` value. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_closure_arity(callee: LkDyn) -> i64 { + // SAFETY: the tag is only set by `lkrt_closure_new`. + unsafe { closure_of(callee) }.params +} + +/// Calls it with an argument block, appending the captured environment. +/// +/// The arity switch mirrors `spawn`'s, and for the same reason: a `LkDyn` is +/// two machine words, so there is no variadic form to call through and each +/// arity needs its own signature. +/// +/// # Safety +/// `callee` must be a `DYN_CLOSURE` value and `args_block` a live handle from +/// `lkrt_spawn_args_new` (ownership moves here), or null for no arguments. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_closure_call(callee: LkDyn, args_block: *mut c_void) -> LkDyn { + // Checked before the block is taken apart, so calling a non-callable says + // so rather than first consuming arguments it will never pass. + // SAFETY: as documented. + unsafe { closure_of(callee) }; + let mut args: Vec = if args_block.is_null() { + Vec::new() + } else { + // SAFETY: ownership of the block moves here. + let block = *unsafe { Box::from_raw(args_block as *mut Vec) }; + block.iter().map(materialize).collect() + }; + // SAFETY: as documented. + unsafe { call_with(callee, &mut args) } +} + +/// The call itself, once the arguments are in a `Vec`. +/// +/// Split out so a caller that already has the arguments — the list HOFs with a +/// closure callback — does not have to allocate an argument *block* just to +/// have this function take it apart again. +/// +/// # Safety +/// `callee` must be a `DYN_CLOSURE` value. +pub(crate) unsafe fn call_with(callee: LkDyn, args: &mut Vec) -> LkDyn { + // SAFETY: as documented. + let closure = unsafe { closure_of(callee) }; + if args.len() as i64 != closure.params { + crate::panic::raise_str("closure called with the wrong number of arguments"); + } + // The environment follows the visible arguments, which is the order the + // native signature declares (`lower_call`'s hidden trailing captures). + args.extend(closure.env.iter().map(materialize)); + let code = closure.code; + match args.len() { + 0 => call0(code), + 1 => call1(code, args[0]), + 2 => call2(code, args[0], args[1]), + 3 => call3(code, args[0], args[1], args[2]), + 4 => call4(code, args[0], args[1], args[2], args[3]), + 5 => call5(code, args[0], args[1], args[2], args[3], args[4]), + 6 => call6(code, args[0], args[1], args[2], args[3], args[4], args[5]), + 7 => call7(code, args[0], args[1], args[2], args[3], args[4], args[5], args[6]), + 8 => call8( + code, args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], + ), + _ => crate::panic::raise_str("closure arity over the native cap"), + } +} + +/// `m.thing(args…)` where `thing` is a map entry or a struct field holding a +/// callable — the interpreter's callable-property path. +/// +/// Its own entry point rather than an argument to [`lkrt_closure_call`] so the +/// *miss* can say what the interpreter says. A map is the one receiver where a +/// miss has two causes, and the interpreter names both; answering "value is not +/// callable" instead would be a different string out of the same `catch`. +/// +/// # Safety +/// `args_block` as [`lkrt_closure_call`]; `name` a NUL-terminated string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_closure_call_property( + property: LkDyn, + args_block: *mut c_void, + name: *const core::ffi::c_char, +) -> LkDyn { + if property.tag != DYN_CLOSURE { + // SAFETY: `name` is a NUL-terminated constant from the module's pool. + let method = unsafe { core::ffi::CStr::from_ptr(name) }.to_string_lossy(); + crate::panic::raise_str(&alloc::format!( + "a Map has no method `{method}`, and this map has no key `{method}` holding a function either" + )); + } + // SAFETY: the tag is checked above; the block contract is the callee's. + unsafe { lkrt_closure_call(property, args_block) } +} + +/// Deep-copies a closure value, for the boundaries that copy (a channel, a +/// goroutine's isolate). The code pointer is shared — it is code. +pub(crate) fn own_closure(value: LkDyn) -> OwnedVal { + // SAFETY: the tag is only set by `lkrt_closure_new`. + let closure = unsafe { closure_of(value) }; + OwnedVal::Closure( + closure.code as usize, + closure.params, + closure.fn_index, + closure.env.clone(), + ) +} + +/// The inverse: a fresh handle in the current arena. +pub(crate) fn materialize_closure(code: usize, params: i64, fn_index: i64, env: &[OwnedVal]) -> LkDyn { + LkDyn { + tag: DYN_CLOSURE, + payload: crate::state::arena_handle(LkClosure { + code: code as *const c_void, + params, + fn_index, + env: env.to_vec(), + }) as i64, + } +} + +/// What `display` prints — the interpreter's exact wording, index and all +/// (`runtime_display_callable`). +/// +/// # Safety +/// `value` must be a `DYN_CLOSURE`. +pub(crate) unsafe fn closure_text(value: LkDyn) -> alloc::string::String { + // SAFETY: as documented. + let closure = unsafe { closure_of(value) }; + alloc::format!("", closure.fn_index, closure.env.len()) +} + +/// # Safety +/// `value` must be a `DYN_CLOSURE`. +unsafe fn closure_of<'a>(value: LkDyn) -> &'a LkClosure { + if value.tag != DYN_CLOSURE { + crate::panic::raise_str(&crate::lkdyn::not_a_function_message(value)); + } + // SAFETY: the payload of a `DYN_CLOSURE` is an `LkClosure` handle. + unsafe { &*(value.payload as *const LkClosure) } +} + +/// One `extern "C"` signature per arity. A `LkDyn` is a two-word aggregate, so +/// there is no variadic call to make instead. +macro_rules! closure_arity { + ($($name:ident($($arg:ident),*);)*) => { + $( + #[allow(clippy::too_many_arguments)] + fn $name(code: *const c_void, $($arg: LkDyn),*) -> LkDyn { + // SAFETY: every `lk_fn_N` a closure can name is a *clone* the + // lowering made for exactly this purpose, with an all-`LkDyn` + // signature of exactly this arity (`SigInfer::value_lambdas`). + let f: extern "C" fn($(closure_arity!(@ty $arg)),*) -> LkDyn = + unsafe { core::mem::transmute(code) }; + f($($arg),*) + } + )* + }; + (@ty $arg:ident) => { LkDyn }; +} + +closure_arity! { + call0(); + call1(a0); + call2(a0, a1); + call3(a0, a1, a2); + call4(a0, a1, a2, a3); + call5(a0, a1, a2, a3, a4); + call6(a0, a1, a2, a3, a4, a5); + call7(a0, a1, a2, a3, a4, a5, a6); + call8(a0, a1, a2, a3, a4, a5, a6, a7); +} diff --git a/lkrt/src/lkdyn.rs b/lkrt/src/lkdyn.rs index 621d8c6c..908b2beb 100644 --- a/lkrt/src/lkdyn.rs +++ b/lkrt/src/lkdyn.rs @@ -6,8 +6,8 @@ //! //! Semantics contract: every operation here must match the VM (the //! differential gates compare stdout byte-for-byte). Type errors are the -//! VM's loud failures — `flush_and_abort()` (the contract compares only -//! `success()` + stdout, not stderr text). +//! VM's loud failures — a raise that, uncaught, exits 1 (the contract +//! compares only `success()` + stdout, not stderr text). // `alloc`, not the std prelude: this module is part of the computation-only // subset that builds without an OS. @@ -34,6 +34,269 @@ pub const DYN_F64: i64 = 3; pub const DYN_STR: i64 = 4; pub const DYN_LIST: i64 = 5; pub const DYN_MAP: i64 = 6; +/// A **raw handle** parked in a cell — not a value, and never produced by +/// boxing. +/// +/// A `try` region carries a register the body assigns back out through a cell, +/// and a cell holds an `LkDyn`. That works by *boxing*, which for a typed +/// container is an element-wise conversion: the round trip would hand back a +/// copy and lose the body's writes. So a typed handle is parked as-is under this +/// tag instead, and the two cell families (`cell_get` / `cell_get_raw`) check +/// the tag rather than trusting the caller — reading a raw handle as a value, or +/// the reverse, is a *loud* failure and not a `Vec` walked as +/// `Vec`. +pub const DYN_RAW: i64 = 7; + +/// A `Set` handle, boxed. +/// +/// `Set` and `Bytes` had no tag, so they could not be *boxed* at all — and +/// boxing is how a value enters a mixed container, a struct field, a bridged +/// return, or anything else that holds `LkDyn`. `[s]` and `{"k": s}` therefore +/// had no lowering, for a reason that had nothing to do with sets: the dynamic +/// carrier simply did not cover every value the language has. +pub const DYN_SET: i64 = 8; +/// A `Bytes` handle, boxed. See [`DYN_SET`]. +pub const DYN_BYTES: i64 = 9; + +/// A **typed map** handle, boxed in place — one tag per carrier. +/// +/// `DYN_MAP` means a `str -> Dyn` map, so a typed carrier used to box by +/// *rebuilding* into one. That is a re-representation, and the fresh table's +/// iteration order is not the original's once the history includes deletions: +/// `println([m])` printed entries in an order the VM never would. A wrong +/// answer, not a fallback — and the rule against it was already written down on +/// [`DYN_RAW`]. +/// +/// Five tags rather than one because there are five carriers; the tag is the +/// only thing that says which. `lkmap::KIND_*` is the same numbering, minus the +/// base. +pub const DYN_TMAP_BASE: i64 = 10; +/// One past the last typed-map tag. +pub const DYN_TMAP_END: i64 = 15; + +/// A **typed list** handle, boxed in place — one tag per carrier. +/// +/// The same rule [`DYN_TMAP_BASE`] states, for the other container: boxing must +/// not re-represent. A typed list used to box by rebuilding element-wise into a +/// `Vec`, and that copy is a *different list*, so both directions of +/// aliasing broke — `let xs = [1]; let c = [xs]; xs.push(2); c[0].len()` answered +/// 1 where the VM answers 2, and `c[0].push(9)` appended to the copy. Wrong +/// answers on programs that compiled fully native. +/// +/// Three tags rather than one because there are three carriers; the tag is the +/// only thing that says which. The numbering below is the `kind` argument of +/// [`lkrt_dyn_from_typed_list`], and matches the lowering's carrier order. +pub const DYN_TLIST_BASE: i64 = 16; +/// `Vec` — `DYN_TLIST_BASE + 0`. +pub const TLIST_I64: i64 = 0; +/// `Vec` — `DYN_TLIST_BASE + 1`. +pub const TLIST_F64: i64 = 1; +/// `Vec<*const c_char>` — `DYN_TLIST_BASE + 2`. +pub const TLIST_STR: i64 = 2; +/// One past the last typed-list tag. +pub const DYN_TLIST_END: i64 = 19; + +/// A **window** handle (`xs.slice(a, b)`), boxed in place. See [`DYN_SET`] for +/// why a carrier without a tag cannot be boxed at all, and therefore cannot +/// enter a list, a map, a struct field, or a `try` region's value. +/// +/// In place, not materialized: a window *is* a range of its source, and boxing +/// it by copying would make `[w]` hold something that stops tracking the list +/// it windows — which the VM's `HeapValue::Slice` does not do either. +pub const DYN_SLICE: i64 = DYN_TMAP_END; + +/// A closure as a **runtime value**: the payload is an `LkClosure` handle (see +/// `lkclosure`). Every other closure in the native build is a compile-time +/// reference, which is why storing one in a container had no form at all. +pub const DYN_CLOSURE: i64 = 20; + +/// A channel and a task, as **runtime values**: the payload is the `i64` id the +/// runtime keys them by. +/// +/// A tag of their own rather than the bare id, because the id is an `Int` and a +/// channel is not: `typeof` answered `Int`, display wrote `1`, and `chan(1) == +/// 1` was *true*. Tracking which `i64`s were really handles caught the direct +/// cases and lost the fact wherever the value escaped — into a list, into a +/// typed parameter — which is most of what a program does with a channel. The +/// tag travels with the value instead. +pub const DYN_CHAN: i64 = 21; +pub const DYN_TASK: i64 = 22; + +/// A stream, as a **runtime value**: the payload is the dyn-list handle this +/// side materializes it into. +/// +/// The materialization is what makes a finite pipeline cheap, and it is sound +/// only where the difference cannot be seen. A tag is how it stays unseen: +/// without one `typeof` answered `List`, display wrote the elements, and a +/// trait dispatched to `impl … for List`. Marking the *value* instead could not +/// work for `stream.from_list(xs)`, whose result is the caller's own list — +/// marking it marked `xs`. A box is a value of its own. +pub const DYN_STREAM: i64 = 23; + +/// Boxes a materialized stream from its list handle. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_stream(handle: *mut c_void) -> LkDyn { + LkDyn { + tag: DYN_STREAM, + payload: handle as i64, + } +} + +/// The list behind a stream — `stream.collect`, and the receiver of every +/// stream operation. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_stream_list(v: LkDyn) -> *mut c_void { + if v.tag != DYN_STREAM { + crate::panic::raise_str("runtime type error"); + } + v.payload as *mut c_void +} + +/// Boxes a channel id. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_chan(id: i64) -> LkDyn { + LkDyn { + tag: DYN_CHAN, + payload: id, + } +} + +/// Boxes a task id. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_task(id: i64) -> LkDyn { + LkDyn { + tag: DYN_TASK, + payload: id, + } +} + +/// The id behind a boxed channel or task. +/// +/// Its own entry rather than [`lkrt_dyn_as_i64`], which would then accept a +/// channel wherever an `Int` is required — `xs[c]` would quietly index by the +/// id where the interpreter refuses. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_as_handle(v: LkDyn) -> i64 { + match v.tag { + DYN_CHAN | DYN_TASK => v.payload, + // The id unboxed, for the paths that still hand one over directly. + DYN_I64 => v.payload, + _ => crate::panic::raise_str("runtime type error"), + } +} + +/// Whether a tag denotes a map of any representation. +pub(crate) fn is_map_tag(tag: i64) -> bool { + tag == DYN_MAP || (DYN_TMAP_BASE..DYN_TMAP_END).contains(&tag) +} + +/// Whether a tag denotes a list of any representation. +pub(crate) fn is_list_tag(tag: i64) -> bool { + tag == DYN_LIST || (DYN_TLIST_BASE..DYN_TLIST_END).contains(&tag) +} + +/// `IsList` / `IsMap` on a boxed value. +/// +/// One tag comparison is not the question: a list has five representations +/// (the boxed one and four typed carriers) and a map six, and the interpreter +/// also answers **true** for a `String` — `let [a, b] = "ab"` is a list +/// destructuring there. Native lowering compared the tag against `DYN_LIST` +/// alone, so a list that happened to be in a typed carrier answered `false`, +/// compiled clean, and skipped the arm that should have run. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_is_list(v: LkDyn) -> i64 { + i64::from(is_list_tag(v.tag) || v.tag == DYN_STR) +} + +/// The map half of [`lkrt_dyn_is_list`]. A `String` is not a map. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_is_map(v: LkDyn) -> i64 { + // A struct instance rides the `Map` carrier and is *not* a map: + // the interpreter's `runtime_value_is_map` is `HeapValue::Map` alone, and an + // `Object` is a different variant. It shows in `let {p: c} = P { p: 3 };` — + // a map pattern, which the interpreter refuses and this side matched. + i64::from(is_map_tag(v.tag) && lkrt_dyn_obj_type_id(v) == 0) +} + +/// Boxes a typed list handle under its carrier's tag. `kind` is `TLIST_*`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_typed_list(handle: *mut c_void, kind: i64) -> LkDyn { + if !(0..DYN_TLIST_END - DYN_TLIST_BASE).contains(&kind) { + crate::panic::raise_str("runtime type error"); + } + LkDyn { + tag: DYN_TLIST_BASE + kind, + payload: handle as i64, + } +} + +/// A boxed list's elements, whatever carrier holds them. +/// +/// A `DYN_LIST` borrows its `Vec`; a typed carrier has to box each +/// element, which is a copy — sound because every caller of this reads. The +/// callers that *write* (`push`) go to [`lkrt_dyn_list_push`] instead, which +/// reaches the carrier itself. +pub(crate) fn dyn_list_values<'a>(v: LkDyn) -> alloc::borrow::Cow<'a, [LkDyn]> { + use alloc::borrow::Cow; + if v.tag == DYN_LIST { + return Cow::Borrowed(dyn_list(v)); + } + if !is_list_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + Cow::Owned(crate::lklist::typed_list_boxed( + v.tag - DYN_TLIST_BASE, + v.payload as *mut c_void, + )) +} + +/// Boxes a typed map handle under its carrier's tag. `kind` is `lkmap::KIND_*`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_typed_map(handle: *mut c_void, kind: i64) -> LkDyn { + if !(0..DYN_TMAP_END - DYN_TMAP_BASE).contains(&kind) { + crate::panic::raise_str("runtime type error"); + } + LkDyn { + tag: DYN_TMAP_BASE + kind, + payload: handle as i64, + } +} + +/// Boxes a `Set` handle. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_set(handle: *mut c_void) -> LkDyn { + LkDyn { + tag: DYN_SET, + payload: handle as i64, + } +} + +/// Boxes a `Bytes` handle. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_bytes(handle: *mut c_void) -> LkDyn { + LkDyn { + tag: DYN_BYTES, + payload: handle as i64, + } +} + +/// Boxes a window handle. See [`DYN_SLICE`]. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_slice(handle: *mut c_void) -> LkDyn { + LkDyn { + tag: DYN_SLICE, + payload: handle as i64, + } +} + +/// The window back out of the box, or a loud failure. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_as_slice(v: LkDyn) -> *mut c_void { + if v.tag != DYN_SLICE { + crate::panic::raise_str("runtime type error"); + } + v.payload as *mut c_void +} /// The by-value dynamic carrier. `payload` holds the value bits: `0`/`1` for /// Bool, the integer itself for I64, `f64::to_bits` for F64, a `*const @@ -52,7 +315,7 @@ impl LkDyn { payload: 0, }; - fn f64_value(self) -> f64 { + pub(crate) fn f64_value(self) -> f64 { f64::from_bits(self.payload as u64) } @@ -104,7 +367,7 @@ unsafe fn dyn_str<'a>(v: LkDyn) -> &'a str { unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("") } -fn dyn_list<'a>(v: LkDyn) -> &'a [LkDyn] { +pub(crate) fn dyn_list<'a>(v: LkDyn) -> &'a [LkDyn] { let handle = v.payload as *mut c_void; if handle.is_null() { return &[]; @@ -205,6 +468,115 @@ pub extern "C" fn lkrt_dyn_truthy(v: LkDyn) -> i64 { i64::from(!(v.tag == DYN_NIL || (v.tag == DYN_BOOL && v.payload == 0))) } +/// `-x` on a boxed value: an Int wraps at `i64::MIN` and a Float gets a real +/// `fneg`, exactly as `Executor::dispatch_neg` does. Anything else is the +/// VM's loud type error. +/// The type name a *caught* type error names its operand by. +/// +/// The VM used to format `RuntimeVal::kind()`, which reports the +/// **representation**: a string of <= 7 bytes was `String` and a longer one +/// `Object`, as was every list, map and set. Its own doc said a caller with the +/// heap should use `HeapValue::type_name` — so the VM now does, and this is the +/// mirror of *that*: the language's type name, one per kind. +pub(crate) fn kind_name_of(v: LkDyn) -> String { + kind_name(v) +} + +pub(crate) fn kind_name(v: LkDyn) -> String { + // A marked struct instance answers the name it was *declared* with. The + // mirrored function got this right and this one did not, so `typeof(p)` on + // a struct read `Map` compiled and `P` interpreted, and a type error + // naming that operand said `Map` too. Third layer of the same rule: the + // language's name for a struct instance is the struct's name. + if let Some(name) = struct_type_name(v) { + return name; + } + match v.tag { + DYN_NIL => "Nil", + DYN_BOOL => "Bool", + DYN_I64 => "Int", + DYN_F64 => "Float", + DYN_STR => "String", + DYN_CHAN => "Channel", + DYN_TASK => "Task", + DYN_STREAM => "Stream", + tag if is_list_tag(tag) => "List", + DYN_SET => "Set", + DYN_BYTES => "Bytes", + DYN_SLICE => "Slice", + DYN_CLOSURE => "Function", + tag if is_map_tag(tag) => "Map", + _ => "Object", + } + .to_string() +} + +/// The declared name of a marked struct instance, or `None` for anything else +/// (including a struct whose declaration never reached this runtime). +/// Whether a value is a marked struct instance rather than an ordinary map. +fn is_struct_instance(v: LkDyn) -> bool { + lkrt_dyn_obj_type_id(v) != 0 +} + +/// Refuses a **struct instance** where a map *collection* operation is asked. +/// +/// A struct rides the `Map` carrier, so every one of these would +/// otherwise answer for the fields: `s.len()` was the field count, `s.keys()` +/// the field names, `"p" in s` true. The interpreter has a different heap value +/// and refuses each of them, naming the struct — so these are its words. +/// +/// Reading a *field* is not among them: `p.x` is what a struct is for, and the +/// carrier is how it is read. +fn reject_struct_receiver(v: LkDyn, method: &str) { + if let Some(name) = struct_type_name(v) { + crate::panic::raise_str(&alloc::format!("{name} has no method '{method}'")); + } +} + +fn struct_type_name(v: LkDyn) -> Option { + let type_id = lkrt_dyn_obj_type_id(v); + if type_id == 0 { + return None; + } + with_struct_types(|types| types.get(&type_id).map(|desc| desc.name.clone())) +} + +/// `typeof(x)` on a boxed value — the VM's `RuntimeVal::type_name_in`. +/// +/// The lowering answers from the proven MIR type where it can; a `Dyn` or a +/// `MapStrDyn` cannot be decided statically (either may be a struct instance at +/// run time), so it asks here. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_type_name(v: LkDyn) -> *mut c_char { + arena_c_string(CString::new(kind_name(v)).unwrap_or_default()) +} + +/// `list.sum()`'s refusal, in the VM's wording — the message names the element +/// that is not a number, which is the only thing that makes it actionable. +pub(crate) fn raise_sum_wants_numbers(value: LkDyn) -> ! { + crate::panic::raise_str(&format!( + "list.sum() adds numbers, and this list holds a {}", + kind_name(value) + )) +} + +/// A binary type error in the VM's wording. `verb` is the operator as the VM +/// spells it — the source operator where one exists (`operator_symbol`), which +/// is now every case the AOT can reach. `Sub` is the one that still names an +/// opcode, and it does so in the VM too. +fn binary_type_error(verb: &str, tail: &str, a: LkDyn, b: LkDyn) -> ! { + crate::panic::raise_str(&format!("{verb} {tail}, got {} and {}", kind_name(a), kind_name(b))) +} + +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_neg(v: LkDyn) -> LkDyn { + match v.tag { + DYN_I64 => from_i64(v.payload.wrapping_neg()), + DYN_F64 => from_f64(-v.f64_value()), + _ => crate::panic::raise_str(&format!("unary '-' expects Int or Float, got {}", kind_name(v))), + } +} + /// `!x`: a Bool negates, Nil is `true`, anything else is the VM's loud /// type error. #[unsafe(no_mangle)] @@ -212,7 +584,7 @@ pub extern "C" fn lkrt_dyn_not(v: LkDyn) -> i64 { match v.tag { DYN_NIL => 1, DYN_BOOL => i64::from(v.payload == 0), - _ => crate::panic::raise_str("runtime type error"), + _ => crate::panic::raise_str(&format!("Not expected Bool or Nil, got {}", kind_name(v))), } } @@ -236,8 +608,11 @@ pub extern "C" fn lkrt_dyn_cast_to_i64(v: LkDyn) -> i64 { DYN_F64 => v.f64_value() as i64, DYN_BOOL => v.payload, DYN_STR => crate::panic::raise_str("cannot cast String to an integer"), - DYN_LIST => crate::panic::raise_str("cannot cast List to an integer"), + tag if is_list_tag(tag) => crate::panic::raise_str("cannot cast List to an integer"), DYN_MAP => crate::panic::raise_str("cannot cast Map to an integer"), + DYN_SET => crate::panic::raise_str("cannot cast Set to an integer"), + DYN_BYTES => crate::panic::raise_str("cannot cast Bytes to an integer"), + DYN_SLICE => crate::panic::raise_str("cannot cast Slice to an integer"), _ => crate::panic::raise_str("cannot cast Nil to an integer"), } } @@ -283,43 +658,307 @@ pub extern "C" fn lkrt_dyn_as_bool(v: LkDyn) -> i64 { // identity lives in a side registry keyed by the arena handle. Handles are // never freed before process exit, so a mark can't dangle or alias. -// Thread-local under std, a spin-locked global on bare metal (no TLS there). -#[cfg(feature = "std")] -std::thread_local! { - static OBJ_TYPE_MARKS: core::cell::RefCell> = - core::cell::RefCell::new(crate::lkmap::FxMap::default()); +/// The declared-struct id a live `str -> Dyn` map handle carries, or `0`. +/// +/// # Safety +/// `handle` must be a live `StrDynMap` handle, or null. +unsafe fn handle_type_id(handle: *mut c_void) -> i64 { + if handle.is_null() { + return 0; + } + // SAFETY: as documented. + unsafe { (*(handle as *mut crate::lkmap::StrDynMap)).type_id } +} + +/// Marks a freshly built struct-instance map with its lowering-assigned +/// type id (`NewObject` of a declared struct). +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_lkmap_obj_mark(handle: *mut c_void, type_id: i64) { + if !handle.is_null() { + // SAFETY: a marked handle is a live `Map`. + unsafe { + (*(handle as *mut crate::lkmap::StrDynMap)).type_id = type_id; + } + } +} + +/// Marks a map as an instance of the struct *named* `name`. +/// +/// The hybrid bridge's need: a struct coming back from the embedded VM arrives +/// as a name and a field map, and the type ids are assigned by the lowering, so +/// only the runtime registry can turn one into the other. Returns 0 when the +/// name is not a declared struct here — the caller then has a plain map, which +/// is what it would have had anyway. +/// +/// # Safety +/// `handle` must be a live `Map` handle or null; `name` a +/// NUL-terminated string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkmap_obj_mark_by_name(handle: *mut c_void, name: *const c_char) -> i64 { + if handle.is_null() || name.is_null() { + return 0; + } + // SAFETY: as documented. + let name = unsafe { core::ffi::CStr::from_ptr(name) } + .to_string_lossy() + .into_owned(); + let Some(type_id) = with_struct_types(|types| types.iter().find(|(_, desc)| desc.name == name).map(|(id, _)| *id)) + else { + return 0; + }; + lkrt_lkmap_obj_mark(handle, type_id); + type_id +} + +/// [`lkrt_lkmap_obj_mark`], and then measures what is already in the map +/// against the declaration. +/// +/// For the construction that *builds* the map first — `P { ..base }`, which +/// rebuilds a map and marks the copy — where the sets happened before this +/// handle was a struct at all, so there is no earlier moment to check them. +/// +/// The ordinary `P { x: 1 }` uses the plain mark: the lowering emits a check per +/// field before the mark, and elides the ones a value's own type already +/// settles. Doing both meant every construction also copied every key into an +/// owned `String` and re-checked every field — 7% of a loop building one struct, +/// plus its share of the allocation traffic, for an answer already known. +/// +/// # Safety +/// `handle` must be a live `Map` handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkmap_obj_mark_checked(handle: *mut c_void, type_id: i64) { + lkrt_lkmap_obj_mark(handle, type_id); + if handle.is_null() || type_id == 0 { + return; + } + // SAFETY: a marked handle is a live `Map`. + let entries: Vec<(String, LkDyn)> = unsafe { &*(handle as *mut crate::lkmap::StrDynMap) } + .iter() + .map(|(key, &value)| (String::from(key.as_str()), value)) + .collect(); + for (key, value) in entries { + check_declared_value(type_id, &key, value); + } } +/// One struct type as `display` needs it: its name, and its field names in +/// **declaration order**. +/// +/// The lowering knows both, but it cannot spell the rendering out at the display +/// site: a *field* holding another struct is a bare `str→Dyn` map by then, and +/// whether a field holds one is not decidable there. So the knowledge has to be +/// available at runtime, where the mark is — and then nesting recurses through +/// the same display for free. (An earlier attempt inlined it and printed a +/// nested struct as a hash-ordered map; see `docs/aot/aot-gaps-and-lkrt.md`.) +#[derive(Default)] +struct StructTypeDesc { + name: String, + /// `(field name, declared-type code)` — see [`DECLARED_ANY`] and friends. + fields: Vec<(String, i64)>, +} + +/// The declared-type codes `obj_ty.field` carries, mirroring the scalar set +/// `val::value_satisfies_declared` checks. Anything else is `DECLARED_ANY`: a +/// container's element type is not something one value carries, so it is not a +/// thing a store can be measured against. +pub const DECLARED_ANY: i64 = 0; +pub const DECLARED_INT: i64 = 1; +pub const DECLARED_FLOAT: i64 = 2; +pub const DECLARED_BOOL: i64 = 3; +pub const DECLARED_STR: i64 = 4; +/// Added to a code to say the field is nullable, so `nil` satisfies it. +pub const DECLARED_NULLABLE: i64 = 16; + +// One table for the process, not one per thread. The generated entry prologue +// registers every declared struct once, on the main thread; a task runs on +// another, and with a thread-local table it found no description at all — so a +// struct handed to a task printed as a map even once its id travelled with it. +// +// Every caller copies what it needs out of the closure and raises afterwards +// (a raise `longjmp`s past drops, so a guard held across one never unlocks). +#[cfg(feature = "std")] +static STRUCT_TYPES: std::sync::Mutex>> = std::sync::Mutex::new(None); + #[cfg(not(feature = "std"))] -static OBJ_TYPE_MARKS_CELL: spin::Mutex>> = spin::Mutex::new(None); +static STRUCT_TYPES_CELL: spin::Mutex>> = spin::Mutex::new(None); -/// Runs `f` with the object type-mark table, however it is stored. #[cfg(feature = "std")] -fn with_obj_type_marks(f: impl FnOnce(&mut crate::lkmap::FxMap) -> R) -> R { - OBJ_TYPE_MARKS.with(|marks| f(&mut marks.borrow_mut())) +fn with_struct_types(f: impl FnOnce(&mut crate::lkmap::FxMap) -> R) -> R { + let mut slot = match STRUCT_TYPES.lock() { + Ok(slot) => slot, + // A raise inside a *different* thread's registration would poison this; + // the description is still readable, and refusing to print is worse + // than printing what is there. + Err(poisoned) => poisoned.into_inner(), + }; + f(slot.get_or_insert_with(crate::lkmap::FxMap::default)) } #[cfg(not(feature = "std"))] -fn with_obj_type_marks(f: impl FnOnce(&mut crate::lkmap::FxMap) -> R) -> R { - let mut slot = OBJ_TYPE_MARKS_CELL.lock(); +fn with_struct_types(f: impl FnOnce(&mut crate::lkmap::FxMap) -> R) -> R { + let mut slot = STRUCT_TYPES_CELL.lock(); f(slot.get_or_insert_with(crate::lkmap::FxMap::default)) } -/// Marks a freshly built struct-instance map with its lowering-assigned -/// type id (`NewObject` of a type that has trait impls). +/// Opens a type's description: `type_id`'s name is `name`, no fields yet. +/// +/// Called from the generated entry prologue, once per declared struct, followed +/// by one [`lkrt_struct_type_field`] per field in declaration order. A sequence +/// of calls rather than a static table because that needs nothing new from +/// codegen — the pieces are the `StrPtr`/`I64` shapes the ABI already has. +/// +/// # Safety +/// `name` must be a valid C string, or null. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_lkmap_obj_mark(handle: *mut c_void, type_id: i64) { - with_obj_type_marks(|marks| marks.insert(handle as usize, type_id)); +pub unsafe extern "C" fn lkrt_struct_type_begin(type_id: i64, name: *const c_char) { + // SAFETY: the caller passes a NUL-terminated string constant. + let name = if name.is_null() { + String::new() + } else { + unsafe { core::ffi::CStr::from_ptr(name) } + .to_string_lossy() + .into_owned() + }; + with_struct_types(|types| { + types.insert( + type_id, + StructTypeDesc { + name, + fields: Vec::new(), + }, + ) + }); +} + +/// Appends one field name to `type_id`'s description. See +/// [`lkrt_struct_type_begin`]. +/// +/// # Safety +/// `field` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_struct_type_field(type_id: i64, field: *const c_char, declared: i64) { + // SAFETY: as above. + let field = if field.is_null() { + String::new() + } else { + unsafe { core::ffi::CStr::from_ptr(field) } + .to_string_lossy() + .into_owned() + }; + with_struct_types(|types| { + if let Some(desc) = types.get_mut(&type_id) { + desc.fields.push((field, declared)); + } + }); } -/// Reads a boxed value's struct type mark; `0` = unmarked (not a struct -/// instance, or a type with no trait impls). +/// Renders a marked struct instance the way the VM does — `Name{f1:v1,f2:v2}`, +/// fields in declaration order, each value quoted as a nested one. +/// +/// `false` (nothing written) when the value is not a marked struct or its type +/// was never described, so the caller falls through to the map rendering — which +/// is also what the VM does for a struct whose declaration is out of reach. +fn display_marked_struct(out: &mut String, v: LkDyn, raise_on_unknown: bool, depth: u32) -> bool { + if v.tag != DYN_MAP || (v.payload as *mut c_void).is_null() { + return false; + } + // SAFETY: a non-null `DYN_MAP` payload is a live `StrDynMap`. + let type_id = unsafe { handle_type_id(v.payload as *mut c_void) }; + if type_id == 0 { + return false; + } + let Some((name, fields)) = + with_struct_types(|types| types.get(&type_id).map(|desc| (desc.name.clone(), desc.fields.clone()))) + else { + return false; + }; + let entries = dyn_map(v); + out.push_str(&name); + out.push('{'); + for (i, (field, _)) in fields.iter().enumerate() { + if i > 0 { + out.push(','); + } + out.push_str(field); + out.push(':'); + match entries.iter().find(|(k, _)| *k == field.as_str()) { + Some((_, value)) => display_into_at(out, *value, true, raise_on_unknown, depth), + None => out.push_str("nil"), + } + } + out.push('}'); + true +} + +/// Reads a boxed value's struct type mark; `0` = not a struct instance. +/// +/// This used to say "or a type with no trait impls", which stopped being true +/// when `trait_env_prescan` started giving *every* declared struct an id (a +/// struct with no methods still has to print). The distinction matters: +/// equality reads the mark to tell two structurally-identical structs apart, +/// and it can only do that if being unmarked means "not a struct". #[unsafe(no_mangle)] pub extern "C" fn lkrt_dyn_obj_type_id(v: LkDyn) -> i64 { if v.tag != DYN_MAP { return 0; } - with_obj_type_marks(|marks| marks.get(&(v.payload as usize)).copied().unwrap_or(0)) + // SAFETY: a `DYN_MAP` payload is a live `StrDynMap`. + unsafe { handle_type_id(v.payload as *mut c_void) } +} + +/// Where the built-in dispatch codes start, above any arena type mark. +/// +/// A struct instance carries a mark; a value of a built-in type does not, and +/// `impl S for Int` is a real impl whose arm has to be reachable. So dispatch +/// asks for *this* id rather than the mark: a marked receiver answers its mark, +/// and everything else answers a code for its language type. +/// +/// The lowering mirrors these nine numbers (`aot/lower/src/trait_env.rs`), +/// because it is what assigns the arm ids. `examples/syntax/trait_builtin.lk` +/// is the conformance check: a disagreement is a wrong answer there, on every +/// kind, immediately. +pub const DISPATCH_BUILTIN_BASE: i64 = 1 << 40; + +/// The language type of a value, as a small code — the built-in half of +/// [`lkrt_dyn_dispatch_type_id`]. Collapses the four list carriers to `List` +/// and the six map carriers to `Map`, because that is what an impl target can +/// name (`impl List` is refused by the language). +fn dispatch_builtin_code(v: LkDyn) -> i64 { + match v.tag { + DYN_NIL => 1, + DYN_BOOL => 2, + DYN_I64 => 3, + DYN_F64 => 4, + DYN_STR => 5, + DYN_SET => 7, + DYN_BYTES => 8, + // A window is its own type for dispatch — the interpreter's + // `heap_dispatch_type` answers `Slice`, not `List` — so + // `impl Describe for List` must not catch one. + DYN_SLICE => 10, + DYN_CHAN => 11, + DYN_TASK => 12, + DYN_STREAM => 13, + tag if is_list_tag(tag) => 6, + tag if is_map_tag(tag) => 9, + _ => 0, + } +} + +/// The id trait dispatch matches an arm against. +/// +/// A marked struct instance answers its mark; anything else answers +/// [`DISPATCH_BUILTIN_BASE`] plus its type code. Without the second half a +/// receiver of a built-in type matched no arm and fell through to +/// [`lkrt_dyn_method_missing`], so `fn show(v: S) -> String { return v.s(); }` +/// raised "runtime type error" for every `impl S for Int` in the program. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_dispatch_type_id(v: LkDyn) -> i64 { + let mark = lkrt_dyn_obj_type_id(v); + if mark != 0 { + return mark; + } + DISPATCH_BUILTIN_BASE + dispatch_builtin_code(v) } /// Dispatch fall-through: no registered impl matched the receiver's mark — @@ -335,28 +974,188 @@ pub extern "C" fn lkrt_dyn_method_missing() { /// Str payloads must be live NUL-terminated strings (arena or interned). #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_dyn_add(a: LkDyn, b: LkDyn) -> LkDyn { - if a.tag == DYN_STR && b.tag == DYN_STR { - let joined = format!("{}{}", unsafe { dyn_str(a) }, unsafe { dyn_str(b) }); + // `Executor::dynamic_add`, in its order — and the order is the rule, not a + // detail: a list operand wins over a string one, so `"p=" + [1, 2]` is the + // list `["p=", 1, 2]` and not the text `p=[1,2]`. + // + // Only the first and last cases were here before, under the belief that the + // VM "only accepts Str + Str"; everything else raised. `"v=" + x` with a + // boxed Int aborted the program where the VM prints `v=1`. + + // 1. Numbers. + if let (Some(x), Some(y)) = (a.as_numeric(), b.as_numeric()) { + return match (x, y) { + (Numeric::Int(x), Numeric::Int(y)) => from_i64(x.wrapping_add(y)), + _ => from_f64(x.as_f64() + y.as_f64()), + }; + } + // 2. Two maps merge, the right side winning. + // + // The **fill sequence** is the VM's, replayed: the left's entries in the + // left's own order minus the keys the right also has, then the right's + // entries in the right's own order (`merge_typed_maps` + + // `typed_map_without_merge_keys`). A merge builds a new table, and a new + // table's iteration order is decided by the order it was filled — so + // "the same members" is not the same answer. This used to merge two + // *unordered* views into a third, which is three different orders. + // A struct instance rides the map carrier and `+` does not accept one: the + // VM's `dynamic_add` sees a different heap value and falls through to + // "Add expected numbers or strings, got P and Map". Falling through here + // reaches that same message, which `kind_name` already spells with the + // struct's name. + if is_map_tag(a.tag) && is_map_tag(b.tag) && !is_struct_instance(a) && !is_struct_instance(b) { + let left = crate::lkmap::map_entries_ordered(a); + let right = crate::lkmap::map_entries_ordered(b); + let replaced: crate::lkmap::FxSet<_> = right.iter().map(|(key, _)| key.clone()).collect(); + let mut merged: Vec<_> = left.into_iter().filter(|(key, _)| !replaced.contains(key)).collect(); + merged.extend(right); + return LkDyn { + tag: DYN_MAP, + payload: crate::lkmap::str_dyn_from_ordered(merged) as i64, + }; + } + // 3. A list on *either* side concatenates; the other operand is one element. + if is_list_tag(a.tag) || is_list_tag(b.tag) { + let mut out: Vec = Vec::new(); + for side in [a, b] { + if is_list_tag(side.tag) { + out.extend_from_slice(&dyn_list_values(side)); + } else { + out.push(side); + } + } + return LkDyn { + tag: DYN_LIST, + payload: arena_handle(out) as i64, + }; + } + // 4. A string on either side: display-concatenate. Both operands are + // scalars by now, which is what makes the bare display exact. + if a.tag == DYN_STR || b.tag == DYN_STR { + let mut joined = String::new(); + display_into(&mut joined, a, false); + display_into(&mut joined, b, false); let ptr = arena_c_string(CString::new(joined).unwrap_or_default()); return LkDyn { tag: DYN_STR, payload: ptr as i64, }; } - match (a.as_numeric(), b.as_numeric()) { - (Some(Numeric::Int(x)), Some(Numeric::Int(y))) => from_i64(x.wrapping_add(y)), - (Some(x), Some(y)) => from_f64(x.as_f64() + y.as_f64()), - _ => crate::panic::raise_str("runtime type error"), + // The one arm of this family that still said "runtime type error", while + // `sub`, `mul`, `div` and `mod` next door all name the operands through + // `binary_type_error`. `nil + 1` therefore read + // `Add expected numbers or strings, got Nil and Int` on the interpreter and + // `runtime error` compiled — the same program, two sentences, and the + // compiled one says nothing a reader can act on. It went unnoticed because + // nothing reached it: boxing a bounds-checked element refused to lower at + // all until `to_dyn` learned the nullable carriers. + binary_type_error("Add", "expected numbers or strings", a, b) +} + +/// A map of any representation as `(key, value)` pairs under the general key, +/// for the merge above. A copy, and sound for the same reason +/// `lkmap::typed_map_keyed` is: the result is a *new* map either way. +pub(crate) fn map_entries(v: LkDyn) -> crate::lkmap::FxMap { + if v.tag == DYN_MAP { + crate::lkmap::boxed_map_keyed(v.payload as *mut c_void) + } else { + crate::lkmap::typed_map_keyed(v.tag - DYN_TMAP_BASE, v.payload as *mut c_void) } } #[unsafe(no_mangle)] pub extern "C" fn lkrt_dyn_sub(a: LkDyn, b: LkDyn) -> LkDyn { - match (a.as_numeric(), b.as_numeric()) { - (Some(Numeric::Int(x)), Some(Numeric::Int(y))) => from_i64(x.wrapping_sub(y)), - (Some(x), Some(y)) => from_f64(x.as_f64() - y.as_f64()), - _ => crate::panic::raise_str("runtime type error"), + if let (Some(x), Some(y)) = (a.as_numeric(), b.as_numeric()) { + return match (x, y) { + (Numeric::Int(x), Numeric::Int(y)) => from_i64(x.wrapping_sub(y)), + (x, y) => from_f64(x.as_f64() - y.as_f64()), + }; + } + // `-` removes, which this had never implemented — while its own error text + // said "expected numbers or list/map lhs", borrowing the VM's rule to + // describe an ability it did not have. The VM's `dynamic_sub` drops every + // element of `b` from a list and every key of `b` from a map. + // + // Order, as everywhere else: the answer keeps the left's own order, since + // removal takes entries away and never adds one. + if is_list_tag(a.tag) && is_list_tag(b.tag) { + let drop = dyn_list_values(b); + let kept: Vec = dyn_list_values(a) + .iter() + .filter(|value| !drop.iter().any(|other| dyn_eq_inner(**value, *other))) + .copied() + .collect(); + return LkDyn { + tag: DYN_LIST, + payload: arena_handle(kept) as i64, + }; } + if is_map_tag(a.tag) && is_map_tag(b.tag) && !is_struct_instance(a) && !is_struct_instance(b) { + let drop: crate::lkmap::FxSet<_> = crate::lkmap::map_entries_ordered(b) + .into_iter() + .map(|(key, _)| key) + .collect(); + let kept: Vec<_> = crate::lkmap::map_entries_ordered(a) + .into_iter() + .filter(|(key, _)| !drop.contains(key)) + .collect(); + return LkDyn { + tag: DYN_MAP, + payload: crate::lkmap::str_dyn_from_ordered(kept) as i64, + }; + } + // The single-value forms, which the VM has as their own arms beside the two + // above: `xs - v` drops the *first* element equal to `v` and `m - k` drops + // that one key. Nothing could reach them here, because the checker refused + // the shape before either executor saw it — so all three places had to be + // opened together or the fix would have been a divergence. + if is_list_tag(a.tag) { + let mut removed = false; + let kept: Vec = dyn_list_values(a) + .iter() + .filter(|value| { + if !removed && dyn_eq_inner(**value, b) { + removed = true; + return false; + } + true + }) + .copied() + .collect(); + return LkDyn { + tag: DYN_LIST, + payload: arena_handle(kept) as i64, + }; + } + // As in `lkrt_dyn_add`: `-` takes a map, not a struct instance. + if is_map_tag(a.tag) && !is_struct_instance(a) { + // A value that cannot be a key cannot be in the map, so removing it + // removes nothing — the same answer `m.delete(k)` gives, because they + // are two spellings of one operation. Removal looks a key up and drops + // it; it does not build one, which is the line `m[k]` and `m.set(k, v)` + // stay on the other side of. + // A value that cannot be a key removes nothing — but the answer still + // has to come back under the tag the caller unboxes: returning `a` + // handed a *typed* map back where `dyn.as_map` wants the boxed one, and + // `{"a": 1} - []` raised where the interpreter answered `{"a": 1}`. The + // present-key path below rebuilds for the same reason. + let Some(drop) = crate::vm_mirror::key_from_dyn_opt(b) else { + let kept = crate::lkmap::map_entries_ordered(a); + return LkDyn { + tag: DYN_MAP, + payload: crate::lkmap::str_dyn_from_ordered(kept) as i64, + }; + }; + let kept: Vec<_> = crate::lkmap::map_entries_ordered(a) + .into_iter() + .filter(|(key, _)| *key != drop) + .collect(); + return LkDyn { + tag: DYN_MAP, + payload: crate::lkmap::str_dyn_from_ordered(kept) as i64, + }; + } + binary_type_error("Sub", "expected numbers or list/map lhs", a, b) } #[unsafe(no_mangle)] @@ -364,48 +1163,73 @@ pub extern "C" fn lkrt_dyn_mul(a: LkDyn, b: LkDyn) -> LkDyn { match (a.as_numeric(), b.as_numeric()) { (Some(Numeric::Int(x)), Some(Numeric::Int(y))) => from_i64(x.wrapping_mul(y)), (Some(x), Some(y)) => from_f64(x.as_f64() * y.as_f64()), - _ => crate::panic::raise_str("runtime type error"), + _ => binary_type_error("*", "expects Int or Float", a, b), } } -/// `/` always produces Float in LK (docs/semantics.md 数值), zero divisor is +/// `/` always produces Float in LK (docs/semantics.md, the numeric adjudication), zero divisor is /// a loud failure. #[unsafe(no_mangle)] pub extern "C" fn lkrt_dyn_div(a: LkDyn, b: LkDyn) -> LkDyn { match (a.as_numeric(), b.as_numeric()) { - (Some(x), Some(y)) => { - let rhs = y.as_f64(); - if rhs == 0.0 { - crate::panic::raise_str("runtime type error"); - } - from_f64(x.as_f64() / rhs) - } - _ => crate::panic::raise_str("runtime type error"), + // `/` yields a `Float` for every numeric pair, and `f64` division by + // zero is an infinity or a NaN rather than a raise — the same as the + // VM, which this file exists to mirror. + (Some(x), Some(y)) => from_f64(x.as_f64() / y.as_f64()), + _ => binary_type_error("/", "expects Int or Float", a, b), } } #[unsafe(no_mangle)] pub extern "C" fn lkrt_dyn_mod(a: LkDyn, b: LkDyn) -> LkDyn { match (a.as_numeric(), b.as_numeric()) { - (Some(Numeric::Int(x)), Some(Numeric::Int(y))) => { - if y == 0 { - crate::panic::raise_str("runtime type error"); - } - from_i64(x.wrapping_rem(y)) - } - (Some(x), Some(y)) => { - let rhs = y.as_f64(); - if rhs == 0.0 { - crate::panic::raise_str("runtime type error"); - } - from_f64(x.as_f64() % rhs) - } - _ => crate::panic::raise_str("runtime type error"), + (Some(Numeric::Int(_)), Some(Numeric::Int(0))) => crate::panic::raise_str("ModInt divisor is zero"), + (Some(Numeric::Int(x)), Some(Numeric::Int(y))) => from_i64(x.wrapping_rem(y)), + (Some(x), Some(y)) => from_f64(x.as_f64() % y.as_f64()), + _ => binary_type_error("%", "expects Int or Float", a, b), } } // ── Equality / ordering ──────────────────────────────────────────────── +/// [`lkrt_dyn_as_i64`] / [`lkrt_dyn_as_str`] for a value used as a **map key**. +/// +/// A key of a type no map can hold is refused by name — the interpreter's +/// wording, which `vm_mirror::key_from_dyn` also raises for a boxed map. A +/// typed carrier does not go through that function (it stores the key +/// unboxed), so without these two it answered the generic "runtime type error" +/// for `m[|x| x] = 1`. +fn reject_non_key(v: LkDyn) { + match v.tag { + DYN_NIL | DYN_BOOL | DYN_I64 | DYN_STR => {} + DYN_F64 => crate::panic::raise_str("Float cannot be a map key or set member"), + _ => crate::panic::raise_str(&alloc::format!( + "{} cannot be a map key or set member: only nil, Bool, Int and String can", + kind_name_of(v) + )), + } +} + +/// An `Int`-carrier map's key. +/// +/// # Safety +/// As [`lkrt_dyn_as_i64`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_as_key_i64(v: LkDyn) -> i64 { + reject_non_key(v); + lkrt_dyn_as_i64(v) +} + +/// A `String`-carrier map's key. +/// +/// # Safety +/// As [`lkrt_dyn_as_str`]: the returned pointer borrows `v`'s payload. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_as_key_str(v: LkDyn) -> *const c_char { + reject_non_key(v); + lkrt_dyn_as_str(v) +} + /// VM equality: Int/Float compare numerically across tags (`1 == 1.0`), /// strings by content, lists elementwise; distinct non-numeric tags are /// simply unequal (not an error). @@ -416,13 +1240,103 @@ pub unsafe extern "C" fn lkrt_dyn_eq(a: LkDyn, b: LkDyn) -> i64 { i64::from(dyn_eq_inner(a, b)) } -fn dyn_eq_inner(a: LkDyn, b: LkDyn) -> bool { +pub(crate) fn dyn_eq_inner(a: LkDyn, b: LkDyn) -> bool { + dyn_eq_at(a, b, 0) +} + +/// [`dyn_eq_inner`], counting how deep it has gone. +/// +/// The interpreter's equality refuses past [`MAX_VALUE_DEPTH`] with "comparison +/// nested deeper than … levels; the values are cyclic or too deeply nested to +/// compare". This had no bound at all, so a value 520 levels deep compared +/// `true` compiled and stopped the program interpreted — the two back ends +/// disagreed about whether the program has an answer. +/// +/// Relying on the stack instead is not the same thing twice: where it lands +/// depends on the build and on how much stack the caller had left, so the +/// threshold is not a property of the language. The message it produced said so +/// out loud — "a native binary is bounded by the real stack, not by +/// LK_MAX_CALL_DEPTH" — which is true of LK recursion and was not what had +/// happened here. +fn dyn_eq_at(a: LkDyn, b: LkDyn, depth: u32) -> bool { + // One value is equal to itself without being walked, which is what the + // interpreter does — `d == d` and `[d, d].unique()` answer for a value 2000 + // levels deep there. Without this the depth bound below turned those into + // refusals, and every comparison of a container against itself paid for a + // full traversal it could not fail. + if a.tag == b.tag && a.payload == b.payload { + // Except a Float: `NaN != NaN`, and two NaNs are the same bits. + if a.tag != DYN_F64 || !a.f64_value().is_nan() { + return true; + } + } + if depth >= MAX_VALUE_DEPTH { + crate::panic::raise_str(&alloc::format!( + "comparison nested deeper than {MAX_VALUE_DEPTH} levels; the values are cyclic or too deeply nested to compare" + )); + } + let depth = depth + 1; if let (Some(x), Some(y)) = (a.as_numeric(), b.as_numeric()) { return match (x, y) { (Numeric::Int(x), Numeric::Int(y)) => x == y, _ => x.as_f64() == y.as_f64(), }; } + // Two maps compare whatever their representations are — a typed carrier + // against a boxed one is `{"a": 1} == {"a": 1, "b": "x"}` written twice, + // and the tag difference is a storage detail. Both sides take the general + // key view, which is a *copy*: sound only because `==` over maps is + // order-free (see `lkmap::typed_map_keyed`). + if is_map_tag(a.tag) && is_map_tag(b.tag) { + // A struct is a marked map and its type is part of its identity; a + // typed carrier is never a struct, so its mark is 0. + if lkrt_dyn_obj_type_id(a) != lkrt_dyn_obj_type_id(b) { + return false; + } + let keyed = |v: LkDyn| { + if v.tag == DYN_MAP { + crate::lkmap::boxed_map_keyed(v.payload as *mut c_void) + } else { + crate::lkmap::typed_map_keyed(v.tag - DYN_TMAP_BASE, v.payload as *mut c_void) + } + }; + let (xs, ys) = (keyed(a), keyed(b)); + return xs.len() == ys.len() + && xs + .iter() + .all(|(k, &v)| ys.get(k).is_some_and(|&w| dyn_eq_at(v, w, depth))); + } + // A window compares by *content*, against another window or against a + // list: the VM says `xs.slice(0, 2) == [3, 1]`, because a window is a range + // of a list and not a distinct kind of value. Element-wise rather than + // handle-wise, and across the tag difference, for the same reason the two + // map representations compare across theirs. + if (a.tag == DYN_SLICE || b.tag == DYN_SLICE) + && (b.tag == DYN_SLICE || is_list_tag(b.tag)) + && (a.tag == DYN_SLICE || is_list_tag(a.tag)) + { + let boxed = |v: LkDyn| -> alloc::vec::Vec { + if v.tag == DYN_SLICE { + // SAFETY: a `DYN_SLICE` payload is a live window handle. + unsafe { crate::lkslice::window_elements(v.payload as *mut c_void) } + .iter() + .map(|value| lkrt_dyn_from_i64(*value)) + .collect() + } else { + dyn_list_values(v).into_owned() + } + }; + let (xs, ys) = (boxed(a), boxed(b)); + return xs.len() == ys.len() && xs.iter().zip(ys).all(|(&x, y)| dyn_eq_at(x, y, depth)); + } + // Two lists compare element-wise across representations, for the same + // reason the two map representations do: `[1]` written as a typed carrier + // and the same list boxed are one value, and which representation a program + // happens to hold is not something it can see. + if is_list_tag(a.tag) && is_list_tag(b.tag) { + let (xs, ys) = (dyn_list_values(a), dyn_list_values(b)); + return xs.len() == ys.len() && xs.iter().zip(ys.iter()).all(|(&x, &y)| dyn_eq_at(x, y, depth)); + } if a.tag != b.tag { return false; } @@ -430,25 +1344,54 @@ fn dyn_eq_inner(a: LkDyn, b: LkDyn) -> bool { DYN_NIL => true, DYN_BOOL => a.payload == b.payload, DYN_STR => unsafe { dyn_str(a) == dyn_str(b) }, - DYN_LIST => { - let (xs, ys) = (dyn_list(a), dyn_list(b)); - xs.len() == ys.len() && xs.iter().zip(ys).all(|(&x, &y)| dyn_eq_inner(x, y)) - } DYN_MAP => { + // A struct instance is a marked map, and its *type* is part of + // its identity: the VM says `P{x:1} != Q{x:1}` and + // `P{x:1} != {"x":1}`, both of which are structurally equal. The + // mark answers all three cases at once — every declared struct + // gets an id (`trait_env_prescan`), and a plain map has none, so + // comparing ids first is exactly the VM's rule. + // + // Checked before the null guard so a marked-but-empty struct is + // not equal to `{}`. + if lkrt_dyn_obj_type_id(a) != lkrt_dyn_obj_type_id(b) { + return false; + } if (a.payload as *mut c_void).is_null() || (b.payload as *mut c_void).is_null() { return a.payload == b.payload; } let (xs, ys) = (dyn_map(a), dyn_map(b)); // Structural, order-free (hash iteration order is not portable, // but key-lookup equality is). - xs.len() == ys.len() && xs.iter().all(|(k, &v)| ys.get(k).is_some_and(|&w| dyn_eq_inner(v, w))) + xs.len() == ys.len() + && xs + .iter() + .all(|(k, &v)| ys.get(k).is_some_and(|&w| dyn_eq_at(v, w, depth))) } + // Both compare by *content*, the same rule their unboxed spellings + // follow (`set.eq` is order-free; `bytes.eq` is byte-wise). + // SAFETY: a `DYN_SET`/`DYN_BYTES` payload is a live handle of that + // kind — the tag is only ever set by `from_set`/`from_bytes`. + DYN_SET => unsafe { crate::lkset::lkrt_lkset_eq(a.payload as *mut c_void, b.payload as *mut c_void) != 0 }, + DYN_BYTES => unsafe { + crate::lkbytes::lkrt_lkbytes_eq(a.payload as *mut c_void, b.payload as *mut c_void) != 0 + }, + // By reference, which is the VM's rule for a callable: `let g = f` + // makes one closure two names, and two lambdas written the same way + // are two closures. Structural equality would call the second pair + // equal. Native lowering keeps that rule by building a lambda used as + // a value *once*, at its definition (`inst/call.rs::bind_lambda`). + DYN_CLOSURE => a.payload == b.payload, + // A channel and a task compare by identity, which is what the + // interpreter's handle equality is. A tag mismatch already answered + // `false` above, so `chan(1) == 1` is false here without an arm. + DYN_CHAN | DYN_TASK | DYN_STREAM => a.payload == b.payload, _ => false, } } macro_rules! dyn_ord { - ($name:ident, $op:tt) => { + ($name:ident, $op:tt, $vm_name:literal) => { /// # Safety /// Str payloads must be live NUL-terminated strings. #[unsafe(no_mangle)] @@ -462,15 +1405,15 @@ macro_rules! dyn_ord { match (a.as_numeric(), b.as_numeric()) { (Some(Numeric::Int(x)), Some(Numeric::Int(y))) => i64::from(x $op y), (Some(x), Some(y)) => i64::from(x.as_f64() $op y.as_f64()), - _ => crate::panic::raise_str("runtime type error"), + _ => binary_type_error($vm_name, "expected Int, Float, or String", a, b), } } }; } -dyn_ord!(lkrt_dyn_lt, <); -dyn_ord!(lkrt_dyn_le, <=); -dyn_ord!(lkrt_dyn_gt, >); -dyn_ord!(lkrt_dyn_ge, >=); +dyn_ord!(lkrt_dyn_lt, <, "<"); +dyn_ord!(lkrt_dyn_le, <=, "<="); +dyn_ord!(lkrt_dyn_gt, >, ">"); +dyn_ord!(lkrt_dyn_ge, >=, ">="); // ── Display (two modes, matching the VM's two display paths) ─────────── @@ -484,11 +1427,59 @@ pub(crate) fn display_for_diagnostics(v: LkDyn) -> String { out } +/// The interpreter's sentence for calling something that is not a function. +/// +/// Two shapes, and which one a value gets is its *representation*: a scalar — +/// `nil`, a Bool, an Int, a Float, or a string short enough to be inline — is +/// named by its display, and anything on the heap is named by its type. The +/// seven-byte cut is the same one `vm_mirror::str_key` makes, and it is visible +/// here because a caught error is printed output. +pub(crate) fn not_a_function_message(v: LkDyn) -> String { + let inline = match v.tag { + DYN_NIL | DYN_BOOL | DYN_I64 | DYN_F64 => true, + DYN_STR => { + let ptr = v.payload as *const c_char; + // SAFETY: a `DYN_STR` payload is a NUL-terminated arena string. + !ptr.is_null() && unsafe { CStr::from_ptr(ptr) }.to_bytes().len() <= 7 + } + _ => false, + }; + if inline { + let mut text = String::new(); + display_into(&mut text, v, false); + return alloc::format!("{text} is not a function"); + } + // The nudge the interpreter attaches to a map, because `use chan;` binds + // the module — which is a map of its members — over the `chan()` global. + let hint = if is_map_tag(v.tag) { + " — an imported module is a map of its members, so call one of them (`m.f(…)`)" + } else { + "" + }; + alloc::format!("this value is not a function: it is a {}{hint}", kind_name(v)) +} + fn display_into(out: &mut String, v: LkDyn, quoted: bool) { display_into_impl(out, v, quoted, true) } fn display_into_impl(out: &mut String, v: LkDyn, quoted: bool, raise_on_unknown: bool) { + display_into_at(out, v, quoted, raise_on_unknown, 0) +} + +/// [`display_into_impl`], counting how deep it has gone. +/// +/// The interpreter refuses to print past [`MAX_VALUE_DEPTH`] — "value nested +/// deeper than … levels; it is cyclic or too deeply nested to print" — and this +/// had no bound, so `println(deep)` printed the value compiled and stopped the +/// program interpreted. +fn display_into_at(out: &mut String, v: LkDyn, quoted: bool, raise_on_unknown: bool, depth: u32) { + if depth >= MAX_VALUE_DEPTH { + crate::panic::raise_str(&alloc::format!( + "value nested deeper than {MAX_VALUE_DEPTH} levels; it is cyclic or too deeply nested to print" + )); + } + let depth = depth + 1; match v.tag { DYN_NIL => out.push_str("nil"), DYN_BOOL => out.push_str(if v.payload != 0 { "true" } else { "false" }), @@ -506,22 +1497,28 @@ fn display_into_impl(out: &mut String, v: LkDyn, quoted: bool, raise_on_unknown: out.push_str(s); } } - DYN_LIST => { - // VM quirk pinned by the differential gate: *mixed* lists render - // their string elements bare (`[1,a b,2]`), unlike typed string - // lists (`["a","b c"]` via the `{:?}` path). VM is the reference. + tag if is_list_tag(tag) => { + // A string inside a container is quoted, whatever the container's + // representation is. This used to pass `false` here, mirroring a VM + // quirk: a *mixed* list rendered its strings bare (`[1,a b,2]`) + // while a typed string list quoted them (`["a","b c"]`) — the same + // value shown two ways, decided by an internal representation no + // program can see. The VM stopped doing that; this follows, and the + // differential gate is what noticed. out.push('['); - for (i, &e) in dyn_list(v).iter().enumerate() { + for (i, &e) in dyn_list_values(v).iter().enumerate() { if i > 0 { out.push(','); } - display_into_impl(out, e, false, raise_on_unknown); + display_into_at(out, e, true, raise_on_unknown, depth); } out.push(']'); } + DYN_MAP if display_marked_struct(out, v, raise_on_unknown, depth) => {} DYN_MAP => { - // VM format: quoted keys, bare values (`{"k":1,"s":txt}`). The - // entry order is the Fx layout order — the mirror discipline + // Quoted keys *and* values (`{"k":1,"s":"txt"}`) — a value in a + // map is inside a container too, and the keys were already quoted. + // The entry order is the Fx layout order — the mirror discipline // (vm_mirror + insert-order replay) makes it the VM's own order, // for bridged returns and mirror-built maps alike. Statically // typed map display stays *out of the lowering subset* @@ -534,94 +1531,717 @@ fn display_into_impl(out: &mut String, v: LkDyn, quoted: bool, raise_on_unknown: if i > 0 { out.push(','); } - out.push_str(&format!("{k:?}")); - out.push(':'); - display_into_impl(out, e, false, raise_on_unknown); - } + // `k.as_str()`, not `k`: the key carries whether it is + // borrowed from the program image, and `{:?}` on the key + // itself printed that (`Owned("x")`) instead of the text. + out.push_str(&format!("{:?}", k.as_str())); + out.push(':'); + display_into_at(out, e, true, raise_on_unknown, depth); + } + } + out.push('}'); + } + // Rendered through the same function the unboxed spelling calls, so a + // set in a list and a set on its own cannot drift apart. + // Rendered straight off the carrier — no copy, so the order is the + // map's own. This is the arm the rebuild used to route through. + tag if (DYN_TMAP_BASE..DYN_TMAP_END).contains(&tag) => { + out.push_str(&crate::lkmap::typed_map_text( + tag - DYN_TMAP_BASE, + v.payload as *mut c_void, + )); + } + DYN_SET => out.push_str(&crate::lkset::set_text(v.payload as *mut c_void)), + DYN_BYTES => out.push_str(&crate::lkbytes::bytes_text(v.payload as *mut c_void)), + // A window renders as the list it windows, which is what the VM shows. + DYN_SLICE => out.push_str(&crate::lkslice::slice_text(v.payload as *mut c_void)), + // SAFETY: the tag is only set by `lkrt_closure_new`. + // + // Which is in `lkclosure`, and that module is `std`-only: a closure + // *value* is deep-copied the way a channel payload is, and the deep-copy + // model lives with the channels. So without `std` this tag can never be + // set, and naming the module here made the whole crate fail to compile + // for a bare-metal target — the x86 kernel links `lkrt` without `std` + // and did not build at all. + #[cfg(feature = "std")] + DYN_CLOSURE => out.push_str(&unsafe { crate::lkclosure::closure_text(v) }), + // The interpreter's rendering: the identity is not part of it. + DYN_CHAN => out.push_str(""), + DYN_TASK => out.push_str(""), + DYN_STREAM => out.push_str(""), + other => { + if raise_on_unknown { + crate::panic::raise_str("runtime type error"); + } + out.push_str(&format!("")); + } + } +} + +/// Bare display: strings render as-is (print/template scalar path). +/// # Safety +/// Str/list payloads must be live arena pointers, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_display(v: LkDyn) -> *mut c_char { + let mut out = String::new(); + display_into(&mut out, v, false); + arena_c_string(CString::new(out).unwrap_or_default()) +} + +/// Quoted display: strings render Rust-`{:?}`-style (in-list element path). +/// # Safety +/// Str/list payloads must be live arena pointers, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_display_quoted(v: LkDyn) -> *mut c_char { + let mut out = String::new(); + display_into(&mut out, v, true); + arena_c_string(CString::new(out).unwrap_or_default()) +} + +/// `len` of a Dyn by runtime tag: list length, map entry count, string +/// Unicode scalar count; scalars are the VM's loud failure. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_len_of(v: LkDyn) -> i64 { + // `len()` has its own wording, which names the struct rather than the + // method. + if let Some(name) = struct_type_name(v) { + crate::panic::raise_str(&alloc::format!("`len()` has no answer for {name}")); + } + match v.tag { + DYN_LIST => dyn_list(v).len() as i64, + // Counted off the carrier — no boxing, which is the whole point of a + // tag that names one. + tag if (DYN_TLIST_BASE..DYN_TLIST_END).contains(&tag) => { + crate::lklist::typed_list_len(tag - DYN_TLIST_BASE, v.payload as *mut c_void) + } + DYN_MAP => { + if (v.payload as *mut c_void).is_null() { + 0 + } else { + dyn_map(v).len() as i64 + } + } + DYN_STR => unsafe { dyn_str(v) }.chars().count() as i64, + // SAFETY: as in `dyn_eq_inner`, the tag guarantees the handle kind. + tag if (DYN_TMAP_BASE..DYN_TMAP_END).contains(&tag) => { + crate::lkmap::typed_map_len(tag - DYN_TMAP_BASE, v.payload as *mut c_void) + } + DYN_SET => unsafe { crate::lkset::lkrt_lkset_len(v.payload as *mut c_void) }, + DYN_BYTES => unsafe { crate::lkbytes::lkrt_lkbytes_len(v.payload as *mut c_void) }, + // SAFETY: a `DYN_SLICE` payload is a live window handle — the tag is + // only ever set by `from_slice`. + DYN_SLICE => unsafe { crate::lkslice::lkrt_lkslice_i64_len(v.payload as *mut c_void) }, + // The interpreter names the operation and what it got, and writes `nil` + // in lower case there. A caught error is printed output, so "runtime + // type error" was a wrong answer and not merely a poor message. + _ => crate::panic::raise_str(&alloc::format!( + "`len()` works on a String, List, Map, Set, Bytes or Slice, got {}", + match kind_name(v).as_str() { + "Nil" => alloc::string::String::from("nil"), + _ => kind_name(v), + } + )), + } +} + +/// Guarded list unboxing: a `Vec` handle for a boxed list of either +/// representation (loud failure otherwise — a method on a non-list is a VM +/// error). +/// +/// **Read-only.** A `DYN_LIST` hands back its own handle, so a write through it +/// would be visible; a typed carrier has to box its elements, so a write +/// through *that* one would be lost. The two cannot both be served here, and +/// every name that reaches this guard — `map`, `filter`, `reduce`, `take`, +/// `skip`, `concat`, `unique`, `sort`, `reverse` — builds a new list and leaves +/// the receiver alone (`sort` and `reverse` answer new lists in this language; +/// they do not sort in place). `push` is the one mutating consumer and it goes +/// to [`lkrt_dyn_list_push`], which reaches the carrier itself. +/// +/// `no_unbox_list_name_mutates_its_receiver` in the lowering is what keeps +/// that true: a mutating name given `unbox_list` would silently start dropping +/// writes here. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_as_list(v: LkDyn) -> *mut c_void { + if v.tag == DYN_LIST { + return v.payload as *mut c_void; + } + if !is_list_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + arena_handle(crate::lklist::typed_list_boxed( + v.tag - DYN_TLIST_BASE, + v.payload as *mut c_void, + )) +} + +/// `xs.push(e)` where `xs` is boxed — appends to the carrier behind the tag, so +/// the box and the original stay one list. +/// +/// The counterpart to [`lkrt_dyn_as_list`]'s read-only rule. `ListPush` used to +/// unbox through that guard, which for a typed carrier meant appending to a +/// materialized copy: `c[0].push(9)` answered as if nothing had been pushed. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_list_push(v: LkDyn, value: LkDyn) { + if v.tag == DYN_LIST { + // SAFETY: a `DYN_LIST` payload is a live `Vec`, uniquely + // reachable through this call for its duration. + unsafe { (*(v.payload as *mut Vec)).push(value) }; + return; + } + if !is_list_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + crate::lklist::typed_list_push(v.tag - DYN_TLIST_BASE, v.payload as *mut c_void, value); +} + +/// `xs.insert(i, v)` / `xs.remove_at(i)` / `pop`'s drop half where `xs` is +/// boxed — the mutating siblings of [`lkrt_dyn_list_push`], and boxed for the +/// same reason: `dyn.as_list` is read-only, so a write through it would land +/// in a materialized copy. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_list_insert(v: LkDyn, index: i64, value: LkDyn) { + if v.tag == DYN_LIST { + // SAFETY: a `DYN_LIST` payload is a live `Vec`. + unsafe { crate::lklist::lkrt_lklist_dyn_insert(v.payload as *mut c_void, index, value) }; + return; + } + if !is_list_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + crate::lklist::typed_list_insert(v.tag - DYN_TLIST_BASE, v.payload as *mut c_void, index, value); +} + +/// See [`lkrt_dyn_list_insert`]. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_list_remove_at(v: LkDyn, index: i64) -> LkDyn { + if v.tag == DYN_LIST { + // SAFETY: as above. + return unsafe { crate::lklist::lkrt_lklist_dyn_remove_at(v.payload as *mut c_void, index) }; + } + if !is_list_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + crate::lklist::typed_list_remove_at(v.tag - DYN_TLIST_BASE, v.payload as *mut c_void, index) +} + +/// See [`lkrt_dyn_list_insert`]. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_list_drop_last(v: LkDyn) { + if v.tag == DYN_LIST { + // SAFETY: as above. + unsafe { crate::lklist::lkrt_lklist_dyn_drop_last(v.payload as *mut c_void) }; + return; + } + if !is_list_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + crate::lklist::typed_list_drop_last(v.tag - DYN_TLIST_BASE, v.payload as *mut c_void); +} + +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_dyn_from_map(handle: *mut c_void) -> LkDyn { + LkDyn { + tag: DYN_MAP, + payload: handle as i64, + } +} + +fn dyn_map<'a>(v: LkDyn) -> &'a crate::lkmap::StrDynMap { + let handle = v.payload as *mut c_void; + debug_assert!(!handle.is_null()); + unsafe { &*(handle as *mut crate::lkmap::StrDynMap) } +} + +/// Constant-string field read on a Dyn: a map tag of **either** representation +/// looks the key up (missing key → Nil, the VM's nil-on-missing); any non-map +/// tag is the VM's loud failure on member access. +/// +/// The typed arm is why this dispatches rather than unboxing. A typed map boxed +/// in place keeps its own carrier, so `dyn.as_map` — which hands back a +/// `str_dyn` handle — cannot serve it, and a member read through that guard +/// raised `runtime type error` on a program the VM answers. Every read of a +/// boxed value has to know both representations; only `len`, display and +/// equality did. +/// +/// # Safety +/// `key` must be a NUL-terminated string; a Map payload must be a live +/// `map_h str_dyn` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_field(v: LkDyn, key: *const c_char) -> LkDyn { + if !is_map_tag(v.tag) || (v.payload as *mut c_void).is_null() { + crate::panic::raise_str("runtime type error"); + } + let key = if key.is_null() { + "" + } else { + unsafe { CStr::from_ptr(key) }.to_str().unwrap_or("") + }; + if v.tag == DYN_MAP { + return dyn_map(v).get(key).copied().unwrap_or(LkDyn::NIL); + } + map_entries(v) + .get(&crate::vm_mirror::str_key(key)) + .copied() + .unwrap_or(LkDyn::NIL) +} + +/// [`lkrt_dyn_field`] read by **position**, with the key as the check. +/// +/// The boxed twin of `lkrt_lkmap_str_dyn_get_at`, for the shape a member chain +/// produces: `nodes[i].next` reads its element as a boxed value, so the field +/// read goes through the tag check rather than through a typed map handle. +/// Only the boxed `Map` representation has a position to read; a +/// typed carrier is never a struct, and falls through to the keyed path. +/// +/// # Safety +/// As [`lkrt_dyn_field`], plus `key_len` bytes readable at `key`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_field_at(v: LkDyn, index: i64, key: *const c_char, key_len: i64) -> LkDyn { + if !is_map_tag(v.tag) || (v.payload as *mut c_void).is_null() { + crate::panic::raise_str("runtime type error"); + } + if v.tag == DYN_MAP + && index >= 0 + && let Some((found, value)) = dyn_map(v).get_index(index as usize) + && found.len() == key_len as usize + // SAFETY: `key_len` bytes are readable at `key`, as documented. + && found.as_bytes() == unsafe { core::slice::from_raw_parts(key as *const u8, key_len as usize) } + { + return *value; + } + // SAFETY: as documented. + unsafe { lkrt_dyn_field(v, key) } +} + +/// Index into a Dyn: a List tag indexes like `lkrt_lklist_dyn_at` +/// (negative-from-tail, OOB → Nil); any non-container tag is the VM's +/// "index on a non-container" loud failure. +/// `container[key]` where *both* are boxed. +/// +/// The static types say nothing about which access this is, so the tag decides +/// — which is what the VM does. A string key reads a field; an integer key +/// indexes a sequence but *looks up* in a map, because an integer-keyed map's +/// keys are keys and not positions (`{3: "a"}[3]` is `"a"`, and there is no +/// element 3). +/// +/// # Safety +/// +/// `key`'s payload must be a valid interned string when its tag says so, which +/// is the runtime's own invariant for a `DYN_STR`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_get(v: LkDyn, key: LkDyn) -> LkDyn { + match key.tag { + DYN_I64 => lkrt_dyn_index(v, key.payload), + DYN_STR => unsafe { lkrt_dyn_field(v, key.payload as *const c_char) }, + // A map's subscript is a **key**, and `nil` and a Bool are keys — the + // interpreter stores them (`m[nil] = 1` gives `{nil:1}`) and answers a + // miss with nil, where this raised "runtime type error". A Float is not + // a key at all and `key_from_dyn` raises with the interpreter's own + // wording for that and for every other non-key kind. + // + // The keyed view is built per lookup, which is what the typed branch of + // `lkrt_dyn_map_has` already does; only `nil` and `Bool` keys reach it, + // and an Int or a String key still takes its own direct path above. + _ if is_map_tag(v.tag) => { + let key = crate::vm_mirror::key_from_dyn(key); + map_entries(v).get(&key).copied().unwrap_or(LkDyn::NIL) + } + _ => crate::panic::raise_str("runtime type error"), + } +} + +/// `c.clear()` on a boxed container, dispatched on the tag. +/// +/// Every carrier has its own `clear`, and the static type usually says which. +/// A receiver that reached two call sites with different carriers is a `Dyn` +/// and has none — `fn empty(c) { c.clear(); }` called with two maps was the +/// shape with no arm, so the whole module fell back. +/// +/// # Safety +/// `v` must be a live boxed container. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_clear(v: LkDyn) { + reject_struct_receiver(v, "clear"); + let handle = v.payload as *mut c_void; + if handle.is_null() { + return; + } + // SAFETY: the payload is a live handle of the carrier its tag names. + unsafe { + match v.tag { + DYN_LIST => crate::lklist::lkrt_lklist_dyn_clear(handle), + DYN_MAP => crate::lkmap::lkrt_lkmap_str_dyn_clear(handle), + DYN_SET => crate::lkset::lkrt_lkset_clear(handle), + tag if is_map_tag(tag) => crate::lkmap::typed_map_clear(tag - DYN_TMAP_BASE, handle), + tag if is_list_tag(tag) => crate::lklist::typed_list_clear(tag - DYN_TLIST_BASE, handle), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// `m.get(k, default)` where the key is boxed. +/// +/// The key kinds split three ways, which is what makes this its own entry +/// rather than `dyn.get` plus a nil test. A key kind a map cannot hold — +/// a Float, a container — *raises*, and the interpreter prefixes that refusal +/// with the call (`map.get() key: …`). A key that is a key but absent answers +/// the default. And so does a key whose stored value is nil: the interpreter +/// cannot tell those apart either, so `{"k": nil}.get("k", 9)` is `9`. +/// +/// `m.has(k)` with a boxed key needs no entry of its own — `dyn.contains`'s +/// map arm is already that question, and total the same way. `m.delete(k)` +/// does need one and does not have it: removing a key of another kind means +/// reaching a carrier by a key it is not indexed by, which is the general-key +/// representation §62 describes. +/// +/// # Safety +/// `v` must be a live boxed map; `key` and `default` live `LkDyn` values. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_map_get_or(v: LkDyn, key: LkDyn, default: LkDyn) -> LkDyn { + reject_struct_receiver(v, "get"); + if !is_map_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + let key = crate::vm_mirror::key_from_dyn_in(key, "map.get() key"); + match map_entries(v).get(&key).copied() { + Some(found) if found.tag != DYN_NIL => found, + _ => default, + } +} + +/// `for pair in m` / `m.keys()` / `m.values()` / `m.has(k)` / `m.delete(k)` on +/// a **boxed** map, dispatched on the tag. +/// +/// The unboxed spellings reach a carrier-specific symbol because the static +/// type names the carrier. A boxed map has no static carrier — the tag is the +/// only thing that says which — and `dyn.as_map`, which hands back a `str_dyn` +/// handle, cannot serve a typed one. Unboxing through that guard is what made +/// `c[0].keys()` raise `runtime type error` on a program the VM answers. +/// +/// Materializing a `str_dyn` copy inside the guard would answer the reads and +/// silently drop `delete`, so the dispatch is per operation rather than per +/// unbox. +/// +/// # Safety +/// A map payload must be a live handle of the carrier its tag names. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_map_pairs(v: LkDyn) -> *mut c_void { + reject_struct_receiver(v, "keys"); + if v.tag == DYN_MAP { + // SAFETY: a `DYN_MAP` payload is a live `StrDynMap`. + return unsafe { crate::lkmap::lkrt_lkmap_str_dyn_iter_pairs(v.payload as *mut c_void) }; + } + if !is_map_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + crate::lkmap::typed_map_pair_list(v.tag - DYN_TMAP_BASE, v.payload as *mut c_void) +} + +/// The `n`th component of every `[key, value]` pair — 0 for `.keys()`, 1 for +/// `.values()`. See [`lkrt_dyn_map_pairs`]. +/// +/// # Safety +/// As [`lkrt_dyn_map_pairs`]. +unsafe fn dyn_map_pair_column(v: LkDyn, column: usize) -> *mut c_void { + reject_struct_receiver(v, if column == 0 { "keys" } else { "values" }); + let pairs = unsafe { lkrt_dyn_map_pairs(v) }; + let column: Vec = dyn_slice(pairs) + .iter() + .map(|pair| dyn_list(*pair).get(column).copied().unwrap_or(LkDyn::NIL)) + .collect(); + arena_handle(column) +} + +/// `for x in v` where `v` is boxed — the VM's `to_iter` normalization, decided +/// by the tag. +/// +/// The loop lowering used to call `dyn.as_list` here, which is a *list* guard: +/// every other iterable answered `runtime type error` once boxed, including +/// every map. `to_iter` is not "unwrap a list", it is "what does this value +/// iterate as", and each carrier already has that answer. +/// +/// # Safety +/// The payload must be a live handle of the carrier its tag names. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_to_iter(v: LkDyn) -> *mut c_void { + // A struct instance is not iterable, and its carrier is a map, which is. + // The VM's wording names the object rather than a method. + if let Some(name) = struct_type_name(v) { + crate::panic::raise_str(&alloc::format!("ToIter target object is not iterable: {name:?}")); + } + if is_map_tag(v.tag) { + return unsafe { lkrt_dyn_map_pairs(v) }; + } + match v.tag { + DYN_LIST => v.payload as *mut c_void, + // A typed carrier snapshots, which is what the VM's `to_iter` does for + // every map too: the loop reads elements as values, and a value read + // out of an `i64` carrier has to be boxed to be one. + tag if (DYN_TLIST_BASE..DYN_TLIST_END).contains(&tag) => arena_handle(crate::lklist::typed_list_boxed( + tag - DYN_TLIST_BASE, + v.payload as *mut c_void, + )), + // A window iterates as itself; `len` and indexing on the loop handle + // are window-relative, which is what the loop wants. + DYN_SLICE => v.payload as *mut c_void, + DYN_SET => unsafe { crate::lkset::lkrt_lkset_iter(v.payload as *mut c_void) }, + // The i64 list the unboxed spelling also iterates: byte values, in + // order, boxed one per element so the loop variable is a value. + DYN_BYTES => { + let values: Vec = crate::lkbytes::bytes_slice(v.payload as *mut c_void) + .iter() + .map(|byte| lkrt_dyn_from_i64(i64::from(*byte))) + .collect(); + arena_handle(values) + } + DYN_STR => unsafe { crate::lkstr::lkrt_str_chars(v.payload as *const c_char) }, + _ => crate::panic::raise_str("runtime type error"), + } +} + +/// `needle in v` where `v` is boxed — the tag decides what membership means. +/// +/// A map answers **key** membership (a stored nil still counts, which is why it +/// is not get-then-test); every other container answers element membership +/// under [`dyn_eq_inner`] — with a byte string excepted below, the only carrier +/// the VM searches by a different rule. Both are what the unboxed spellings +/// already do; this +/// is the one entry point that can pick between them at run time, which is what +/// a boxed haystack needs — `"a" in c[0]` used to drop the whole program to the +/// VM because the lowering had no arm for a `Dyn` haystack at all. +/// +/// How deep a value may nest before the comparison stops descending. +/// +/// Mirrors `core::val::MAX_VALUE_DEPTH`. The VM's comparator answers rather +/// than reports past this bound, and has to: `sort_by` wants an `Ordering`, and +/// raising half way through a sort would leave the list rearranged anyway. +const MAX_VALUE_DEPTH: u32 = 512; + +/// Where a value's *kind* sits in the sort order. +/// +/// The VM keeps two tables — one over `RuntimeVal`, one over `HeapValue` — and +/// reaches the second only for two heap values. Flattening them is sound +/// because the two agree wherever both apply: a short string is +/// `RuntimeVal::ShortStr` (4) against any heap value (5), and a long one is a +/// heap `String` (0) against `Bytes` (1), `List` (2), `Map` (3) — the same +/// relative order either way. Which representation a string happens to have is +/// not something a program can see, and this is why. +fn kind_rank(v: LkDyn) -> u8 { + // Checked before the map test: `DYN_SLICE` shares a value with + // `DYN_TMAP_END`, and `is_map_tag` excludes the end of the range for + // exactly that reason. A window is a list here as it is everywhere else. + if is_list_tag(v.tag) || v.tag == DYN_SLICE { + return 5; + } + if is_map_tag(v.tag) { + // A struct instance is a *marked map* in this runtime and a + // `HeapValue::Object` in the VM, which ranks above `Map`: + // `[{"k": 1}, P { x: 1 }].sort()` keeps that order and the other + // spelling reverses. The tag cannot tell the two apart; the mark can. + return if lkrt_dyn_obj_type_id(v) != 0 { 8 } else { 6 }; + } + match v.tag { + DYN_NIL => 0, + DYN_BOOL => 1, + DYN_I64 | DYN_F64 => 2, + DYN_STR => 3, + DYN_BYTES => 4, + DYN_SET => 7, + DYN_CLOSURE => 9, + DYN_CHAN => 11, + DYN_TASK => 12, + DYN_STREAM => 13, + _ => 10, + } +} + +/// A sequence's elements, whether it is a list carrier or a window. +fn sequence_elements<'a>(v: LkDyn) -> alloc::borrow::Cow<'a, [LkDyn]> { + if v.tag == DYN_SLICE { + // SAFETY: a `DYN_SLICE` payload is a live window handle. + return alloc::borrow::Cow::Owned( + unsafe { crate::lkslice::window_elements(v.payload as *mut c_void) } + .iter() + .map(|value| lkrt_dyn_from_i64(*value)) + .collect(), + ); + } + dyn_list_values(v) +} + +/// The VM's `compare_runtime_values`, mirrored — the order `sort`, `min` and +/// `max` use on a list whose elements are not all one carrier. +/// +/// This is the mirror the boxed carrier was declined for, and the reasons it +/// was declined are the three things below that a copy would have got wrong: +/// the two rank tables are not one table until you check that they agree, a +/// window is a list but shares a tag value with the end of the map range, and a +/// struct is a marked map here and a distinct heap kind there. +/// +/// Everything that is not nil, a bool, a number, a string or a sequence +/// compares **by kind alone** — two maps are equal, two byte strings are equal, +/// two structs are equal. That is the VM's rule and it is deliberate there: a +/// map has no order against another map, and grouping them deterministically +/// beats calling the comparison a failure. +pub(crate) fn dyn_compare(a: LkDyn, b: LkDyn) -> core::cmp::Ordering { + dyn_compare_at(a, b, 0) +} + +fn dyn_compare_at(a: LkDyn, b: LkDyn, depth: u32) -> core::cmp::Ordering { + use core::cmp::Ordering; + let (rank_a, rank_b) = (kind_rank(a), kind_rank(b)); + if rank_a != rank_b { + return rank_a.cmp(&rank_b); + } + match rank_a { + 0 => Ordering::Equal, + 1 => (a.payload != 0).cmp(&(b.payload != 0)), + // Int and Float share a rank and compare as numbers, so `1 < 1.5 < 2` + // holds however each was written. Two Ints stay exact; anything else + // goes through the total float order, which is where NaN and `-0.0` + // are decided (see `lklist::compare_floats`). + 2 => { + if a.tag == DYN_I64 && b.tag == DYN_I64 { + a.payload.cmp(&b.payload) + } else { + let as_f64 = |v: LkDyn| { + if v.tag == DYN_I64 { + v.payload as f64 + } else { + v.f64_value() + } + }; + crate::lklist::compare_floats(as_f64(a), as_f64(b)) } - out.push('}'); } - other => { - if raise_on_unknown { - crate::panic::raise_str("runtime type error"); + // SAFETY: a `DYN_STR` payload is a live NUL-terminated string. + 3 => unsafe { dyn_str(a).as_bytes().cmp(dyn_str(b).as_bytes()) }, + // Lexicographic, and a prefix sorts before what extends it — which is + // what `==` already treats a list as. + 5 => { + if depth >= MAX_VALUE_DEPTH { + return Ordering::Equal; } - out.push_str(&format!("")); + let (xs, ys) = (sequence_elements(a), sequence_elements(b)); + for (x, y) in xs.iter().zip(ys.iter()) { + let ordering = dyn_compare_at(*x, *y, depth + 1); + if ordering != Ordering::Equal { + return ordering; + } + } + xs.len().cmp(&ys.len()) } + _ => Ordering::Equal, } } -/// Bare display: strings render as-is (print/template scalar path). +/// `receiver.contains(needle)` — the *method*, which is not `needle in receiver`. +/// +/// The two differ on exactly one carrier and it matters: the VM gives a map +/// `in` (asking after a key) and gives it no `contains` method at all, so +/// `m.contains("k")` raises there. `lkrt_dyn_contains` is the operator and +/// answers for a map; lowering it for the method would have made a native +/// build answer `true` where the VM stops the program. +/// +/// Every other carrier the operator accepts, the method accepts too, so this +/// rejects the map tag and defers. +/// /// # Safety -/// Str/list payloads must be live arena pointers, or null. +/// `v` and `needle` must be live `LkDyn` values. #[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_dyn_display(v: LkDyn) -> *mut c_char { - let mut out = String::new(); - display_into(&mut out, v, false); - arena_c_string(CString::new(out).unwrap_or_default()) +pub unsafe extern "C" fn lkrt_dyn_seq_contains(v: LkDyn, needle: LkDyn) -> i64 { + if is_map_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + unsafe { lkrt_dyn_contains(v, needle) } } -/// Quoted display: strings render Rust-`{:?}`-style (in-list element path). /// # Safety -/// Str/list payloads must be live arena pointers, or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_dyn_display_quoted(v: LkDyn) -> *mut c_char { - let mut out = String::new(); - display_into(&mut out, v, true); - arena_c_string(CString::new(out).unwrap_or_default()) -} - -/// `len` of a Dyn by runtime tag: list length, map entry count, string -/// Unicode scalar count; scalars are the VM's loud failure. +/// The payload must be a live handle of the carrier its tag names. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_dyn_len_of(v: LkDyn) -> i64 { +pub unsafe extern "C" fn lkrt_dyn_contains(v: LkDyn, needle: LkDyn) -> i64 { + // `in` names the object rather than a method. + if let Some(name) = struct_type_name(v) { + crate::panic::raise_str(&alloc::format!("Contains haystack object is not searchable: {name:?}")); + } + if is_map_tag(v.tag) { + if needle.tag == DYN_STR { + return unsafe { lkrt_dyn_map_has(v, needle.payload as *const c_char) }; + } + // Total, the way the interpreter's `map_contains` is: a needle that + // cannot be a key is not a member. + let Some(key) = crate::vm_mirror::key_from_dyn_opt(needle) else { + return 0; + }; + return i64::from(map_entries(v).contains_key(&key)); + } + if is_list_tag(v.tag) { + return i64::from(dyn_list_values(v).iter().any(|&e| dyn_eq_inner(e, needle))); + } + if !matches!(v.tag, DYN_STR | DYN_SET | DYN_SLICE | DYN_BYTES) { + // The interpreter's own sentence for a haystack that is not one. + crate::panic::raise_str(&alloc::format!( + "Contains haystack expected string/list/map/set/bytes/slice, got {}", + kind_name(v) + )); + } match v.tag { - DYN_LIST => dyn_list(v).len() as i64, - DYN_MAP => { - if (v.payload as *mut c_void).is_null() { - 0 + // A string's members are its substrings, which is what the unboxed + // spelling answers; it was the one carrier `in` did not reach here. + DYN_STR => unsafe { crate::lkstr::lkrt_str_contains(v.payload as *const c_char, lkrt_dyn_as_str(needle)) }, + DYN_SET => unsafe { crate::lkset::lkrt_lkset_has(v.payload as *mut c_void, needle) }, + DYN_SLICE => { + // SAFETY: a `DYN_SLICE` payload is a live window handle. + let window = unsafe { crate::lkslice::window_elements(v.payload as *mut c_void) }; + i64::from(window.iter().any(|&e| dyn_eq_inner(lkrt_dyn_from_i64(e), needle))) + } + // A byte string is the one carrier whose membership is *not* `==`: + // the VM asks `RuntimeVal::Int(byte)` and answers false for everything + // else, so `97.0 in "ab".bytes()` is false while `97.0 in [97]` is + // true. Spelled out rather than delegated, because delegating is + // exactly what made it wrong the other way. + DYN_BYTES => { + let bytes = crate::lkbytes::bytes_slice(v.payload as *mut c_void); + let Some(byte) = (if needle.tag == DYN_I64 { + u8::try_from(needle.payload).ok() } else { - dyn_map(v).len() as i64 - } + None + }) else { + return 0; + }; + i64::from(bytes.contains(&byte)) } - DYN_STR => unsafe { dyn_str(v) }.chars().count() as i64, _ => crate::panic::raise_str("runtime type error"), } } -/// Guarded list unboxing: the handle behind a `DYN_LIST` tag (loud failure -/// otherwise — iterating a non-container is a VM error). +/// `m.keys()` on a boxed map. See [`lkrt_dyn_map_pairs`]. +/// +/// # Safety +/// As [`lkrt_dyn_map_pairs`]. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_dyn_as_list(v: LkDyn) -> *mut c_void { - if v.tag != DYN_LIST { - crate::panic::raise_str("runtime type error"); - } - v.payload as *mut c_void +pub unsafe extern "C" fn lkrt_dyn_map_keys(v: LkDyn) -> *mut c_void { + unsafe { dyn_map_pair_column(v, 0) } } +/// `m.values()` on a boxed map. See [`lkrt_dyn_map_pairs`]. +/// +/// # Safety +/// As [`lkrt_dyn_map_pairs`]. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_dyn_from_map(handle: *mut c_void) -> LkDyn { - LkDyn { - tag: DYN_MAP, - payload: handle as i64, - } -} - -fn dyn_map<'a>(v: LkDyn) -> &'a crate::lkmap::StrDynMap { - let handle = v.payload as *mut c_void; - debug_assert!(!handle.is_null()); - unsafe { &*(handle as *mut crate::lkmap::StrDynMap) } +pub unsafe extern "C" fn lkrt_dyn_map_values(v: LkDyn) -> *mut c_void { + unsafe { dyn_map_pair_column(v, 1) } } -/// Constant-string field read on a Dyn: a Map tag looks the key up (missing -/// key → Nil, the VM's nil-on-missing); any non-map tag is the VM's loud -/// failure on member access. +/// `m.has(k)` on a boxed map — presence, which is order-free, so it reads the +/// keyed view rather than the ordered snapshot. /// /// # Safety -/// `key` must be a NUL-terminated string; a Map payload must be a live -/// `map_h str_dyn` handle. +/// `key` must be NUL-terminated; the payload as [`lkrt_dyn_map_pairs`]. #[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_dyn_field(v: LkDyn, key: *const c_char) -> LkDyn { - if v.tag != DYN_MAP || (v.payload as *mut c_void).is_null() { +pub unsafe extern "C" fn lkrt_dyn_map_has(v: LkDyn, key: *const c_char) -> i64 { + reject_struct_receiver(v, "has"); + if !is_map_tag(v.tag) { crate::panic::raise_str("runtime type error"); } let key = if key.is_null() { @@ -629,37 +2249,103 @@ pub unsafe extern "C" fn lkrt_dyn_field(v: LkDyn, key: *const c_char) -> LkDyn { } else { unsafe { CStr::from_ptr(key) }.to_str().unwrap_or("") }; - dyn_map(v).get(key).copied().unwrap_or(LkDyn::NIL) + if v.tag == DYN_MAP { + return i64::from(dyn_map(v).contains_key(key)); + } + i64::from(map_entries(v).contains_key(&crate::vm_mirror::str_key(key))) } -/// Index into a Dyn: a List tag indexes like `lkrt_lklist_dyn_at` -/// (negative-from-tail, OOB → Nil); any non-container tag is the VM's -/// "index on a non-container" loud failure. -/// `container[key]` where *both* are boxed. -/// -/// The static types say nothing about which access this is, so the tag decides -/// — which is what the VM does. An integer key indexes, a string key reads a -/// field, and anything else is the VM's error. +/// `m.delete(k)` / `m.remove(k)` on a boxed map — removes **in place**, so the +/// box and the original stay one map, and answers the removed value or nil. /// /// # Safety +/// `key` must be NUL-terminated; the payload as [`lkrt_dyn_map_pairs`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_dyn_map_delete(v: LkDyn, key: *const c_char) -> LkDyn { + reject_struct_receiver(v, "delete"); + if v.tag == DYN_MAP { + // SAFETY: a `DYN_MAP` payload is a live `StrDynMap`; `key` is the + // caller's NUL-terminated key. + return unsafe { crate::lkmap::lkrt_lkmap_str_dyn_delete(v.payload as *mut c_void, key) }; + } + if !is_map_tag(v.tag) { + crate::panic::raise_str("runtime type error"); + } + crate::lkmap::typed_map_delete(v.tag - DYN_TMAP_BASE, v.payload as *mut c_void, key) +} + +/// `c[k] = v` where `c` is boxed — stores into the carrier behind the tag, so +/// the box and the original stay one container. /// -/// `key`'s payload must be a valid interned string when its tag says so, which -/// is the runtime's own invariant for a `DYN_STR`. +/// One entry point for both containers, because the key rule is one rule: an +/// integer key on a map is a *key*, not a position (the same adjudication +/// [`lkrt_dyn_index`] states for reads). The key travels boxed so this side can +/// apply it; a key of the wrong shape for the carrier raises. +/// +/// The store twin of [`lkrt_dyn_list_push`]: `dyn.as_list` and `dyn.as_map` are +/// read-only, and a write through either would land in a materialized copy. #[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_dyn_get(v: LkDyn, key: LkDyn) -> LkDyn { - match key.tag { - DYN_I64 => lkrt_dyn_index(v, key.payload), - DYN_STR => unsafe { lkrt_dyn_field(v, key.payload as *const c_char) }, - _ => crate::panic::raise_str("runtime type error"), +pub extern "C" fn lkrt_dyn_index_set(v: LkDyn, key: LkDyn, value: LkDyn) { + if is_map_tag(v.tag) { + if v.tag == DYN_MAP { + // SAFETY: a boxed string key is a live NUL-terminated string. + unsafe { check_declared_field(v, lkrt_dyn_as_str(key), value) }; + // SAFETY: a `DYN_MAP` payload is a live `StrDynMap`; the key + // pointer is the boxed key's own NUL-terminated string. + unsafe { crate::lkmap::lkrt_lkmap_str_dyn_set(v.payload as *mut c_void, lkrt_dyn_as_str(key), value) }; + return; + } + crate::lkmap::typed_map_set(v.tag - DYN_TMAP_BASE, v.payload as *mut c_void, key, value); + return; + } + let index = lkrt_dyn_as_i64(key); + if v.tag == DYN_LIST { + // SAFETY: a `DYN_LIST` payload is a live `Vec`. + unsafe { lkrt_lklist_dyn_set(v.payload as *mut c_void, index, value) }; + return; + } + if !is_list_tag(v.tag) { + crate::panic::raise_str("runtime type error"); } + crate::lklist::typed_list_set(v.tag - DYN_TLIST_BASE, v.payload as *mut c_void, index, value); } +/// An integer key on a map is a *key*, not a position. +/// +/// `{3: 4}[3]` is `4` and there is no element 3 — so a map tag of either +/// representation looks up here rather than indexing. This is the same +/// entry point a constant integer key lowers to directly, which is why the +/// rule lives here and not only in [`lkrt_dyn_get`]. #[unsafe(no_mangle)] pub extern "C" fn lkrt_dyn_index(v: LkDyn, index: i64) -> LkDyn { - if v.tag != DYN_LIST { - crate::panic::raise_str("runtime type error"); + if is_map_tag(v.tag) { + return map_entries(v) + .get(&crate::vm_mirror::RtKey::Int(index)) + .copied() + .unwrap_or(LkDyn::NIL); + } + // A string indexes by character, which is what `s[0]` does on a *typed* + // `Str` already. It reaches here whenever the same string is boxed — + // `[a, b]` destructuring one, for instance, since `IsList` calls a string + // list-like the way the interpreter does. + if v.tag == DYN_STR { + // SAFETY: a `DYN_STR` payload is a live NUL-terminated string. + return unsafe { crate::lkstr::lkrt_str_char_at(v.payload as *const c_char, index) }; + } + if !is_list_tag(v.tag) { + // Two wordings, and which one a value gets is whether it lives on the + // heap: a scalar is named plainly, a heap value is named in quotes by + // the object's type. `dyn_list_values` raises here too, but it is + // shared by every carrier walk and can only say "runtime type error" — + // which is what `nil[0]` used to answer where the interpreter says + // "Nil is not indexable". + let name = kind_name(v); + if matches!(v.tag, DYN_NIL | DYN_BOOL | DYN_I64 | DYN_F64) { + crate::panic::raise_str(&alloc::format!("{name} is not indexable")); + } + crate::panic::raise_str(&alloc::format!("index target object is not indexable: {name:?}")); } - let values = dyn_list(v); + let values = dyn_list_values(v); let len = values.len() as i64; let idx = if index < 0 { len + index } else { index }; if idx < 0 || idx >= len { @@ -730,6 +2416,40 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_push(handle: *mut c_void, value: LkDyn) unsafe { (*(handle as *mut Vec)).push(value) }; } +/// Joins a boxed list with `separator`, each element written bare. +/// +/// `display_into(.., quoted = false)` is the same renderer `lkrt_dyn_display` +/// uses, which is the one the VM's `join` uses too: a string element joins +/// unquoted, while the *quoted* form is what an element gets when it is printed +/// inside a list. Sharing the renderer is the point — the alternative is a +/// second opinion on how `2.0` or `nil` looks. +/// +/// # Safety +/// `handle` must be a live handle from [`lkrt_lklist_dyn_new`], or null; +/// `separator` a valid C string, or null for empty. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_dyn_join(handle: *mut c_void, separator: *const c_char) -> *mut c_char { + let sep = if separator.is_null() { + "" + } else { + // SAFETY: caller guarantees a valid C string. + unsafe { core::ffi::CStr::from_ptr(separator) }.to_str().unwrap_or("") + }; + if handle.is_null() { + return arena_c_string(CString::default()); + } + // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_dyn_new`. + let values = unsafe { &*(handle as *mut Vec) }; + let mut out = String::new(); + for (index, value) in values.iter().enumerate() { + if index > 0 { + out.push_str(sep); + } + display_into(&mut out, *value, false); + } + arena_c_string(CString::new(out).unwrap_or_default()) +} + /// VM indexing semantics: negative counts from the tail, out-of-bounds reads /// yield nil (not an error) — the Dyn carrier holds the nil itself. /// # Safety @@ -753,18 +2473,15 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_at(handle: *mut c_void, index: i64) -> #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lklist_dyn_set(handle: *mut c_void, index: i64, value: LkDyn) { if handle.is_null() { - return; + crate::panic::raise_str("runtime error"); } let values = unsafe { &mut *(handle as *mut Vec) }; - let len = values.len() as i64; - let idx = if index < 0 { len + index } else { index }; - if idx < 0 { - return; - } - let idx = idx as usize; - if idx >= values.len() { - values.resize(idx + 1, LkDyn::NIL); - } + // Out of range is a halt, matching the VM's `list index N out of bounds`. + // This used to *grow* the list to fit (and silently ignore an index before + // the start), so `xs[9] = 1` on a three-element list raised interpreted and + // appended six nils compiled. The wording comes from the one helper the + // typed lists use, because a caught error is printed output. + let idx = crate::lklist::store_index_or_raise(index, values.len()); values[idx] = value; } @@ -795,34 +2512,14 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_eq(a: *mut c_void, b: *mut c_void) -> i i64::from(lhs.len() == rhs.len() && lhs.iter().zip(rhs).all(|(&x, &y)| dyn_eq_inner(x, y))) } -/// The VM's `Contains` (`in`) equality on a Mixed list is `RuntimeVal`'s -/// *derived* `PartialEq` — strictly same-variant: no Int/Float coercion -/// (`1.0 in [1, 2]` is false, unlike `==`), floats by value (`0.0 == -0.0`, -/// `NaN != NaN`, unlike `unique()`'s to_bits), ShortStr (≤7 bytes) by -/// content, heap objects (lists/maps/longer strings) by handle. -fn contains_eq(a: LkDyn, b: LkDyn) -> bool { - if a.tag != b.tag { - return false; - } - match a.tag { - DYN_NIL => true, - DYN_BOOL | DYN_I64 => a.payload == b.payload, - DYN_F64 => a.f64_value() == b.f64_value(), - DYN_STR => { - let (sa, sb) = unsafe { (dyn_str(a), dyn_str(b)) }; - if sa.len() <= 7 && sb.len() <= 7 { - sa == sb - } else { - a.payload == b.payload - } - } - DYN_LIST | DYN_MAP => a.payload == b.payload, - _ => false, - } -} - -/// `needle in xs` under [`contains_eq`] (the `in` operator's semantics — -/// *not* `dyn_eq_inner`, which is the `==` operator's). +/// `needle in xs` under [`dyn_eq_inner`]. +/// +/// `in` and `==` were two rules here and are one in the VM: `list_contains`'s +/// mixed arm calls `runtime_values_equal`, the function `==` calls, and says +/// above itself that it used to be handle identity and that +/// `[1, 2] in [[1, 2], [3]]` answered false for it. This mirror kept the rule +/// the VM had dropped, so that line — and `-`, and `index_of` — answered false +/// compiled and true interpreted. /// # Safety /// `handle` must be a live handle from [`lkrt_lklist_dyn_new`], or null. #[unsafe(no_mangle)] @@ -831,7 +2528,7 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_contains(handle: *mut c_void, value: Lk return 0; } let values = unsafe { &*(handle as *mut Vec) }; - i64::from(values.iter().any(|&e| contains_eq(e, value))) + i64::from(values.iter().any(|&e| dyn_eq_inner(e, value))) } fn dyn_slice<'a>(handle: *mut c_void) -> &'a [LkDyn] { @@ -856,24 +2553,16 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_slice_from(handle: *mut c_void, start: arena_handle(tail) } -/// `xs.take(n)` — the first `n` elements (mirrors `lkrt_lklist_i64_take`). -/// # Safety -/// `handle` must be a live handle from [`lkrt_lklist_dyn_new`], or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_dyn_take(handle: *mut c_void, n: i64) -> *mut c_void { - let values = dyn_slice(handle); - let count = (n as usize).min(values.len()); - arena_handle(values[..count].to_vec()) -} - -/// `xs.skip(n)` — without the first `n` (zero/negative copies everything). +/// Range slice of a boxed list, sharing `lklist::slice_bounds` — one rule, not +/// a fourth copy of "negative counts from the tail and everything clamps". +/// /// # Safety /// `handle` must be a live handle from [`lkrt_lklist_dyn_new`], or null. #[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_dyn_skip(handle: *mut c_void, n: i64) -> *mut c_void { +pub unsafe extern "C" fn lkrt_lklist_dyn_slice(handle: *mut c_void, start: i64, end: i64) -> *mut c_void { let values = dyn_slice(handle); - let start = if n > 0 { (n as usize).min(values.len()) } else { 0 }; - arena_handle(values[start..].to_vec()) + let (start, end) = crate::lklist::slice_bounds(values.len(), start, end); + arena_handle(values[start..end].to_vec()) } /// `xs.chain(ys)` / `xs.concat(ys)` — a fresh concatenation. @@ -894,12 +2583,6 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_chain(a: *mut c_void, b: *mut c_void) - /// `handle` must be a live dyn-list handle (or null); `f` a compiled lambda. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lklist_dyn_map_fn(handle: *mut c_void, f: extern "C" fn(LkDyn) -> LkDyn) -> *mut c_void { - // Snapshotted before the callback runs. `f`/`p` re-enters generated code, - // which can push to *this* list (reallocating its buffer) or raise and - // longjmp past the borrow — either way a slice held across the call is - // unsound. CLAUDE.md's lkrt rule ("never call a raise-capable function while - // holding a lock guard or RefCell borrow") is the same rule; a slice borrow - // is just a third way to hold one. // Indexed, re-dereferencing the handle each step: `f`/`p` re-enters generated // code, which can push to *this* list (reallocating its buffer) or raise and // longjmp past a borrow, so none may be held across the call. Re-deref rather @@ -924,12 +2607,6 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_filter_fn( handle: *mut c_void, p: extern "C" fn(LkDyn) -> bool, ) -> *mut c_void { - // Snapshotted before the callback runs. `f`/`p` re-enters generated code, - // which can push to *this* list (reallocating its buffer) or raise and - // longjmp past the borrow — either way a slice held across the call is - // unsound. CLAUDE.md's lkrt rule ("never call a raise-capable function while - // holding a lock guard or RefCell borrow") is the same rule; a slice borrow - // is just a third way to hold one. // Indexed, re-dereferencing the handle each step: `f`/`p` re-enters generated // code, which can push to *this* list (reallocating its buffer) or raise and // longjmp past a borrow, so none may be held across the call. Re-deref rather @@ -957,12 +2634,6 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_reduce_fn( init: LkDyn, f: extern "C" fn(LkDyn, LkDyn) -> LkDyn, ) -> LkDyn { - // Snapshotted before the callback runs. `f`/`p` re-enters generated code, - // which can push to *this* list (reallocating its buffer) or raise and - // longjmp past the borrow — either way a slice held across the call is - // unsound. CLAUDE.md's lkrt rule ("never call a raise-capable function while - // holding a lock guard or RefCell borrow") is the same rule; a slice borrow - // is just a third way to hold one. // Indexed, re-dereferencing the handle each step: `f`/`p` re-enters generated // code, which can push to *this* list (reallocating its buffer) or raise and // longjmp past a borrow, so none may be held across the call. Re-deref rather @@ -979,15 +2650,103 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_reduce_fn( acc } +/// `xs.map(f)` where `f` is a closure *value* rather than a compiled address. +/// +/// The three `*_fn` helpers above take a raw function pointer, which is only +/// available when the lowering knows which lambda the callback register names. +/// A callback read out of a container or passed through a parameter is a +/// `DYN_CLOSURE`, and these three are the same folds called through it. +/// +/// # Safety +/// `handle` must be a live dyn-list handle (or null); `callee` a `DYN_CLOSURE`. +// `std`-only for the reason the closure arm of `display_into` is: a closure +// value cannot exist without `lkclosure`, which is where the deep-copy model +// that owns its captures lives. +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_dyn_map_closure(handle: *mut c_void, callee: LkDyn) -> *mut c_void { + // Indexed, re-dereferencing the handle each step, for the reason the `*_fn` + // helpers document: the callback re-enters generated code. + let len = dyn_slice(handle).len(); + let mut mapped: Vec = Vec::with_capacity(len); + for index in 0..len { + let Some(&value) = dyn_slice(handle).get(index) else { + break; + }; + // SAFETY: as documented. + mapped.push(unsafe { crate::lkclosure::call_with(callee, &mut alloc::vec![value]) }); + } + arena_handle(mapped) +} + +/// `xs.filter(p)` with a closure value. +/// +/// The predicate's result is judged the way the interpreter judges it +/// (`core_methods::list_filter`): a `Bool` is itself, `nil` is false, anything +/// else is true. The `*_fn` path cannot do that — it demands a `Bool`-returning +/// callback at compile time — but a closure's return type is not known here. +/// +/// # Safety +/// As [`lkrt_lklist_dyn_map_closure`]. +// `std`-only for the reason the closure arm of `display_into` is: a closure +// value cannot exist without `lkclosure`, which is where the deep-copy model +// that owns its captures lives. +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_dyn_filter_closure(handle: *mut c_void, callee: LkDyn) -> *mut c_void { + let len = dyn_slice(handle).len(); + let mut kept: Vec = Vec::new(); + for index in 0..len { + let Some(&value) = dyn_slice(handle).get(index) else { + break; + }; + // SAFETY: as documented. + let verdict = unsafe { crate::lkclosure::call_with(callee, &mut alloc::vec![value]) }; + let keep = match verdict.tag { + DYN_BOOL => verdict.payload != 0, + DYN_NIL => false, + _ => true, + }; + if keep { + kept.push(value); + } + } + arena_handle(kept) +} + +/// `xs.reduce(init, f)` with a closure value. +/// +/// # Safety +/// As [`lkrt_lklist_dyn_map_closure`]. +// `std`-only for the reason the closure arm of `display_into` is: a closure +// value cannot exist without `lkclosure`, which is where the deep-copy model +// that owns its captures lives. +#[cfg(feature = "std")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_dyn_reduce_closure(handle: *mut c_void, init: LkDyn, callee: LkDyn) -> LkDyn { + let len = dyn_slice(handle).len(); + let mut acc = init; + for index in 0..len { + let Some(&value) = dyn_slice(handle).get(index) else { + break; + }; + // SAFETY: as documented. + acc = unsafe { crate::lkclosure::call_with(callee, &mut alloc::vec![acc, value]) }; + } + acc +} + /// `xs.chunk(size)` — split into `size`-element groups, last group short. -/// `size <= 0` is a VM error (loud failure). +/// `size <= 0` raises the interpreter's sentence. /// # Safety /// `handle` must be a live handle from [`lkrt_lklist_dyn_new`], or null. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lklist_dyn_chunk(handle: *mut c_void, size: i64) -> *mut c_void { if size <= 0 { - crate::rt_eprintln!("list.chunk() size must be positive"); - crate::panic::raise_str("runtime type error"); + // The message *is* the error, as it is in the interpreter. Printing it + // and raising "runtime type error" put the explanation on stderr and a + // different sentence in the `catch`. + crate::panic::raise_str("list.chunk() size must be positive"); } let chunks: Vec = dyn_slice(handle) .chunks(size as usize) @@ -1022,41 +2781,22 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_zip(a: *mut c_void, b: *mut c_void) -> arena_handle(pairs) } -/// The VM's `runtime_values_equal` (core_methods.rs) — used by `unique()`, -/// and deliberately *not* `dyn_eq_inner`: numerics compare by `to_bits` -/// (`0.0 != -0.0`, `1 == 1.0`), strings compare by content only when both -/// fit the VM's 7-byte `ShortStr` inline form (longer strings are heap -/// objects there and compare by handle), lists/maps compare by handle. -fn unique_eq(a: LkDyn, b: LkDyn) -> bool { - match (a.tag, b.tag) { - (DYN_NIL, DYN_NIL) => true, - (DYN_BOOL, DYN_BOOL) | (DYN_I64, DYN_I64) | (DYN_F64, DYN_F64) => a.payload == b.payload, - (DYN_I64, DYN_F64) => (a.payload as f64).to_bits() == b.payload as u64, - (DYN_F64, DYN_I64) => a.payload as u64 == (b.payload as f64).to_bits(), - (DYN_STR, DYN_STR) => { - // Longer strings are heap objects in the VM with no stable - // identity across list representations (typed String lists - // re-alloc every element on read, so `[s, s].unique()` keeps - // both) — and native constants intern, so pointer identity - // over-merges literals. "Never equal" matches the VM on every - // shape except a Mixed-list variable repeat (docs/semantics.md). - let (sa, sb) = unsafe { (dyn_str(a), dyn_str(b)) }; - sa.len() <= 7 && sb.len() <= 7 && sa == sb - } - (DYN_LIST, DYN_LIST) | (DYN_MAP, DYN_MAP) => a.payload == b.payload, - _ => false, - } -} - -/// `xs.unique()` — order-preserving dedup under [`unique_eq`]. O(n²) like -/// the VM. +/// `xs.unique()` — order-preserving dedup under `==`. O(n²), like the VM's +/// mixed-list path. +/// +/// This used to call a `unique_eq` of its own: numerics by `to_bits`, strings +/// "never equal" past seven bytes, lists and maps by handle. That mirrored the +/// VM *of the time*; once the VM's equality became heap-aware, the two drifted +/// apart with nothing to catch it — `[s, s].unique()` and `[[1], [1]].unique()` +/// answered differently on the two backends, and the differential corpus +/// deliberately did not cover them. /// # Safety /// `handle` must be a live handle from [`lkrt_lklist_dyn_new`], or null. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lklist_dyn_unique(handle: *mut c_void) -> *mut c_void { let mut unique: Vec = Vec::new(); for &item in dyn_slice(handle) { - if !unique.iter().any(|&seen| unique_eq(seen, item)) { + if !unique.iter().any(|&seen| dyn_eq_inner(seen, item)) { unique.push(item); } } @@ -1071,8 +2811,8 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_unique(handle: *mut c_void) -> *mut c_v pub unsafe extern "C" fn lkrt_lklist_dyn_flatten(handle: *mut c_void) -> *mut c_void { let mut flat: Vec = Vec::new(); for &item in dyn_slice(handle) { - if item.tag == DYN_LIST { - flat.extend_from_slice(dyn_list(item)); + if is_list_tag(item.tag) { + flat.extend_from_slice(&dyn_list_values(item)); } else { flat.push(item); } @@ -1093,6 +2833,140 @@ pub unsafe extern "C" fn lkrt_lklist_dyn_display(handle: *mut c_void) -> *mut c_ arena_c_string(CString::new(out).unwrap_or_default()) } +/// Refuses a store into a declared field whose type the value does not satisfy. +/// +/// The native half of the interpreter's rule (`val::value_satisfies_declared`): +/// a `struct P { v: Int }` whose `v` can hold a String makes the declaration +/// decorative, and the type checker only sees the stores it can type. A store +/// through an untyped binding reaches here. +/// +/// Scalars only, and `DECLARED_ANY` for everything else, so the common store +/// costs one table lookup and one tag test. +/// +/// # Safety +/// `key` must be a NUL-terminated string. +pub(crate) unsafe fn check_declared_field(target: LkDyn, key: *const c_char, value: LkDyn) { + // SAFETY: as documented. + unsafe { check_declared_field_of(lkrt_dyn_obj_type_id(target), key, value) } +} + +/// The declared-field check for a store whose struct type only the *mark* +/// knows — a write through a value the lowering could not name. +/// +/// # Safety +/// `key` must be a NUL-terminated string; `handle` a live map handle or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_check_marked_field(handle: *mut c_void, key: *const c_char, value: LkDyn) { + // SAFETY: as documented. + let type_id = unsafe { handle_type_id(handle) }; + // SAFETY: as documented. + unsafe { check_declared_field_of(type_id, key, value) } +} + +/// [`lkrt_check_marked_field`] with the field name as a boxed string — a store +/// whose key is computed (`p[name] = v`). +/// +/// # Safety +/// `handle` must be a live map handle or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_check_marked_field_dyn(handle: *mut c_void, key: LkDyn, value: LkDyn) { + if key.tag != DYN_STR { + return; + } + // SAFETY: a `DYN_STR` payload is a live NUL-terminated string. + unsafe { lkrt_check_marked_field(handle, key.payload as *const c_char, value) } +} + +/// The declared-field check with the declaration **passed in**. +/// +/// The lowering knows the struct's type and the field's declared code, so a +/// store it cannot rule out statically needs no table lookup at run time: the +/// code is a constant and this is a tag compare. The table-driven form above is +/// for a store through a value whose struct type only the mark knows. +/// +/// # Safety +/// `type_name` and `key` must be NUL-terminated strings. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_check_declared_field( + type_name: *const c_char, + key: *const c_char, + declared: i64, + value: LkDyn, +) { + if satisfies_declared(declared, value) { + return; + } + // SAFETY: as documented. + let (type_name, key) = unsafe { + ( + core::ffi::CStr::from_ptr(type_name).to_string_lossy().into_owned(), + core::ffi::CStr::from_ptr(key).to_string_lossy().into_owned(), + ) + }; + raise_declared_field(&type_name, &key, declared, value); +} + +/// # Safety +/// As [`check_declared_field`]. +unsafe fn check_declared_field_of(type_id: i64, key: *const c_char, value: LkDyn) { + if type_id == 0 || key.is_null() { + return; + } + // SAFETY: as documented. + let key = unsafe { core::ffi::CStr::from_ptr(key) }.to_string_lossy().into_owned(); + check_declared_value(type_id, &key, value); +} + +/// The check itself, once the field name is a `str`. +fn check_declared_value(type_id: i64, key: &str, value: LkDyn) { + let Some(declared) = with_struct_types(|types| { + types + .get(&type_id) + .and_then(|desc| desc.fields.iter().find(|(name, _)| name == key).map(|(_, code)| *code)) + }) else { + return; + }; + if satisfies_declared(declared, value) { + return; + } + let type_name = with_struct_types(|types| types.get(&type_id).map(|desc| desc.name.clone())).unwrap_or_default(); + raise_declared_field(&type_name, key, declared, value); +} + +/// Whether `value` may be stored in a field declared with `declared`. +fn satisfies_declared(declared: i64, value: LkDyn) -> bool { + if declared == DECLARED_ANY { + return true; + } + if declared & DECLARED_NULLABLE != 0 && value.tag == DYN_NIL { + return true; + } + match declared & !DECLARED_NULLABLE { + DECLARED_INT => value.tag == DYN_I64, + // An `Int` satisfies a `Float` field: the language never coerces at a + // typed boundary, so it stays an `Int` and the field holds one. + DECLARED_FLOAT => value.tag == DYN_I64 || value.tag == DYN_F64, + DECLARED_BOOL => value.tag == DYN_BOOL, + DECLARED_STR => value.tag == DYN_STR, + _ => true, + } +} + +fn raise_declared_field(type_name: &str, key: &str, declared: i64, value: LkDyn) -> ! { + let declared_name = match declared & !DECLARED_NULLABLE { + DECLARED_INT => "Int", + DECLARED_FLOAT => "Float", + DECLARED_BOOL => "Bool", + DECLARED_STR => "String", + _ => "Any", + }; + let suffix = if declared & DECLARED_NULLABLE != 0 { "?" } else { "" }; + crate::panic::raise_str(&alloc::format!( + "field `{key}` of {type_name} is declared {declared_name}{suffix}, and a {} cannot be stored in it", + kind_name_of(value) + )) +} + #[cfg(test)] mod tests { use super::*; @@ -1138,10 +3012,17 @@ mod tests { let mixed = unsafe { lkrt_dyn_add(lkrt_dyn_from_i64(2), lkrt_dyn_from_f64(0.5)) }; assert_eq!(mixed.tag, DYN_F64); assert_eq!(mixed.f64_value(), 2.5); - // `/` always yields Float (semantics.md 数值). + // `/` yields a Float, even for two Ints — the rule the checker always + // stated and that both executors now implement. let div = lkrt_dyn_div(lkrt_dyn_from_i64(20), lkrt_dyn_from_i64(4)); assert_eq!(div.tag, DYN_F64); assert_eq!(div.f64_value(), 5.0); + let fractional = lkrt_dyn_div(lkrt_dyn_from_i64(7), lkrt_dyn_from_i64(2)); + assert_eq!(fractional.f64_value(), 3.5); + // And `f64` division by zero is an infinity rather than a raise. + let infinite = lkrt_dyn_div(lkrt_dyn_from_i64(1), lkrt_dyn_from_i64(0)); + assert_eq!(infinite.tag, DYN_F64); + assert!(infinite.f64_value().is_infinite()); let cat = unsafe { lkrt_dyn_add(s("foo"), s("bar")) }; assert_eq!(cat.tag, DYN_STR); assert_eq!(text(cat.payload as *mut c_char), "foobar"); @@ -1174,32 +3055,44 @@ mod tests { lkrt_lklist_dyn_push(xs, lkrt_dyn_from_bool(1)); lkrt_lklist_dyn_push(xs, lkrt_dyn_from_nil()); } - // Comma-separated no spaces; strings {:?}-quoted; 2.0 → "2" (Rust - // to_string); bare-vs-quoted only differs for strings. - // Mixed lists render string elements bare (VM's Mixed-list path). - assert_eq!(text(unsafe { lkrt_lklist_dyn_display(xs) }), "[1,b c,2,true,nil]"); + // Comma-separated no spaces; `2.0` → "2" (Rust to_string); a string + // inside a container is `{:?}`-quoted, whatever the container's + // representation is. This asserted the bare form, mirroring a VM quirk + // where a *mixed* list rendered strings bare and a typed string list + // quoted them — one value, two renderings, decided by an internal + // representation no program can see. + assert_eq!(text(unsafe { lkrt_lklist_dyn_display(xs) }), "[1,\"b c\",2,true,nil]"); assert_eq!(text(unsafe { lkrt_dyn_display(s("b c")) }), "b c"); assert_eq!(text(unsafe { lkrt_dyn_display_quoted(s("b c")) }), "\"b c\""); } + /// `unique()` dedups by `==`, like everything else. + /// + /// This test used to pin a `unique_eq` of its own — numerics by `to_bits`, + /// strings "never equal" past seven bytes, lists by handle — described as + /// "VM handle semantics". It *was* the VM's rule once; the VM's equality + /// later became heap-aware and this did not follow, so the two backends + /// disagreed about `[s, s].unique()` and `[[1], [1]].unique()` with nothing + /// to catch it. There is one equality now. #[test] - fn unique_eq_is_vm_handle_semantics() { - // Numerics by to_bits: 1 == 1.0 dedups, 0.0 vs -0.0 does not. - assert!(unique_eq(lkrt_dyn_from_i64(1), lkrt_dyn_from_f64(1.0))); - assert!(!unique_eq(lkrt_dyn_from_f64(0.0), lkrt_dyn_from_f64(-0.0))); - // ShortStr (≤7 bytes) by content; longer strings never dedup - // (docs/semantics.md unique() 裁决). - assert!(unique_eq(s("ab"), s("ab"))); - assert!(!unique_eq(s("longer-than-seven"), s("longer-than-seven"))); - // Lists by handle, not structure. + fn unique_dedups_by_the_same_equality_as_everything_else() { + // Numerics by value: `1 == 1.0` dedups, and so do the two zeros. + assert!(dyn_eq_inner(lkrt_dyn_from_i64(1), lkrt_dyn_from_f64(1.0))); + assert!(dyn_eq_inner(lkrt_dyn_from_f64(0.0), lkrt_dyn_from_f64(-0.0))); + // …and no NaN equals any NaN, so a list of them never dedups. + assert!(!dyn_eq_inner(lkrt_dyn_from_f64(f64::NAN), lkrt_dyn_from_f64(f64::NAN))); + // Strings by content, at any length. + assert!(dyn_eq_inner(s("ab"), s("ab"))); + assert!(dyn_eq_inner(s("longer-than-seven"), s("longer-than-seven"))); + // Lists structurally, not by handle. let xs = lkrt_lklist_dyn_new(); let ys = lkrt_lklist_dyn_new(); unsafe { lkrt_lklist_dyn_push(xs, lkrt_dyn_from_i64(7)); lkrt_lklist_dyn_push(ys, lkrt_dyn_from_i64(7)); } - assert!(unique_eq(lkrt_dyn_from_list(xs), lkrt_dyn_from_list(xs))); - assert!(!unique_eq(lkrt_dyn_from_list(xs), lkrt_dyn_from_list(ys))); + assert!(dyn_eq_inner(lkrt_dyn_from_list(xs), lkrt_dyn_from_list(xs))); + assert!(dyn_eq_inner(lkrt_dyn_from_list(xs), lkrt_dyn_from_list(ys))); // The chunk/enumerate/zip/flatten family (VM core_methods shapes). let src = lkrt_lklist_dyn_new(); unsafe { @@ -1228,4 +3121,103 @@ mod tests { assert_eq!(unsafe { lkrt_lklist_dyn_at(xs, -1) }.payload, 20); // tail assert_eq!(unsafe { lkrt_lklist_dyn_at(xs, 9) }.tag, DYN_NIL); // OOB → nil } + + /// A marked struct renders `Name{f:v,…}` in declaration order, with nested + /// values quoted — and a **nested struct** renders as a struct, which is the + /// whole reason the type description lives here rather than at the display + /// site (see `docs/aot/aot-gaps-and-lkrt.md`). + #[test] + fn a_marked_struct_displays_like_the_vm() { + // struct P { name: String, v: Int } + unsafe { + lkrt_struct_type_begin(101, c"P".as_ptr()); + lkrt_struct_type_field(101, c"name".as_ptr(), DECLARED_ANY); + lkrt_struct_type_field(101, c"v".as_ptr(), DECLARED_ANY); + // struct Outer { inner: P, tag: String } + lkrt_struct_type_begin(102, c"Outer".as_ptr()); + lkrt_struct_type_field(102, c"inner".as_ptr(), DECLARED_ANY); + lkrt_struct_type_field(102, c"tag".as_ptr(), DECLARED_ANY); + } + + let inner = crate::lkmap::lkrt_lkmap_str_dyn_new(); + unsafe { + crate::lkmap::lkrt_lkmap_str_dyn_set(inner, c"name".as_ptr(), s("a, b")); + crate::lkmap::lkrt_lkmap_str_dyn_set(inner, c"v".as_ptr(), lkrt_dyn_from_i64(-3)); + } + lkrt_lkmap_obj_mark(inner, 101); + let inner_dyn = lkrt_dyn_from_map(inner); + assert_eq!( + text(unsafe { lkrt_dyn_display(inner_dyn) }), + r#"P{name:"a, b",v:-3}"#, + "declaration order, string field quoted" + ); + + let outer = crate::lkmap::lkrt_lkmap_str_dyn_new(); + unsafe { + crate::lkmap::lkrt_lkmap_str_dyn_set(outer, c"inner".as_ptr(), inner_dyn); + crate::lkmap::lkrt_lkmap_str_dyn_set(outer, c"tag".as_ptr(), s("x")); + } + lkrt_lkmap_obj_mark(outer, 102); + assert_eq!( + text(unsafe { lkrt_dyn_display(lkrt_dyn_from_map(outer)) }), + r#"Outer{inner:P{name:"a, b",v:-3},tag:"x"}"#, + "a nested struct is a struct, not a hash-ordered map" + ); + + // An unmarked map is still a map: order is the layout's, and that is + // deliberately outside the lowering subset. + let plain = crate::lkmap::lkrt_lkmap_str_dyn_new(); + unsafe { crate::lkmap::lkrt_lkmap_str_dyn_set(plain, c"k".as_ptr(), lkrt_dyn_from_i64(1)) }; + assert_eq!( + text(unsafe { lkrt_dyn_display(lkrt_dyn_from_map(plain)) }), + r#"{"k":1}"# + ); + } + + /// `in` finds every heap carrier, and finds it by content. + /// + /// Two separate defects met here. A catch-all arm answered `false` for any + /// tag added after it was written, so a `Set`, a `Bytes`, a window or a + /// typed map was never in any list at all. Under it, the arms that did + /// answer compared heap values by *handle* — which the VM had already + /// stopped doing, so `[1, 2] in [[1, 2], [3]]` was false compiled and true + /// interpreted. + /// + /// Both are gone by delegating to `dyn_eq_inner`, so the assertion that + /// matters is the one this test could not make before: a carrier equals a + /// *different* handle holding the same bytes. + #[test] + fn every_heap_carrier_is_found_by_content() { + // SAFETY: both pointers are live NUL-terminated literals. + let (bytes, other_bytes) = unsafe { + ( + crate::lkbytes::lkrt_lkbytes_from_str(c"ab".as_ptr()), + crate::lkbytes::lkrt_lkbytes_from_str(c"cd".as_ptr()), + ) + }; + let set = crate::lkset::lkrt_lkset_new(); + let slice_src = crate::lklist::lkrt_lklist_i64_new(); + let window = unsafe { crate::lkslice::lkrt_lkslice_i64_new(slice_src, 0, 0) }; + let tmap = crate::lkmap::lkrt_lkmap_str_i64_new(); + + for boxed in [ + lkrt_dyn_from_bytes(bytes), + lkrt_dyn_from_set(set), + lkrt_dyn_from_slice(window), + lkrt_dyn_from_typed_map(tmap, crate::lkmap::KIND_STR_I64), + ] { + assert!(dyn_eq_inner(boxed, boxed), "tag {} must find itself", boxed.tag); + } + + // A second handle over the same bytes is the same value — the VM says + // `"ab".bytes() in ["ab".bytes()]`, two allocations, is true. + let same = unsafe { crate::lkbytes::lkrt_lkbytes_from_str(c"ab".as_ptr()) }; + assert!(dyn_eq_inner(lkrt_dyn_from_bytes(bytes), lkrt_dyn_from_bytes(same))); + + // …and different bytes are still not it. + assert!(!dyn_eq_inner( + lkrt_dyn_from_bytes(bytes), + lkrt_dyn_from_bytes(other_bytes) + )); + } } diff --git a/lkrt/src/lklist.rs b/lkrt/src/lklist.rs index 2d25097f..9e513528 100644 --- a/lkrt/src/lklist.rs +++ b/lkrt/src/lklist.rs @@ -110,39 +110,246 @@ pub extern "C" fn lkrt_lklist_i64_from_range(start: i64, end: i64, step: i64, in crate::state::arena_handle(out) } -/// `xs.take(n)` — a fresh list of the first `n` elements. VM edge exactness: -/// the count casts through `usize` (`take_prefix(n as usize)`), so a negative -/// `n` wraps huge and takes everything. +/// `xs.take(n)` / `xs.skip(n)` — a fresh prefix / suffix. A negative count +/// raises, as in the VM: a count has no negative meaning, and the cast this +/// used to perform (`-1 as usize`) took the whole list instead. /// -/// # Safety -/// `handle` must be a live `List` handle, or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_i64_take(handle: *mut c_void, n: i64) -> *mut c_void { - let values: &[i64] = if handle.is_null() { - &[] - } else { - unsafe { &*(handle as *mut Vec) } +/// One macro over both directions and every carrier. Neither operation looks at +/// the element, and the four hand-written copies (`i64` and boxed, take and +/// skip) spelled that raise message four times while `f64` and `str` had no copy +/// at all — so `[1.5, 2.5].take(1)` dropped its module to the VM. +macro_rules! list_window { + ($name:ident, $elem:ty, $method:literal, $window:expr, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void, n: i64) -> *mut c_void { + if n < 0 { + crate::panic::raise_str(&format!( + concat!("list.", $method, "() count must be non-negative, got {}"), + n + )); + } + let values: &[$elem] = if handle.is_null() { + &[] + } else { + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + unsafe { &*(handle as *mut Vec<$elem>) } + }; + // Clamped once, so both directions see an in-range cut. + let cut = (n as usize).min(values.len()); + let window: fn(&[$elem], usize) -> &[$elem] = $window; + crate::state::arena_handle(window(values, cut).to_vec()) + } }; - let count = (n as usize).min(values.len()); - crate::state::arena_handle(values[..count].to_vec()) } -/// `xs.skip(n)` — a fresh list without the first `n` elements. The VM only -/// drains for `n > 0` (zero/negative copies everything). +list_window!( + lkrt_lklist_i64_take, + i64, + "take", + |v, cut| &v[..cut], + "`take(n)` on a `List`." +); +list_window!( + lkrt_lklist_f64_take, + f64, + "take", + |v, cut| &v[..cut], + "`take(n)` on a `List`." +); +list_window!( + lkrt_lklist_str_take, + *const c_char, + "take", + |v, cut| &v[..cut], + "`take(n)` on a `List`." +); +list_window!( + lkrt_lklist_dyn_take, + crate::lkdyn::LkDyn, + "take", + |v, cut| &v[..cut], + "`take(n)` on a boxed-element list." +); +list_window!( + lkrt_lklist_i64_skip, + i64, + "skip", + |v, cut| &v[cut..], + "`skip(n)` on a `List`." +); +list_window!( + lkrt_lklist_f64_skip, + f64, + "skip", + |v, cut| &v[cut..], + "`skip(n)` on a `List`." +); +list_window!( + lkrt_lklist_str_skip, + *const c_char, + "skip", + |v, cut| &v[cut..], + "`skip(n)` on a `List`." +); +list_window!( + lkrt_lklist_dyn_skip, + crate::lkdyn::LkDyn, + "skip", + |v, cut| &v[cut..], + "`skip(n)` on a boxed-element list." +); + +/// The write position a list *method* names, in the VM's exact wording. /// -/// # Safety -/// `handle` must be a live `List` handle, or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_i64_skip(handle: *mut c_void, n: i64) -> *mut c_void { - let values: &[i64] = if handle.is_null() { - &[] - } else { - unsafe { &*(handle as *mut Vec) } +/// Deliberately not [`store_index_or_raise`]: that is the index-*assignment* +/// path (`xs[i] = v`, "list index N out of bounds"), and the method path words +/// the same range failure differently — `list.insert() index N out of bounds +/// (len=N)`. Which index appears also differs between the two messages: the +/// before-the-start one names the index *as written* (so a reader sees the `-9` +/// they typed), the out-of-bounds one names the *resolved* position. A caught +/// error is printed output, so each wording is part of an answer. +/// +/// `allow_len` is the caller's upper bound: `insert` accepts `len`, because that +/// is where an append goes; `remove_at` does not. A null handle is a list of +/// zero, which makes every index a range failure without a special case. +fn method_index_or_raise(method: &str, index: i64, len: usize, allow_len: bool) -> usize { + let resolved = if index < 0 { len as i64 + index } else { index }; + if resolved < 0 { + crate::panic::raise_str(&alloc::format!( + "list.{method}() index {index} is before the start of a list of {len}" + )); + } + let resolved = resolved as usize; + if if allow_len { resolved > len } else { resolved >= len } { + crate::panic::raise_str(&alloc::format!( + "list.{method}() index {resolved} out of bounds (len={len})" + )); + } + resolved +} + +/// `xs.pop()`'s mutation half: drops the last element, answering nothing. +/// +/// `pop` is a read *and* a drop, and the read already exists — `xs.last()` +/// lowers to the carrier's `Maybe` machinery, which is verified and, for `f64`, +/// the only portable shape available: a by-value `{double, i64}` return is a +/// mixed-class aggregate whose registers differ across targets, so Cranelift's +/// scalar signatures cannot model it (hence lkrt's `_get_out` shims). A +/// `*_pop -> LkMaybeF64` would have needed a fifth mechanism for one carrier. +/// So the lowering reads the last element the way `last()` does and then calls +/// this, and the empty case needs no special agreement: reading past the end is +/// already nil, and dropping from empty is already nothing. +macro_rules! list_drop_last { + ($name:ident, $elem:ty, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void) { + if handle.is_null() { + return; + } + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + unsafe { (*(handle as *mut Vec<$elem>)).pop() }; + } + }; +} + +list_drop_last!(lkrt_lklist_i64_drop_last, i64, "`pop()`'s drop half on a `List`."); +list_drop_last!(lkrt_lklist_f64_drop_last, f64, "`pop()`'s drop half on a `List`."); +list_drop_last!( + lkrt_lklist_str_drop_last, + *const c_char, + "`pop()`'s drop half on a `List`. The element pointer is arena-owned, so \ + the value the lowering already read stays valid." +); +list_drop_last!( + lkrt_lklist_dyn_drop_last, + crate::lkdyn::LkDyn, + "`pop()`'s drop half on a boxed-element list." +); + +/// `xs.insert(i, v)` — in place, like `push` and `set`. Answers nothing: the VM +/// evaluates it to the list itself, which the lowering supplies from the +/// receiver it already holds (see `list_clear!` for why returning the handle +/// would be wrong). +macro_rules! list_insert { + ($name:ident, $elem:ty, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void, index: i64, value: $elem) { + if handle.is_null() { + // Still range-checked, so a bad index raises the same message it + // would for a real empty list. + method_index_or_raise("insert", index, 0, true); + return; + } + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + let values = unsafe { &mut *(handle as *mut Vec<$elem>) }; + let at = method_index_or_raise("insert", index, values.len(), true); + values.insert(at, value); + } + }; +} + +list_insert!(lkrt_lklist_i64_insert, i64, "`insert(i, v)` on a `List`."); +list_insert!(lkrt_lklist_f64_insert, f64, "`insert(i, v)` on a `List`."); +list_insert!( + lkrt_lklist_str_insert, + *const c_char, + "`insert(i, v)` on a `List`." +); +list_insert!( + lkrt_lklist_dyn_insert, + crate::lkdyn::LkDyn, + "`insert(i, v)` on a boxed-element list." +); + +/// `xs.remove_at(i)` — removes the element at `i` and answers it. Unlike `pop` +/// the answer is never nil: an out-of-range index raises first, so there is +/// always an element to hand back. +macro_rules! list_remove_at { + ($name:ident, $elem:ty, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void, index: i64) -> $elem { + if handle.is_null() { + // A list of zero: every index is out of range, and this raises + // rather than returning. + method_index_or_raise("remove_at", index, 0, false); + } + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + let values = unsafe { &mut *(handle as *mut Vec<$elem>) }; + let at = method_index_or_raise("remove_at", index, values.len(), false); + values.remove(at) + } }; - let start = if n > 0 { (n as usize).min(values.len()) } else { 0 }; - crate::state::arena_handle(values[start..].to_vec()) } +list_remove_at!(lkrt_lklist_i64_remove_at, i64, "`remove_at(i)` on a `List`."); +list_remove_at!(lkrt_lklist_f64_remove_at, f64, "`remove_at(i)` on a `List`."); +list_remove_at!( + lkrt_lklist_str_remove_at, + *const c_char, + "`remove_at(i)` on a `List`." +); +list_remove_at!( + lkrt_lklist_dyn_remove_at, + crate::lkdyn::LkDyn, + "`remove_at(i)` on a boxed-element list." +); + /// `words.map(f)` over a `str` list (`fn(*const c_char) -> *const c_char` /// callback returning an arena-owned string). /// @@ -588,6 +795,54 @@ fn display_joined(parts: impl Iterator) -> *mut c_char { crate::lkstr::arena_c_string(alloc::ffi::CString::new(out).unwrap_or_default()) } +/// `xs.clear()` — empties the list in place, and answers nothing. +/// +/// The VM's `clear` evaluates to the list, but the *helper* does not hand it +/// back: a pointer-returning ABI entry has to be `Constructs` (a fresh handle +/// the scope-drop pass may release) or `Retained`, and this is neither — it +/// would be the caller's own list, which that pass would then free. The lowering +/// already holds the receiver and uses it as the expression's value, so there is +/// nothing to return. `pointer_returning_entries_are_not_marked_borrowed` is +/// the test that says so. +/// +/// One macro over every carrier rather than one function per element type: the +/// operation does not depend on the element at all, and writing it four times is +/// how three of the four end up missing. (`pop` / `insert` / `remove_at` do +/// depend on the element — they are the next piece of work, tracked separately.) +macro_rules! list_clear { + ($name:ident, $elem:ty, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void) { + if handle.is_null() { + return; + } + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + unsafe { (*(handle as *mut Vec<$elem>)).clear() }; + } + }; +} + +list_clear!(lkrt_lklist_i64_clear, i64, "`clear()` on a `List`."); +list_clear!(lkrt_lklist_f64_clear, f64, "`clear()` on a `List`."); +list_clear!( + lkrt_lklist_str_clear, + *const c_char, + "`clear()` on a `List`. The element pointers are arena-owned, so \ + dropping them is not a leak this crate can do anything about (see the \ + module header's ownership note)." +); +list_clear!( + lkrt_lklist_dyn_clear, + crate::lkdyn::LkDyn, + "`clear()` on a boxed-element list. This was a hand-written copy in \ + `lkdyn.rs` — so the macro above claimed to cover every carrier while \ + covering three, which is the shape it was written to prevent." +); + /// Appends `value` to the list. /// /// # Safety @@ -655,11 +910,43 @@ pub unsafe extern "C" fn lkrt_lklist_i64_get(handle: *mut c_void, index: i64, pr } } +/// A store index resolved against `len`: a negative one counts from the end, +/// exactly as the read does. `None` means it is out of range even after that, +/// which is a *halt* for a store — unlike a read, which answers nil. +fn store_index(index: i64, len: usize) -> Option { + let len = len as i64; + let resolved = if index < 0 { len + index } else { index }; + (resolved >= 0 && resolved < len).then_some(resolved as usize) +} + +/// The same, raising in the VM's exact wording when it is out of range. +/// +/// One message, because the VM has one: out of range at either end is +/// `list index N out of bounds`. A caught error is printed output, so the text +/// is part of the answer and has to match the VM's to the character. +/// +/// It used to be two, the negative end saying `list index must be +/// non-negative` — a rule the language does not have, `xs[-1]` being the last +/// element. The two builds agreed only by being wrong the same way. +/// +/// `N` is the index **as written**, at both ends. The VM briefly reported the +/// resolved one for a negative index — `-6` for `xs.set(-9, v)` on a +/// three-element list, a number the program never wrote — because it resolved +/// when the key was built and raised several steps later. It now raises at the +/// resolution point, where the original is still in hand, so this side does not +/// have to mirror a worse message to agree. +pub(crate) fn store_index_or_raise(index: i64, len: usize) -> usize { + match store_index(index, len) { + Some(resolved) => resolved, + None => crate::panic::raise_str(&alloc::format!("list index {index} out of bounds")), + } +} + /// Stores `value` at `index`. Unlike indexing (`get`), the VM treats an -/// out-of-range or **negative** store index as a fatal error (`list index N out of -/// bounds` / `list index must be non-negative`), not a nil/grow — so this -/// `abort()`s on an invalid index, matching the VM's *halt* (a loud failure, never -/// a silent wrong write). An in-range store is the only non-aborting path. +/// out-of-range store index as a fatal error (`list index N out of bounds`), +/// not a nil/grow — so this raises, matching the VM's *halt* (a loud failure, +/// never a silent wrong write). A negative index counts from the end, as +/// `xs[-1] = v` does in the VM. /// /// # Safety /// `handle` must be a live handle from [`lkrt_lklist_i64_new`], or null. @@ -670,10 +957,30 @@ pub unsafe extern "C" fn lkrt_lklist_i64_set(handle: *mut c_void, index: i64, va } // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_i64_new`. let values = unsafe { &mut *(handle as *mut Vec) }; - if index < 0 || index as usize >= values.len() { + let index = store_index_or_raise(index, values.len()); + values[index] = value; +} + +/// Stores `value` at `index` in a `str` list; the same index rule as +/// [`lkrt_lklist_i64_set`]. +/// +/// The carrier had `at` but no `set`, so `xs[i] = s` and `xs.set(i, s)` on a +/// string list dropped the whole module to the VM while the same two lines on an +/// `Int` list stayed native — a difference in the list's internal representation +/// deciding the fate of a program that cannot see it. +/// +/// # Safety +/// `handle` must be a live handle from [`lkrt_lklist_str_new`], or null; +/// `value` a valid string-constant pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_str_set(handle: *mut c_void, index: i64, value: *const c_char) { + if handle.is_null() { crate::panic::raise_str("runtime error"); } - values[index as usize] = value; + // SAFETY: `handle` addresses a `Vec<*const c_char>` from `lkrt_lklist_str_new`. + let values = unsafe { &mut *(handle as *mut Vec<*const c_char>) }; + let index = store_index_or_raise(index, values.len()); + values[index] = value; } /// Stores `value` at `index` in an `f64` list; aborts on an invalid index (see @@ -688,10 +995,8 @@ pub unsafe extern "C" fn lkrt_lklist_f64_set(handle: *mut c_void, index: i64, va } // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_f64_new`. let values = unsafe { &mut *(handle as *mut Vec) }; - if index < 0 || index as usize >= values.len() { - crate::panic::raise_str("runtime error"); - } - values[index as usize] = value; + let index = store_index_or_raise(index, values.len()); + values[index] = value; } /// A `Maybe` returned by value: `present == 0` means the element was absent @@ -819,12 +1124,19 @@ pub extern "C" fn lkrt_maybe_f64_unwrap(value: f64, present: i64) -> f64 { value } -/// Unwraps a `Maybe` in a scalar (arithmetic/comparison) context: returns -/// `value` when `present != 0`, otherwise `abort()`s. This matches the VM, which -/// *halts* when a `nil` (out-of-range) element is used numerically (e.g. -/// `xs[oob] + 1`) — so an out-of-range index in arithmetic is a loud abort, never a -/// silent wrong value. In a `for x in xs` loop the index is always in range, so the -/// guard never fires. +/// Unwraps a `Maybe` in a scalar context: returns `value` when +/// `present != 0`, otherwise raises. +/// +/// The interpreter does **not** halt here, which this used to say: it raises a +/// catchable error naming the operator and both operand types, so +/// `try { xs[9] + 1 } catch e { e }` is a string a program can read. This helper +/// is handed a value and a bit and can only say `"runtime error"`, which is a +/// different string — so arithmetic and comparison now go through +/// `lkrt_rt_maybe_guard` instead, which is handed the sentence itself, built +/// where the operator and the operand types are still known. What is left +/// reaching here is the contexts that have no operator to name. +/// +/// In a `for x in xs` loop the index is always in range, so neither fires. #[unsafe(no_mangle)] pub extern "C" fn lkrt_maybe_i64_unwrap(value: i64, present: i64) -> i64 { if present == 0 { @@ -888,6 +1200,40 @@ pub unsafe extern "C" fn lkrt_lklist_f64_contains(handle: *mut c_void, needle: f i64::from(values.contains(&needle)) } +/// `x in xs` where the list holds `i64` and the needle is an `f64`. +/// +/// Numeric comparison, the same rule `==` uses: the element is widened, not +/// the needle narrowed, so `1 in [1.0]` and `1.0 in [1, 2]` answer the same +/// way `1 == 1.0` does. The VM spells it `*value as f64 == *needle`; this is +/// that expression. +/// +/// # Safety +/// `handle` must be a live handle from [`lkrt_lklist_i64_new`], or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_i64_contains_f64(handle: *mut c_void, needle: f64) -> i64 { + if handle.is_null() { + return 0; + } + // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_i64_new`. + let values = unsafe { &*(handle as *mut Vec) }; + i64::from(values.iter().any(|value| *value as f64 == needle)) +} + +/// `x in xs` where the list holds `f64` and the needle is an `i64` (see +/// [`lkrt_lklist_i64_contains_f64`]). +/// +/// # Safety +/// `handle` must be a live handle from [`lkrt_lklist_f64_new`], or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_f64_contains_i64(handle: *mut c_void, needle: i64) -> i64 { + if handle.is_null() { + return 0; + } + // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_f64_new`. + let values = unsafe { &*(handle as *mut Vec) }; + i64::from(values.contains(&(needle as f64))) +} + /// Linear membership test for a string list — by *content*, matching the /// VM's `TypedList::String` contains (which stringifies and compares text, /// for short and long strings alike). @@ -910,80 +1256,496 @@ pub unsafe extern "C" fn lkrt_lklist_str_contains(handle: *mut c_void, needle: * ) } -/// Range slice of an `i64` list (`xs[1..5]`), exactly the VM's list slice: -/// negative indices count from the tail, everything clamps. +/// The half-open range `[start, end)` a two-argument `slice` names, resolved +/// against a list of `len` elements. /// -/// # Safety -/// `handle` must be a live handle from [`lkrt_lklist_i64_new`], or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_i64_slice(handle: *mut c_void, start: i64, end: i64) -> *mut c_void { - let values: &[i64] = if handle.is_null() { - &[] - } else { - // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_i64_new`. - unsafe { &*(handle as *mut Vec) } +/// One function because it is one rule (see the negative-position rule the VM +/// and this crate share): negative counts from the tail, everything clamps, and +/// an inverted range is empty rather than a panic. Writing it out per carrier is +/// how four implementations of one rule start. +pub(crate) fn slice_bounds(len: usize, start: i64, end: i64) -> (usize, usize) { + let signed_len = len as i64; + let start = if start < 0 { (signed_len + start).max(0) } else { start } as usize; + let end = (if end < 0 { (signed_len + end).max(0) } else { end } as usize).min(len); + (start.min(end), end) +} + +/// Range slice of a list carrier (`xs[1..5]` / `xs.slice(1, 5)`), exactly the +/// VM's: negative indices count from the tail, everything clamps. +macro_rules! list_slice { + ($name:ident, $elem:ty, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void, start: i64, end: i64) -> *mut c_void { + let values: &[$elem] = if handle.is_null() { + &[] + } else { + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + unsafe { &*(handle as *mut Vec<$elem>) } + }; + let (start, end) = slice_bounds(values.len(), start, end); + crate::state::arena_handle(values[start..end].to_vec()) + } }; - let len = values.len() as i64; - let start = if start < 0 { (len + start).max(0) } else { start } as usize; - let end = (if end < 0 { (len + end).max(0) } else { end } as usize).min(values.len()); - let start = start.min(end); - crate::state::arena_handle(values[start..end].to_vec()) } -/// `.slice(start, end)` method: negative indexes abort (the VM's loud -/// non-negative error), `end` clamps to len, `start >= end` yields empty. +list_slice!(lkrt_lklist_i64_slice, i64, "`i64` list range slice."); +list_slice!(lkrt_lklist_f64_slice, f64, "`f64` list range slice."); +list_slice!( + lkrt_lklist_str_slice, + *const c_char, + "`str` list range slice (elements are interned string-constant pointers)." +); + +/// `xs.sort()` — a fresh ascending copy (the VM sorts a snapshot, the receiver +/// is untouched). /// -/// # Safety -/// `handle` must be a live `i64` list handle, or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_i64_slice_method(handle: *mut c_void, start: i64, end: i64) -> *mut c_void { - if start < 0 || end < 0 { - crate::panic::raise_str("runtime error"); +/// Unlike `reverse`, this *is* about the element, and each carrier's order has +/// to be the one `typed_list_sorted` uses — not merely "ascending": +/// +/// * `i64`: `sort_unstable`, which is what the VM calls. `compare_runtime_values` +/// on two `Int`s *is* `i64`'s `Ord`, and equal integers are indistinguishable, +/// so the algorithm cannot show. +/// * `f64`: [`compare_floats`], which is a *total* order. The obvious mirror of +/// the VM — `partial_cmp().unwrap_or(Equal)` — is not one, and Rust's `sort_by` +/// detects that and panics; writing this arm is what found it, on both +/// backends. See [`compare_floats`]. +/// * `str`: `sort_by` on the bytes, which is what `Arc`'s `Ord` does in the +/// VM. LK strings hold no interior NUL, so the C representation compares the +/// same bytes. +/// +/// The boxed carrier is deliberately absent: its order is +/// `compare_runtime_values` across *kinds*, which needs two rank tables, a +/// depth-limited recursive list comparison, and the slice view — a mirror of +/// that size wants its own conformance test (see `vm_mirror`), not a copy. +/// The VM's `val::compare_floats`, mirrored: a *total* ascending order over +/// floats. +/// +/// `partial_cmp(..).unwrap_or(Equal)` is not one — a NaN reads equal to every +/// value while those values stay ordered — and Rust's `sort_by` detects that and +/// panics ("user-provided comparison function does not correctly implement a +/// total order"). In lkrt a panic is an abort, so `[NaN, 5.0, 1.0, …].sort()` +/// killed the process; in the VM it killed the interpreter. Both sides now order +/// NaN instead: all NaNs equal, every NaN greater than every number, `-0.0` and +/// `0.0` still equal (which is what `==` says). +pub(crate) fn compare_floats(left: f64, right: f64) -> core::cmp::Ordering { + match left.partial_cmp(&right) { + Some(ordering) => ordering, + None => match (left.is_nan(), right.is_nan()) { + (true, true) => core::cmp::Ordering::Equal, + (true, false) => core::cmp::Ordering::Greater, + (false, true) => core::cmp::Ordering::Less, + (false, false) => core::cmp::Ordering::Equal, + }, } - let values: &[i64] = if handle.is_null() { - &[] - } else { - // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_i64_new`. - unsafe { &*(handle as *mut Vec) } +} + +macro_rules! list_sort { + ($name:ident, $elem:ty, $sort:expr, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void) -> *mut c_void { + let mut values: Vec<$elem> = if handle.is_null() { + Vec::new() + } else { + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + unsafe { (*(handle as *mut Vec<$elem>)).clone() } + }; + let sort: fn(&mut Vec<$elem>) = $sort; + sort(&mut values); + crate::state::arena_handle(values) + } }; - let end = (end as usize).min(values.len()); - let start = (start as usize).min(end); - crate::state::arena_handle(values[start..end].to_vec()) } -/// `xs.sort()` — a fresh ascending copy (the VM sorts a snapshot, the -/// receiver is untouched; integer order equals `compare_runtime_values`). +/// `sum()` / `min()` / `max()` on a typed list. +/// +/// The empty answers are the VM's: `sum` is `0` (the identity a fold would +/// start from) and `min`/`max` are nil — so those two box their result, the way +/// `first`/`last` already do. +/// +/// The orders are the same ones `list_sort!` uses on each carrier, which is +/// what keeps `xs.sort().first()` and `xs.min()` from disagreeing here as well. /// /// # Safety -/// `handle` must be a live `i64` list handle, or null. +/// `handle` must be a live list handle of the carrier named by the entry point. #[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_i64_sort(handle: *mut c_void) -> *mut c_void { - let mut values: Vec = if handle.is_null() { - Vec::new() - } else { - // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_i64_new`. - unsafe { (*(handle as *mut Vec)).clone() } +pub unsafe extern "C" fn lkrt_lklist_i64_sum(handle: *mut c_void) -> i64 { + // SAFETY: a live `List` handle, as the ABI declares. + let values: &Vec = unsafe { &*(handle as *mut Vec) }; + // Wrapping, because `+` wraps: one rule for adding integers. + values.iter().fold(0i64, |total, value| total.wrapping_add(*value)) +} + +/// # Safety +/// `handle` must be a live `List` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_f64_sum(handle: *mut c_void) -> f64 { + // SAFETY: as above. + let values: &Vec = unsafe { &*(handle as *mut Vec) }; + values.iter().sum() +} + +macro_rules! list_extreme { + ($name:ident, $elem:ty, $box_value:expr, $order:expr, $want_max:expr, $doc:literal) => { + #[doc = $doc] + /// + /// # Safety + /// `handle` must be a live list handle of this carrier. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void) -> crate::lkdyn::LkDyn { + // SAFETY: a live list handle of this carrier, as the ABI declares. + let values: &Vec<$elem> = unsafe { &*(handle as *mut Vec<$elem>) }; + let mut best: Option<&$elem> = None; + for value in values.iter() { + best = Some(match best { + None => value, + // Ties keep the earlier element, as the VM's does: `min` + // names a *value*, and the first element that has it is the + // one a reader would point at. + Some(current) => { + #[allow(clippy::redundant_closure_call)] + let ordering = ($order)(current, value); + let keep = match ordering { + core::cmp::Ordering::Less => !$want_max, + core::cmp::Ordering::Equal => true, + core::cmp::Ordering::Greater => $want_max, + }; + if keep { current } else { value } + } + }); + } + match best { + #[allow(clippy::redundant_closure_call)] + Some(value) => ($box_value)(value), + None => crate::lkdyn::lkrt_dyn_from_nil(), + } + } }; - values.sort_unstable(); - crate::state::arena_handle(values) } -/// `xs.reverse()` — a fresh reversed copy (non-mutating, like the VM). +list_extreme!( + lkrt_lklist_i64_min, + i64, + |value: &i64| crate::lkdyn::lkrt_dyn_from_i64(*value), + |a: &i64, b: &i64| a.cmp(b), + false, + "`min()` on a `List`." +); +list_extreme!( + lkrt_lklist_i64_max, + i64, + |value: &i64| crate::lkdyn::lkrt_dyn_from_i64(*value), + |a: &i64, b: &i64| a.cmp(b), + true, + "`max()` on a `List`." +); +list_extreme!( + lkrt_lklist_f64_min, + f64, + |value: &f64| crate::lkdyn::lkrt_dyn_from_f64(*value), + |a: &f64, b: &f64| compare_floats(*a, *b), + false, + "`min()` on a `List` — the same total order `f64_sort` uses." +); +list_extreme!( + lkrt_lklist_f64_max, + f64, + |value: &f64| crate::lkdyn::lkrt_dyn_from_f64(*value), + |a: &f64, b: &f64| compare_floats(*a, *b), + true, + "`max()` on a `List`." +); +list_extreme!( + lkrt_lklist_str_min, + *const c_char, + |value: &*const c_char| crate::lkdyn::lkrt_dyn_from_str(*value), + |a: &*const c_char, b: &*const c_char| str_order(*a, *b), + false, + "`min()` on a `List`." +); +list_extreme!( + lkrt_lklist_str_max, + *const c_char, + |value: &*const c_char| crate::lkdyn::lkrt_dyn_from_str(*value), + |a: &*const c_char, b: &*const c_char| str_order(*a, *b), + true, + "`max()` on a `List`." +); + +/// The `str_sort` comparator, as a function so `min`/`max` order strings the +/// same way rather than by a second copy of it. +fn str_order(left: *const c_char, right: *const c_char) -> core::cmp::Ordering { + match (left.is_null(), right.is_null()) { + (true, true) => core::cmp::Ordering::Equal, + (true, false) => core::cmp::Ordering::Less, + (false, true) => core::cmp::Ordering::Greater, + // SAFETY: a non-null element of a live `str` list is a NUL-terminated + // arena string. + (false, false) => unsafe { CStr::from_ptr(left).to_bytes().cmp(CStr::from_ptr(right).to_bytes()) }, + } +} + +list_sort!( + lkrt_lklist_i64_sort, + i64, + |values| values.sort_unstable(), + "`sort()` on a `List`." +); +list_sort!( + lkrt_lklist_f64_sort, + f64, + |values| values.sort_by(|left, right| compare_floats(*left, *right)), + "`sort()` on a `List`." +); +list_sort!( + lkrt_lklist_str_sort, + *const c_char, + |values| values.sort_by(|left, right| { + // A null element cannot occur in a live `str` list; ordering it first + // keeps the comparator total rather than reaching for `CStr` on null. + match (left.is_null(), right.is_null()) { + (true, true) => core::cmp::Ordering::Equal, + (true, false) => core::cmp::Ordering::Less, + (false, true) => core::cmp::Ordering::Greater, + (false, false) => unsafe { CStr::from_ptr(*left).to_bytes().cmp(CStr::from_ptr(*right).to_bytes()) }, + } + }), + "`sort()` on a `List`." +); + +/// `xs.sum()` on a boxed-element list. +/// +/// Two accumulators rather than one, and that is the VM's, not a convenience: +/// an `Int` element advances *both* the wrapping integer total and the float +/// one, so the float sum is over every element in written order. Promoting on +/// the first float instead would fold a different sequence, and float addition +/// is not associative — `[1e308, 1.0, -1e308]` is where the two disagree. /// /// # Safety -/// `handle` must be a live `i64` list handle, or null. +/// `handle` must be a live boxed list handle, or null. #[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_lklist_i64_reverse(handle: *mut c_void) -> *mut c_void { - let mut values: Vec = if handle.is_null() { - Vec::new() +pub unsafe extern "C" fn lkrt_lklist_dyn_sum(handle: *mut c_void) -> crate::lkdyn::LkDyn { + use crate::lkdyn::{DYN_F64, DYN_I64}; + if handle.is_null() { + return crate::lkdyn::lkrt_dyn_from_i64(0); + } + // SAFETY: `handle` addresses a `Vec` from the boxed constructor. + let values: &Vec = unsafe { &*(handle as *mut Vec) }; + let mut total_int: i64 = 0; + let mut total_float = 0.0f64; + let mut saw_float = false; + for value in values { + match value.tag { + DYN_I64 => { + total_int = total_int.wrapping_add(value.payload); + total_float += value.payload as f64; + } + DYN_F64 => { + saw_float = true; + total_float += value.f64_value(); + } + _ => crate::lkdyn::raise_sum_wants_numbers(*value), + } + } + if saw_float { + crate::lkdyn::lkrt_dyn_from_f64(total_float) } else { - // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_i64_new`. - unsafe { (*(handle as *mut Vec)).clone() } + crate::lkdyn::lkrt_dyn_from_i64(total_int) + } +} + +list_sort!( + lkrt_lklist_dyn_sort, + crate::lkdyn::LkDyn, + |values| values.sort_by(|left, right| crate::lkdyn::dyn_compare(*left, *right)), + "`sort()` on a boxed-element list — the VM's cross-kind order." +); +list_extreme!( + lkrt_lklist_dyn_min, + crate::lkdyn::LkDyn, + |value: &crate::lkdyn::LkDyn| *value, + |a: &crate::lkdyn::LkDyn, b: &crate::lkdyn::LkDyn| crate::lkdyn::dyn_compare(*a, *b), + false, + "`min()` on a boxed-element list." +); +list_extreme!( + lkrt_lklist_dyn_max, + crate::lkdyn::LkDyn, + |value: &crate::lkdyn::LkDyn| *value, + |a: &crate::lkdyn::LkDyn, b: &crate::lkdyn::LkDyn| crate::lkdyn::dyn_compare(*a, *b), + true, + "`max()` on a boxed-element list." +); + +/// `xs.reverse()` — a fresh reversed copy (non-mutating, like the VM). +/// +/// Like [`list_clear`], the operation does not look at the element, so it is one +/// macro over every carrier. It was written for `i64` alone, which is why +/// `[1.5, 2.5].reverse()` dropped its whole module to the VM. +macro_rules! list_reverse { + ($name:ident, $elem:ty, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void) -> *mut c_void { + let mut values: Vec<$elem> = if handle.is_null() { + Vec::new() + } else { + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + unsafe { (*(handle as *mut Vec<$elem>)).clone() } + }; + values.reverse(); + crate::state::arena_handle(values) + } + }; +} + +list_reverse!(lkrt_lklist_i64_reverse, i64, "`reverse()` on a `List`."); +list_reverse!(lkrt_lklist_f64_reverse, f64, "`reverse()` on a `List`."); +list_reverse!( + lkrt_lklist_str_reverse, + *const c_char, + "`reverse()` on a `List`. The element pointers are arena-owned and \ + shared with the source list, which is what makes copying them sound." +); +list_reverse!( + lkrt_lklist_dyn_reverse, + crate::lkdyn::LkDyn, + "`reverse()` on a boxed-element list." +); + +/// `xs.index_of(v)` and `xs.count(v)` — one scan per carrier, two accumulators. +/// +/// The VM writes them as a single `typed_list_scan` and says why above it: +/// "`index_of` and `count` are the same scan with different accumulators, and +/// writing them apart is how two spellings of one operation come to disagree". +/// They were apart here — two macros — and had already diverged, not in the +/// comparison but in *which carriers exist*: `index_of` had four and `count` +/// had two, so `["a", "b"].count("a")` had nothing to lower to while +/// `["a", "b"].index_of("a")` did. Generating both from one scan makes a +/// carrier that answers one question answer the other by construction. +/// +/// The scan owns the loop rather than taking an element predicate, which is +/// what lets the string carrier walk to the needle's NUL once instead of once +/// per element. +macro_rules! list_scan { + ($index_of:ident, $count:ident, $elem:ty, $needle:ty, $scan:expr, $what:literal) => { + #[doc = concat!("`index_of` on ", $what, " — the first position holding the needle, or nil.")] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $index_of(handle: *mut c_void, needle: $needle) -> crate::lkdyn::LkDyn { + if handle.is_null() { + return crate::lkdyn::LkDyn::NIL; + } + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + let values: &Vec<$elem> = unsafe { &*(handle as *mut Vec<$elem>) }; + let scan: fn(&[$elem], $needle, &mut dyn FnMut(usize) -> bool) = $scan; + let mut found = None; + scan(values.as_slice(), needle, &mut |index| { + found = Some(index); + false + }); + match found { + Some(index) => crate::lkdyn::lkrt_dyn_from_i64(index as i64), + None => crate::lkdyn::LkDyn::NIL, + } + } + + #[doc = concat!("`count` on ", $what, " — how many elements equal the needle.")] + /// # Safety + /// `handle` must be a live list handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $count(handle: *mut c_void, needle: $needle) -> i64 { + if handle.is_null() { + return 0; + } + // SAFETY: `handle` addresses a `Vec<$elem>` from the matching + // constructor. + let values: &Vec<$elem> = unsafe { &*(handle as *mut Vec<$elem>) }; + let scan: fn(&[$elem], $needle, &mut dyn FnMut(usize) -> bool) = $scan; + let mut found = 0i64; + scan(values.as_slice(), needle, &mut |_| { + found += 1; + true + }); + found + } }; - values.reverse(); - crate::state::arena_handle(values) } +list_scan!( + lkrt_lklist_i64_index_of, + lkrt_lklist_i64_count, + i64, + i64, + |values, needle, on_match| { + for (index, value) in values.iter().enumerate() { + if *value == needle && !on_match(index) { + return; + } + } + }, + "a `List`" +); +list_scan!( + lkrt_lklist_f64_index_of, + lkrt_lklist_f64_count, + f64, + f64, + |values, needle, on_match| { + for (index, value) in values.iter().enumerate() { + if *value == needle && !on_match(index) { + return; + } + } + }, + "a `List` (an `Int` needle is coerced by the lowering, the way `contains` takes one)" +); +list_scan!( + lkrt_lklist_str_index_of, + lkrt_lklist_str_count, + *const c_char, + *const c_char, + |values, needle, on_match| { + if needle.is_null() { + return; + } + // Once, not per element: `CStr::from_ptr` walks to the NUL. + let needle = unsafe { CStr::from_ptr(needle) }; + for (index, &p) in values.iter().enumerate() { + if !p.is_null() && unsafe { CStr::from_ptr(p) } == needle && !on_match(index) { + return; + } + } + }, + "a `List`" +); +list_scan!( + lkrt_lklist_dyn_index_of, + lkrt_lklist_dyn_count, + crate::lkdyn::LkDyn, + crate::lkdyn::LkDyn, + |values, needle, on_match| { + for (index, &value) in values.iter().enumerate() { + if crate::lkdyn::dyn_eq_inner(value, needle) && !on_match(index) { + return; + } + } + }, + "a boxed-element list" +); + /// Creates a fresh, empty `f64` list handle. #[unsafe(no_mangle)] pub extern "C" fn lkrt_lklist_f64_new() -> *mut c_void { @@ -1114,6 +1876,58 @@ pub unsafe extern "C" fn lkrt_lklist_str_join(handle: *mut c_void, separator: *c crate::lkstr::arena_c_string(CString::new(parts.join(sep)).unwrap_or_default()) } +/// Joins an `i64` list with `separator`, elements written as the VM writes them. +/// +/// `[1, 2].join("-")` used to raise in the VM ("list must contain only strings") +/// and was therefore left unlowered here on purpose — one arbitrary rule turning +/// into a second one in another back end. The VM renders every element now, so +/// this renders them the same way: `i64::to_string`, exactly what +/// `lkrt_lklist_i64_display` puts between its brackets. +/// +/// # Safety +/// See [`lkrt_lklist_str_join`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_i64_join(handle: *mut c_void, separator: *const c_char) -> *mut c_char { + use alloc::ffi::CString; + let sep = join_separator(separator); + let values: &[i64] = if handle.is_null() { + &[] + } else { + // SAFETY: `handle` addresses a `Vec` created by `lkrt_lklist_i64_new`. + unsafe { &*(handle as *mut Vec) } + }; + let parts: Vec = values.iter().map(i64::to_string).collect(); + crate::lkstr::arena_c_string(CString::new(parts.join(sep)).unwrap_or_default()) +} + +/// Joins an `f64` list with `separator`; see [`lkrt_lklist_i64_join`]. +/// +/// # Safety +/// See [`lkrt_lklist_str_join`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lklist_f64_join(handle: *mut c_void, separator: *const c_char) -> *mut c_char { + use alloc::ffi::CString; + let sep = join_separator(separator); + let values: &[f64] = if handle.is_null() { + &[] + } else { + // SAFETY: `handle` addresses a `Vec` created by `lkrt_lklist_f64_new`. + unsafe { &*(handle as *mut Vec) } + }; + let parts: Vec = values.iter().map(f64::to_string).collect(); + crate::lkstr::arena_c_string(CString::new(parts.join(sep)).unwrap_or_default()) +} + +/// The separator a `join` was handed; null and invalid UTF-8 both mean empty, +/// matching the three `*_join` entry points that share it. +fn join_separator<'a>(separator: *const c_char) -> &'a str { + if separator.is_null() { + return ""; + } + // SAFETY: caller guarantees a valid C string. + unsafe { CStr::from_ptr(separator) }.to_str().unwrap_or("") +} + /// Structural equality for two `i64` lists (1 = equal), the VM's typed-list /// `==`: same length and element-wise `==`. Null handles compare as empty. /// @@ -1210,6 +2024,183 @@ pub unsafe extern "C" fn lkrt_lklist_str_eq(a: *mut c_void, b: *mut c_void) -> i i64::from(equal) } +/// A typed list's elements, boxed — the carrier read behind every `DYN_TLIST_*` +/// consumer. +/// +/// A copy, and only reads use it: `lkdyn::dyn_list_values` documents why, and +/// [`lkrt_dyn_list_push`](crate::lkrt_dyn_list_push) is the write that does not. +pub(crate) fn typed_list_boxed(kind: i64, handle: *mut c_void) -> alloc::vec::Vec { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR, lkrt_dyn_from_f64, lkrt_dyn_from_i64, lkrt_dyn_from_str}; + if handle.is_null() { + return alloc::vec::Vec::new(); + } + // SAFETY: `kind` names the carrier the caller tagged this handle with. + unsafe { + match kind { + TLIST_I64 => (*(handle as *mut Vec)) + .iter() + .map(|&v| lkrt_dyn_from_i64(v)) + .collect(), + TLIST_F64 => (*(handle as *mut Vec)) + .iter() + .map(|&v| lkrt_dyn_from_f64(v)) + .collect(), + TLIST_STR => (*(handle as *mut Vec<*const c_char>)) + .iter() + .map(|&v| lkrt_dyn_from_str(v)) + .collect(), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// Element count without boxing anything. +pub(crate) fn typed_list_len(kind: i64, handle: *mut c_void) -> i64 { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR}; + if handle.is_null() { + return 0; + } + // SAFETY: as in [`typed_list_boxed`]. + unsafe { + match kind { + TLIST_I64 => (*(handle as *mut Vec)).len() as i64, + TLIST_F64 => (*(handle as *mut Vec)).len() as i64, + TLIST_STR => (*(handle as *mut Vec<*const c_char>)).len() as i64, + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// `xs.push(v)` on a **boxed** typed list: appends to the carrier itself, so +/// the box and the original stay one list. +/// +/// The element is unboxed back to the carrier's type. A value the carrier +/// cannot hold is the VM's loud failure — the same one the unboxed spelling +/// gives, because the static types would have rejected it there. +/// `xs.insert(i, v)` / `xs.remove_at(i)` / `pop`'s drop half on a **typed** +/// list handle, by carrier kind. +/// +/// The siblings of [`typed_list_push`], and there for the same reason it is: a +/// boxed list has no static carrier, and unboxing one through `dyn.as_list` +/// materializes a copy for three of the four — so a write through that guard +/// lands on the copy and the original never changes. +pub(crate) fn typed_list_insert(kind: i64, handle: *mut c_void, index: i64, value: crate::lkdyn::LkDyn) { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR}; + // SAFETY: `handle` addresses a list of the carrier `kind` names; the + // per-carrier entry points range-check the index themselves. + unsafe { + match kind { + TLIST_I64 => lkrt_lklist_i64_insert(handle, index, crate::lkdyn::lkrt_dyn_as_i64(value)), + TLIST_F64 => lkrt_lklist_f64_insert(handle, index, crate::lkdyn::lkrt_dyn_as_f64(value)), + TLIST_STR => lkrt_lklist_str_insert(handle, index, crate::lkdyn::lkrt_dyn_as_str(value)), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +pub(crate) fn typed_list_remove_at(kind: i64, handle: *mut c_void, index: i64) -> crate::lkdyn::LkDyn { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR}; + // SAFETY: as above. + unsafe { + match kind { + TLIST_I64 => crate::lkdyn::lkrt_dyn_from_i64(lkrt_lklist_i64_remove_at(handle, index)), + TLIST_F64 => crate::lkdyn::lkrt_dyn_from_f64(lkrt_lklist_f64_remove_at(handle, index)), + TLIST_STR => crate::lkdyn::lkrt_dyn_from_str(lkrt_lklist_str_remove_at(handle, index)), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +pub(crate) fn typed_list_drop_last(kind: i64, handle: *mut c_void) { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR}; + // SAFETY: as above. + unsafe { + match kind { + TLIST_I64 => lkrt_lklist_i64_drop_last(handle), + TLIST_F64 => lkrt_lklist_f64_drop_last(handle), + TLIST_STR => lkrt_lklist_str_drop_last(handle), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +pub(crate) fn typed_list_push(kind: i64, handle: *mut c_void, value: crate::lkdyn::LkDyn) { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR}; + if handle.is_null() { + crate::panic::raise_str("runtime type error"); + } + // SAFETY: as in [`typed_list_boxed`], and the handle is uniquely reachable + // through this call for its duration. + unsafe { + match kind { + TLIST_I64 => (*(handle as *mut Vec)).push(crate::lkdyn::lkrt_dyn_as_i64(value)), + TLIST_F64 => (*(handle as *mut Vec)).push(crate::lkdyn::lkrt_dyn_as_f64(value)), + TLIST_STR => (*(handle as *mut Vec<*const c_char>)).push(crate::lkdyn::lkrt_dyn_as_str(value)), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// `xs[i] = v` on a **boxed** typed list: stores into the carrier itself, so +/// the box and the original stay one list. +/// +/// The index rule is [`store_index_or_raise`]'s, the same one the unboxed +/// spelling uses — out of range is the VM's halt, and a negative index counts +/// from the end. The element unboxes back to the carrier's type, like +/// [`typed_list_push`]. +/// `xs.clear()` on a **typed** list handle, by carrier kind. +/// +/// The sibling of [`typed_list_set`]: a boxed list has no static carrier and +/// the tag is the only thing that says which. +pub(crate) fn typed_list_clear(kind: i64, handle: *mut c_void) { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR}; + if handle.is_null() { + return; + } + // SAFETY: `handle` addresses a list of the carrier `kind` names. + unsafe { + match kind { + TLIST_I64 => lkrt_lklist_i64_clear(handle), + TLIST_F64 => lkrt_lklist_f64_clear(handle), + TLIST_STR => lkrt_lklist_str_clear(handle), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +pub(crate) fn typed_list_set(kind: i64, handle: *mut c_void, index: i64, value: crate::lkdyn::LkDyn) { + use crate::lkdyn::{TLIST_F64, TLIST_I64, TLIST_STR}; + if handle.is_null() { + crate::panic::raise_str("runtime type error"); + } + // SAFETY: as in [`typed_list_push`]. + unsafe { + match kind { + TLIST_I64 => { + let values = &mut *(handle as *mut Vec); + // Unboxed *before* the index is resolved, because both can + // raise and the VM reports the type first. + let v = crate::lkdyn::lkrt_dyn_as_i64(value); + let idx = store_index_or_raise(index, values.len()); + values[idx] = v; + } + TLIST_F64 => { + let values = &mut *(handle as *mut Vec); + let v = crate::lkdyn::lkrt_dyn_as_f64(value); + let idx = store_index_or_raise(index, values.len()); + values[idx] = v; + } + TLIST_STR => { + let values = &mut *(handle as *mut Vec<*const c_char>); + let v = crate::lkdyn::lkrt_dyn_as_str(value); + let idx = store_index_or_raise(index, values.len()); + values[idx] = v; + } + _ => crate::panic::raise_str("runtime type error"), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lkrt/src/lkmap.rs b/lkrt/src/lkmap.rs index ae09843b..0725988b 100644 --- a/lkrt/src/lkmap.rs +++ b/lkrt/src/lkmap.rs @@ -24,30 +24,51 @@ use alloc::{ use core::ffi::{CStr, c_char, c_void}; use crate::lklist::{LkMaybeF64, LkMaybeI64}; +use crate::vm_mirror::{RtKey, str_key}; -// The exact carrier the VM uses (`core::util::fast_map::FastHashMap` = -// `hashbrown::HashMap` + `FxBuildHasher`, fixed seed): iteration order is a -// deterministic function of key hashes + operation sequence, so a native map -// built by the same operation sequence iterates in the *same* order — the -// deep-coverage plan's "mirror the Fx order" adjudication. Do not swap either -// piece independently of `core/src/util/fast_map.rs`. -pub(crate) type FxMap = hashbrown::HashMap; +// The exact carrier the VM uses (`core::util::value_map::ValueMap`): a +// program's `Map` iterates in **insertion order**, so a native map built by the +// same sequence of operations iterates the same way for a structural reason — +// both append to a vector — rather than because both happen to land on the same +// hash layout. That older arrangement is what `vm_mirror` was for, and its +// correctness rested on both builds linking one `hashbrown`, one rustc deriving +// the same `Hash` discriminants, and one fixed seed. +// +// Keep in step with `core/src/util/value_map.rs`, including `shift_remove` +// (order-preserving) over `swap_remove`. +pub(crate) type FxMap = indexmap::IndexMap; /// The set counterpart of [`FxMap`]. hashbrown rather than std so the same /// type serves both builds — `rustc_hash::FxHashSet` is an alias for std's. pub(crate) type FxSet = hashbrown::HashSet; -type StrI64Map = FxMap; -type I64I64Map = FxMap; -type StrF64Map = FxMap; -type I64F64Map = FxMap; +type StrI64Map = FxMap; +type I64I64Map = FxMap; +type StrF64Map = FxMap; +type I64F64Map = FxMap; /// Insert-or-update without allocating when the key is already present: the /// common map workload pattern is repeated updates of existing keys, and /// `insert(key.to_string(), ..)` would heap-allocate the key on every call. -fn set_str_key(map: &mut FxMap, key: &str, value: V) { +fn set_str_key(map: &mut FxMap, key: &str, value: V) { match map.get_mut(key) { Some(slot) => *slot = value, None => { - map.insert(key.to_string(), value); + map.insert(StrKey::Owned(String::from(key)), value); + } + } +} + +/// [`set_str_key`] for a key that is a **program constant** — see +/// [`lkrt_lkmap_str_dyn_set_const`]. +/// +/// # Safety +/// `key` must live as long as the process. +unsafe fn set_static_str_key(map: &mut FxMap, key: &str, value: V) { + match map.get_mut(key) { + Some(slot) => *slot = value, + None => { + // SAFETY: as documented. + let key: &'static str = unsafe { core::mem::transmute::<&str, &'static str>(key) }; + map.insert(StrKey::Static(key), value); } } } @@ -137,6 +158,34 @@ pub unsafe extern "C" fn lkrt_lkmap_str_i64_set_ik( unsafe { with_ik_key(prefix, suffix, |key| set_str_key(map, key, value)) } } +/// `m.clear()` — empties the map in place. +/// +/// One macro over the five carriers, which are all `FxMap`. It was the only +/// container method the *map* lacked natively while the list and the set both +/// had it, so `m.clear()` dropped its whole module to the VM for a reason no +/// program can see. +macro_rules! map_clear { + ($name:ident, $map:ty, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void) { + if handle.is_null() { + return; + } + // SAFETY: `handle` addresses a map of the matching carrier. + unsafe { (*(handle as *mut $map)).clear() }; + } + }; +} + +map_clear!(lkrt_lkmap_str_i64_clear, StrI64Map, "Empties a `str -> i64` map."); +map_clear!(lkrt_lkmap_i64_i64_clear, I64I64Map, "Empties an `i64 -> i64` map."); +map_clear!(lkrt_lkmap_str_f64_clear, StrF64Map, "Empties a `str -> f64` map."); +map_clear!(lkrt_lkmap_i64_f64_clear, I64F64Map, "Empties an `i64 -> f64` map."); +map_clear!(lkrt_lkmap_str_dyn_clear, StrDynMap, "Empties a `str -> Dyn` map."); + /// Returns the number of entries. /// /// # Safety @@ -183,7 +232,7 @@ pub unsafe extern "C" fn lkrt_lkmap_str_i64_without(handle: *mut c_void, key: *c // SAFETY: `handle` addresses a `StrI64Map` from `lkrt_lkmap_str_i64_new`. unsafe { (*(handle as *mut StrI64Map)).clone() } }; - copy.remove(unsafe { key_str(key) }); + copy.shift_remove(unsafe { key_str(key) }); crate::state::arena_handle(copy) } @@ -200,7 +249,7 @@ pub unsafe extern "C" fn lkrt_lkmap_str_f64_without(handle: *mut c_void, key: *c // SAFETY: `handle` addresses a `StrF64Map` from `lkrt_lkmap_str_f64_new`. unsafe { (*(handle as *mut StrF64Map)).clone() } }; - copy.remove(unsafe { key_str(key) }); + copy.shift_remove(unsafe { key_str(key) }); crate::state::arena_handle(copy) } @@ -218,7 +267,11 @@ pub unsafe extern "C" fn lkrt_lkmap_str_dyn_without(handle: *mut c_void, key: *c // SAFETY: `handle` addresses a `StrDynMap` from `lkrt_lkmap_str_dyn_new`. unsafe { (*(handle as *mut StrDynMap)).clone() } }; - copy.remove(unsafe { key_str(key) }); + copy.shift_remove(unsafe { key_str(key) }); + // A struct instance with a field taken away is not that struct: the copy is + // an ordinary map. (A map pattern refuses to match a struct, so nothing + // reaches here with one today — the id would be a lie if anything did.) + copy.type_id = 0; crate::state::arena_handle(copy) } @@ -257,6 +310,82 @@ pub unsafe extern "C" fn lkrt_lkmap_str_dyn_merge(base: *mut c_void, overlay: *m crate::state::arena_handle(out) } +/// `merge(base, overlay)` where the **overlay is a typed carrier**, iterated in +/// place. +/// +/// The lowering used to convert the overlay to a `str -> Dyn` map first, with the +/// claim that "the rebuild replays the source order". Re-inserting a table's +/// entries into a fresh table in its *iteration* order is a different insertion +/// sequence from the one that built it, so the copy does not always iterate the +/// same way — and the overlay's order is the tail of the merged result's. +/// +/// Struct update syntax (`P { ..base, x: 42 }`) is what reaches this: the +/// overlay is the `{x: 42}` field literal, which is a typed map. Refusing it +/// would cost the feature its lowering; copying it is the thing that is wrong. +/// So nothing is copied — the overlay is walked where it lives. +/// +/// # Safety +/// `base` must be a live `StrDynMap` handle or null; `overlay` a live handle of +/// the carrier `kind` names, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkmap_str_dyn_merge_typed( + base: *mut c_void, + overlay: *mut c_void, + kind: i64, +) -> *mut c_void { + let empty = StrDynMap::default(); + // SAFETY: caller passes a live `StrDynMap` handle (or null). + let base: &StrDynMap = if base.is_null() { + &empty + } else { + unsafe { &*(base as *mut StrDynMap) } + }; + // The overlay's keys, in its own order — borrowed, not rebuilt. + let overlay_pairs = typed_map_pairs(kind, overlay); + let mut out = StrDynMap::default(); + for (key, &value) in base { + if !overlay_pairs.iter().any(|(k, _)| k == key) { + out.insert(key.clone(), value); + } + } + for (key, value) in overlay_pairs { + out.insert(key, value); + } + crate::state::arena_handle(out) +} + +/// A typed string-keyed map's entries **in its own iteration order**, boxed. +/// +/// A `Vec`, not a map: the order is the payload here, and a hash table would +/// impose its own. See [`typed_map_keyed`] for the order-free counterpart that +/// equality uses. +fn typed_map_pairs(kind: i64, handle: *mut c_void) -> Vec<(StrKey, crate::lkdyn::LkDyn)> { + use crate::lkdyn::{lkrt_dyn_from_bool, lkrt_dyn_from_f64, lkrt_dyn_from_i64}; + if handle.is_null() { + return Vec::new(); + } + // SAFETY: `kind` names the carrier the caller tagged this handle with. + unsafe { + match kind { + KIND_STR_I64 => (*(handle as *mut StrI64Map)) + .iter() + .map(|(k, v)| (k.clone(), lkrt_dyn_from_i64(*v))) + .collect(), + KIND_STR_F64 => (*(handle as *mut StrF64Map)) + .iter() + .map(|(k, v)| (k.clone(), lkrt_dyn_from_f64(*v))) + .collect(), + KIND_STR_BOOL => (*(handle as *mut StrI64Map)) + .iter() + .map(|(k, v)| (k.clone(), lkrt_dyn_from_bool(*v))) + .collect(), + // An int-keyed overlay has no string keys to merge into a field map; + // the VM refuses it before this can be reached. + _ => crate::panic::raise_str("runtime type error"), + } + } +} + /// Fresh zero-capacity rebuild in `src`'s iteration order — the VM's /// `__lk_make_struct` copies the merged field map into the new object /// (`runtime_object_fields_from_map`), so the native carrier replays the @@ -352,7 +481,7 @@ macro_rules! map_iter_family { // SAFETY: as above. let map = unsafe { &mut *(handle as *mut $carrier) }; #[allow(clippy::redundant_closure_call)] - match map.remove(unsafe { key_str(key) }) { + match map.shift_remove(unsafe { key_str(key) }) { Some(v) => ($box_val)(&v), None => crate::lkdyn::LkDyn::NIL, } @@ -398,8 +527,14 @@ map_iter_family!( "`for pair in m` snapshot over `Map`." ); -macro_rules! map_to_dyn { - ($name:ident, $carrier:ty, $box_val:expr, $doc:literal) => { +/// `for pair in m` over an **int**-keyed map: the same `[key, value]` snapshot +/// the string-keyed carriers produce, with the key boxed as an `Int`. +/// +/// Its own function rather than an arm of `map_iter_family!` because that macro +/// boxes the key with `boxed_str_key` — the key kind is the one thing the two +/// families do not share. +macro_rules! int_map_iter { + ($name:ident, $keys:ident, $values:ident, $delete:ident, $carrier:ty, $box_val:expr, $doc:literal) => { #[doc = $doc] /// # Safety /// `handle` must be a live map handle of the matching carrier. @@ -407,33 +542,435 @@ macro_rules! map_to_dyn { pub unsafe extern "C" fn $name(handle: *mut c_void) -> *mut c_void { // SAFETY: `handle` addresses the matching carrier map. let map = unsafe { &*(handle as *mut $carrier) }; - let mut out = StrDynMap::default(); - for (k, v) in map.iter() { + #[allow(clippy::redundant_closure_call)] + pair_list( + map.iter() + .map(|(k, v)| (crate::lkdyn::lkrt_dyn_from_i64(k.0), ($box_val)(v))) + .collect(), + ) + } + + #[doc = $doc] + /// `.keys()` — the keys, boxed as `Int`, in the map's own order. + /// # Safety + /// `handle` must be a live map handle of the matching carrier. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $keys(handle: *mut c_void) -> *mut c_void { + // SAFETY: as above. + let map = unsafe { &*(handle as *mut $carrier) }; + let keys: Vec = map.keys().map(|k| crate::lkdyn::lkrt_dyn_from_i64(k.0)).collect(); + crate::state::arena_handle(keys) + } + + #[doc = $doc] + /// `.values()` — the values, boxed, in the map's own order. + /// # Safety + /// `handle` must be a live map handle of the matching carrier. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $values(handle: *mut c_void) -> *mut c_void { + // SAFETY: as above. + let map = unsafe { &*(handle as *mut $carrier) }; + #[allow(clippy::redundant_closure_call)] + let values: Vec = map.values().map(|v| ($box_val)(v)).collect(); + crate::state::arena_handle(values) + } + + #[doc = $doc] + /// `.delete(k)` — removes and returns the value, or nil when absent. + /// The string families generate this from their own macro; leaving it + /// out here is why `m.delete(k)` lowered for a string-keyed map and + /// dropped the module to the VM for an integer-keyed one. + /// # Safety + /// `handle` must be a live map handle of the matching carrier. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $delete(handle: *mut c_void, key: i64) -> crate::lkdyn::LkDyn { + // SAFETY: as above. + let map = unsafe { &mut *(handle as *mut $carrier) }; + #[allow(clippy::redundant_closure_call)] + match map.shift_remove(&crate::vm_mirror::IntKey(key)) { + Some(v) => ($box_val)(&v), + None => crate::lkdyn::LkDyn::NIL, + } + } + }; +} + +int_map_iter!( + lkrt_lkmap_i64_i64_iter_pairs, + lkrt_lkmap_i64_i64_keys, + lkrt_lkmap_i64_i64_values, + lkrt_lkmap_i64_i64_delete, + I64I64Map, + |v: &i64| crate::lkdyn::lkrt_dyn_from_i64(*v), + "`for pair in m` snapshot over `Map`." +); +int_map_iter!( + lkrt_lkmap_i64_f64_iter_pairs, + lkrt_lkmap_i64_f64_keys, + lkrt_lkmap_i64_f64_values, + lkrt_lkmap_i64_f64_delete, + I64F64Map, + |v: &f64| crate::lkdyn::lkrt_dyn_from_f64(*v), + "`for pair in m` snapshot over `Map`." +); + +/// A boxed **typed** map: the carrier kind, as the `LkDyn` tag carries it. +/// +/// Boxing used to mean `*_to_dyn` — rebuilding the map into a `StrDynMap` by +/// re-inserting in iteration order. That is a *re-representation*, and +/// `DYN_RAW`'s doc already spells out why it cannot be one: a fresh table filled +/// by a different insertion sequence has a different layout, so the copy +/// iterates in a different order than the original. With deletions in the +/// history the two diverge, and `println([m])` printed its entries in an order +/// the VM never would — a wrong answer, not a fallback. +/// +/// So a typed map boxes by tagging its handle in place, and the tag says which +/// carrier it is. The rebuild survives in exactly one place, [`typed_map_keyed`], +/// because its one consumer is equality — which is order-free. +pub(crate) const KIND_STR_I64: i64 = 0; +pub(crate) const KIND_STR_F64: i64 = 1; +pub(crate) const KIND_STR_BOOL: i64 = 2; +pub(crate) const KIND_I64_I64: i64 = 3; +pub(crate) const KIND_I64_F64: i64 = 4; + +/// `{"a":1,"b":2}` / `{3:4,1:2}` — the carrier's own iteration order, no copy. +pub(crate) fn typed_map_text(kind: i64, handle: *mut c_void) -> String { + // SAFETY: the tag the caller decoded `kind` from is only ever set by + // `dyn.from_typed_map` on a handle of that carrier. + unsafe { + let ptr = match kind { + KIND_STR_I64 => lkrt_lkmap_str_i64_display(handle), + KIND_STR_F64 => lkrt_lkmap_str_f64_display(handle), + KIND_STR_BOOL => lkrt_lkmap_str_bool_display(handle), + KIND_I64_I64 => lkrt_lkmap_i64_i64_display(handle), + KIND_I64_F64 => lkrt_lkmap_i64_f64_display(handle), + _ => crate::panic::raise_str("runtime type error"), + }; + CStr::from_ptr(ptr).to_str().unwrap_or("").to_string() + } +} + +/// Entry count without a copy. +/// `m.clear()` on a **typed** map handle, by carrier kind. +/// +/// The sibling of [`typed_map_len`], for the same reason: a boxed map has no +/// static carrier and the tag is the only thing that says which. +pub(crate) fn typed_map_clear(kind: i64, handle: *mut c_void) { + // SAFETY: as in `typed_map_len`. + unsafe { + match kind { + KIND_STR_I64 | KIND_STR_BOOL => lkrt_lkmap_str_i64_clear(handle), + KIND_STR_F64 => lkrt_lkmap_str_f64_clear(handle), + KIND_I64_I64 => lkrt_lkmap_i64_i64_clear(handle), + KIND_I64_F64 => lkrt_lkmap_i64_f64_clear(handle), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +pub(crate) fn typed_map_len(kind: i64, handle: *mut c_void) -> i64 { + // SAFETY: as in `typed_map_text`. + unsafe { + match kind { + KIND_STR_I64 | KIND_STR_BOOL => lkrt_lkmap_str_i64_len(handle), + KIND_STR_F64 => lkrt_lkmap_str_f64_len(handle), + KIND_I64_I64 => lkrt_lkmap_i64_i64_len(handle), + KIND_I64_F64 => lkrt_lkmap_i64_f64_len(handle), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// `for pair in m` / `.keys()` / `.values()` off the carrier, as the +/// `[key, value]` snapshot list, in the carrier's own order. +/// +/// One function per operation would be five dispatches; the pair snapshot is +/// what every one of them is built from, and it is the only shape all five +/// carriers already produce. `keys` and `values` project it — they are cold +/// paths, and paying one snapshot there is what buys the int-keyed carriers +/// the two methods the unboxed lowering never gave them. +pub(crate) fn typed_map_pair_list(kind: i64, handle: *mut c_void) -> *mut c_void { + // SAFETY: as in `typed_map_text`. + unsafe { + match kind { + KIND_STR_I64 => lkrt_lkmap_str_i64_iter_pairs(handle), + KIND_STR_F64 => lkrt_lkmap_str_f64_iter_pairs(handle), + KIND_STR_BOOL => lkrt_lkmap_str_bool_iter_pairs(handle), + KIND_I64_I64 => lkrt_lkmap_i64_i64_iter_pairs(handle), + KIND_I64_F64 => lkrt_lkmap_i64_f64_iter_pairs(handle), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// `.delete(k)` on a boxed typed map — removes in place, so the box and the +/// original stay one map. +/// +/// String keys only, which is the set the unboxed lowering also serves: an +/// int-keyed carrier has no `delete` symbol to dispatch to, and inventing one +/// here would give the boxed spelling a method the plain one does not have. +pub(crate) fn typed_map_delete(kind: i64, handle: *mut c_void, key: *const c_char) -> crate::lkdyn::LkDyn { + // SAFETY: as in `typed_map_text`; `key` is the caller's NUL-terminated key. + unsafe { + match kind { + KIND_STR_I64 => lkrt_lkmap_str_i64_delete(handle, key), + KIND_STR_F64 => lkrt_lkmap_str_f64_delete(handle, key), + KIND_STR_BOOL => lkrt_lkmap_str_bool_delete(handle, key), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// `m[k] = v` on a **boxed** typed map, by carrier kind. +/// +/// The counterpart of [`typed_map_delete`], and the key rule is the one +/// [`crate::lkdyn::lkrt_dyn_index`] states: an integer key on a map is a *key*, +/// not a position, so each kind takes the key its carrier is keyed by and a key +/// of the other shape raises. +/// +/// A value the carrier cannot hold raises rather than widening it. The +/// allocation belongs to whoever built the map, and their other aliases read it +/// by its static type, so a `str -> i64` map cannot become a `str -> Dyn` one +/// here; the carrier is decided at the literal instead (see +/// `docs/semantics.md`, "拓宽一个列表的载体,只有构造点能做"). +pub(crate) fn typed_map_set(kind: i64, handle: *mut c_void, key: crate::lkdyn::LkDyn, value: crate::lkdyn::LkDyn) { + use crate::lkdyn::{lkrt_dyn_as_f64, lkrt_dyn_as_i64, lkrt_dyn_as_str}; + // SAFETY: as in `typed_map_delete`; the key pointer, when one is taken, is + // the boxed key's own NUL-terminated string. + unsafe { + match kind { + KIND_STR_I64 => lkrt_lkmap_str_i64_set(handle, lkrt_dyn_as_str(key), lkrt_dyn_as_i64(value)), + KIND_STR_F64 => lkrt_lkmap_str_f64_set(handle, lkrt_dyn_as_str(key), lkrt_dyn_as_f64(value)), + // A `bool` carrier stores its members as `i64`; only a boxed bool + // belongs in one, so this does not go through `as_i64`. + KIND_STR_BOOL => lkrt_lkmap_str_i64_set(handle, lkrt_dyn_as_str(key), lkrt_dyn_as_bool(value)), + KIND_I64_I64 => lkrt_lkmap_i64_i64_set(handle, lkrt_dyn_as_i64(key), lkrt_dyn_as_i64(value)), + KIND_I64_F64 => lkrt_lkmap_i64_f64_set(handle, lkrt_dyn_as_i64(key), lkrt_dyn_as_f64(value)), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// A boxed bool as the `i64` a `bool` carrier stores. An `Int` is *not* +/// accepted: the two are distinct types in this language, and the carrier +/// merely shares their machine representation. +fn lkrt_dyn_as_bool(v: crate::lkdyn::LkDyn) -> i64 { + if v.tag != crate::lkdyn::DYN_BOOL { + crate::panic::raise_str("runtime type error"); + } + v.payload +} + +/// The entries under the general key type, for **equality only**. +/// +/// This is a copy, and that is fine here and nowhere else: `==` over maps is +/// order-free, so a different layout cannot change the answer. Display must +/// never come through this. +/// A map's entries **in its own iteration order**, under the general key type. +/// +/// [`typed_map_keyed`] answers the same entries as a hash map, which is right +/// for equality and wrong for anything that fills a new map from them: the +/// order a map is filled in decides the order it iterates in, so a merge built +/// from an unordered view produces the same members in an order the VM never +/// would. +pub(crate) fn map_entries_ordered(v: crate::lkdyn::LkDyn) -> Vec<(RtKey, crate::lkdyn::LkDyn)> { + use crate::lkdyn::{DYN_MAP, DYN_TMAP_BASE, lkrt_dyn_from_bool, lkrt_dyn_from_f64, lkrt_dyn_from_i64}; + let handle = v.payload as *mut c_void; + if handle.is_null() { + return Vec::new(); + } + if v.tag == DYN_MAP { + // SAFETY: a `DYN_MAP` payload is a live `StrDynMap`. + return unsafe { (*(handle as *mut StrDynMap)).iter() } + .map(|(k, v)| (str_key(k), *v)) + .collect(); + } + // SAFETY: the tag the caller holds is only set by `dyn.from_typed_map` on a + // handle of that carrier. + unsafe { + match v.tag - DYN_TMAP_BASE { + KIND_STR_I64 => (*(handle as *mut StrI64Map)) + .iter() + .map(|(k, v)| (str_key(k), lkrt_dyn_from_i64(*v))) + .collect(), + KIND_STR_F64 => (*(handle as *mut StrF64Map)) + .iter() + .map(|(k, v)| (str_key(k), lkrt_dyn_from_f64(*v))) + .collect(), + KIND_STR_BOOL => (*(handle as *mut StrI64Map)) + .iter() + .map(|(k, v)| (str_key(k), lkrt_dyn_from_bool(*v))) + .collect(), + KIND_I64_I64 => (*(handle as *mut I64I64Map)) + .iter() + .map(|(k, v)| (RtKey::Int(k.0), lkrt_dyn_from_i64(*v))) + .collect(), + KIND_I64_F64 => (*(handle as *mut I64F64Map)) + .iter() + .map(|(k, v)| (RtKey::Int(k.0), lkrt_dyn_from_f64(*v))) + .collect(), + _ => crate::panic::raise_str("runtime type error"), + } + } +} + +/// Fills a fresh `str -> Dyn` map from an **ordered** entry sequence. +/// +/// The sequence is the payload: filling in another order gives the same members +/// and a different iteration order. A non-string key raises rather than being +/// stringified — the boxed map carrier is string-keyed, and answering +/// `{"3": 1}` where the VM answers `{3: 1}` would be a wrong answer dressed as +/// a conversion. +pub(crate) fn str_dyn_from_ordered(entries: Vec<(RtKey, crate::lkdyn::LkDyn)>) -> *mut c_void { + let mut out = StrDynMap::default(); + for (key, value) in entries { + match &key { + RtKey::ShortStr(_) | RtKey::String(_) => { + out.insert(StrKey::Owned(crate::vm_mirror::key_str(&key).to_string()), value) + } + _ => crate::panic::raise_str("map merge with a non-string key has no native carrier"), + }; + } + crate::state::arena_handle(out) +} + +pub(crate) fn typed_map_keyed(kind: i64, handle: *mut c_void) -> FxMap { + use crate::lkdyn::{lkrt_dyn_from_bool, lkrt_dyn_from_f64, lkrt_dyn_from_i64}; + let mut out: FxMap = FxMap::default(); + if handle.is_null() { + return out; + } + // SAFETY: as in `typed_map_text`. + unsafe { + match kind { + KIND_STR_I64 => { + for (k, v) in (*(handle as *mut StrI64Map)).iter() { + out.insert(str_key(k), lkrt_dyn_from_i64(*v)); + } + } + KIND_STR_F64 => { + for (k, v) in (*(handle as *mut StrF64Map)).iter() { + out.insert(str_key(k), lkrt_dyn_from_f64(*v)); + } + } + KIND_STR_BOOL => { + for (k, v) in (*(handle as *mut StrI64Map)).iter() { + out.insert(str_key(k), lkrt_dyn_from_bool(*v)); + } + } + KIND_I64_I64 => { + for (k, v) in (*(handle as *mut I64I64Map)).iter() { + out.insert(RtKey::Int(k.0), lkrt_dyn_from_i64(*v)); + } + } + KIND_I64_F64 => { + for (k, v) in (*(handle as *mut I64F64Map)).iter() { + out.insert(RtKey::Int(k.0), lkrt_dyn_from_f64(*v)); + } + } + // `kind` is decoded from a tag the range check above admitted, so + // this is unreachable — and a loud failure rather than a silent + // empty map if the encoding ever drifts. + _ => crate::panic::raise_str("runtime type error"), + } + } + out +} + +/// The general map key, re-exported so the `dyn` layer can name the type its +/// keyed views return without reaching into the mirror. +pub(crate) type MapKey = RtKey; + +/// The same view of a **boxed** (`str -> Dyn`) map, so equality can compare one +/// against a typed one. +pub(crate) fn boxed_map_keyed(handle: *mut c_void) -> FxMap { + let mut out: FxMap = FxMap::default(); + if handle.is_null() { + return out; + } + // SAFETY: a `DYN_MAP` payload is a live `StrDynMap`. + for (k, v) in unsafe { (*(handle as *mut StrDynMap)).iter() } { + out.insert(str_key(k), *v); + } + out +} + +/// `println(m)` for a statically typed map: `{"a":1,"b":2}` / `{3:4,1:2}`. +/// +/// Rendered from the carrier's own iteration order, with no rebuild — the +/// order question is therefore not asked twice. That order is the VM's: +/// `vm_mirror` replays both stages of `typed_map_from_entries` and +/// `lit_protocol_matches_vm_iteration_order` compares against `lk-core` +/// directly, so a hasher or layout drift fails there rather than as a +/// mismatched line of output. +/// +/// Keys render like the boxed-map arm in `lkdyn`: a string through Rust's +/// `{:?}` (the VM's quoting and escaping), an int as its decimal text. +macro_rules! map_display { + ($name:ident, $carrier:ty, $key:expr, $val:expr, $doc:literal) => { + #[doc = $doc] + /// # Safety + /// `handle` must be a live map handle of the matching carrier, or null. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name(handle: *mut c_void) -> *mut c_char { + let empty = <$carrier>::default(); + // SAFETY: caller passes a live handle of the matching carrier. + let map: &$carrier = if handle.is_null() { + &empty + } else { + unsafe { &*(handle as *mut $carrier) } + }; + let mut out = String::from("{"); + for (i, (k, v)) in map.iter().enumerate() { + if i > 0 { + out.push(','); + } + #[allow(clippy::redundant_closure_call)] + out.push_str(&($key)(k)); + out.push(':'); #[allow(clippy::redundant_closure_call)] - out.insert(k.clone(), ($box_val)(v)); + out.push_str(&($val)(v)); } - crate::state::arena_handle(out) + out.push('}'); + crate::lkstr::arena_c_string(alloc::ffi::CString::new(out).unwrap_or_default()) } }; } -map_to_dyn!( - lkrt_lkmap_str_i64_to_dyn, +map_display!( + lkrt_lkmap_str_i64_display, StrI64Map, - |v: &i64| crate::lkdyn::lkrt_dyn_from_i64(*v), - "`Map` → boxed-value map (iteration-order-preserving)." + |k: &StrKey| format!("{:?}", k.as_str()), + |v: &i64| v.to_string(), + "`Map` display." ); -map_to_dyn!( - lkrt_lkmap_str_f64_to_dyn, +map_display!( + lkrt_lkmap_str_f64_display, StrF64Map, - |v: &f64| crate::lkdyn::lkrt_dyn_from_f64(*v), - "`Map` → boxed-value map." + |k: &StrKey| format!("{:?}", k.as_str()), + |v: &f64| v.to_string(), + "`Map` display." +); +map_display!( + lkrt_lkmap_i64_i64_display, + I64I64Map, + |k: &crate::vm_mirror::IntKey| k.0.to_string(), + |v: &i64| v.to_string(), + "`Map` display." ); -map_to_dyn!( - lkrt_lkmap_str_bool_to_dyn, +map_display!( + lkrt_lkmap_i64_f64_display, + I64F64Map, + |k: &crate::vm_mirror::IntKey| k.0.to_string(), + |v: &f64| v.to_string(), + "`Map` display." +); +map_display!( + lkrt_lkmap_str_bool_display, StrI64Map, - |v: &i64| crate::lkdyn::lkrt_dyn_from_bool(*v), - "The bool map carrier → boxed-value map." + |k: &StrKey| format!("{:?}", k.as_str()), + |v: &i64| if *v != 0 { "true" } else { "false" }.to_string(), + "The bool map carrier's display." ); /// Creates a fresh, empty `Map` handle. @@ -452,7 +989,7 @@ pub unsafe extern "C" fn lkrt_lkmap_i64_i64_set(handle: *mut c_void, key: i64, v return; } // SAFETY: `handle` addresses an `I64I64Map` from `lkrt_lkmap_i64_i64_new`. - unsafe { (*(handle as *mut I64I64Map)).insert(key, value) }; + unsafe { (*(handle as *mut I64I64Map)).insert(crate::vm_mirror::IntKey(key), value) }; } /// Returns the number of entries. @@ -479,7 +1016,7 @@ pub unsafe extern "C" fn lkrt_lkmap_i64_i64_get_pair(handle: *mut c_void, key: i } // SAFETY: as above. let map = unsafe { &*(handle as *mut I64I64Map) }; - match map.get(&key) { + match map.get(&crate::vm_mirror::IntKey(key)) { Some(&value) => LkMaybeI64 { value, present: 1 }, None => LkMaybeI64 { value: 0, present: 0 }, } @@ -593,7 +1130,7 @@ pub unsafe extern "C" fn lkrt_lkmap_i64_f64_set(handle: *mut c_void, key: i64, v return; } // SAFETY: `handle` addresses an `I64F64Map` from `lkrt_lkmap_i64_f64_new`. - unsafe { (*(handle as *mut I64F64Map)).insert(key, value) }; + unsafe { (*(handle as *mut I64F64Map)).insert(crate::vm_mirror::IntKey(key), value) }; } /// Returns the number of entries. @@ -620,7 +1157,7 @@ pub unsafe extern "C" fn lkrt_lkmap_i64_f64_get_pair(handle: *mut c_void, key: i } // SAFETY: as above. let map = unsafe { &*(handle as *mut I64F64Map) }; - match map.get(&key) { + match map.get(&crate::vm_mirror::IntKey(key)) { Some(&value) => LkMaybeF64 { value, present: 1 }, None => LkMaybeF64 { value: 0.0, present: 0 }, } @@ -648,13 +1185,190 @@ pub unsafe extern "C" fn lkrt_lkmap_i64_f64_get_out( // ── Mixed-value map (`Map`, plan M4.2 Dyn) ──────────────── -pub(crate) type StrDynMap = FxMap; +/// A `str -> Dyn` map, plus the declared-struct id when this map *is* a struct +/// instance. +/// +/// The id rides the value rather than a side table because a value crosses +/// threads. The table it replaced was thread-local, so a struct sent to a task +/// arrived on the other side as an ordinary map: `typeof` answered `Map` where +/// the interpreter said `P`, and `println` printed `{"p":1,"q":2}` for +/// `P{p:1,q:2}`. Carrying it here also drops a hash lookup from every `typeof`, +/// trait dispatch and declared-field check. +/// A `str -> Dyn` map's key. +/// +/// `Static` borrows a string constant out of the program image, which is what a +/// struct's field names and a map literal's keys are: the lowering emits them as +/// data symbols (`materialize_key` interns a global), so they outlive every map +/// that uses them. Copying each one into an owned `String` per *instance* was +/// an allocation and a free per field per construction — the frees alone were +/// 42% of a loop building one struct. +/// +/// `Owned` is for a key computed at run time, which must be owned because the +/// string it came from can be released while the map lives. +/// +/// Hashing and comparison go through `as_str`, so the two forms of the same text +/// are one key — and the hash is `str`'s, byte for byte what `String` gave +/// before, which is what keeps map iteration order identical (`vm_mirror` +/// asserts that order against the VM). +#[derive(Clone, Debug)] +pub(crate) enum StrKey { + Static(&'static str), + Owned(String), +} + +impl StrKey { + pub(crate) fn as_str(&self) -> &str { + match self { + Self::Static(text) => text, + Self::Owned(text) => text.as_str(), + } + } +} + +impl core::ops::Deref for StrKey { + type Target = str; + + fn deref(&self) -> &str { + self.as_str() + } +} + +impl PartialEq for StrKey { + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl core::borrow::Borrow for StrKey { + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl core::hash::Hash for StrKey { + fn hash(&self, state: &mut H) { + self.as_str().hash(state); + } +} + +impl PartialEq for StrKey { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Eq for StrKey {} + +impl From<&str> for StrKey { + fn from(text: &str) -> Self { + Self::Owned(String::from(text)) + } +} + +impl core::fmt::Display for StrKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Default, Clone)] +pub(crate) struct StrDynMap { + entries: FxMap, + /// The declared struct's id, or `0` for an ordinary map. Written by + /// `lkrt_lkmap_obj_mark` right after construction. + pub(crate) type_id: i64, +} + +impl core::ops::Deref for StrDynMap { + type Target = FxMap; + + fn deref(&self) -> &Self::Target { + &self.entries + } +} + +impl core::ops::DerefMut for StrDynMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.entries + } +} + +impl<'a> IntoIterator for &'a StrDynMap { + type Item = (&'a StrKey, &'a crate::lkdyn::LkDyn); + type IntoIter = <&'a FxMap as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + (&self.entries).into_iter() + } +} + +/// [`lkrt_lkmap_str_i64_new`] at a known size — a literal knows its own. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_lkmap_str_i64_new_sized(capacity: i64) -> *mut c_void { + let capacity = usize::try_from(capacity).unwrap_or(0).min(1 << 20); + crate::state::arena_handle(StrI64Map::with_capacity_and_hasher(capacity, rustc_hash::FxBuildHasher)) +} + +/// [`lkrt_lkmap_str_f64_new`] at a known size. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_lkmap_str_f64_new_sized(capacity: i64) -> *mut c_void { + let capacity = usize::try_from(capacity).unwrap_or(0).min(1 << 20); + crate::state::arena_handle(StrF64Map::with_capacity_and_hasher(capacity, rustc_hash::FxBuildHasher)) +} + +/// [`lkrt_lkmap_str_i64_set`] with a **program-constant** key, borrowed rather +/// than copied. See [`lkrt_lkmap_str_dyn_set_const`]. +/// +/// # Safety +/// As [`lkrt_lkmap_str_i64_set`], and `key` must live as long as the process. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkmap_str_i64_set_const(handle: *mut c_void, key: *const c_char, value: i64) { + if handle.is_null() { + return; + } + // SAFETY: as documented. + unsafe { + let map = &mut *(handle as *mut StrI64Map); + set_static_str_key(map, key_str(key), value); + } +} + +/// [`lkrt_lkmap_str_f64_set`] with a **program-constant** key. +/// +/// # Safety +/// As [`lkrt_lkmap_str_i64_set_const`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkmap_str_f64_set_const(handle: *mut c_void, key: *const c_char, value: f64) { + if handle.is_null() { + return; + } + // SAFETY: as documented. + unsafe { + let map = &mut *(handle as *mut StrF64Map); + set_static_str_key(map, key_str(key), value); + } +} #[unsafe(no_mangle)] pub extern "C" fn lkrt_lkmap_str_dyn_new() -> *mut c_void { crate::state::arena_handle(StrDynMap::default()) } +/// [`lkrt_lkmap_str_dyn_new`] for a map whose size is known before it is +/// filled — a struct literal and a map literal both are. +/// +/// Growing costs a rehash of everything inserted so far, and the cost is not +/// linear in the field count: three fields cost 135ns each and six cost 277ns, +/// which is the table doubling under them. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_lkmap_str_dyn_new_sized(capacity: i64) -> *mut c_void { + let capacity = usize::try_from(capacity).unwrap_or(0).min(1 << 20); + crate::state::arena_handle(StrDynMap { + entries: FxMap::with_capacity_and_hasher(capacity, rustc_hash::FxBuildHasher), + type_id: 0, + }) +} + /// # Safety /// `handle` must be a live handle from [`lkrt_lkmap_str_dyn_new`], or null; /// `key` must be a NUL-terminated string. @@ -664,7 +1378,45 @@ pub unsafe extern "C" fn lkrt_lkmap_str_dyn_set(handle: *mut c_void, key: *const return; } let map = unsafe { &mut *(handle as *mut StrDynMap) }; - set_str_key(map, unsafe { key_str(key) }, value); + let key = unsafe { key_str(key) }; + // Replacing an existing key keeps the key that is already there, so a + // repeated store costs no allocation either way. + match map.entries.get_mut(key) { + Some(existing) => *existing = value, + None => { + map.entries.insert(StrKey::Owned(String::from(key)), value); + } + } +} + +/// [`lkrt_lkmap_str_dyn_set`] for a key that is a **program constant** — a +/// struct's field name, a map literal's key. +/// +/// The key is borrowed rather than copied, which is an allocation and a later +/// free saved per field per construction. +/// +/// # Safety +/// As [`lkrt_lkmap_str_dyn_set`], and `key` must point at data that lives as +/// long as the process: the lowering only passes interned globals here +/// (`materialize_key`), which are symbols in the program image. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkmap_str_dyn_set_const( + handle: *mut c_void, + key: *const c_char, + value: crate::lkdyn::LkDyn, +) { + if handle.is_null() { + return; + } + let map = unsafe { &mut *(handle as *mut StrDynMap) }; + // SAFETY: as documented — the caller guarantees program lifetime. + let key: &'static str = unsafe { core::mem::transmute::<&str, &'static str>(key_str(key)) }; + match map.entries.get_mut(key) { + Some(existing) => *existing = value, + None => { + map.entries.insert(StrKey::Static(key), value); + } + } } /// A missing key is `nil` — the Dyn carrier's Nil tag *is* the absent case, @@ -684,6 +1436,48 @@ pub unsafe extern "C" fn lkrt_lkmap_str_dyn_get(handle: *mut c_void, key: *const .unwrap_or(crate::lkdyn::LkDyn::NIL) } +/// A declared struct field, read by **position** with the key as the check. +/// +/// A field read was a hash lookup: `strlen` + UTF-8 validation of the key, +/// then hash and probe. Measured at ~107ns each, which is the whole cost of a +/// loop that reads a field (the same loop with the read hoisted out is +/// unmeasurable). A declared struct has a fixed field order that the compiler +/// knows, so the position is a compile-time constant. +/// +/// The key is still passed and still compared, because position alone is not a +/// guarantee: an instance built somewhere this lowering did not see — through +/// the hybrid bridge, or by a merge — may store its fields in another order. +/// The comparison is a length test and a byte compare against a constant, not +/// a hash; a mismatch falls back to the lookup, so the answer is the same +/// either way. +/// +/// # Safety +/// `handle` must be a live handle from [`lkrt_lkmap_str_dyn_new`], or null; +/// `key` must be a NUL-terminated string of `key_len` bytes. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkmap_str_dyn_get_at( + handle: *mut c_void, + index: i64, + key: *const c_char, + key_len: i64, +) -> crate::lkdyn::LkDyn { + if handle.is_null() { + return crate::lkdyn::LkDyn::NIL; + } + let map = unsafe { &*(handle as *mut StrDynMap) }; + if index >= 0 + && let Some((found, value)) = map.get_index(index as usize) + && found.len() == key_len as usize + // SAFETY: `key` is `key_len` readable bytes, as documented. + && found.as_bytes() == unsafe { core::slice::from_raw_parts(key as *const u8, key_len as usize) } + { + return *value; + } + map.get(unsafe { key_str(key) }) + .copied() + .unwrap_or(crate::lkdyn::LkDyn::NIL) +} + /// Key membership (distinct from `get`: a stored-nil value still counts). /// /// # Safety diff --git a/lkrt/src/lkprocess.rs b/lkrt/src/lkprocess.rs new file mode 100644 index 00000000..cb8be7c0 --- /dev/null +++ b/lkrt/src/lkprocess.rs @@ -0,0 +1,175 @@ +//! Native `process`: the current process, and child processes. +//! +//! Every member reports with the stdlib module's own sentence, because a caught +//! error's message is program output. The one that is *not* an error path is +//! `output`, whose four-key map is built through the VM's two-stage +//! construction so it iterates — and therefore prints — the same way. + +use alloc::string::{String, ToString as _}; +use alloc::vec::Vec; +use core::ffi::{CStr, c_char, c_void}; + +use std::process::Command; + +use crate::abi::{c_str, owned_c_string, raising}; + +/// The `args` list, whose elements are the same `*const c_char` a native +/// `List` holds. +/// +/// # Safety +/// `handle` must be a live string-list handle, or null for "no arguments". +unsafe fn argv(handle: *mut c_void) -> Vec { + if handle.is_null() { + return Vec::new(); + } + // SAFETY: a live string-list handle, as the ABI declares. + let values: &Vec<*const c_char> = unsafe { &*(handle as *mut Vec<*const c_char>) }; + values + .iter() + .map(|&ptr| { + if ptr.is_null() { + String::new() + } else { + // SAFETY: list elements are NUL-terminated LK strings. + unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() + } + }) + .collect() +} + +fn run(cmd: &str, args: &[String]) -> Result { + Command::new(cmd) + .args(args) + .output() + .map_err(|err| alloc::format!("failed to execute '{cmd}': {err}")) +} + +/// `process.id()`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_process_id() -> i64 { + std::process::id() as i64 +} + +/// `process.set_cwd(path)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_process_set_cwd(path: *const c_char) -> i64 { + raising(|| { + let path = c_str(path, "process.set_cwd path")?; + std::env::set_current_dir(path.as_str()).map_err(|err| alloc::format!("failed to set cwd '{path}': {err}"))?; + Ok(1) + }) +} + +/// `process.exit(code)` — does not return. +/// +/// Nothing is flushed here on purpose, and it took measuring to be sure of +/// that: generated code prints through **C stdio** (`printf`), and +/// `std::process::exit` calls libc `exit`, which flushes those streams — so an +/// unterminated `print("partial")` still reaches the terminal. The Rust-side +/// stream that `io.std.write` uses flushes at every write (see `io.rs`). A +/// belt-and-braces `stdout().flush()` here would have been flushing the stream +/// that was never the one at risk. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_process_exit(code: i64) { + if code < i64::from(i32::MIN) || code > i64::from(i32::MAX) { + crate::panic::raise_str(&alloc::format!("process.exit code must fit in i32, got {code}")); + } + std::process::exit(code as i32); +} + +/// The one-argument spellings — `process.status("true")` with no argument list. +/// +/// # Safety +/// `cmd` must be a NUL-terminated LK string. +/// +/// Separate entry points rather than a null handle materialised at the call +/// site: the module-call lowering passes exactly the arguments a row declares, +/// and inventing a null pointer for a missing one is the kind of thing that +/// works until a row's parameter is not a pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_process_status_noargs(cmd: *const c_char) -> i64 { + // SAFETY: a null handle is "no arguments", which `argv` handles. + unsafe { lkrt_process_status(cmd, core::ptr::null_mut()) } +} + +/// # Safety +/// `cmd` must be a NUL-terminated LK string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_process_output_string_noargs(cmd: *const c_char) -> *mut c_char { + // SAFETY: see above. + unsafe { lkrt_process_output_string(cmd, core::ptr::null_mut()) } +} + +/// # Safety +/// `cmd` must be a NUL-terminated LK string. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_process_output_noargs(cmd: *const c_char) -> *mut c_void { + // SAFETY: see above. + unsafe { lkrt_process_output(cmd, core::ptr::null_mut()) } +} + +/// `process.status(cmd[, args])` — the exit code, or -1 when a signal killed +/// the child (the stdlib's `code().unwrap_or(-1)`). +/// +/// # Safety +/// `args` must be a live string-list handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_process_status(cmd: *const c_char, args: *mut c_void) -> i64 { + raising(|| { + let cmd = c_str(cmd, "process command")?; + // SAFETY: forwarded from the ABI, which declares the same contract. + let argv = unsafe { argv(args) }; + let output = run(cmd.as_str(), &argv)?; + Ok(i64::from(output.status.code().unwrap_or(-1))) + }) +} + +/// `process.output_string(cmd[, args])` — the child's stdout as text. +/// +/// # Safety +/// `args` must be a live string-list handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_process_output_string(cmd: *const c_char, args: *mut c_void) -> *mut c_char { + raising(|| { + let cmd = c_str(cmd, "process command")?; + // SAFETY: forwarded from the ABI, which declares the same contract. + let argv = unsafe { argv(args) }; + let output = run(cmd.as_str(), &argv)?; + let stdout = String::from_utf8(output.stdout).map_err(|_| "command stdout is not valid UTF-8".to_string())?; + owned_c_string(stdout) + }) +} + +/// `process.output(cmd[, args])` — `status`, `success`, `stdout`, `stderr`, in +/// the stdlib module's insertion order, with the two streams as `Bytes`. +/// +/// # Safety +/// `args` must be a live string-list handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_process_output(cmd: *const c_char, args: *mut c_void) -> *mut c_void { + raising(|| { + let cmd = c_str(cmd, "process command")?; + // SAFETY: forwarded from the ABI, which declares the same contract. + let argv = unsafe { argv(args) }; + let output = run(cmd.as_str(), &argv)?; + let pairs = alloc::vec![ + ( + String::from("status"), + crate::lkdyn::lkrt_dyn_from_i64(i64::from(output.status.code().unwrap_or(-1))) + ), + ( + String::from("success"), + crate::lkdyn::lkrt_dyn_from_bool(i64::from(output.status.success())) + ), + ( + String::from("stdout"), + crate::lkdyn::lkrt_dyn_from_bytes(crate::lkbytes::bytes_handle(output.stdout)) + ), + ( + String::from("stderr"), + crate::lkdyn::lkrt_dyn_from_bytes(crate::lkbytes::bytes_handle(output.stderr)) + ), + ]; + Ok(crate::vm_mirror::str_dyn_map_mirrored(pairs)) + }) +} diff --git a/lkrt/src/lkrandom.rs b/lkrt/src/lkrandom.rs new file mode 100644 index 00000000..2d594b61 --- /dev/null +++ b/lkrt/src/lkrandom.rs @@ -0,0 +1,152 @@ +//! Native `random`: the same `rand` crate the stdlib module uses, and the same +//! bounds, defaults and refusals. +//! +//! Values cannot be compared between the back ends — that is what makes them +//! random — so what has to match is everything *around* the value: an inclusive +//! range, `0.5` as the default probability, the 16 MiB byte cap, nil for an +//! empty `choice`, and each refusal's exact sentence, since a caught error's +//! message is program output. +//! +//! None of these entries may be `Pure` in the ABI schema. CSE merges equal +//! `Pure` calls in a dominance scope, and `random.int(1, 6)` twice is two +//! rolls; `nondeterministic_entries_are_not_pure` pins that. +//! +//! `std`-only: the entropy source is the OS's. + +use alloc::vec::Vec; +use core::ffi::c_void; + +use rand::Rng as _; + +/// The stdlib module's cap, and its wording depends on the number. +const MAX_RANDOM_BYTES: usize = 16 * 1024 * 1024; + +/// `random.int(min, max)` — **inclusive** at both ends. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_random_int(min: i64, max: i64) -> i64 { + if max < min { + crate::panic::raise_str("random.int() max must be >= min"); + } + rand::rng().random_range(min..=max) +} + +/// `random.float()` — the half-open unit interval, `rand`'s `random::()`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_random_float() -> f64 { + rand::rng().random() +} + +/// `random.bool()` — a fair coin, the module's default probability. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_random_bool() -> i64 { + i64::from(rand::rng().random_bool(0.5)) +} + +/// `random.bool(probability)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_random_bool_p(probability: f64) -> i64 { + if !(0.0..=1.0).contains(&probability) { + crate::panic::raise_str("random.bool() probability must be in 0..=1"); + } + i64::from(rand::rng().random_bool(probability)) +} + +/// `random.bytes(len)`. +/// +/// Two different refusals, and the negative one is not this module's sentence: +/// the stdlib reads the length through its shared `usize_arg`, so a negative +/// length reports as a *type* problem (`random.bytes len expects non-negative +/// Int, got Int`) while an oversized one reports the cap. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_random_bytes(len: i64) -> *mut c_void { + if len < 0 { + crate::panic::raise_str("random.bytes len expects non-negative Int, got Int"); + } + let len = len as usize; + if len > MAX_RANDOM_BYTES { + crate::panic::raise_str(&alloc::format!("random.bytes() len exceeds {MAX_RANDOM_BYTES}")); + } + let mut data = alloc::vec![0u8; len]; + rand::rng().fill(data.as_mut_slice()); + crate::lkbytes::bytes_handle(data) +} + +/// A uniform index into a list of `len` elements, or `None` when it is empty +/// (`random.choice([])` is nil, not a raise). +fn choice_index(len: usize) -> Option { + (len > 0).then(|| rand::rng().random_range(0..len)) +} + +/// The module's shuffle: Fisher–Yates walking down from the end, the same +/// direction and the same `random_range(0..=i)` the stdlib uses. +fn shuffle_in_place(values: &mut [T]) { + for i in (1..values.len()).rev() { + let j = rand::rng().random_range(0..=i); + values.swap(i, j); + } +} + +macro_rules! choice_and_shuffle { + ($choice:ident, $shuffle:ident, $elem:ty, $list:ty, $to_dyn:expr) => { + /// `random.choice(xs)` for one list carrier. + /// + /// # Safety + /// `handle` must be a live list handle of this carrier. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $choice(handle: *mut c_void) -> crate::lkdyn::LkDyn { + // SAFETY: a live list handle of this carrier, as the ABI declares. + let values: &$list = unsafe { &*(handle as *mut $list) }; + match choice_index(values.len()) { + Some(index) => { + let value: $elem = values[index].clone(); + #[allow(clippy::redundant_closure_call)] + ($to_dyn)(value) + } + None => crate::lkdyn::lkrt_dyn_from_nil(), + } + } + + /// `random.shuffle(xs)` for one list carrier — a new list, like the VM's + /// (the module builds a fresh one rather than reordering the argument). + /// + /// # Safety + /// `handle` must be a live list handle of this carrier. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $shuffle(handle: *mut c_void) -> *mut c_void { + // SAFETY: a live list handle of this carrier, as the ABI declares. + let values: &$list = unsafe { &*(handle as *mut $list) }; + let mut out: $list = values.clone(); + shuffle_in_place(out.as_mut_slice()); + crate::state::arena_handle(out) + } + }; +} + +choice_and_shuffle!( + lkrt_random_choice_i64, + lkrt_random_shuffle_i64, + i64, + Vec, + crate::lkdyn::lkrt_dyn_from_i64 +); +choice_and_shuffle!( + lkrt_random_choice_f64, + lkrt_random_shuffle_f64, + f64, + Vec, + crate::lkdyn::lkrt_dyn_from_f64 +); +choice_and_shuffle!( + lkrt_random_choice_str, + lkrt_random_shuffle_str, + *const core::ffi::c_char, + Vec<*const core::ffi::c_char>, + crate::lkdyn::lkrt_dyn_from_str +); +choice_and_shuffle!( + lkrt_random_choice_dyn, + lkrt_random_shuffle_dyn, + crate::lkdyn::LkDyn, + Vec, + |value| value +); diff --git a/lkrt/src/lkregex.rs b/lkrt/src/lkregex.rs new file mode 100644 index 00000000..c5057060 --- /dev/null +++ b/lkrt/src/lkregex.rs @@ -0,0 +1,165 @@ +//! Native `regex`: the same `regex` crate the stdlib module uses, with the same +//! bounded compile cache. +//! +//! Sharing the crate is what makes the syntax, the match semantics *and* the +//! parse-error text one rule — `regex.is_match("(", x)` raises a three-line +//! message that is the crate's own `Display`, and a caught error's message is +//! program output. +//! +//! The cache is not an optimisation detail either: compiling a pattern costs +//! far more than matching with it, so a pattern inside a loop is the normal +//! case. The stdlib module caches up to 128 patterns and clears wholesale when +//! it fills; this mirrors that, including the limit, so the two back ends have +//! the same worst case rather than one of them quietly growing without bound. +//! +//! Locking discipline (see `chan.rs`): a raise `longjmp`s past Rust drops, so +//! the compile error is raised **after** the guard is gone, never while holding +//! it. + +use alloc::string::{String, ToString as _}; +use alloc::vec::Vec; +use core::ffi::{CStr, c_char, c_void}; + +use regex::Regex; + +/// The stdlib module's limit, for the same reason: an unbounded cache keyed by +/// a program-supplied string is a leak with extra steps. +const CACHE_LIMIT: usize = 128; + +fn cache() -> &'static std::sync::Mutex> { + static CACHE: std::sync::OnceLock>> = std::sync::OnceLock::new(); + CACHE.get_or_init(|| std::sync::Mutex::new(hashbrown::HashMap::new())) +} + +fn view(text: *const c_char) -> &'static str { + if text.is_null() { + return ""; + } + // SAFETY: LK strings reaching the ABI are NUL-terminated and outlive the call. + unsafe { CStr::from_ptr(text) }.to_str().unwrap_or("") +} + +/// Compiles or reuses a pattern. Returns the error text rather than raising, so +/// the caller can raise with no lock guard alive. +fn compiled(pattern: &str) -> Result { + if let Ok(cache) = cache().lock() + && let Some(regex) = cache.get(pattern) + { + return Ok(regex.clone()); + } + let regex = Regex::new(pattern).map_err(|err| alloc::format!("invalid regex: {err}"))?; + if let Ok(mut cache) = cache().lock() { + if cache.len() >= CACHE_LIMIT { + cache.clear(); + } + cache.insert(pattern.to_string(), regex.clone()); + } + Ok(regex) +} + +fn regex_or_raise(pattern: *const c_char) -> Regex { + match compiled(view(pattern)) { + Ok(regex) => regex, + Err(message) => crate::panic::raise_str(&message), + } +} + +fn str_list(values: Vec) -> *mut c_void { + let mut list: Vec<*const c_char> = Vec::with_capacity(values.len()); + for value in values { + let ptr = crate::lkstr::arena_c_string(alloc::ffi::CString::new(value).unwrap_or_default()); + list.push(ptr.cast_const()); + } + crate::state::arena_handle(list) +} + +/// One match, as the stdlib module's `match_map` builds it: keys `text`, +/// `start`, `end`, in that insertion order and through the VM's own two-stage +/// map construction, because the iteration order is what gets printed. +fn match_map(text: &str, start: usize, end: usize) -> *mut c_void { + let pairs = alloc::vec![ + ( + String::from("text"), + crate::lkdyn::lkrt_dyn_from_str( + crate::lkstr::arena_c_string(alloc::ffi::CString::new(text).unwrap_or_default()).cast_const() + ) + ), + (String::from("start"), crate::lkdyn::lkrt_dyn_from_i64(start as i64)), + (String::from("end"), crate::lkdyn::lkrt_dyn_from_i64(end as i64)), + ]; + crate::vm_mirror::str_dyn_map_mirrored(pairs) +} + +/// `regex.find(text, pattern)` — the match map, or nil when there is none. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_regex_find(text: *const c_char, pattern: *const c_char) -> crate::lkdyn::LkDyn { + let regex = regex_or_raise(pattern); + match regex.find(view(text)) { + Some(m) => crate::lkdyn::lkrt_dyn_from_map(match_map(m.as_str(), m.start(), m.end())), + None => crate::lkdyn::lkrt_dyn_from_nil(), + } +} + +/// `regex.find_all(text, pattern)` — every match, as a list of match maps. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_regex_find_all(text: *const c_char, pattern: *const c_char) -> *mut c_void { + let regex = regex_or_raise(pattern); + let values: Vec = regex + .find_iter(view(text)) + .map(|m| crate::lkdyn::lkrt_dyn_from_map(match_map(m.as_str(), m.start(), m.end()))) + .collect(); + crate::state::arena_handle(values) +} + +/// `regex.captures(text, pattern)` — group 0 first, then each group, with +/// **nil for a group that did not participate**; nil when nothing matched at +/// all. +/// +/// Two different nils, and they are not interchangeable: `captures("abc", "z")` +/// is nil because there was no match, while `captures("xaq", "(a)(z)?")` is +/// `["a", "a", nil]` — a list whose last element is nil. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_regex_captures(text: *const c_char, pattern: *const c_char) -> crate::lkdyn::LkDyn { + let regex = regex_or_raise(pattern); + let Some(captures) = regex.captures(view(text)) else { + return crate::lkdyn::lkrt_dyn_from_nil(); + }; + let values: Vec = captures + .iter() + .map(|capture| match capture { + Some(value) => crate::lkdyn::lkrt_dyn_from_str( + crate::lkstr::arena_c_string(alloc::ffi::CString::new(value.as_str()).unwrap_or_default()).cast_const(), + ), + None => crate::lkdyn::lkrt_dyn_from_nil(), + }) + .collect(); + crate::lkdyn::lkrt_dyn_from_list(crate::state::arena_handle(values)) +} + +/// `regex.is_match(text, pattern)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_regex_is_match(text: *const c_char, pattern: *const c_char) -> i64 { + let regex = regex_or_raise(pattern); + i64::from(regex.is_match(view(text))) +} + +/// `regex.split(text, pattern)` — a string list, empty pieces included, exactly +/// as `Regex::split` yields them. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_regex_split(text: *const c_char, pattern: *const c_char) -> *mut c_void { + let regex = regex_or_raise(pattern); + str_list(regex.split(view(text)).map(ToString::to_string).collect()) +} + +/// `regex.replace(text, pattern, replacement)` — every match, and the +/// replacement keeps the crate's `$1` capture syntax. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_regex_replace( + text: *const c_char, + pattern: *const c_char, + replacement: *const c_char, +) -> *mut c_char { + let regex = regex_or_raise(pattern); + let replaced = regex.replace_all(view(text), view(replacement)).into_owned(); + crate::lkstr::arena_c_string(alloc::ffi::CString::new(replaced).unwrap_or_default()) +} diff --git a/lkrt/src/lkset.rs b/lkrt/src/lkset.rs index fa3d3285..e6c3aa50 100644 --- a/lkrt/src/lkset.rs +++ b/lkrt/src/lkset.rs @@ -2,7 +2,10 @@ //! `RuntimeSet` — a hash set of map keys (`Nil`/`Bool`/`Int`/strings; a //! `Float` key is the VM's loud error, containers would need heap-handle //! identity and stay out of the native subset). Elements arrive as boxed -//! `LkDyn` values; iteration/`values()` is *not* exposed (hash order). +//! `LkDyn` values. +//! +//! Iteration *is* exposed, and the reason it was not is worth keeping: this +//! module used to define its own key type. See the `use` below. // `alloc`, not the std prelude: this module is part of the computation-only // subset that builds without an OS. @@ -20,49 +23,90 @@ use core::ffi::{CStr, c_char, c_void}; use crate::lkmap::FxSet; -use crate::lkdyn::{DYN_BOOL, DYN_F64, DYN_I64, DYN_NIL, DYN_STR, LkDyn}; - -/// The VM's `RuntimeMapKey` equality, minus heap-handle identity: the VM's -/// short/long string split is canonical by length, so plain content equality -/// is equivalent. -#[derive(Clone, PartialEq, Eq, Hash)] -enum RtKey { - Nil, - Bool(bool), - Int(i64), - Str(String), -} +use crate::lkdyn::LkDyn; +// The *same* key type the map mirror uses, not a second one. +// +// This module used to define its own four-variant `RtKey` that folded both +// string shapes into one `Str(String)`, on the argument that the VM's split is +// by length and so equality is unaffected. True for equality — and false for +// the **hash**, which is what a set's iteration order is made of. So the two +// definitions agreed about membership and disagreed about order, and the way +// that showed up was `for x in s` never being lowered at all (this module's own +// header said "iteration is not exposed (hash order)"). +// +// One key type, one hash, and the order conformance test can then say something. +use crate::vm_mirror::{RtKey, key_from_dyn, key_from_dyn_in, key_str, str_key}; type LkSet = FxSet; -fn key_from_dyn(v: LkDyn) -> RtKey { - match v.tag { - DYN_NIL => RtKey::Nil, - DYN_BOOL => RtKey::Bool(v.payload != 0), - DYN_I64 => RtKey::Int(v.payload), - DYN_STR => { - let ptr = v.payload as *const c_char; - let text = if ptr.is_null() { - "" - } else { - // SAFETY: DYN_STR payloads are NUL-terminated arena strings. - unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("") - }; - RtKey::Str(text.to_owned()) +fn set_mut<'a>(handle: *mut c_void) -> &'a mut LkSet { + // SAFETY: `handle` addresses an `LkSet` from `lkrt_lkset_new`/`from_*`. + unsafe { &mut *(handle as *mut LkSet) } +} + +/// The set operations, with the **insertion sequence** the VM states: the +/// receiver's members in its own order, then the argument's in its own order. +/// +/// A set's iteration order is its hash order, so filling the answer in another +/// sequence gives the same members in a different order — and a set printed one +/// way here and another way there is a wrong answer under the mirror +/// discipline, not a cosmetic difference. `kind` selects the operation; +/// `SET_OP_*` names the numbering. +/// +/// # Safety +/// Both handles must be live `Set` handles from this module. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkset_combine(a: *mut c_void, b: *mut c_void, kind: i64) -> *mut c_void { + let (mine, theirs) = (set_mut(a), set_mut(b)); + let mut out = LkSet::default(); + match kind { + SET_OP_UNION => { + out.extend(mine.iter().cloned()); + out.extend(theirs.iter().cloned()); + } + SET_OP_INTERSECTION => out.extend(mine.iter().filter(|key| theirs.contains(*key)).cloned()), + SET_OP_DIFFERENCE => out.extend(mine.iter().filter(|key| !theirs.contains(*key)).cloned()), + SET_OP_SYMMETRIC_DIFFERENCE => { + out.extend(mine.iter().filter(|key| !theirs.contains(*key)).cloned()); + out.extend(theirs.iter().filter(|key| !mine.contains(*key)).cloned()); } - // Float is the VM's loud "cannot be used as a key" error; containers - // compare by heap-handle identity, which native cannot mirror. The - // loud-failure contract compares success + stdout only, not text. - DYN_F64 => crate::panic::raise_str("runtime error"), - _ => crate::panic::raise_str("runtime error"), + _ => crate::panic::raise_str("runtime type error"), } + crate::state::arena_handle(out) } -fn set_mut<'a>(handle: *mut c_void) -> &'a mut LkSet { - // SAFETY: `handle` addresses an `LkSet` from `lkrt_lkset_new`/`from_*`. - unsafe { &mut *(handle as *mut LkSet) } +/// The three predicates. `is_disjoint` stops at the first shared member and +/// allocates nothing, which is why it is not `intersection().is_empty()`. +/// +/// # Safety +/// As [`lkrt_lkset_combine`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkset_relate(a: *mut c_void, b: *mut c_void, kind: i64) -> i64 { + let (mine, theirs) = (set_mut(a), set_mut(b)); + let answer = match kind { + SET_REL_SUBSET => mine.iter().all(|key| theirs.contains(key)), + SET_REL_SUPERSET => theirs.iter().all(|key| mine.contains(key)), + SET_REL_DISJOINT => !mine.iter().any(|key| theirs.contains(key)), + _ => crate::panic::raise_str("runtime type error"), + }; + i64::from(answer) } +/// `union`. See [`lkrt_lkset_combine`]. +pub const SET_OP_UNION: i64 = 0; +/// `intersection`. +pub const SET_OP_INTERSECTION: i64 = 1; +/// `difference`. +pub const SET_OP_DIFFERENCE: i64 = 2; +/// `symmetric_difference`. +pub const SET_OP_SYMMETRIC_DIFFERENCE: i64 = 3; +/// `is_subset`. See [`lkrt_lkset_relate`]. +pub const SET_REL_SUBSET: i64 = 0; +/// `is_superset`. +pub const SET_REL_SUPERSET: i64 = 1; +/// `is_disjoint`. +pub const SET_REL_DISJOINT: i64 = 2; + /// Creates a fresh, empty `Set` handle. #[unsafe(no_mangle)] pub extern "C" fn lkrt_lkset_new() -> *mut c_void { @@ -87,7 +131,7 @@ pub unsafe extern "C" fn lkrt_lkset_from_str_list(handle: *mut c_void) -> *mut c // SAFETY: list elements are NUL-terminated arena strings. unsafe { CStr::from_ptr(item) }.to_str().unwrap_or("") }; - set.insert(RtKey::Str(text.to_owned())); + set.insert(str_key(text)); } } crate::state::arena_handle(set) @@ -110,13 +154,41 @@ pub unsafe extern "C" fn lkrt_lkset_from_i64_list(handle: *mut c_void) -> *mut c crate::state::arena_handle(set) } +/// `Set(list)` over a `List` handle — the boxed spelling. +/// +/// Needed because a *constant* list is `List` as soon as its elements are +/// not one uniform type, and "one uniform type" splits strings by length: +/// `["ab", "aaaaaaaaaa"]` is a short string and a long one, so +/// `Set(["ab", "aaaaaaaaaa"])` had no arm while `Set(["ab", "z"])` did. Each +/// element goes through the same `key_from_dyn` the `add` path uses, so a +/// member the VM refuses (a float, a container) raises here too. +/// +/// # Safety +/// `handle` must be a live `List` handle, or null (→ empty set). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkset_from_dyn_list(handle: *mut c_void) -> *mut c_void { + let mut set = LkSet::default(); + if !handle.is_null() { + // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_dyn_new`. + let items = unsafe { &*(handle as *mut Vec) }; + for &item in items { + set.insert(key_from_dyn_in(item, "Set() item")); + } + } + crate::state::arena_handle(set) +} + /// `set.has(v)` / `set.contains(v)` → 0/1. /// /// # Safety /// `handle` must be a live `Set` handle. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lkset_has(handle: *mut c_void, value: LkDyn) -> i64 { - let key = key_from_dyn(value); + // Total: a value that cannot be a key is not a member. `add` still refuses, + // because there the key is being *built*. + let Some(key) = crate::vm_mirror::key_from_dyn_opt(value) else { + return 0; + }; i64::from(set_mut(handle).contains(&key)) } @@ -126,7 +198,7 @@ pub unsafe extern "C" fn lkrt_lkset_has(handle: *mut c_void, value: LkDyn) -> i6 /// `handle` must be a live `Set` handle. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lkset_add(handle: *mut c_void, value: LkDyn) -> i64 { - let key = key_from_dyn(value); + let key = key_from_dyn_in(value, "set.add() value"); i64::from(set_mut(handle).insert(key)) } @@ -158,6 +230,134 @@ pub unsafe extern "C" fn lkrt_lkset_clear(handle: *mut c_void) { set_mut(handle).clear(); } +/// `for x in s` — a snapshot of the members as a dyn list, in the set's own +/// iteration order. +/// +/// The order is the hash layout's, and it is the VM's because both sides key by +/// the *same* [`RtKey`] and fill by the same insertion sequence — a set has no +/// second stage, so there is nothing else in the order. This could not be +/// exposed while this module kept its own one-variant string key: membership +/// agreed, the hash did not. +/// +/// # Safety +/// `handle` must be a live `Set` handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkset_iter(handle: *mut c_void) -> *mut c_void { + let empty = LkSet::default(); + // SAFETY: caller passes a live `LkSet` handle. + let set: &LkSet = if handle.is_null() { + &empty + } else { + unsafe { &*(handle as *mut LkSet) } + }; + let members: Vec = set.iter().map(member_dyn).collect(); + crate::state::arena_handle(members) +} + +/// One member, boxed back into the value it was made from. +fn member_dyn(key: &RtKey) -> LkDyn { + match key { + RtKey::Nil => LkDyn::NIL, + RtKey::Bool(v) => crate::lkdyn::lkrt_dyn_from_bool(i64::from(*v)), + RtKey::Int(v) => crate::lkdyn::lkrt_dyn_from_i64(*v), + other => { + let text = alloc::ffi::CString::new(key_str(other)).unwrap_or_default(); + crate::lkdyn::lkrt_dyn_from_str(crate::lkstr::arena_c_string(text)) + } + } +} + +/// The mirror of `RuntimeMapKey::display_order`: nil, then Bool, then Int by +/// value, then String by content. +/// +/// A set's display order is the one container order that needs **no** mirror +/// discipline, because it is not the hash order — it is imposed, and imposed on +/// the members' *values*. So this is content comparison on both sides, and +/// nothing about hashers or table layout can drift it apart. (Iteration order, +/// `for x in s`, is a different question and still the hash order's.) +fn display_order(a: &RtKey, b: &RtKey) -> core::cmp::Ordering { + fn kind(key: &RtKey) -> u8 { + match key { + RtKey::Nil => 0, + RtKey::Bool(_) => 1, + RtKey::Int(_) => 2, + _ => 3, + } + } + // Reached only for two members of the same kind, so the string arm is the + // one place `key_str` is called — and there both sides are strings. + kind(a).cmp(&kind(b)).then_with(|| match (a, b) { + (RtKey::Bool(x), RtKey::Bool(y)) => x.cmp(y), + (RtKey::Int(x), RtKey::Int(y)) => x.cmp(y), + _ if kind(a) == 3 => key_str(a).cmp(key_str(b)), + _ => core::cmp::Ordering::Equal, + }) +} + +/// One member as `Set(…)` renders it: a string quoted with Rust's `{:?}` (the +/// VM's `quote_string`), everything else bare. +fn member_text(key: &RtKey) -> String { + match key { + RtKey::Nil => "nil".to_string(), + RtKey::Bool(v) => v.to_string(), + RtKey::Int(v) => v.to_string(), + _ => format!("{:?}", key_str(key)), + } +} + +/// `println(s)` → `Set([1,2,3])`, sorted by member. +/// +/// # Safety +/// `handle` must be a live `Set` handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkset_display(handle: *mut c_void) -> *mut c_char { + crate::lkstr::arena_c_string(alloc::ffi::CString::new(set_text(handle)).unwrap_or_default()) +} + +/// `Set([1,2,3])` as text, sorted by member — also what the boxed-value +/// renderer calls, so a set inside a list renders through this one function. +pub(crate) fn set_text(handle: *mut c_void) -> String { + let empty = LkSet::default(); + // SAFETY: caller passes a live `LkSet` handle. + let set: &LkSet = if handle.is_null() { + &empty + } else { + unsafe { &*(handle as *mut LkSet) } + }; + let mut members: Vec<&RtKey> = set.iter().collect(); + members.sort_by(|a, b| display_order(a, b)); + let mut out = String::from("Set(["); + for (i, key) in members.iter().enumerate() { + if i > 0 { + out.push(','); + } + out.push_str(&member_text(key)); + } + out.push_str("])"); + out +} + +/// `a == b` → 0/1: same size and every member of `a` present in `b`. +/// +/// Order-free, like the VM's — a set is its member set. +/// +/// # Safety +/// Both handles must be live `Set` handles, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkset_eq(a: *mut c_void, b: *mut c_void) -> i64 { + let empty = LkSet::default(); + // SAFETY: caller passes live `LkSet` handles. + let borrow = |h: *mut c_void| -> &LkSet { + if h.is_null() { + &empty + } else { + unsafe { &*(h as *mut LkSet) } + } + }; + let (x, y) = (borrow(a), borrow(b)); + i64::from(x.len() == y.len() && x.iter().all(|k| y.contains(k))) +} + #[cfg(test)] mod tests { use super::*; @@ -192,4 +392,57 @@ mod tests { assert_eq!(lkrt_lkset_has(set, lkrt_dyn_from_i64(1)), 0); } } + + /// The order-conformance check that the single `RtKey` makes possible: a + /// set iterates in exactly the order the VM's `FastHashSet` + /// does, for the same members inserted in the same sequence. + /// + /// This is what the module could not say while it kept its own key type — + /// membership agreed and the hash did not, so `for x in s` was simply left + /// out of the native subset rather than being wrong. + #[test] + fn set_iteration_order_matches_the_vm() { + use lk_core::val::{MirrorMember, set_iteration_order}; + + let cases: alloc::vec::Vec> = vec![ + (0..64).map(|i| MirrorMember::Int(i * 3 - 7)).collect(), + // Short (inline) and long (heap) keys mixed: the two shapes hash + // differently, which is the whole reason one key type is required. + (0..48) + .map(|i| { + if i % 3 == 0 { + MirrorMember::Str(alloc::format!("member_number_{i}")) + } else { + MirrorMember::Str(alloc::format!("m{i}")) + } + }) + .collect(), + ]; + for members in cases { + let vm_order = set_iteration_order(members.iter().cloned()); + + let handle = lkrt_lkset_new(); + for member in &members { + let boxed = match member { + MirrorMember::Int(v) => crate::lkdyn::lkrt_dyn_from_i64(*v), + MirrorMember::Str(v) => s(v), + }; + unsafe { lkrt_lkset_add(handle, boxed) }; + } + // SAFETY: just built above. + let native = unsafe { &*(handle as *mut LkSet) }; + let native_order: alloc::vec::Vec = native + .iter() + .map(|k| match k { + RtKey::Int(v) => MirrorMember::Int(*v), + other => MirrorMember::Str(key_str(other).to_string()), + }) + .collect(); + + assert_eq!( + native_order, vm_order, + "set iteration order drifted from the VM's RuntimeSet" + ); + } + } } diff --git a/lkrt/src/lkslice.rs b/lkrt/src/lkslice.rs new file mode 100644 index 00000000..208a2c44 --- /dev/null +++ b/lkrt/src/lkslice.rs @@ -0,0 +1,427 @@ +//! List windows — what `xs.slice(a, b)` returns. +//! +//! A window is `(source handle, start, len)`, not a copy of the elements: the +//! VM's `HeapValue::Slice` (`core/src/val/runtime_model.rs`) in native form. +//! Until this module existed the native `.slice()` returned a fresh list, so +//! the same program had two different answers depending on the backend — +//! `w.to_list()` existed only on one side, and a write to the source showed +//! through the window on one side and not the other. +//! +//! The source is addressed **by handle**, re-read on every access. A pointer +//! into the `Vec`'s buffer would dangle the moment a `push` reallocated it; +//! going through the handle costs one extra load and cannot. +//! +//! Keeping the source alive is not this module's job but the ABI's: the +//! constructors here are annotated [`Receiver::ConstructsView`], which is what +//! stops the scope-drop pass from releasing a source that a live window still +//! points at. +//! +//! [`Receiver::ConstructsView`]: lk_aot_abi::Receiver::ConstructsView + +// `alloc`, not the std prelude — same computation-only subset as `lklist`. +#[allow(unused_imports)] +use alloc::{boxed::Box, vec::Vec}; + +use core::ffi::c_void; + +use crate::lklist::LkMaybeI64; + +/// A window over a `Vec` handle. +/// +/// `start`/`len` are positions in the *source*, already clamped to it at +/// construction. They are not re-clamped on read: a source that shrank after +/// the window was taken reads as absent element by element, which is what the +/// VM does (`slice_element` asks the list and takes nil for an answer). +pub struct LkSliceI64 { + source: *mut c_void, + start: usize, + len: usize, +} + +/// The elements of a live `Vec` handle, or empty for null. +/// +/// # Safety +/// `handle` must be a live `i64` list handle, or null. +unsafe fn source_values<'a>(handle: *mut c_void) -> &'a [i64] { + if handle.is_null() { + return &[]; + } + // SAFETY: `handle` addresses a `Vec` from `lkrt_lklist_i64_new`. + unsafe { &*(handle as *mut Vec) } +} + +/// The window behind a handle, or `None` for null. +/// +/// # Safety +/// `handle` must be a live window handle from [`lkrt_lkslice_i64_new`], or null. +unsafe fn window<'a>(handle: *mut c_void) -> Option<&'a LkSliceI64> { + if handle.is_null() { + return None; + } + // SAFETY: `handle` addresses an `LkSliceI64` from `lkrt_lkslice_i64_new`. + Some(unsafe { &*(handle as *mut LkSliceI64) }) +} + +/// A `slice` bound against a length: negative counts from the end, and the +/// result is clamped into `0..=len`. The VM's `slice_position` says the same. +fn resolve_position(index: i64, len: usize) -> usize { + let len = len as i64; + let resolved = if index < 0 { len + index } else { index }; + resolved.clamp(0, len) as usize +} + +/// `xs.slice(start, end)` — a window over `xs`, no copy. +/// +/// A negative bound counts from the end (`-1` is the last element, as in +/// `xs[-1]`) and past-the-end bounds clamp, matching `slice_position` in the VM. +/// It used to raise on a negative, which is what the VM did *for lists* — while +/// the VM's string slice clamped to 0 and this crate's string slice already +/// counted from the end. Four implementations, three conventions. +/// +/// # Safety +/// `handle` must be a live `i64` list handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_new(handle: *mut c_void, start: i64, end: i64) -> *mut c_void { + // SAFETY: the caller guarantees a live `i64` list handle or null. + let source_len = unsafe { source_values(handle) }.len(); + let end = resolve_position(end, source_len); + let start = resolve_position(start, source_len).min(end); + crate::state::arena_handle(LkSliceI64 { + source: handle, + start, + len: end - start, + }) +} + +/// `w.len()`. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_len(handle: *mut c_void) -> i64 { + // SAFETY: the caller guarantees a live window handle or null. + unsafe { window(handle) }.map_or(0, |w| w.len as i64) +} + +/// `w[i]` as `Maybe`: a negative index counts from the window's end, and +/// anything outside it is absent — the VM's `slice_element`, which resolves the +/// index against the window and then reads through to the source. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_get_pair(handle: *mut c_void, index: i64) -> LkMaybeI64 { + const ABSENT: LkMaybeI64 = LkMaybeI64 { value: 0, present: 0 }; + // SAFETY: the caller guarantees a live window handle or null. + let Some(w) = (unsafe { window(handle) }) else { + return ABSENT; + }; + let index = if index < 0 { w.len as i64 + index } else { index }; + if index < 0 || index as usize >= w.len { + return ABSENT; + } + // SAFETY: `w.source` was a live `i64` list handle when the window was + // taken, and the window keeps it alive (`Receiver::ConstructsView`). + let values = unsafe { source_values(w.source) }; + match values.get(w.start + index as usize) { + Some(&value) => LkMaybeI64 { value, present: 1 }, + // Only reachable if the source shrank after the window was taken. + None => ABSENT, + } +} + +/// `w.slice(start, end)` — a window on a window, resolved against the *original* +/// source rather than nested, so that re-slicing in a loop does not build a +/// chain. Matches `dispatch_slice_builtin_method`. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_sub(handle: *mut c_void, start: i64, end: i64) -> *mut c_void { + // SAFETY: the caller guarantees a live window handle or null. + let Some(w) = (unsafe { window(handle) }) else { + return crate::state::arena_handle(LkSliceI64 { + source: core::ptr::null_mut(), + start: 0, + len: 0, + }); + }; + let end = resolve_position(end, w.len); + let start = resolve_position(start, w.len).min(end); + crate::state::arena_handle(LkSliceI64 { + source: w.source, + start: w.start + start, + len: end - start, + }) +} + +/// The window's elements, without copying them. +/// +/// Every read below goes through here rather than through `to_list`: a window +/// exists precisely so that asking it for a sum does not allocate a list first. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +unsafe fn window_values<'a>(handle: *mut c_void) -> &'a [i64] { + // SAFETY: the caller guarantees a live window handle or null. + let Some(w) = (unsafe { window(handle) }) else { + return &[]; + }; + // SAFETY: as in `lkrt_lkslice_i64_get_pair`. + let values = unsafe { source_values(w.source) }; + let end = (w.start + w.len).min(values.len()); + &values[w.start.min(end)..end] +} + +/// `w.sum()` — wrapping, as the VM's list sum is. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_sum(handle: *mut c_void) -> i64 { + // SAFETY: the caller guarantees a live window handle or null. + unsafe { window_values(handle) } + .iter() + .fold(0i64, |total, value| total.wrapping_add(*value)) +} + +/// `w.min()` / `w.max()` — absent on an empty window, which is the nil the VM +/// answers there. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_min(handle: *mut c_void) -> crate::lkdyn::LkDyn { + // SAFETY: the caller guarantees a live window handle or null. + maybe(unsafe { window_values(handle) }.iter().min().copied()) +} + +/// The `max` half of [`lkrt_lkslice_i64_min`]. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_max(handle: *mut c_void) -> crate::lkdyn::LkDyn { + // SAFETY: the caller guarantees a live window handle or null. + maybe(unsafe { window_values(handle) }.iter().max().copied()) +} + +/// `w.contains(v)`. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_contains(handle: *mut c_void, value: i64) -> i64 { + // SAFETY: the caller guarantees a live window handle or null. + i64::from(unsafe { window_values(handle) }.contains(&value)) +} + +/// `w.count(v)` — how many elements of the window equal `v`. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_count(handle: *mut c_void, value: i64) -> i64 { + // SAFETY: the caller guarantees a live window handle or null. + unsafe { window_values(handle) }.iter().filter(|v| **v == value).count() as i64 +} + +/// `w.index_of(v)` — the position *within the window*, or absent. +/// +/// Absent rather than `-1`: `-1` is a legal position (the last element), so +/// `w[w.index_of(v)]` would quietly read the end instead of failing. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_index_of(handle: *mut c_void, value: i64) -> crate::lkdyn::LkDyn { + // SAFETY: the caller guarantees a live window handle or null. + maybe( + unsafe { window_values(handle) } + .iter() + .position(|candidate| *candidate == value) + .map(|index| index as i64), + ) +} + +/// `w.take(n)` / `w.skip(n)` — a sub-window, not a copy. +/// +/// A separate entry from `sub` because a **count is not a position**: a +/// negative one is the refusal the VM gives, where `sub` would measure from +/// the end. Same split as `lklist`'s and `lkbytes`'s windows. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_take(handle: *mut c_void, count: i64) -> *mut c_void { + if count < 0 { + crate::panic::raise_str(&alloc::format!("slice.take() count must be non-negative, got {count}")); + } + // SAFETY: the caller guarantees a live window handle or null. + unsafe { lkrt_lkslice_i64_sub(handle, 0, count.min(window_len(handle))) } +} + +/// The `skip` half of [`lkrt_lkslice_i64_take`]. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_skip(handle: *mut c_void, count: i64) -> *mut c_void { + if count < 0 { + crate::panic::raise_str(&alloc::format!("slice.skip() count must be non-negative, got {count}")); + } + // SAFETY: the caller guarantees a live window handle or null. + let len = window_len(handle); + unsafe { lkrt_lkslice_i64_sub(handle, count.min(len), len) } +} + +/// The window's length as an `i64`, for the two count guards above. +fn window_len(handle: *mut c_void) -> i64 { + // SAFETY: the callers guarantee a live window handle or null. + unsafe { window(handle) }.map_or(0, |w| w.len as i64) +} + +/// An `Int?` answer as the boxed carrier every other optional-answering helper +/// uses (`bytes_h.min`, `list_h.i64_index_of`): a `Maybe` return is +/// declared in codegen rather than in the ABI table, and these are ordinary +/// table rows. +fn maybe(value: Option) -> crate::lkdyn::LkDyn { + match value { + Some(value) => crate::lkdyn::lkrt_dyn_from_i64(value), + None => crate::lkdyn::LkDyn::NIL, + } +} + +/// `w.to_list()` — the copy, asked for explicitly. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_to_list(handle: *mut c_void) -> *mut c_void { + // SAFETY: the caller guarantees a live window handle or null. + let items: Vec = match unsafe { window(handle) } { + // SAFETY: as in `lkrt_lkslice_i64_get_pair`. + Some(w) => { + let values = unsafe { source_values(w.source) }; + let end = (w.start + w.len).min(values.len()); + values[w.start.min(end)..end].to_vec() + } + None => Vec::new(), + }; + crate::state::arena_handle(items) +} + +/// The window's elements, for the boxed carrier's display and equality. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +pub(crate) unsafe fn window_elements<'a>(handle: *mut c_void) -> &'a [i64] { + // SAFETY: the caller guarantees a live window handle or null. + unsafe { window_values(handle) } +} + +/// `println(w)` — the same rendering as the list it windows, because a window +/// *is* a list as far as the language is concerned. +pub(crate) fn slice_text(handle: *mut c_void) -> alloc::string::String { + // SAFETY: callers pass a live window handle or null. + let values = unsafe { window_values(handle) }; + let mut text = alloc::string::String::with_capacity(values.len() * 4 + 2); + text.push('['); + for (index, value) in values.iter().enumerate() { + if index > 0 { + text.push(','); + } + text.push_str(&alloc::format!("{value}")); + } + text.push(']'); + text +} + +/// `println(w)` / string interpolation. +/// +/// # Safety +/// `handle` must be a live window handle, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_lkslice_i64_display(handle: *mut c_void) -> *mut core::ffi::c_char { + // The text is built from the window directly rather than from a + // materialized list: rendering is a read, and a read does not need a copy. + crate::lkstr::arena_c_string(alloc::ffi::CString::new(slice_text(handle)).unwrap_or_default()) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Build an `i64` list handle the way generated code does. + fn list(values: &[i64]) -> *mut c_void { + let handle = crate::lklist::lkrt_lklist_i64_new(); + for &value in values { + unsafe { crate::lklist::lkrt_lklist_i64_push(handle, value) }; + } + handle + } + + fn read(window: *mut c_void, index: i64) -> Option { + let got = unsafe { lkrt_lkslice_i64_get_pair(window, index) }; + (got.present != 0).then_some(got.value) + } + + #[test] + fn a_window_reads_through_to_its_source() { + let source = list(&[3, 1, 4, 1, 5, 9, 2, 6]); + let window = unsafe { lkrt_lkslice_i64_new(source, 1, 4) }; + assert_eq!(unsafe { lkrt_lkslice_i64_len(window) }, 3); + // The window is `[1, 4, 1]`. + assert_eq!(read(window, 0), Some(1)); + assert_eq!(read(window, 1), Some(4)); + assert_eq!(read(window, 2), Some(1)); + assert_eq!(read(window, -1), Some(1)); + assert_eq!(read(window, 3), None); + assert_eq!(read(window, -4), None); + } + + /// The point of a view: it is not a snapshot. A `push` that reallocates the + /// source must not be able to leave the window pointing at freed memory, + /// which is why the source is addressed by handle rather than by data + /// pointer. + #[test] + fn a_window_sees_the_source_change_under_it() { + let source = list(&[10, 20, 30]); + let window = unsafe { lkrt_lkslice_i64_new(source, 0, 3) }; + for extra in 0..64 { + unsafe { crate::lklist::lkrt_lklist_i64_push(source, extra) }; + } + assert_eq!(read(window, 0), Some(10)); + assert_eq!(unsafe { lkrt_lkslice_i64_len(window) }, 3); + } + + #[test] + fn bounds_clamp_and_a_sub_window_resolves_against_the_original() { + let source = list(&[0, 1, 2, 3, 4]); + let window = unsafe { lkrt_lkslice_i64_new(source, 2, 99) }; + assert_eq!(unsafe { lkrt_lkslice_i64_len(window) }, 3); + + let inner = unsafe { lkrt_lkslice_i64_sub(window, 1, 3) }; + assert_eq!(unsafe { lkrt_lkslice_i64_len(inner) }, 2); + assert_eq!(read(inner, 0), Some(3)); + assert_eq!(read(inner, 1), Some(4)); + } + + #[test] + fn to_list_copies_exactly_the_window() { + let source = list(&[7, 8, 9, 10]); + let window = unsafe { lkrt_lkslice_i64_new(source, 1, 3) }; + let copied = unsafe { lkrt_lkslice_i64_to_list(window) }; + assert_eq!(unsafe { crate::lklist::lkrt_lklist_i64_len(copied) }, 2); + let first = unsafe { crate::lklist::lkrt_lklist_i64_get_pair(copied, 0) }; + assert_eq!((first.value, first.present), (8, 1)); + } + + #[test] + fn an_empty_window_is_empty() { + let source = list(&[1, 2, 3]); + let window = unsafe { lkrt_lkslice_i64_new(source, 2, 2) }; + assert_eq!(unsafe { lkrt_lkslice_i64_len(window) }, 0); + assert_eq!(read(window, 0), None); + } +} diff --git a/lkrt/src/lkstr.rs b/lkrt/src/lkstr.rs index bd5af800..f1b78973 100644 --- a/lkrt/src/lkstr.rs +++ b/lkrt/src/lkstr.rs @@ -113,6 +113,49 @@ pub unsafe extern "C" fn lkrt_str_slice_chars(s: *const c_char, start: i64, end: arena_c_string(CString::new(sliced).unwrap_or_default()) } +/// `s.take(n)` / `s.skip(n)` — a prefix in *characters*, and the rest of one. +/// +/// A separate symbol from `slice_chars`, and that is the whole point: a **count +/// is not a position**, so a negative one is the refusal the VM gives rather +/// than something measured from the tail. Lowered as `slice_chars(s, 0, n)`, +/// `"abc".take(-1)` answered `"ab"` compiled and raised interpreted — while +/// `xs.take(-1)` and `b.take(-1)` raised on both ends, because those two +/// carriers have their own guarded helpers. This is String's. +/// +/// # Safety +/// `s` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_take(s: *const c_char, count: i64) -> *mut c_char { + str_window(s, count, true) +} + +/// The `skip` half of [`lkrt_str_take`]. +/// +/// # Safety +/// `s` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_skip(s: *const c_char, count: i64) -> *mut c_char { + str_window(s, count, false) +} + +fn str_window(s: *const c_char, count: i64, take: bool) -> *mut c_char { + if count < 0 { + // Raised before anything is allocated: a raise longjmps past drops. + crate::panic::raise_str(&alloc::format!( + "string.{}() count must be non-negative, got {count}", + if take { "take" } else { "skip" } + )); + } + let text = view(s); + let count = count as usize; + let kept: String = if take { + text.chars().take(count).collect() + } else { + text.chars().skip(count).collect() + }; + arena_c_string(CString::new(kept).unwrap_or_default()) +} + /// Byte-wise lexicographic comparison of two C strings, returning `-1`/`0`/`1` /// (the sign of the ordering). The caller compares the result against `0` to /// realize `==`/`!=`/`<`/`<=`/`>`/`>=`, matching the VM's string comparison @@ -223,6 +266,34 @@ pub extern "C" fn lkrt_i64_to_str(n: i64) -> *mut c_char { arena_c_string(unsafe { CString::from_vec_unchecked(bytes) }) } +/// Renders the carrier as an *unsigned* decimal string. +/// +/// The same rendering `lkrt_i64_to_str` does, for the one case where the carrier +/// is not an `i64`: a `u64` above `i64::MAX` has bit 63 set, and reading that as +/// a sign turns a physical address into a negative number. The compiler picks +/// this at the display site, which is the last place the width still exists. +/// +/// 20 digits fits `u64::MAX` exactly (18446744073709551615), and there is no +/// sign to leave room for. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_u64_to_str(n: i64) -> *mut c_char { + let mut buf = [0u8; 20]; + let mut magnitude = n as u64; + let mut at = buf.len(); + loop { + at -= 1; + buf[at] = b'0' + (magnitude % 10) as u8; + magnitude /= 10; + if magnitude == 0 { + break; + } + } + let mut bytes = Vec::with_capacity(buf.len() - at + 1); + bytes.extend_from_slice(&buf[at..]); + // SAFETY: decimal digits are ASCII, never NUL. + arena_c_string(unsafe { CString::from_vec_unchecked(bytes) }) +} + /// Renders an `f64` as its display string. The VM formats floats with Rust's /// `f64::to_string()` (see `runtime_value_display_string`), so this uses the same — /// giving byte-identical output (`2.0 → "2"`, `1.0/3.0 → "0.3333333333333333"`). @@ -283,38 +354,6 @@ pub unsafe extern "C" fn lkrt_str_trim(s: *const c_char) -> *mut c_char { arena_c_string(CString::new(view(s).trim()).unwrap_or_default()) } -/// `s.find(needle)` — *byte* index of the first match, `-1` when absent -/// (the VM returns the Rust `str::find` byte position). -/// -/// # Safety -/// Both pointers must be valid C strings, or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_str_find(s: *const c_char, needle: *const c_char) -> i64 { - view(s).find(view(needle)).map_or(-1, |pos| pos as i64) -} - -/// `s.substring(start, length)` — *byte*-indexed (the VM slices bytes here, -/// unlike the char-based range slice): the end clamps to the byte length, -/// `end <= start` yields the empty string, and a non-boundary index is the -/// VM's panic — flush-and-abort. -/// -/// # Safety -/// `s` must be a valid C string, or null. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn lkrt_str_substring(s: *const c_char, start: i64, length: i64) -> *mut c_char { - let text = view(s); - let start = start as usize; - let end = start.saturating_add(length as usize).min(text.len()); - if end <= start { - return arena_c_string(CString::default()); - } - let Some(sliced) = text.get(start..end) else { - crate::rt_eprintln!("string.substring() index is not a char boundary"); - crate::panic::raise_str("runtime error"); - }; - arena_c_string(CString::new(sliced).unwrap_or_default()) -} - /// `s.reverse()` — char-wise reversal. /// /// # Safety @@ -325,13 +364,23 @@ pub unsafe extern "C" fn lkrt_str_reverse(s: *const c_char) -> *mut c_char { arena_c_string(CString::new(reversed).unwrap_or_default()) } -/// `s.repeat(n)` — `n <= 0` yields the empty string. +/// `s.repeat(n)` — `n == 0` yields the empty string, `n < 0` raises. +/// +/// This said "`n <= 0` yields the empty string", which is a rule the +/// interpreter does not have: it raises for a negative count, the way +/// `take`, `skip` and the `pad_*` widths all do. So `"ab".repeat(-1)` +/// answered `""` compiled and stopped the program interpreted — the only one +/// of the four guards that was not mirrored. /// /// # Safety /// `s` must be a valid C string, or null. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_str_repeat(s: *const c_char, n: i64) -> *mut c_char { - if n <= 0 { + if n < 0 { + // Raised before anything is allocated: a raise longjmps past drops. + crate::panic::raise_str(&alloc::format!("string.repeat() count must be non-negative, got {n}")); + } + if n == 0 { return arena_c_string(CString::default()); } arena_c_string(CString::new(view(s).repeat(n as usize)).unwrap_or_default()) @@ -343,7 +392,35 @@ pub unsafe extern "C" fn lkrt_str_repeat(s: *const c_char, n: i64) -> *mut c_cha /// All pointers must be valid C strings, or null. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_str_replace(s: *const c_char, from: *const c_char, to: *const c_char) -> *mut c_char { - arena_c_string(CString::new(view(s).replace(view(from), view(to))).unwrap_or_default()) + unsafe { lkrt_str_replace_limited(s, from, to, -1) } +} + +/// `s.replace(from, to)` with a cap on how many occurrences are replaced: +/// `limit` negative means every one, otherwise at most that many from the +/// left. +/// +/// This is what the method's `all` parameter compiles to, which is why it is a +/// count rather than a flag — `all: false` is "at most one" and `all: true` is +/// "no limit", and both are the same primitive. The flag can be a runtime +/// value, so picking between two entries at compile time would not have +/// covered `s.replace(a, b, flag)`. +/// +/// # Safety +/// All pointers must be valid C strings, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_replace_limited( + s: *const c_char, + from: *const c_char, + to: *const c_char, + limit: i64, +) -> *mut c_char { + let (s, from, to) = (view(s), view(from), view(to)); + let replaced = if limit < 0 { + s.replace(from, to) + } else { + s.replacen(from, to, limit as usize) + }; + arena_c_string(CString::new(replaced).unwrap_or_default()) } /// The module `string.len(s)` — **byte** length (`str::len`), unlike the @@ -356,6 +433,29 @@ pub unsafe extern "C" fn lkrt_str_byte_len(s: *const c_char) -> i64 { view(s).len() as i64 } +/// `s.index_of(needle)` — the *character* position of the first occurrence, or +/// nil. +/// +/// Characters, not bytes, because that is what `s.len()` counts and `s[i]` +/// indexes — an answer in bytes could not be handed back to either. Nil rather +/// than -1 for a miss, because -1 is a valid index into a string (it is the +/// last character), so `s[s.index_of(x)]` would quietly answer that instead of +/// failing. +/// +/// This replaces `lkrt_str_find`, which reported a byte offset and -1, and so +/// disagreed with the VM twice over. +/// +/// # Safety +/// Both pointers must be valid C strings, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_index_of(s: *const c_char, needle: *const c_char) -> crate::lkdyn::LkDyn { + let text = view(s); + match text.find(view(needle)) { + Some(byte) => crate::lkdyn::lkrt_dyn_from_i64(text[..byte].chars().count() as i64), + None => crate::lkdyn::LkDyn::NIL, + } +} + /// `string.strip_prefix(s, prefix)` — the stripped remainder, or nil (a /// boxed Dyn: the module returns `String?`). /// @@ -387,19 +487,114 @@ pub unsafe extern "C" fn lkrt_str_strip_suffix(s: *const c_char, suffix: *const } } -/// `string.count(s, needle)` — non-overlapping matches; an empty needle -/// counts *byte* length + 1 (the stdlib module's exact rule). +/// `string.to_int(s[, base])` — the number, or nil when the text is not one. +/// +/// Whitespace is trimmed and the answer is boxed because the module returns +/// `Int?`. Must stay byte-identical to `lk_stdlib_string::to_int`'s String arm: +/// `i64::from_str_radix` on the trimmed text, so `"42.0"`, `""` and an +/// out-of-range number are all nil rather than a guess. +/// +/// # Safety +/// `s` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_to_int(s: *const c_char, base: i64) -> crate::lkdyn::LkDyn { + if !(2..=36).contains(&base) { + crate::panic::raise_str("to_int() base must be between 2 and 36"); + } + match i64::from_str_radix(view(s).trim(), base as u32) { + Ok(value) => crate::lkdyn::lkrt_dyn_from_i64(value), + Err(_) => crate::lkdyn::LkDyn::NIL, + } +} + +/// `string.to_float(s)` — see [`lkrt_str_to_int`]. `"nan"`, `"inf"` and +/// `"-inf"` parse: they are Float values. +/// +/// # Safety +/// `s` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_to_float(s: *const c_char) -> crate::lkdyn::LkDyn { + match view(s).trim().parse::() { + Ok(value) => crate::lkdyn::lkrt_dyn_from_f64(value), + Err(_) => crate::lkdyn::LkDyn::NIL, + } +} + +/// `string.count(s, needle)` — non-overlapping matches. +/// +/// No special case for the empty needle: `str::matches("")` already answers one +/// match between every pair of characters and at both ends, which is the same +/// rule stated in *characters*. The special case here said *bytes* + 1, so +/// `string.count("中中", "")` was 7 compiled and 3 interpreted. /// /// # Safety /// Both pointers must be valid C strings, or null. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_str_count(s: *const c_char, needle: *const c_char) -> i64 { + view(s).matches(view(needle)).count() as i64 +} + +/// `string.strip(s, chars)` — both ends, every character that is in `chars`. +/// +/// A *set* of characters, not an affix: `strip_prefix`/`strip_suffix` next door +/// are the once-each operations. Byte-identical to the VM's `str::trim_matches`. +/// +/// # Safety +/// Both pointers must be valid C strings, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_strip(s: *const c_char, chars: *const c_char) -> *mut c_char { + let set = view(chars); + let stripped = view(s).trim_matches(|ch| set.contains(ch)); + arena_c_string(CString::new(stripped).unwrap_or_default()) +} + +/// `s.pad_left(width[, fill])` / `s.pad_right(…)` — widened to `width` +/// **characters** by repeating `fill` from its start. +/// +/// Characters, because that is the unit everything else in the language counts. +/// And the fill repeats by `cycle().take(n)` rather than by slicing a repeated +/// string, so there is no byte boundary to get wrong. +/// +/// # Safety +/// Both pointers must be valid C strings, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_pad_left(s: *const c_char, width: i64, fill: *const c_char) -> *mut c_char { + pad(s, width, fill, true, "pad_left") +} + +/// The right-hand half of [`lkrt_str_pad_left`]. +/// +/// # Safety +/// Both pointers must be valid C strings, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_pad_right(s: *const c_char, width: i64, fill: *const c_char) -> *mut c_char { + pad(s, width, fill, false, "pad_right") +} + +fn pad(s: *const c_char, width: i64, fill: *const c_char, left: bool, name: &str) -> *mut c_char { + // Raised before anything is allocated: a raise longjmps past Rust drops. + if width < 0 { + crate::panic::raise_str(&alloc::format!( + "string.{name}() width must be non-negative, got {width}" + )); + } + let fill = view(fill); + if fill.is_empty() { + crate::panic::raise_str(&alloc::format!("string.{name}() fill must not be empty")); + } let text = view(s); - let pat = view(needle); - if pat.is_empty() { - return text.len() as i64 + 1; + let len = text.chars().count(); + let width = width as usize; + if len >= width { + return arena_c_string(CString::new(text).unwrap_or_default()); } - text.matches(pat).count() as i64 + let padding: String = fill.chars().cycle().take(width - len).collect(); + let padded = if left { + alloc::format!("{padding}{text}") + } else { + alloc::format!("{text}{padding}") + }; + arena_c_string(CString::new(padded).unwrap_or_default()) } /// `string.capitalize(s)` — first char uppercased, the rest lowercased @@ -482,16 +677,60 @@ pub unsafe extern "C" fn lkrt_str_chars(s: *const c_char) -> *mut core::ffi::c_v crate::state::arena_handle_owning_strings(elements, owned) } +/// `string.byte_at(s, i)` — one byte, as a number, or absent past the end. +/// +/// The one string read that allocates nothing. `char_at` below answers a +/// *string* of one character, which means an allocation, which means it cannot +/// be used from an interrupt handler or on a target with no allocator running +/// yet — and a freestanding program that wants to put a message on a serial +/// port needs exactly this and nothing else. +/// +/// Bytes rather than characters, and deliberately: a byte index is O(1) where a +/// character index is a scan, and code that pushes a message to a device is +/// working in bytes anyway. Absent rather than a raise for out of range, +/// because the caller is a loop bounded by `len` and a raise would be a cost +/// paid on every iteration of the common case. +/// +/// This used to answer `-1`, and so did the VM — a sentinel where the language +/// says nil everywhere else it means absent, and where `string.byte_at` (the +/// same operation, module-spelled) already answered nil. +/// +/// # Safety +/// `s` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_str_byte_at(s: *const c_char, index: i64) -> crate::lklist::LkMaybeI64 { + use crate::lklist::LkMaybeI64; + let text = view(s).as_bytes(); + if index < 0 || index >= text.len() as i64 { + return LkMaybeI64 { value: 0, present: 0 }; + } + LkMaybeI64 { + value: text[index as usize] as i64, + present: 1, + } +} + /// `s[i]` — single-char read as a Dyn (char-indexed; out of bounds is nil, -/// exactly the VM's `index_string_at`). A negative index counts back from -/// the *byte* length (the VM's quirk — exact for ASCII). +/// exactly the VM's `index_string_at`). A negative index counts back from the +/// *character* count, like `s.len()` and like `s[i]` — it used to count back +/// from the byte length on both sides, so `"中文abc"[-1]` answered nil. /// /// # Safety /// `s` must be a valid C string, or null. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_str_char_at(s: *const c_char, index: i64) -> crate::lkdyn::LkDyn { let text = view(s); - let idx = if index < 0 { text.len() as i64 + index } else { index }; + let idx = if index < 0 { + // For ASCII the byte length *is* the character count. + let len = if text.is_ascii() { + text.len() as i64 + } else { + text.chars().count() as i64 + }; + len + index + } else { + index + }; if idx < 0 { return crate::lkdyn::LkDyn::NIL; } diff --git a/lkrt/src/mmio.rs b/lkrt/src/mmio.rs deleted file mode 100644 index a5aab289..00000000 --- a/lkrt/src/mmio.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Volatile memory-mapped I/O. -//! -//! These exist as *calls* rather than inline loads and stores for one reason: -//! Cranelift has no volatile flag. Its `MemFlags` can say `notrap`, `aligned`, -//! `readonly`, `can_move` and an alias region, and none of those means "this -//! access must happen exactly as written". `can_move` only forbids moving an -//! access; it does not stop the egraph pass proving two loads of one address -//! equal and keeping a single one. -//! -//! That was measured, not assumed. Lowering `volatile_read_u32(p)` twice to -//! inline loads produced one `mov (%rdi),%esi` followed by `lea (%rsi,%rsi,1)` -//! — the second read gone and `a + b` folded to `a * 2`. For a device register, -//! whose two reads can legitimately differ and whose reads can have side -//! effects, that is a miscompile. -//! -//! An opaque call cannot be collapsed that way, and the bodies below use -//! `read_volatile`/`write_volatile`, where Rust guarantees the access happens. -//! The cost is one call per access — negligible beside the tens to hundreds of -//! nanoseconds an MMIO access takes in hardware. -//! -//! All entries are `WritesHost` in the ABI table, including the reads: the -//! effect annotation drives CSE, and a read that may change device state is not -//! pure no matter what it returns. - -/// # Safety -/// -/// `addr` must be a valid, mapped address for an aligned `u8` access. Nothing -/// here can check that — it is the claim the caller makes by writing `unsafe` -/// in LK. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_read_u8(addr: i64) -> i64 { - unsafe { core::ptr::read_volatile(addr as usize as *const u8) as i64 } -} - -/// # Safety -/// -/// As [`lkrt_mmio_read_u8`], for a `u16` access. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_read_u16(addr: i64) -> i64 { - unsafe { core::ptr::read_volatile(addr as usize as *const u16) as i64 } -} - -/// # Safety -/// -/// As [`lkrt_mmio_read_u8`], for a `u32` access. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_read_u32(addr: i64) -> i64 { - unsafe { core::ptr::read_volatile(addr as usize as *const u32) as i64 } -} - -/// # Safety -/// -/// As [`lkrt_mmio_read_u8`], for a `u64` access. -/// -/// The result is reinterpreted rather than range-checked: a 64-bit register -/// with its top bit set reads back as a negative `i64`, which is the same bit -/// pattern the VM's carrier holds. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_read_u64(addr: i64) -> i64 { - unsafe { core::ptr::read_volatile(addr as usize as *const u64) as i64 } -} - -/// # Safety -/// -/// `addr` must be a valid, mapped, writable address for an aligned `u8` access. -/// -/// `value` is truncated to the access width, matching what the LK type checker -/// already required of the argument. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_write_u8(addr: i64, value: i64) { - unsafe { core::ptr::write_volatile(addr as usize as *mut u8, value as u8) } -} - -/// # Safety -/// -/// As [`lkrt_mmio_write_u8`], for a `u16` access. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_write_u16(addr: i64, value: i64) { - unsafe { core::ptr::write_volatile(addr as usize as *mut u16, value as u16) } -} - -/// # Safety -/// -/// As [`lkrt_mmio_write_u8`], for a `u32` access. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_write_u32(addr: i64, value: i64) { - unsafe { core::ptr::write_volatile(addr as usize as *mut u32, value as u32) } -} - -/// # Safety -/// -/// As [`lkrt_mmio_write_u8`], for a `u64` access. -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_mmio_write_u64(addr: i64, value: i64) { - unsafe { core::ptr::write_volatile(addr as usize as *mut u64, value as u64) } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// A round trip through real memory: the address is a local's, so the - /// access is valid and the value must survive it. - #[test] - fn volatile_round_trips_through_memory() { - let mut cell: u32 = 0; - let addr = (&raw mut cell) as usize as i64; - lkrt_mmio_write_u32(addr, 0xDEAD_BEEF_u32 as i64); - assert_eq!(cell, 0xDEAD_BEEF); - assert_eq!(lkrt_mmio_read_u32(addr), 0xDEAD_BEEF_i64); - } - - #[test] - fn narrower_widths_touch_only_their_own_bytes() { - let mut cell: u64 = 0; - let addr = (&raw mut cell) as usize as i64; - lkrt_mmio_write_u8(addr, 0xFF); - // Only the low byte, whichever end the target calls low. - assert_eq!(cell & 0xFF, 0xFF); - assert_eq!(cell & !0xFF, 0); - assert_eq!(lkrt_mmio_read_u8(addr), 0xFF); - } - - /// Writes truncate to the access width rather than spilling into the - /// neighbouring bytes. - #[test] - fn writes_truncate_to_the_access_width() { - let mut cell: u64 = 0; - let addr = (&raw mut cell) as usize as i64; - lkrt_mmio_write_u8(addr, 0x1FF); - assert_eq!(cell & 0xFF, 0xFF); - assert_eq!(cell & !0xFF, 0); - } - - /// A full-width read is a bit pattern, not a magnitude: the top bit comes - /// back as a negative `i64`, matching the VM's carrier. - #[test] - fn full_width_reads_reinterpret_rather_than_saturate() { - let mut cell: u64 = u64::MAX; - let addr = (&raw mut cell) as usize as i64; - assert_eq!(lkrt_mmio_read_u64(addr), -1); - } -} diff --git a/lkrt/src/net.rs b/lkrt/src/net.rs index 1757a795..0b827330 100644 --- a/lkrt/src/net.rs +++ b/lkrt/src/net.rs @@ -1,5 +1,5 @@ use crate::{ - abi::{aborting, c_str, owned_c_string}, + abi::{c_str, owned_c_string, raising}, state::{HandleKind, with_runtime}, }; use std::{ @@ -10,7 +10,7 @@ use std::{ #[unsafe(no_mangle)] pub extern "C" fn lkrt_socket_addr(host: *const c_char, port: i64) -> *mut c_char { - aborting(|| { + raising(|| { if !(0..=65535).contains(&port) { return Err(format!("socket.addr port expects integer 0..65535, got {port}")); } @@ -21,16 +21,23 @@ pub extern "C" fn lkrt_socket_addr(host: *const c_char, port: i64) -> *mut c_cha #[unsafe(no_mangle)] pub extern "C" fn lkrt_tcp_connect(addr: *const c_char) -> i64 { - aborting(|| { + raising(|| { let addr = c_str(addr, "tcp.connect addr")?; let stream = TcpStream::connect(addr.as_str()).map_err(|err| format!("tcp connect {addr}: {err}"))?; Ok(with_runtime(|rt| rt.insert_stream(stream))) }) } +/// `tcp.read(stream, max)` — the bytes read, as a `Bytes` **value**. +/// +/// It used to answer the one-shot host handle (`insert_bytes`, read once with +/// `take_bytes`). That made `tcp.read` produce something that was not the +/// language's `Bytes`: you could hand it to `bytes.to_string_utf8` exactly once +/// and to nothing else. A `Bytes` is an ordinary value, so this hands back the +/// arena handle every other producer does. #[unsafe(no_mangle)] -pub extern "C" fn lkrt_tcp_read(stream: i64, max_bytes: i64) -> i64 { - aborting(|| { +pub extern "C" fn lkrt_tcp_read(stream: i64, max_bytes: i64) -> *mut core::ffi::c_void { + raising(|| { let max = checked_read_len(max_bytes)?; let mut stream = with_runtime(|rt| { rt.stream(stream)? @@ -40,43 +47,26 @@ pub extern "C" fn lkrt_tcp_read(stream: i64, max_bytes: i64) -> i64 { let mut buffer = vec![0u8; max]; let read = stream.read(&mut buffer).map_err(|err| format!("tcp read: {err}"))?; buffer.truncate(read); - Ok(with_runtime(|rt| rt.insert_bytes(buffer))) + Ok(crate::lkbytes::bytes_handle(buffer)) }) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_tcp_write_str(stream: i64, data: *const c_char) -> i64 { - aborting(|| { + raising(|| { let data = c_str(data, "tcp.write data")?; write_stream(stream, data.as_bytes()) }) } #[unsafe(no_mangle)] -pub extern "C" fn lkrt_tcp_write_bytes(stream: i64, data: i64) -> i64 { - aborting(|| { - let data = with_runtime(|rt| rt.take_bytes(data))?; - write_stream(stream, &data) - }) +pub extern "C" fn lkrt_tcp_write_bytes(stream: i64, data: *mut core::ffi::c_void) -> i64 { + raising(|| write_stream(stream, crate::lkbytes::bytes_slice(data))) } #[unsafe(no_mangle)] pub extern "C" fn lkrt_tcp_close(stream: i64) -> i64 { - aborting(|| with_runtime(|rt| rt.close_kind(stream, HandleKind::TcpStream)).map(i64::from)) -} - -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_bytes_to_string_utf8(bytes: i64) -> *mut c_char { - aborting(|| { - let bytes = with_runtime(|rt| rt.take_bytes(bytes))?; - let value = core::str::from_utf8(&bytes).map_err(|err| format!("bytes are not valid UTF-8: {err}"))?; - owned_c_string(value) - }) -} - -#[unsafe(no_mangle)] -pub extern "C" fn lkrt_bytes_free(bytes: i64) -> i64 { - aborting(|| with_runtime(|rt| rt.close_kind(bytes, HandleKind::Bytes)).map(i64::from)) + raising(|| with_runtime(|rt| rt.close_kind(stream, HandleKind::TcpStream)).map(i64::from)) } #[unsafe(no_mangle)] @@ -132,8 +122,11 @@ mod tests { let stream = lkrt_tcp_connect(addr.as_ptr()); let request = CString::new("ping").expect("request cstring"); assert_eq!(lkrt_tcp_write_str(stream, request.as_ptr()), 4); + // A `Bytes` **value** now, not the one-shot host handle: read it twice + // to say so. let bytes = lkrt_tcp_read(stream, 4); - let response = lkrt_bytes_to_string_utf8(bytes); + assert_eq!(unsafe { crate::lkbytes::lkrt_lkbytes_len(bytes) }, 4); + let response = unsafe { crate::lkbytes::lkrt_lkbytes_utf8(bytes) }; // SAFETY: response is an lkrt-owned NUL-terminated CString pointer. let response_text = unsafe { core::ffi::CStr::from_ptr(response) } .to_str() @@ -142,7 +135,7 @@ mod tests { // SAFETY: the pointer came from an lkrt owned-string return. unsafe { lkrt_string_free(response) }; assert_eq!(response_text, "pong"); - assert_eq!(lkrt_bytes_free(bytes), 0); + assert_eq!(unsafe { crate::lkbytes::lkrt_lkbytes_len(bytes) }, 4); assert_eq!(lkrt_tcp_close(stream), 1); assert_eq!(lkrt_tcp_close(stream), 0); server.join().expect("server thread"); diff --git a/lkrt/src/panic.rs b/lkrt/src/panic.rs index 8dd7ba11..15e54b64 100644 --- a/lkrt/src/panic.rs +++ b/lkrt/src/panic.rs @@ -1,11 +1,11 @@ -//! Native protected calls (deep-coverage plan G: `try$call`): a setjmp/ +//! Native protected regions (deep-coverage plan G): a setjmp/ //! longjmp handler stack plus mutable capture cells. //! //! The generated code executes `_setjmp` itself (declared `returns_twice` in //! the IR — the compiler must see it); this module owns the jump buffers, //! the raised value, and the raise entry points. `raise` with no live -//! handler stays `flush_and_abort()` — an uncaught error's observable -//! behaviour (flushed stdout + abnormal exit) is unchanged. +//! handler flushes stdout, prints the error, and exits 1 — the same status +//! the VM gives, because an uncaught error is the program failing. //! //! longjmp-over-Rust-frames safety: the frames skipped between a raise and //! its handler only hold arena-owned values and plain temporaries (the arena @@ -199,6 +199,20 @@ pub extern "C" fn lkrt_rt_current_error() -> LkDyn { } fn raise_current(value: LkDyn) -> ! { + // The rule at the top of this module, asked rather than trusted. A raise + // taken with a runtime borrow live does not fail here — it fails at the next + // runtime operation, which is somewhere else entirely and reads as a bug in + // whatever code happened to be next. Saying it at the raise is the + // difference between a name and a puzzle. + #[cfg(feature = "std")] + if crate::state::runtime_borrow_is_live() { + crate::rt_eprintln!( + "lkrt: a raise was taken while a runtime borrow was live; the borrow would never be \ + released. This is an lkrt bug — the entry that raised must drop its runtime borrow \ + first (see `raising` in abi.rs)." + ); + crate::abi::flush_and_abort() + } with_current_error(|slot| slot.set(value)); let target = with_handlers(|handlers| handlers.pop()); match target { @@ -222,19 +236,27 @@ fn raise_current(value: LkDyn) -> ! { crate::abi::flush_and_abort() } } - // Uncaught: surface the error before dying — the VM prints its - // uncaught message to stderr, a silent abort loses it. (Only the - // stderr *text* differs across backends; the differential contract - // compares stdout + success only.) + // Uncaught: surface the error before dying — a silent abort loses it. + // Exit 1 rather than abort: the program failed, the runtime did not. + // + // `Error: ` is the label the whole language reports with — parse errors, + // type errors, and every `diagnostic::error` in the CLI. This said `lk: + // uncaught error: ` for as long as the divergence was written off as + // "only the stderr text differs, and the differential compares stdout + + // success only" — which says what the gate looked at, not what a reader + // gets: the same failing program read two different ways depending on + // which backend ran it, and `lk:` named a program that a compiled binary + // is not. `an_uncaught_error_exits_and_reads_the_same_on_both_backends` now compares + // the two byte for byte. None => { - crate::rt_eprintln!("lk: uncaught error: {}", crate::lkdyn::display_for_diagnostics(value)); - crate::abi::flush_and_abort() + crate::rt_eprintln!("Error: {}", crate::lkdyn::display_for_diagnostics(value)); + crate::abi::flush_and_exit_failure() } } } /// Internal guard entry: raises a message string to the nearest `try` frame -/// (arena-owned), or aborts loudly — every lkrt guard that mirrors a +/// (arena-owned), or reports it and exits 1 — every lkrt guard that mirrors a /// *catchable* VM error routes through here (G3). `panic` stays fatal. pub(crate) fn raise_str(message: &str) -> ! { let owned = arena_c_string(CString::new(message).unwrap_or_default()); @@ -268,6 +290,31 @@ pub unsafe extern "C" fn lkrt_rt_raise_msg(message: *const c_char) { raise_current(crate::lkdyn::lkrt_dyn_from_str(owned)) } +/// Raises `message` when a nullable carrier turned out to be absent. +/// +/// The counterpart of the `lkrt_maybe_*_unwrap` family, for the sites that know +/// what the interpreter would have said. Those helpers raise a fixed +/// `"runtime error"` because they are handed a value and a bit and nothing else; +/// this one is handed the sentence, built where the operator and both operand +/// types are still known. `try { xs[9] + 1 } catch e { e }` therefore reads the +/// same on both backends, which it did not. +/// +/// # Safety +/// `message` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_rt_maybe_guard(present: i64, message: *const c_char) { + if present != 0 { + return; + } + if message.is_null() { + raise_str("runtime error"); + } + // SAFETY: caller passes a NUL-terminated string; the copy outlives the + // raising frame because it goes into the arena. + let text = unsafe { core::ffi::CStr::from_ptr(message) }.to_owned(); + raise_current(crate::lkdyn::lkrt_dyn_from_str(arena_c_string(text))) +} + // ── Mutable capture cells ─────────────────────────────────────────────── // The VM promotes a local assigned inside a closure to an `UpvalCell` (a // shared mutable box). Natively a cell is an arena-owned `LkDyn` slot passed @@ -289,6 +336,46 @@ pub unsafe extern "C" fn lkrt_rt_cell_get(cell: *mut c_void) -> LkDyn { unsafe { *(cell as *mut LkDyn) } } +/// Allocates a cell parking a **raw handle** — a typed container, which cannot +/// survive being boxed (see [`crate::lkdyn::DYN_RAW`]). +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_rt_cell_new_raw(handle: i64) -> *mut c_void { + crate::state::arena_handle(LkDyn { + tag: crate::lkdyn::DYN_RAW, + payload: handle, + }) +} + +/// Reads a raw-handle cell. Raises if the cell holds a boxed value instead — +/// the two families must not be crossed, and this is where that is caught. +/// +/// # Safety +/// `cell` must be a live handle from [`lkrt_rt_cell_new_raw`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_rt_cell_get_raw(cell: *mut c_void) -> i64 { + // SAFETY: `cell` addresses an `LkDyn` from one of the cell constructors. + let value = unsafe { *(cell as *mut LkDyn) }; + if value.tag != crate::lkdyn::DYN_RAW { + crate::panic::raise_str("runtime error"); + } + value.payload +} + +/// Writes a raw-handle cell. +/// +/// # Safety +/// `cell` must be a live handle from [`lkrt_rt_cell_new_raw`]. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_rt_cell_set_raw(cell: *mut c_void, handle: i64) { + // SAFETY: as above. + unsafe { + *(cell as *mut LkDyn) = LkDyn { + tag: crate::lkdyn::DYN_RAW, + payload: handle, + } + }; +} + /// Writes a cell. /// /// # Safety diff --git a/lkrt/src/stack_guard.c b/lkrt/src/stack_guard.c new file mode 100644 index 00000000..2fb9c5ad --- /dev/null +++ b/lkrt/src/stack_guard.c @@ -0,0 +1,101 @@ +/* Stack-exhaustion diagnostic for native LK binaries. + * + * The VM bounds recursion by counting frames: past `LK_MAX_CALL_DEPTH` (100000 + * by default) it raises `call depth limit exceeded`, which `try` can catch. A + * native binary has no counter — it has the real stack — so runaway recursion + * walked into the guard page and the process died on SIGSEGV: **exit 139 and not + * one byte of output**. Same program, same mistake, and one backend explained it + * while the other said nothing. + * + * Rust's own binaries print "has overflowed its stack" because `lang_start` + * installs this handler during runtime init. A generated binary's `main` is + * Cranelift's, so that init never runs and the handler is never installed. This + * file installs it from the entry prologue instead. + * + * In C rather than Rust for the same reason `try_trampoline.c` is: `sigaltstack` + * and `sigaction` are libc shapes whose structs are platform-specific, and lkrt + * has no `libc` dependency to spell them with. `build.rs` skips this file for + * bare-metal targets, which have no signals to handle. + * + * Everything the handler does is async-signal-safe: `write` and `_exit`, no + * allocation and no locks. That is also why it cannot raise into the enclosing + * `try` the way the VM's depth error does — that needs a value on the arena and + * lkrt's handler stack behind a `RefCell`, neither of which a signal handler may + * touch. So the native answer is a diagnosed exit, not a catchable error, and the + * two backends still differ in *that* respect; the difference is written down in + * docs/semantics.md rather than left for a reader to discover as exit 139. + */ + +#include +#include +#include +#include +#include +#include + +/* The handler needs a stack of its own: the thread's is what just ran out. + * Sized generously and statically, because allocating here would defeat the + * point. */ +static char lk_alt_stack[65536]; + +/* The address range a stack overflow can fault in. Established at install time + * from this frame's address (the prologue runs from `main`, so it is near the + * base) and `RLIMIT_STACK`. */ +static uintptr_t lk_stack_low; +static uintptr_t lk_stack_high; + +static const char LK_STACK_MSG[] = + "Error: stack exhausted: recursion too deep. A native binary is bounded by the real stack, not by " + "LK_MAX_CALL_DEPTH, and cannot catch this the way the VM does.\n"; + +static void lk_on_fault(int sig, siginfo_t *info, void *context) { + (void)context; + uintptr_t addr = (uintptr_t)info->si_addr; + if (addr >= lk_stack_low && addr <= lk_stack_high) { + /* `write` and `_exit`: the two calls a handler may make. Exit 1, which is + * what the VM exits with for the same program. */ + ssize_t written = write(2, LK_STACK_MSG, sizeof(LK_STACK_MSG) - 1); + (void)written; + _exit(1); + } + /* Not the stack — a genuine bad access. Put the default action back and + * return, so the fault re-triggers and the process dies exactly as it did + * before this file existed. Reporting it as "stack exhausted" would be a + * message that names the wrong cause. */ + signal(sig, SIG_DFL); +} + +void lk_install_stack_guard(void) { + stack_t alt; + memset(&alt, 0, sizeof(alt)); + alt.ss_sp = lk_alt_stack; + alt.ss_size = sizeof(lk_alt_stack); + alt.ss_flags = 0; + if (sigaltstack(&alt, NULL) != 0) { + return; + } + + char here; + struct rlimit limit; + /* An unlimited stack still faults somewhere; 64 MiB is a bound wide enough + * to cover a real overflow and narrow enough that a wild pointer elsewhere + * in the address space is not mistaken for one. */ + size_t span = (size_t)64 * 1024 * 1024; + if (getrlimit(RLIMIT_STACK, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY) { + span = (size_t)limit.rlim_cur; + } + uintptr_t base = (uintptr_t)&here; + /* One page of slack above, because the prologue's frame is not the very top. + * Saturating below, so a small `base` cannot wrap the range open. */ + lk_stack_high = base + 4096; + lk_stack_low = (base > span + 4096) ? base - span - 4096 : 0; + + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_sigaction = lk_on_fault; + action.sa_flags = SA_ONSTACK | SA_SIGINFO; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + /* Some platforms (macOS) report a guard-page hit as SIGBUS. */ + sigaction(SIGBUS, &action, NULL); +} diff --git a/lkrt/src/state.rs b/lkrt/src/state.rs index bf1d4ae4..2cee701f 100644 --- a/lkrt/src/state.rs +++ b/lkrt/src/state.rs @@ -77,15 +77,34 @@ pub(crate) fn with_runtime(f: impl FnOnce(&mut RuntimeState) -> R) -> R { f(&mut RUNTIME.lock()) } +/// Whether this thread is *inside* a [`with_runtime`] call. +/// +/// Asked on the raise path, which is the one place the answer has to be no: a +/// raise `_longjmp`s past every Rust frame between here and the handler, so a +/// live borrow's `RefMut` never drops and the flag stays set. What follows is +/// not a crash but a puzzle — the *next* runtime operation, arbitrarily far +/// away and in unrelated code, panics with "already mutably borrowed". +/// +/// Every lkrt entry that can raise is written to avoid this: the `raising` / +/// `status` wrappers make the closure return a `Result`, so ordinary Rust drops +/// run before the raise happens outside it, and the handful of direct +/// `raise_str` calls drop their guard explicitly first. That is a rule enforced +/// by reading, which is why it is also checked here — cheaply, since a raise is +/// already doing a `CString` allocation and a `longjmp`. +#[cfg(feature = "std")] +pub(crate) fn runtime_borrow_is_live() -> bool { + RUNTIME.with(|state| state.try_borrow_mut().is_err()) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum HandleKind { - Bytes, #[cfg(feature = "std")] TcpStream, } pub(crate) struct RuntimeState { next_handle: i64, + #[cfg(feature = "std")] resources: HashMap, owned_strings: HashSet, /// Container handles (lists/maps) with their typed drop functions — the @@ -100,6 +119,7 @@ impl RuntimeState { const fn new() -> Self { Self { next_handle: 0, + #[cfg(feature = "std")] resources: HashMap::with_hasher(FxBuildHasher), owned_strings: HashSet::with_hasher(FxBuildHasher), owned_containers: HashMap::with_hasher(FxBuildHasher), @@ -107,17 +127,27 @@ impl RuntimeState { } } +/// A host resource behind an `i64` handle. +/// +/// `TcpStream` is the only kind left, so the whole family is `std`-only: without +/// an OS there is no host resource to hold. It is still an enum rather than the +/// stream itself because the *handle* machinery (`close_any`, `close_kind`) is +/// about "a resource of some kind", and a second kind is a plausible addition. +/// +/// `Bytes` used to be one of these — a *one-shot* value, read with `take_bytes`, +/// which removed it. Every producer now answers the arena `Bytes` handle +/// ([`crate::lkbytes`]) instead, because a `Bytes` in the language is an +/// ordinary value you may read twice; the one-shot kind, its two accessors and +/// the `bytes.to_string_utf8`/`bytes.free` ABI entries over it are gone with it. +#[cfg(feature = "std")] enum Resource { - Bytes(Vec), - #[cfg(feature = "std")] TcpStream(TcpStream), } +#[cfg(feature = "std")] impl Resource { fn kind(&self) -> HandleKind { match self { - Resource::Bytes(_) => HandleKind::Bytes, - #[cfg(feature = "std")] Resource::TcpStream(_) => HandleKind::TcpStream, } } @@ -135,37 +165,16 @@ impl RuntimeState { pub(crate) fn stream(&self, handle: i64) -> Result<&TcpStream, String> { match self.resources.get(&handle) { Some(Resource::TcpStream(stream)) => Ok(stream), - Some(resource) => Err(wrong_kind_error(handle, HandleKind::TcpStream, resource.kind())), None => Err(format!("tcp stream handle {handle} is closed or invalid")), } } - pub(crate) fn insert_bytes(&mut self, bytes: Vec) -> i64 { - let handle = self.next_handle(); - self.resources.insert(handle, Resource::Bytes(bytes)); - handle - } - - pub(crate) fn take_bytes(&mut self, handle: i64) -> Result, String> { - let Some(resource) = self.resources.remove(&handle) else { - return Err(format!("bytes handle {handle} is closed or invalid")); - }; - match resource { - Resource::Bytes(bytes) => Ok(bytes), - // Unreachable without `std`: `Bytes` is the only variant there. - #[cfg(feature = "std")] - other => { - let actual = other.kind(); - self.resources.insert(handle, other); - Err(wrong_kind_error(handle, HandleKind::Bytes, actual)) - } - } - } - + #[cfg(feature = "std")] pub(crate) fn close_any(&mut self, handle: i64) -> bool { self.resources.remove(&handle).is_some() } + #[cfg(feature = "std")] pub(crate) fn close_kind(&mut self, handle: i64, expected: HandleKind) -> Result { let Some(resource) = self.resources.get(&handle) else { return Ok(false); @@ -234,6 +243,7 @@ impl RuntimeState { } pub(crate) fn cleanup(&mut self) { + #[cfg(feature = "std")] self.resources.clear(); for ptr in self.owned_strings.drain() { // SAFETY: All entries are pointers produced by CString::into_raw @@ -308,3 +318,23 @@ pub(crate) fn arena_handle_owning_strings(value: T, collect: ContainerOwnedSt fn wrong_kind_error(handle: i64, expected: HandleKind, actual: HandleKind) -> String { format!("handle {handle} has kind {actual:?}, expected {expected:?}") } + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::*; + + /// The raise-path guard has to be able to say "yes". + /// + /// A predicate that is always `false` costs nothing, breaks nothing, and + /// silently stops being a check — which is the only way this one can fail, + /// since the thing it protects against is not reachable from a test (a raise + /// under a live borrow aborts the process by design). + #[test] + fn a_live_runtime_borrow_is_visible_to_the_raise_path() { + assert!(!runtime_borrow_is_live(), "no borrow outside `with_runtime`"); + with_runtime(|_| { + assert!(runtime_borrow_is_live(), "the borrow `with_runtime` holds is its own"); + }); + assert!(!runtime_borrow_is_live(), "and it is released on the way out"); + } +} diff --git a/lkrt/src/system.rs b/lkrt/src/system.rs new file mode 100644 index 00000000..cbd6e0a5 --- /dev/null +++ b/lkrt/src/system.rs @@ -0,0 +1,345 @@ +//! The system-control instructions: descriptor tables, control registers, and +//! the TLB. +//! +//! Split from `cpu.rs` rather than added to it because the two files answer to +//! different rules. `cpu.rs` holds operations every architecture has some +//! spelling of — a barrier, an interrupt mask, a wait — and each is implemented +//! per architecture. Nothing here has a portable meaning at all: an IDT is an +//! x86 concept, and aarch64's equivalent is a vector *base* register with a +//! fixed layout, not a table of gates. So these are gated like `port.rs` is, +//! and raise elsewhere rather than pretending. +//! +//! # Why these are named instructions and not an `asm!` construct +//! +//! `cpu.rs` says the line is drawn at operations that are "a fixed instruction +//! sequence with no operands", and that reading and writing arbitrary system +//! registers is out because the register name is a *compile-time* operand — the +//! one thing that shape cannot express. That line is intact here, and it is +//! worth restating because these look at first glance like the thing it +//! excluded. They are not. Each entry below names one register or one table: +//! `cpu_read_cr3` is as specific an operation as `cpu_irq_save`, with a +//! signature a type checker can state. What remains excluded is +//! `read_system_register(name)`, where the operand decides which instruction is +//! emitted — that still needs an assembler in the build, and still is not here. +//! +//! # What each one exists for +//! +//! Nothing here is speculative; each has a caller in the bare-metal kernel that +//! is otherwise forced to be Rust: +//! +//! | intrinsic | the thing it unblocks | +//! | --- | --- | +//! | `cpu_load_idt` | LK building its own interrupt table | +//! | `cpu_load_gdt` + `cpu_reload_segments` | LK building its own GDT | +//! | `cpu_load_task_register` | the TSS, and with it ring 3 | +//! | `cpu_read_cr2` | which address faulted, in a page-fault handler | +//! | `cpu_read_cr3` / `cpu_write_cr3` | switching address spaces | +//! | `cpu_invalidate_page` | changing a mapping that is already live | +//! +//! Deliberately absent: `rdmsr`/`wrmsr`, and CR0/CR4. Paging and protection are +//! already on by the time any LK code runs, and no caller wants an MSR yet. +//! They are one entry each to add on the day something does. +//! +//! All entries are `WritesHost` in the ABI table, the reads included. `cr2` +//! changes behind the code's back — that is its entire purpose — so collapsing +//! two reads of it would report the first fault's address for the second. + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] +fn unsupported(name: &str) -> ! { + crate::panic::raise_str(name); +} + +/// The operand `lidt` and `lgdt` take: a limit and a base, packed with no +/// padding between them. +/// +/// Built here rather than by the caller for two reasons. The layout is +/// `#[repr(packed)]` — a 16-bit field immediately followed by a 64-bit one, +/// which no LK type describes — and the CPU reads it *during* the instruction +/// and never again, so the natural place for it is a stack temporary whose +/// lifetime is exactly the call. A caller-supplied address would be a lifetime +/// nothing checks, in service of hiding nothing. +#[cfg(any(target_arch = "x86_64", target_arch = "x86"))] +#[repr(C, packed)] +struct PseudoDescriptor { + limit: u16, + base: u64, +} + +/// Points the CPU at an interrupt descriptor table. +/// +/// `limit` is the table's size in bytes *minus one*, which is what the hardware +/// field holds: a limit of 0 means one addressable byte, so a 256-gate table is +/// `256 * 16 - 1`. Passed as the caller states it rather than as a gate count, +/// because that is the number the manual talks about and translating it here +/// would make one of the two spellings wrong at every call site. +/// +/// # Safety +/// +/// `base` must point at a correctly formed table that stays alive for as long +/// as it is loaded. A malformed gate is not a fault the kernel can report — the +/// CPU triple-faults trying to report it, and the machine resets. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_load_idt(base: i64, limit: i64) { + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + { + let descriptor = PseudoDescriptor { + limit: limit as u16, + base: base as u64, + }; + // No `nostack`: the operand *is* a stack temporary, and the address of + // it is what the instruction reads. No `nomem` for the same reason — + // with it the compiler would be free to leave the two fields + // unwritten, since nothing else reads them. + unsafe { + core::arch::asm!("lidt [{}]", in(reg) &descriptor, options(preserves_flags)); + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] + { + let _ = (base, limit); + unsupported("cpu_load_idt requires x86"); + } +} + +/// Points the CPU at a global descriptor table. +/// +/// **Not sufficient on its own.** The segment registers hold *cached* +/// descriptors, loaded when each was last written; `lgdt` changes the table +/// they came from and nothing else. Until [`lkrt_cpu_reload_segments`] runs, +/// the CPU is still using the descriptors from the old table — which is fine +/// while the entries agree and silently wrong the moment they do not. +/// +/// # Safety +/// +/// As [`lkrt_cpu_load_idt`]. Additionally, entry 0 must be null and the +/// currently cached selectors must remain valid in the new table until they are +/// reloaded. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_load_gdt(base: i64, limit: i64) { + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + { + let descriptor = PseudoDescriptor { + limit: limit as u16, + base: base as u64, + }; + unsafe { + core::arch::asm!("lgdt [{}]", in(reg) &descriptor, options(preserves_flags)); + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))] + { + let _ = (base, limit); + unsupported("cpu_load_gdt requires x86"); + } +} + +/// Reloads CS and the data segments from the current GDT. +/// +/// The other half of `lgdt`. CS cannot be written by `mov` — the only ways to +/// change it are a far jump, a far call, a far return, or an interrupt return. +/// A far return is used here because it needs nothing but the stack: push the +/// new selector and the address to continue at, and `retfq` loads both. +/// +/// FS and GS are left alone, and that is not an oversight. Writing either +/// zeroes its 64-bit base, which on a kernel that keeps per-CPU state there +/// would silently point every access at address 0. A kernel that wants them +/// reloaded knows it, and can say so. +/// +/// # Safety +/// +/// `code` must select a present, executable, 64-bit code segment and `data` a +/// present, writable data segment, both in the table currently loaded. A wrong +/// selector faults on the `retfq` itself, at which point CS is already whatever +/// the fault handler's gate says — so this is not a failure a handler can +/// report usefully. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_reload_segments(code: i64, data: i64) { + #[cfg(target_arch = "x86_64")] + { + unsafe { + core::arch::asm!( + // The far return's frame: the target selector under the target + // address, popped as CS:RIP. + "push {code}", + "lea {tmp}, [rip + 2f]", + "push {tmp}", + "retfq", + "2:", + // SS last of the three: `mov ss` blocks interrupts for exactly + // one instruction, which is the window a stack switch needs. + // Here there is no switch — the stack pointer is unchanged — + // so the ordering is only about keeping that property true if + // one is ever added. + "mov ds, {data:x}", + "mov es, {data:x}", + "mov ss, {data:x}", + code = in(reg) code as u64, + data = in(reg) data as u64, + tmp = lateout(reg) _, + ); + } + } + #[cfg(not(target_arch = "x86_64"))] + { + let _ = (code, data); + unsupported("cpu_reload_segments requires x86-64"); + } +} + +/// Loads the task register with a TSS selector. +/// +/// In long mode the TSS holds no saved registers — hardware task switching is +/// gone — and exists for one field the CPU still reads: the ring-0 stack it +/// switches to when an interrupt arrives from ring 3. Without this, ring 3 is +/// not reachable at all; the first interrupt after entering it would push its +/// frame onto the *user's* stack. +/// +/// # Safety +/// +/// `selector` must name a present 64-bit TSS descriptor (type 9) in the current +/// GDT, and the segment it names must stay mapped. Loading a busy TSS +/// descriptor faults. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_load_task_register(selector: i64) { + #[cfg(target_arch = "x86_64")] + { + unsafe { + core::arch::asm!("ltr {0:x}", in(reg) selector as u16, options(nostack, preserves_flags)); + } + } + #[cfg(not(target_arch = "x86_64"))] + { + let _ = selector; + unsupported("cpu_load_task_register requires x86-64"); + } +} + +/// The address whose access caused the last page fault. +/// +/// The CPU writes this on every `#PF` and nothing else does. A handler that +/// wants to say *what* was touched has no other source for it — the faulting +/// instruction's operand is not on the stack. +/// +/// # Safety +/// +/// Requires ring 0. The value is only meaningful inside a page-fault handler, +/// before the next fault overwrites it. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_read_cr2() -> i64 { + #[cfg(target_arch = "x86_64")] + { + let value: u64; + unsafe { + core::arch::asm!("mov {}, cr2", out(reg) value, options(nostack, preserves_flags)); + } + value as i64 + } + #[cfg(not(target_arch = "x86_64"))] + { + unsupported("cpu_read_cr2 requires x86-64"); + } +} + +/// The physical address of the current address space's top-level page table. +/// +/// # Safety +/// +/// Requires ring 0. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_read_cr3() -> i64 { + #[cfg(target_arch = "x86_64")] + { + let value: u64; + unsafe { + core::arch::asm!("mov {}, cr3", out(reg) value, options(nostack, preserves_flags)); + } + value as i64 + } + #[cfg(not(target_arch = "x86_64"))] + { + unsupported("cpu_read_cr3 requires x86-64"); + } +} + +/// Switches address spaces, and flushes the TLB in doing so. +/// +/// This is the whole difference between a thread and a process, as one +/// instruction: every virtual address means something different afterwards. +/// +/// # Safety +/// +/// `value` must be the physical address of a valid PML4, and the code that runs +/// after this instruction must be mapped *in the new space at the same address* +/// — including the return path out of here. A kernel mapped into every address +/// space is what makes that true; a kernel mapped into only some of them makes +/// this instruction the last one that runs. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_write_cr3(value: i64) { + #[cfg(target_arch = "x86_64")] + { + // No `nomem`: this invalidates every cached translation, so it orders + // against essentially all memory. Claiming otherwise would let the + // compiler hoist a load of the new space's memory above the switch, + // where it would read the old space. + unsafe { + core::arch::asm!("mov cr3, {}", in(reg) value as u64, options(nostack, preserves_flags)); + } + } + #[cfg(not(target_arch = "x86_64"))] + { + let _ = value; + unsupported("cpu_write_cr3 requires x86-64"); + } +} + +/// Drops one page's cached translation. +/// +/// Needed because a page table is not the thing the CPU consults — the TLB is, +/// and it does not notice a write to the table behind it. Changing a mapping +/// that was already used and *not* calling this leaves the old translation +/// live, for an unbounded time and only on the cores that cached it, which is +/// as hard a bug as this layer produces. +/// +/// One page rather than the whole TLB: reloading CR3 also flushes, and is the +/// blunt version. `invlpg` is the one to reach for when a single mapping +/// changed. +/// +/// # Safety +/// +/// Requires ring 0. `address` is a *virtual* address, and any address in the +/// page selects it. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_cpu_invalidate_page(address: i64) { + #[cfg(target_arch = "x86_64")] + { + unsafe { + core::arch::asm!("invlpg [{}]", in(reg) address as u64, options(preserves_flags)); + } + } + #[cfg(not(target_arch = "x86_64"))] + { + let _ = address; + unsupported("cpu_invalidate_page requires x86-64"); + } +} + +#[cfg(test)] +mod tests { + /// The reads are the only entries safe to call from a hosted test — they + /// take no operand and change nothing — and even they need ring 0, which a + /// test process does not have. So what is asserted here is the thing that + /// can be: that the pseudo-descriptor the loads build has the layout the + /// hardware reads, ten bytes with no padding between the limit and the + /// base. + /// + /// Worth a test because the failure is silent in the worst way: with + /// natural alignment the base would sit at offset 8, the CPU would read six + /// bytes of padding and the low two bytes of the base as the address, and + /// load a table from somewhere near zero. + #[test] + #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] + fn pseudo_descriptor_is_packed() { + assert_eq!(core::mem::size_of::(), 10); + assert_eq!(core::mem::align_of::(), 1); + } +} diff --git a/lkrt/src/textcodec.rs b/lkrt/src/textcodec.rs new file mode 100644 index 00000000..7452a136 --- /dev/null +++ b/lkrt/src/textcodec.rs @@ -0,0 +1,202 @@ +//! Native `encoding.base64` / `encoding.hex` / `encoding.url`, mirroring the +//! stdlib module's exact conventions. +//! +//! Same argument as [`crate::encoding`] and `datetime`: the *same crates* the +//! stdlib module uses (`base64`, `hex`), so the produced text is byte-identical +//! and the differential corpora can compare stdout directly. Where the stdlib +//! writes the algorithm out by hand — percent-encoding a URI component — this +//! writes the same one, because the pair's two directions have to agree with +//! each other before they agree with anything else. +//! +//! `base64.decode` and `hex.decode` answer `Bytes`, which is an arena handle +//! ([`crate::lkbytes`]) — the same shape a `List` has. Both raise on malformed +//! input with the stdlib module's exact message, because the raise text is part +//! of the contract. + +use alloc::ffi::CString; +use alloc::string::String; +use core::ffi::{CStr, c_char}; + +use base64::Engine as _; + +use crate::lkbytes::bytes_handle; +use crate::lkstr::arena_c_string; + +fn view<'a>(p: *const c_char) -> &'a str { + if p.is_null() { + return ""; + } + // SAFETY: non-null pointers are NUL-terminated per the ABI. + unsafe { CStr::from_ptr(p) }.to_str().unwrap_or("") +} + +fn out(text: String) -> *mut c_char { + arena_c_string(CString::new(text).unwrap_or_default()) +} + +/// `encoding.base64.encode(data)` — standard alphabet with padding, the +/// stdlib module's `STANDARD` engine. +/// +/// # Safety +/// `data` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_base64_encode(data: *const c_char) -> *mut c_char { + out(base64::engine::general_purpose::STANDARD.encode(view(data).as_bytes())) +} + +/// `encoding.hex.encode(data)` — lowercase, the `hex` crate's `encode`. +/// +/// # Safety +/// `data` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_hex_encode(data: *const c_char) -> *mut c_char { + out(hex::encode(view(data).as_bytes())) +} + +/// `encoding.base64.encode(bytes)` — the `Bytes` carrier of the same member. +/// +/// Both carriers exist because the language's `encode` takes `Bytes | String`; +/// a string is encoded as its UTF-8 bytes, which is the one the `*const +/// c_char` entry point above already does. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_base64_encode_bytes(handle: *mut core::ffi::c_void) -> *mut c_char { + out(base64::engine::general_purpose::STANDARD.encode(crate::lkbytes::bytes_slice(handle))) +} + +/// `encoding.hex.encode(bytes)`. +/// +/// # Safety +/// `handle` must be a live `Bytes` handle. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_hex_encode_bytes(handle: *mut core::ffi::c_void) -> *mut c_char { + out(hex::encode(crate::lkbytes::bytes_slice(handle))) +} + +/// `encoding.base64.decode(text)` — raises on malformed input. +/// +/// # Safety +/// `text` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_base64_decode(text: *const c_char) -> *mut core::ffi::c_void { + match base64::engine::general_purpose::STANDARD.decode(view(text).as_bytes()) { + Ok(bytes) => bytes_handle(bytes), + Err(error) => crate::panic::raise_str(&alloc::format!("invalid base64 data: {error}")), + } +} + +/// `encoding.hex.decode(text)` — raises on malformed input. +/// +/// # Safety +/// `text` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_hex_decode(text: *const c_char) -> *mut core::ffi::c_void { + match hex::decode(view(text)) { + Ok(bytes) => bytes_handle(bytes), + Err(error) => crate::panic::raise_str(&alloc::format!("invalid hex data: {error}")), + } +} + +/// Whether `byte` survives a URI component unescaped. +/// +/// `encodeURIComponent`'s set, which is what the stdlib module uses: +/// `A-Za-z0-9-_.!~*'()`. +fn unreserved(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'!' | b'~' | b'*' | b'\'' | b'(' | b')') +} + +/// `encoding.url.encode_component(value)`. +/// +/// # Safety +/// `value` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_url_encode_component(value: *const c_char) -> *mut c_char { + let value = view(value); + let mut encoded = String::with_capacity(value.len()); + for &byte in value.as_bytes() { + if unreserved(byte) { + encoded.push(byte as char); + } else { + encoded.push('%'); + encoded.push_str(&alloc::format!("{byte:02X}")); + } + } + out(encoded) +} + +/// `encoding.url.decode_component(value)` — raises on a malformed escape, with +/// the stdlib module's exact three messages. +/// +/// # Safety +/// `value` must be a valid C string, or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lkrt_url_decode_component(value: *const c_char) -> *mut c_char { + let bytes = view(value).as_bytes(); + let mut decoded = alloc::vec::Vec::with_capacity(bytes.len()); + let mut index = 0; + while index < bytes.len() { + if bytes[index] != b'%' { + decoded.push(bytes[index]); + index += 1; + continue; + } + let Some(escape) = bytes.get(index + 1..index + 3) else { + crate::panic::raise_str("invalid percent encoding: incomplete escape"); + }; + let Ok(escape) = core::str::from_utf8(escape) else { + crate::panic::raise_str("invalid percent encoding: non-UTF-8 escape"); + }; + let Ok(byte) = u8::from_str_radix(escape, 16) else { + crate::panic::raise_str("invalid percent encoding: expected two hex digits"); + }; + decoded.push(byte); + index += 3; + } + match String::from_utf8(decoded) { + Ok(text) => out(text), + Err(error) => crate::panic::raise_str(&alloc::format!("invalid percent-encoded UTF-8: {error}")), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn encoded(input: &str) -> String { + let c = CString::new(input).expect("no interior NUL"); + let ptr = unsafe { lkrt_url_encode_component(c.as_ptr()) }; + unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() + } + + fn decoded(input: &str) -> String { + let c = CString::new(input).expect("no interior NUL"); + let ptr = unsafe { lkrt_url_decode_component(c.as_ptr()) }; + unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned() + } + + /// The pair's two directions have to agree with each other. They did not: + /// the encoder was *form* encoding (a space became `+`) while the decoder + /// only undid `%XX`. + #[test] + fn a_component_round_trips() { + for original in ["a b&c=d", "", "plain", "+literal+", "100%", "héllo", "a/b?c#d"] { + assert_eq!(encoded(original).as_str(), encoded(original).as_str()); + assert_eq!(decoded(&encoded(original)), original, "round trip of {original:?}"); + } + // A space is `%20`, not `+`: this is a component, and `+` is the literal + // `+` there (`encodeURIComponent`'s rule). + assert_eq!(encoded("a b"), "a%20b"); + assert_eq!(decoded("a+b"), "a+b"); + } + + #[test] + fn base64_and_hex_match_their_crates() { + let hi = CString::new("hi").expect("no interior NUL"); + let b64 = unsafe { lkrt_base64_encode(hi.as_ptr()) }; + assert_eq!(unsafe { CStr::from_ptr(b64) }.to_str().expect("utf-8"), "aGk="); + let hex = unsafe { lkrt_hex_encode(hi.as_ptr()) }; + assert_eq!(unsafe { CStr::from_ptr(hex) }.to_str().expect("utf-8"), "6869"); + } +} diff --git a/lkrt/src/try_trampoline.c b/lkrt/src/try_trampoline.c index 6552adf9..94fd063c 100644 --- a/lkrt/src/try_trampoline.c +++ b/lkrt/src/try_trampoline.c @@ -1,24 +1,16 @@ -/* Native protected-call trampoline for the Cranelift backend (deep-coverage - * plan G: `try$call`). +/* Protected-region trampoline for the Cranelift backend (deep-coverage plan G). * * Cranelift cannot emit `setjmp` (a `returns_twice` call its SSA/regalloc model - * does not support), so the string-IR path's inline `_setjmp` has no Cranelift - * equivalent. This trampoline hoists the `setjmp` into a C frame that outlives - * the try-body call and is the `_longjmp` target: it drives the same lkrt - * protocol the generated code otherwise would (`lkrt_rt_try_push` → `_setjmp` → - * body / `lkrt_rt_try_pop`, or `lkrt_rt_current_error` on a caught raise). + * does not support). This trampoline hoists the `setjmp` into a C frame that + * outlives the try-body call and is the `_longjmp` target: it drives the same + * lkrt protocol the generated code otherwise would (`lkrt_rt_try_push` → + * `_setjmp` → body / `lkrt_rt_try_pop`). * - * The try-body is a lowered `lk_fn_N` returning `LkDyn` by value; only - * integer/pointer-width parameters are supported (the Cranelift lowering rejects - * float/carrier params and passes each argument as one `i64` word), so a fixed - * arity switch covers every callable shape without touching the FP registers. + * The try-body is a lowered `lk_fn_N` taking one argument: the address of the + * caller's word buffer, which it reads its own inputs out of. So this file has + * no idea how many values a region crosses, and nothing here caps it. */ -typedef struct LkDyn { - long long tag; - long long payload; -} LkDyn; - /* lkrt runtime hooks (Rust `#[no_mangle] extern "C"`, linked from the same * staticlib). `_setjmp` is the BSD-semantics variant (no signal-mask * save/restore) matching lkrt's `_longjmp` raise path; declared with a `void*` @@ -27,53 +19,48 @@ typedef struct LkDyn { extern int _setjmp(void *env); extern void *lkrt_rt_try_push(void); extern void lkrt_rt_try_pop(void); -extern LkDyn lkrt_rt_current_error(void); -static LkDyn lk_call_body(const void *body, long long argc, const long long *a) { - switch (argc) { - case 0: - return ((LkDyn(*)(void))body)(); - case 1: - return ((LkDyn(*)(long long))body)(a[0]); - case 2: - return ((LkDyn(*)(long long, long long))body)(a[0], a[1]); - case 3: - return ((LkDyn(*)(long long, long long, long long))body)(a[0], a[1], a[2]); - case 4: - return ((LkDyn(*)(long long, long long, long long, long long))body)(a[0], a[1], a[2], a[3]); - case 5: - return ((LkDyn(*)(long long, long long, long long, long long, long long))body)(a[0], a[1], a[2], a[3], - a[4]); - case 6: - return ((LkDyn(*)(long long, long long, long long, long long, long long, long long))body)( - a[0], a[1], a[2], a[3], a[4], a[5]); - case 7: - return ((LkDyn(*)(long long, long long, long long, long long, long long, long long, long long))body)( - a[0], a[1], a[2], a[3], a[4], a[5], a[6]); - case 8: - return ((LkDyn(*)(long long, long long, long long, long long, long long, long long, long long, - long long))body)(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]); - default: - /* The Cranelift lowering caps arity at LK_TRY_MAX_ARGS and falls back to - * the string-IR path above it, so this is unreachable. */ - __builtin_trap(); +/* Runs `body(argv)` under a fresh try frame, for the `try { … } catch e { … }` + * *statement* — where the body produces no value and the only question is + * whether it finished. + * + * Returns 1 when the body returned, 0 when it raised. The caught value stays + * where `lkrt_rt_current_error` can be asked for it, so the caller reads it + * only on the path that needs it. + * + * The body reads its inputs out of `argv` itself (`body_signature` in + * `aot/codegen/src/clif.rs`), which is why there is no arity here. There used to + * be: a switch casting `body` to one of nine `(long long, …)` prototypes, with a + * trapping `default`. It reloaded a buffer the caller had already filled, and it + * put a ceiling of eight on how many values a region could cross — past which + * the lowering refused a program that was otherwise fine. */ +long long lkrt_rt_try_region(const void *body, const long long *argv) { + void *buf = lkrt_rt_try_push(); + if (_setjmp(buf) == 0) { + ((void (*)(const long long *))body)(argv); + lkrt_rt_try_pop(); + return 1; } + /* The raise path's pop already happened inside lkrt's raise. */ + return 0; } -/* Runs `body(argv[0..argc])` under a fresh try frame. On normal return, writes - * `*out_ok = 1` and returns the body's `LkDyn`. On a raise inside the body, the - * `_longjmp` lands here with a non-zero `_setjmp` result: writes `*out_ok = 0` - * and returns the caught error (`lkrt_rt_current_error`). The failure path's - * handler pop already happened inside lkrt's raise, matching the string-IR - * catch arm (which likewise does not pop on the raise path). */ -LkDyn lkrt_rt_try_call(const void *body, long long argc, const long long *argv, long long *out_ok) { +/* Runs `thunk(state)` under a fresh try frame, for a caller that has a closure + * rather than a lowered body — a spawned task. + * + * A raise is thread-local: the handler stack a `try` pushes lives on the thread + * that pushed it, and a spawned task starts with an empty one. So a raise inside + * a task had no handler at all and took the uncaught path, which prints and + * exits the *process* — where the interpreter delivers it to `task.await`. + * + * Returns 1 when the thunk returned, 0 when it raised; the raised value is + * `lkrt_rt_current_error()` on this thread. */ +long long lkrt_rt_try_thunk(void (*thunk)(void *), void *state) { void *buf = lkrt_rt_try_push(); if (_setjmp(buf) == 0) { - LkDyn r = lk_call_body(body, argc, argv); + thunk(state); lkrt_rt_try_pop(); - *out_ok = 1; - return r; + return 1; } - *out_ok = 0; - return lkrt_rt_current_error(); + return 0; } diff --git a/lkrt/src/uuid.rs b/lkrt/src/uuid.rs new file mode 100644 index 00000000..72fa47cf --- /dev/null +++ b/lkrt/src/uuid.rs @@ -0,0 +1,53 @@ +//! Native `uuid`: the same `uuid` crate the stdlib module uses, so the text, +//! the accepted input forms, and the parse-error wording are one rule. +//! +//! That last part is why sharing matters here more than usual: `uuid.parse` +//! raises `invalid UUID: {err}` where `{err}` is the crate's own `Display` +//! (`invalid character: found `n` at 0`), and a caught error's message *is* +//! program output. A hand-written parser would have had to reproduce that +//! sentence. +//! +//! `std`-only: `v4` draws from the OS entropy source, which bare metal does not +//! have. The other two members would work without it, but splitting the module +//! across the `std` line for two functions the no_std profile has no way to +//! reach anyway is not worth the cfg. + +use alloc::string::ToString as _; +use core::ffi::{CStr, c_char}; + +fn view(text: *const c_char) -> &'static str { + if text.is_null() { + return ""; + } + // SAFETY: LK strings reaching the ABI are NUL-terminated and outlive the call. + unsafe { CStr::from_ptr(text) }.to_str().unwrap_or("") +} + +fn out(text: alloc::string::String) -> *mut c_char { + crate::lkstr::arena_c_string(alloc::ffi::CString::new(text).unwrap_or_default()) +} + +/// `uuid.v4()`. +/// +/// Not `Pure` in the ABI schema, and it must never become so: two calls in one +/// block are two different UUIDs, and CSE would merge them. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_uuid_v4() -> *mut c_char { + out(uuid::Uuid::new_v4().to_string()) +} + +/// `uuid.parse(text)` — canonical lowercase hyphenated form, or a raise +/// carrying the crate's own reason. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_uuid_parse(text: *const c_char) -> *mut c_char { + match uuid::Uuid::parse_str(view(text)) { + Ok(parsed) => out(parsed.to_string()), + Err(error) => crate::panic::raise_str(&alloc::format!("invalid UUID: {error}")), + } +} + +/// `uuid.is_valid(text)`. +#[unsafe(no_mangle)] +pub extern "C" fn lkrt_uuid_is_valid(text: *const c_char) -> i64 { + i64::from(uuid::Uuid::parse_str(view(text)).is_ok()) +} diff --git a/lkrt/src/vm_mirror.rs b/lkrt/src/vm_mirror.rs index baa6a285..f0b47150 100644 --- a/lkrt/src/vm_mirror.rs +++ b/lkrt/src/vm_mirror.rs @@ -1,24 +1,26 @@ //! VM map-layout mirror (deep-coverage plan D1, user adjudication: "native //! replicates the Fx order, the VM is untouched"). //! -//! The VM materializes a map literal in **two stages** -//! (`exec/const_load.rs` + `val/runtime_model.rs::typed_map_from_entries`): -//! stage 1 inserts the serialized entries, in order, into a fresh -//! `FastHashMap`; stage 2 iterates *that* map (Fx -//! hash order) and inserts into the final typed map keyed by `Arc`. -//! Iteration order of the result is therefore a deterministic function of -//! the key hashes and both insertion sequences — nothing else. This module -//! replays both stages with hash-identical key types, so `for k in m` / -//! `.keys()` iterate in exactly the VM's order. +//! The VM materializes a map literal in two stages (`exec/const_load.rs` + +//! `val/runtime_model.rs::typed_map_from_entries`): stage 1 inserts the +//! serialized entries, in order, into a `ValueMap`; +//! stage 2 iterates that and inserts into the final typed map keyed by +//! `Arc`. Both carriers are insertion-ordered, so the result iterates in +//! the order the literal was *written*. This module replays the same two +//! stages. //! -//! Hash identity argument: [`RtKey`] mirrors `RuntimeMapKey`'s variant order -//! (same `derive(Hash)` discriminants under the same rustc) and field hashing -//! (`MirrorShortStr` = `ShortStr`'s exact field order; `String` hashes its -//! `str` content exactly like `Arc`); the hasher and table -//! implementation are the same `hashbrown + FxBuildHasher` the VM's -//! `fast_map` uses, resolved to one version by the workspace lockfile. The -//! lkrt order-conformance test compares against `lk-core` directly, so a -//! drift in any of these assumptions fails loudly. +//! **The hash-identity argument is retired.** Both sides now carry a map's +//! entries in a vector and iterate it, so `for k in m` agrees between the two +//! back ends because both append — not because both land on the same hash +//! layout. What that used to rest on is worth recording, since it is the kind +//! of invariant that holds until it silently does not: `RtKey` had to mirror +//! `RuntimeMapKey`'s `derive(Hash)` discriminants under the same rustc, and +//! both builds had to resolve to one `hashbrown` with one fixed seed. `RtKey` +//! now only has to be *self*-consistent — equal keys hash equally — which is +//! an ordinary requirement rather than a coincidence to defend. +//! +//! The order-conformance test stays: it compares against `lk-core` directly, +//! so a divergence still fails loudly. // `alloc`, not the std prelude: this module is part of the computation-only // subset that builds without an OS. @@ -40,7 +42,7 @@ use crate::state::arena_handle; /// Field-order/type mirror of `lk_values::ShortStr` (`len: u8, data: [u8; 7]`). #[derive(Clone, Copy, PartialEq, Eq, Hash)] -struct MirrorShortStr { +pub(crate) struct MirrorShortStr { len: u8, data: [u8; 7], } @@ -50,7 +52,7 @@ struct MirrorShortStr { /// keeps the discriminant numbering aligned. #[derive(Clone, PartialEq, Eq, Hash)] #[allow(dead_code)] -enum RtKey { +pub(crate) enum RtKey { Nil, Bool(bool), Int(i64), @@ -59,7 +61,46 @@ enum RtKey { Obj(u64), } -fn key_from_dyn(v: LkDyn) -> RtKey { +/// The VM's canonical string key: ≤ 7 bytes is always the inline `ShortStr` +/// runtime value, 8+ always a heap string. The split is by length alone, so it +/// is deterministic — and it is *load-bearing for the hash*, which is why a +/// set cannot keep its own one-variant version of this and still iterate in the +/// VM's order. +pub(crate) fn str_key(text: &str) -> RtKey { + if text.len() <= 7 { + let mut data = [0u8; 7]; + data[..text.len()].copy_from_slice(text.as_bytes()); + RtKey::ShortStr(MirrorShortStr { + len: text.len() as u8, + data, + }) + } else { + RtKey::String(text.to_owned()) + } +} + +pub(crate) fn key_from_dyn(v: LkDyn) -> RtKey { + key_from_dyn_in(v, "") +} + +/// The key a value would be, or `None` when it cannot be one. +/// +/// For *membership* only: `1.5 in s` is `false` rather than a refusal, because +/// a value that cannot be a key is not a member and `in` is a predicate. See +/// the interpreter's `map_contains`, which says the same thing at more length — +/// building the key and propagating its failure made the answer depend on the +/// map's internal carrier, which no program can see. +pub(crate) fn key_from_dyn_opt(v: LkDyn) -> Option { + match v.tag { + DYN_NIL | DYN_BOOL | DYN_I64 | DYN_STR => Some(key_from_dyn(v)), + _ => None, + } +} + +/// [`key_from_dyn`] with the call named, for the paths where the interpreter +/// prefixes the refusal with it (`Set() item: …`, `set.add() value: …`). A +/// caught error is printed output, so the prefix is part of the answer. +pub(crate) fn key_from_dyn_in(v: LkDyn, context: &str) -> RtKey { match v.tag { DYN_NIL => RtKey::Nil, DYN_BOOL => RtKey::Bool(v.payload != 0), @@ -72,26 +113,37 @@ fn key_from_dyn(v: LkDyn) -> RtKey { // SAFETY: DYN_STR payloads are NUL-terminated arena strings. unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("") }; - // The VM's canonical string split: ≤ 7 bytes is always the - // inline `ShortStr` runtime value, 8+ always a heap string. - if text.len() <= 7 { - let mut data = [0u8; 7]; - data[..text.len()].copy_from_slice(text.as_bytes()); - RtKey::ShortStr(MirrorShortStr { - len: text.len() as u8, - data, - }) - } else { - RtKey::String(text.to_owned()) - } + str_key(text) } - // Float keys are the VM's loud "cannot be used as a key" error; - // container keys (heap-handle identity) are outside the subset. - _ => crate::panic::raise_str("runtime error"), + // Everything else is the VM's loud "cannot be used as a key" error, and + // it has **two** wordings: a `Float` says only that, because the reason + // is the float itself (`0.0` and `-0.0` are equal and hash apart, and + // `NaN` is not equal to itself), while any other value names its type + // and lists what may be a key (`RuntimeMapKey::from_value`). One + // wording for both said `Float` about a `Bytes` and about a function — + // a caught error is printed output, so it was a wrong answer, not just + // a poor message. + crate::lkdyn::DYN_F64 => crate::panic::raise_str(&alloc::format!( + "{}Float cannot be a map key or set member", + prefix(context) + )), + _ => crate::panic::raise_str(&alloc::format!( + "{}{} cannot be a map key or set member: only nil, Bool, Int and String can", + prefix(context), + crate::lkdyn::kind_name_of(v) + )), + } +} + +fn prefix(context: &str) -> alloc::string::String { + if context.is_empty() { + alloc::string::String::new() + } else { + alloc::format!("{context}: ") } } -fn key_str(key: &RtKey) -> &str { +pub(crate) fn key_str(key: &RtKey) -> &str { match key { RtKey::ShortStr(s) => core::str::from_utf8(&s.data[..s.len as usize]).unwrap_or(""), RtKey::String(s) => s.as_str(), @@ -103,30 +155,57 @@ fn key_str(key: &RtKey) -> &str { /// pairs, in the given order (the decoders' path: serde's document/sorted /// order plays the VM's stage-1 insertion order). pub(crate) fn str_dyn_map_mirrored(pairs: Vec<(String, LkDyn)>) -> *mut c_void { - let mut stage1: LitBuilder = LitBuilder::default(); + let mut stage1: FxMap = FxMap::default(); for (key, value) in pairs { - let rt_key = if key.len() <= 7 { - let mut data = [0u8; 7]; - data[..key.len()].copy_from_slice(key.as_bytes()); - RtKey::ShortStr(MirrorShortStr { - len: key.len() as u8, - data, - }) - } else { - RtKey::String(key) - }; - stage1.insert(rt_key, value); + stage1.insert(str_key(&key), value); } let mut out = StrDynMap::default(); for (key, value) in &stage1 { - out.insert(key_str(key).to_owned(), *value); + out.insert(crate::lkmap::StrKey::Owned(key_str(key).to_owned()), *value); } arena_handle(out) } +/// A map key that is an `i64`, hashing exactly as [`RtKey::Int`] does. +/// +/// The int-keyed carriers are keyed by this rather than by a bare `i64` +/// because the VM never re-keys them: `typed_map_from_entries` returns +/// `Mixed` for a non-string key, and `Mixed` *is* the stage-1 +/// `FastHashMap`. A native `FxMap` hashes +/// the key differently (no discriminant) and is filled by a second insertion +/// sequence, so it iterates in a different order — `{1: 1.5, 2: 2.5}` came out +/// `2,1` in the VM and `1,2` natively. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) struct IntKey(pub(crate) i64); + +/// `RtKey`'s derived `Hash` writes the discriminant first. `Int` is the third +/// variant, and a repr-less enum's discriminant is an `isize`. +/// +/// Written out rather than delegating to `RtKey::Int(k).hash(state)` so a map +/// lookup does not build the (String-carrying, 32-byte) enum; +/// `int_key_hashes_like_the_mirror_enum` is what keeps the two in agreement. +const RTKEY_INT_DISCRIMINANT: isize = 2; + +impl core::hash::Hash for IntKey { + fn hash(&self, state: &mut H) { + RTKEY_INT_DISCRIMINANT.hash(state); + self.0.hash(state); + } +} + /// Stage-1 literal builder: the mirror of the VM's -/// `FastHashMap` (values ride along boxed). -type LitBuilder = FxMap; +/// `FastHashMap` (values ride along boxed), plus +/// the order the entries were written in. +/// +/// One field, now. There used to be a second — an explicit log of first- +/// occurrence order — because the table's own iteration was hash order and a +/// non-string-keyed literal (which gets no stage 2 in the VM) had to replay the +/// *written* sequence instead. The table iterates in that sequence itself now, +/// so the log was a copy of it. +#[derive(Default)] +struct LitBuilder { + stage1: FxMap, +} /// Starts a map-literal build (VM stage 1, zero capacity). #[unsafe(no_mangle)] @@ -141,13 +220,20 @@ pub extern "C" fn lkrt_lkmap_lit_new() -> *mut c_void { #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lkmap_lit_set(builder: *mut c_void, key: LkDyn, value: LkDyn) { // SAFETY: `builder` addresses a `LitBuilder` from `lkrt_lkmap_lit_new`. - let map = unsafe { &mut *(builder as *mut LitBuilder) }; - map.insert(key_from_dyn(key), value); + let lit = unsafe { &mut *(builder as *mut LitBuilder) }; + // A repeated key updates in place and keeps its original position, which + // is `IndexMap::insert`'s own behaviour. + lit.stage1.insert(key_from_dyn(key), value); } -fn builder<'a>(handle: *mut c_void) -> &'a LitBuilder { +fn builder<'a>(handle: *mut c_void) -> &'a FxMap { // SAFETY: callers pass a live `LitBuilder` handle. - unsafe { &*(handle as *mut LitBuilder) } + &unsafe { &*(handle as *mut LitBuilder) }.stage1 +} + +/// The literal's entries in written order, which is what the table gives. +fn literal_order<'a>(handle: *mut c_void) -> impl Iterator { + builder(handle).iter() } /// Finishes into `Map` (VM stage 2: iterate stage 1 in its hash @@ -209,7 +295,7 @@ pub unsafe extern "C" fn lkrt_lkmap_lit_finish_str_bool(handle: *mut c_void) -> pub unsafe extern "C" fn lkrt_lkmap_lit_finish_str_dyn(handle: *mut c_void) -> *mut c_void { let mut out: StrDynMap = StrDynMap::default(); for (key, value) in builder(handle) { - out.insert(key_str(key).to_owned(), *value); + out.insert(crate::lkmap::StrKey::Owned(key_str(key).to_owned()), *value); } arena_handle(out) } @@ -220,15 +306,15 @@ pub unsafe extern "C" fn lkrt_lkmap_lit_finish_str_dyn(handle: *mut c_void) -> * /// As [`lkrt_lkmap_lit_finish_str_i64`], with `Int` keys and values. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lkmap_lit_finish_i64_i64(handle: *mut c_void) -> *mut c_void { - let mut out: FxMap = FxMap::default(); - for (key, value) in builder(handle) { + let mut out: FxMap = FxMap::default(); + for (key, value) in literal_order(handle) { let RtKey::Int(k) = key else { crate::panic::raise_str("runtime error") }; if value.tag != DYN_I64 { crate::panic::raise_str("runtime error"); } - out.insert(*k, value.payload); + out.insert(IntKey(*k), value.payload); } arena_handle(out) } @@ -239,15 +325,15 @@ pub unsafe extern "C" fn lkrt_lkmap_lit_finish_i64_i64(handle: *mut c_void) -> * /// As [`lkrt_lkmap_lit_finish_str_i64`], with `Int` keys, `F64` values. #[unsafe(no_mangle)] pub unsafe extern "C" fn lkrt_lkmap_lit_finish_i64_f64(handle: *mut c_void) -> *mut c_void { - let mut out: FxMap = FxMap::default(); - for (key, value) in builder(handle) { + let mut out: FxMap = FxMap::default(); + for (key, value) in literal_order(handle) { let RtKey::Int(k) = key else { crate::panic::raise_str("runtime error") }; if value.tag != DYN_F64 { crate::panic::raise_str("runtime error"); } - out.insert(*k, f64::from_bits(value.payload as u64)); + out.insert(IntKey(*k), f64::from_bits(value.payload as u64)); } arena_handle(out) } @@ -304,4 +390,62 @@ mod tests { let _ = RuntimeMapKey::Nil; let _ = RuntimeVal::Nil; } + + fn fx_hash(value: impl core::hash::Hash) -> u64 { + use core::hash::BuildHasher; + rustc_hash::FxBuildHasher.hash_one(value) + } + + /// [`IntKey`] exists to hash exactly like [`RtKey::Int`], and it writes the + /// discriminant out by hand rather than building the enum. This is what + /// says the hand-written version is the same one — including the + /// assumption that a repr-less enum's discriminant hashes as an `isize`. + #[test] + fn int_key_hashes_like_the_mirror_enum() { + for k in [0i64, 1, -1, 2, 42, -9999, i64::MAX, i64::MIN] { + assert_eq!( + fx_hash(IntKey(k)), + fx_hash(RtKey::Int(k)), + "IntKey({k}) must hash as RtKey::Int({k})" + ); + } + } + + /// The int-keyed counterpart of the load-bearing check above, and the one + /// that would have caught the divergence: the VM runs *no* stage 2 for a + /// non-string key (`typed_map_from_entries` hands back the stage-1 table), + /// so the finisher replays the literal insertion sequence instead of + /// iterating stage 1. Rehashing into an `FxMap` — which is what it + /// used to do — made `{1: 1.5, 2: 2.5}` iterate `1,2` against the VM's + /// `2,1`. + #[test] + fn int_lit_protocol_matches_vm_iteration_order() { + use lk_core::val::typed_map_iteration_int_keys; + + // Small literals (where the divergence first showed) and a large one + // that forces several table growths. + for keys in [ + vec![1i64, 2], + vec![1, 3], + vec![1, 2, 5, 9], + vec![-3, 7, 0, 12, -100], + (0..64).map(|i| i * 7 - 13).collect::>(), + ] { + let vm_order = typed_map_iteration_int_keys(keys.iter().map(|&k| (k, k * 2))); + + let builder_handle = lkrt_lkmap_lit_new(); + for &k in &keys { + unsafe { lkrt_lkmap_lit_set(builder_handle, lkrt_dyn_from_i64(k), lkrt_dyn_from_i64(k * 2)) }; + } + let map_handle = unsafe { lkrt_lkmap_lit_finish_i64_i64(builder_handle) }; + // SAFETY: just built by the finisher above. + let native = unsafe { &*(map_handle as *mut FxMap) }; + let native_order: Vec = native.keys().map(|k| k.0).collect(); + + assert_eq!( + native_order, vm_order, + "int-keyed iteration order drifted from the VM's typed_map_from_entries for {keys:?}" + ); + } + } } diff --git a/lsp/Cargo.toml b/lsp/Cargo.toml index 3cc367c0..7c0b8b03 100644 --- a/lsp/Cargo.toml +++ b/lsp/Cargo.toml @@ -27,3 +27,8 @@ dashmap = { workspace = true } ropey = "1" twox-hash = "2" once_cell = { workspace = true } + +[dev-dependencies] +# `RuntimeVal`'s `PartialEq` exists for test code only — see the `testing` +# feature in lk-core. +lk-core = { path = "../core", features = ["testing"] } diff --git a/lsp/src/analyzer/analysis_impl.rs b/lsp/src/analyzer/analysis_impl.rs index ea3ea65b..bb5ea508 100644 --- a/lsp/src/analyzer/analysis_impl.rs +++ b/lsp/src/analyzer/analysis_impl.rs @@ -1115,51 +1115,73 @@ impl LkAnalyzer { diags } + /// Diagnostics for a check that already ran. + /// + /// The errors come from `TokenCacheEntry::document_types`, the one check + /// this document gets. Running another one here is what this used to do, + /// and it type-checked every file twice per analysis. pub(crate) fn collect_type_diagnostics( - program: &Program, + errors: &[RecordedTypeError], tokens: &[token::Token], spans: &[Span], content: &str, origins: Option<&[macro_system::MacroTokenOrigin]>, ) -> Vec { - let mut checker = TypeChecker::new_strict(); - match program.type_check(&mut checker) { - Ok(_) => Vec::new(), - Err(err) => { - let range = Self::type_error_range(&err, tokens, spans, content); - let mut message = Self::type_error_from_anyhow(&err) + errors + .iter() + .map(|recorded| { + let range = Self::type_error_range(recorded, tokens, spans, content); + let mut message = recorded + .typed + .as_ref() .map(|type_error| type_error.message.clone()) - .unwrap_or_else(|| err.to_string()); + .unwrap_or_else(|| recorded.message.clone()); if let Some(origins) = origins { - if let Some(span) = lk_core::syntax::type_error_span(&err, tokens, spans) { - if let Some(note) = macro_origin_note_for_span(origins, &span) { - message.push('\n'); - message.push_str(¬e); + if let Some(type_error) = recorded.typed.as_ref() { + if let Some(span) = lk_core::syntax::typed_error_span(type_error, tokens, spans) { + if let Some(note) = macro_origin_note_for_span(origins, &span) { + message.push('\n'); + message.push_str(¬e); + } } } } - let mut diagnostic = Diagnostic::new( - range, - Some(DiagnosticSeverity::ERROR), - None, - Some("lk".to_string()), - message, - None, - None, - ); - diagnostic.code = Some(NumberOrString::String("lk_type_error".to_string())); - vec![diagnostic] - } - } + // A lint is advice, not a rejection: `lk check` only reports + // the implicit-`Any` finding under `--strict`, and a program + // carrying it compiles and runs. Rendering it as `ERROR` made + // three of this repository's own examples red in the editor + // while the compiler accepted them — the editor saying the + // program is broken when it is not. + let is_lint = recorded.typed.as_ref().is_some_and(|type_error| type_error.lint); + let severity = if is_lint { + DiagnosticSeverity::WARNING + } else { + DiagnosticSeverity::ERROR + }; + let mut diagnostic = + Diagnostic::new(range, Some(severity), None, Some("lk".to_string()), message, None, None); + diagnostic.code = Some(NumberOrString::String( + if is_lint { "lk_type_lint" } else { "lk_type_error" }.to_string(), + )); + diagnostic + }) + .collect() } pub(crate) fn type_error_range( - err: &anyhow::Error, + recorded: &RecordedTypeError, tokens: &[token::Token], spans: &[Span], content: &str, ) -> Range { - if let Some(type_error) = Self::type_error_from_anyhow(err) { + // The statement the error came from, when one was recorded. Preferred + // over the search below, which matches the *first* token in the file + // that looks like the offending expression — in `let a = 1; let b = 1;` + // that is the wrong `1`. + if let Some(span) = &recorded.span { + return Self::span_to_range(span); + } + if let Some(type_error) = recorded.typed.as_ref() { if let Some(expr) = &type_error.expr { if let Some(range) = Self::range_for_expr(expr, tokens, spans) { return range; @@ -1169,8 +1191,7 @@ impl LkAnalyzer { return range; } } - let message = err.to_string(); - if let Some(range) = Self::implicit_any_error_range(None, &message, tokens, spans) { + if let Some(range) = Self::implicit_any_error_range(None, &recorded.message, tokens, spans) { return range; } Self::default_error_range(content) @@ -1208,10 +1229,6 @@ impl LkAnalyzer { Some(&rest[..end]) } - pub(crate) fn type_error_from_anyhow(err: &anyhow::Error) -> Option<&typ::TypeError> { - err.downcast_ref::() - } - pub(crate) fn range_for_expr(expr: &Expr, tokens: &[token::Token], spans: &[Span]) -> Option { match expr { Expr::Var(name) => { diff --git a/lsp/src/analyzer/core_impl.rs b/lsp/src/analyzer/core_impl.rs index 55638d8a..af8c244c 100644 --- a/lsp/src/analyzer/core_impl.rs +++ b/lsp/src/analyzer/core_impl.rs @@ -194,269 +194,135 @@ impl LkAnalyzer { } } - /// Compute type inlay hints for simple `let name = expr;` without explicit annotations. - /// Places a TYPE hint like `: Int` right after the pattern (before '='). - #[cfg(test)] - pub fn compute_type_inlay_hints(&self, content: &str, range: Range) -> Vec { - let (tokens, spans) = match Tokenizer::tokenize_enhanced_with_spans(content) { - Ok(pair) => pair, - Err(_) => return Vec::new(), + /// The document's one type check, for callers that want more than the types + /// by name — hover needs the spans, to tell two bindings of one name apart. + pub(crate) fn document_types_for(&mut self, content: &str) -> Arc { + match self.tokenize_with_spans_cached(content) { + Ok(entry) => entry.document_types(content), + Err(_) => Arc::new(DocumentTypes::default()), + } + } + + /// The type of every binding in the document, by name. + /// + /// Later bindings win, which is what a completion at the end of the file + /// wants: a name rebound in an inner scope reads as its most recent type. + pub fn binding_types(&mut self, content: &str) -> HashMap { + let Ok(entry) = self.tokenize_with_spans_cached(content) else { + return HashMap::new(); }; - self.compute_type_inlay_hints_from_tokens(&tokens, &spans, range) + entry + .document_types(content) + .bindings + .iter() + .map(|binding| (binding.name.clone(), binding.ty.clone())) + .collect() } - /// Variant that reuses a pre-tokenized buffer for performance. - pub fn compute_type_inlay_hints_from_tokens( - &self, - tokens: &[token::Token], - spans: &[Span], - range: Range, - ) -> Vec { + /// Type hints for `let` bindings whose type the source leaves unwritten. + /// + /// Reads the types the checker recorded while checking the whole document + /// (`observed_bindings`) instead of re-deriving them here. What that buys + /// is not tidiness: this used to slice the token stream, re-parse the + /// right-hand side on its own, and infer it in a *fresh* checker with no + /// variables, no functions and no imports in scope — so `y := x + 1` got + /// no hint, and neither did anything else that mentioned a name. + pub fn compute_type_inlay_hints(&mut self, content: &str, range: Range) -> Vec { + let Ok(entry) = self.tokenize_with_spans_cached(content) else { + return Vec::new(); + }; + let document_types = entry.document_types(content); + let observed = &document_types.bindings; + + // Group by binding site: a destructuring `let` records one entry per + // name, and there is no single type to write at the end of `[a, b]`. + let mut by_site: HashMap<(u32, u32), Vec<&lk_core::typ::ObservedBinding>> = HashMap::new(); + for binding in observed.iter() { + by_site + .entry((binding.span.end.line, binding.span.end.column)) + .or_default() + .push(binding); + } + let mut hints: Vec = Vec::new(); - use token::Token as T; - let mut i = 0usize; - while i < tokens.len() { - if !matches!(tokens[i], T::Let) { - i += 1; + for bindings in by_site.values() { + let [binding] = bindings[..] else { continue; - } - let let_idx = i; - i += 1; - - // Capture pattern region until top-level ':' (annotation) or '=' (assignment) - let start_pat = i; - let mut end_pat = i; - let mut paren = 0i32; - let mut bracket = 0i32; - let mut brace = 0i32; - let mut saw_colon = false; - let mut found_assign = false; - while i < tokens.len() { - match &tokens[i] { - T::LParen => paren += 1, - T::RParen => { - if paren > 0 { - paren -= 1; - } - } - T::LBracket => bracket += 1, - T::RBracket => { - if bracket > 0 { - bracket -= 1; - } - } - T::LBrace => brace += 1, - T::RBrace => { - if brace > 0 { - brace -= 1; - } - } - T::Assign if paren == 0 && bracket == 0 && brace == 0 => { - found_assign = true; - break; - } - T::Colon if paren == 0 && bracket == 0 && brace == 0 => { - saw_colon = true; - break; - } - _ => {} - } - end_pat = i; - i += 1; - } - if !found_assign || saw_colon { - // Skip cases without '=' or with explicit annotation + }; + if binding.annotated { continue; } - - // Determine RHS expression token range: after '=' until next top-level ';' - let mut j = i + 1; // i at '=' - let mut depth = 0i32; - let mut end_expr = j; - while j < tokens.len() { - match &tokens[j] { - T::LParen | T::LBracket | T::LBrace => depth += 1, - T::RParen | T::RBracket | T::RBrace => depth -= 1, - T::Semicolon if depth == 0 => break, - _ => {} - } - end_expr = j; - j += 1; - } - if end_expr > i { - // Parse expression and infer type - let expr_tokens = &tokens[i + 1..=end_expr]; - if !expr_tokens.is_empty() { - if let Ok(expr) = ExprParser::new(expr_tokens).parse() { - let mut checker = TypeChecker::new_strict(); - if let Ok(typ) = checker.infer_resolved_type(&expr) { - // Place hint at end of pattern - let pat_tok_idx = if end_pat >= start_pat { end_pat } else { start_pat }; - if pat_tok_idx < spans.len() { - let sp = &spans[pat_tok_idx]; - let pos = Position::new(sp.end.line - 1, sp.end.column.saturating_sub(1)); - if pos.line >= range.start.line && pos.line <= range.end.line { - let label = format!(": {}", typ.display()); - hints.push(InlayHint { - position: pos, - label: InlayHintLabel::from(label), - kind: Some(InlayHintKind::TYPE), - text_edits: None, - tooltip: None, - padding_left: Some(true), - padding_right: Some(false), - data: None, - }); - } - } - } - } - } - } - - // Advance to end of statement - i = j; - while i < tokens.len() && !matches!(tokens[i], T::Semicolon) { - i += 1; - } - if i < tokens.len() { - i += 1; - } - // Prevent infinite loop on invalid sequences - if i <= let_idx { - i = let_idx + 1; + let Some(rendered) = readable_type(&binding.ty) else { + continue; + }; + let position = Position::new( + binding.span.end.line.saturating_sub(1), + binding.span.end.column.saturating_sub(1), + ); + if position.line < range.start.line || position.line > range.end.line { + continue; } + hints.push(InlayHint { + position, + label: InlayHintLabel::from(format!(": {rendered}")), + kind: Some(InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + data: None, + padding_left: Some(true), + padding_right: Some(false), + }); } + hints.sort_by_key(|hint| (hint.position.line, hint.position.character)); hints } - /// Compute type hints for short declarations: `name := expr;` - #[cfg(test)] - pub fn compute_define_type_hints(&self, content: &str, range: Range) -> Vec { - let (tokens, spans) = match Tokenizer::tokenize_enhanced_with_spans(content) { - Ok(pair) => pair, - Err(_) => return Vec::new(), + /// `-> T` hints for functions whose return type the source leaves unwritten. + /// + /// The type comes from `function_sigs`, which the document's one type check + /// already filled in. This used to walk the body looking for `return` + /// statements, re-parse each returned expression on its own, and infer it in + /// a *fresh* checker — so `fn f() { return greet("x"); }` got no hint, for + /// exactly the reason `let who = greet("x")` got none. + pub fn compute_function_return_type_hints(&mut self, content: &str, range: Range) -> Vec { + let Ok(entry) = self.tokenize_with_spans_cached(content) else { + return Vec::new(); }; - self.compute_define_type_hints_from_tokens(&tokens, &spans, range) - } - - /// Variant that reuses a pre-tokenized buffer for performance. - pub fn compute_define_type_hints_from_tokens( - &self, - tokens: &[token::Token], - spans: &[Span], - range: Range, - ) -> Vec { - let mut hints: Vec = Vec::new(); - use token::Token as T; - let mut i = 0usize; - while i + 2 < tokens.len() { - match (&tokens[i], &tokens[i + 1], &tokens[i + 2]) { - (T::Id(_), T::Colon, T::Assign) => { - // Parse expression from i+3 to next top-level ';' - let mut j = i + 3; - let mut depth = 0i32; - let mut end_expr = j; - while j < tokens.len() { - match &tokens[j] { - T::LParen | T::LBracket | T::LBrace => depth += 1, - T::RParen | T::RBracket | T::RBrace => depth -= 1, - T::Semicolon if depth == 0 => break, - _ => {} - } - end_expr = j; - j += 1; - } - if end_expr >= i + 3 { - let expr_tokens = &tokens[i + 3..=end_expr]; - if let Ok(expr) = ExprParser::new(expr_tokens).parse() { - let mut checker = TypeChecker::new_strict(); - if let Ok(typ) = checker.infer_resolved_type(&expr) { - if i < spans.len() { - let sp = &spans[i]; - let pos = Position::new(sp.end.line - 1, sp.end.column.saturating_sub(1)); - if pos.line >= range.start.line && pos.line <= range.end.line { - let label = format!(": {}", typ.display()); - hints.push(InlayHint { - position: pos, - label: InlayHintLabel::from(label), - kind: Some(InlayHintKind::TYPE), - text_edits: None, - tooltip: None, - padding_left: Some(true), - padding_right: Some(false), - data: None, - }); - } - } - } - } - } - // Advance to next ';' - i = j; - while i < tokens.len() && !matches!(tokens[i], T::Semicolon) { - i += 1; - } - if i < tokens.len() { - i += 1; - } - } - _ => i += 1, - } + let types = entry.document_types(content); + if types.function_returns.is_empty() { + return Vec::new(); } - hints - } - - /// Compute type inlay hints for function return types: place a TYPE hint like `-> Int` - /// after the parameter list. If multiple return statements exist (e.g., branches), - /// the displayed type is a union of all discovered return expression types. - #[cfg(test)] - pub fn compute_function_return_type_hints(&self, content: &str, range: Range) -> Vec { - let (tokens, spans) = match Tokenizer::tokenize_enhanced_with_spans(content) { - Ok(pair) => pair, - Err(_) => return Vec::new(), - }; - self.compute_function_return_type_hints_from_tokens(&tokens, &spans, range) - } + let tokens = entry.tokens.clone(); + let spans = entry.spans.clone(); - /// Variant that reuses a pre-tokenized buffer for performance. - pub fn compute_function_return_type_hints_from_tokens( - &self, - tokens: &[token::Token], - spans: &[Span], - range: Range, - ) -> Vec { - let mut hints: Vec = Vec::new(); use token::Token as T; + let mut hints: Vec = Vec::new(); let mut i = 0usize; while i < tokens.len() { if !matches!(tokens[i], T::Fn) { i += 1; continue; } - // fn name ( params ) { body } - let mut j = i + 1; - // Skip function name if present - if matches!(tokens.get(j), Some(T::Id(_))) { - j += 1; - } else { + let Some(T::Id(name)) = tokens.get(i + 1) else { i += 1; continue; - } - // Expect parameter list - if !matches!(tokens.get(j), Some(T::LParen)) { + }; + if !matches!(tokens.get(i + 2), Some(T::LParen)) { i += 1; continue; } + + // Walk to the `)` that closes the parameter list. let mut depth = 0i32; - // find matching ')' + let mut j = i + 2; + let mut rparen = None; while j < tokens.len() { match &tokens[j] { T::LParen => depth += 1, T::RParen => { depth -= 1; if depth == 0 { - j += 1; + rparen = Some(j); break; } } @@ -464,105 +330,34 @@ impl LkAnalyzer { } j += 1; } - let rparen_idx = j.saturating_sub(1); - // Expect function body starting '{' - if !matches!(tokens.get(j), Some(T::LBrace)) { - i = j; + let Some(rparen) = rparen else { break }; + i = rparen + 1; + + // Already annotated: the hint exists to show what was left unwritten. + if matches!(tokens.get(rparen + 1), Some(T::FnArrow)) { continue; } - // Find matching '}' for the body - let mut body_depth = 0i32; - let body_start = j + 1; // after '{' - j += 1; - let mut body_end = body_start; - while j < tokens.len() { - match &tokens[j] { - T::LBrace => body_depth += 1, - T::RBrace => { - if body_depth == 0 { - body_end = j; - break; - } - body_depth -= 1; - } - _ => {} - } - j += 1; - } - if body_end <= body_start { - i = j + 1; + let Some(return_type) = types.function_returns.get(name) else { + continue; + }; + let Some(rendered) = readable_type(return_type) else { + continue; + }; + let Some(span) = spans.get(rparen) else { continue }; + let position = Position::new(span.end.line.saturating_sub(1), span.end.column.saturating_sub(1)); + if position.line < range.start.line || position.line > range.end.line { continue; } - // Within body, scan for all `return ;` occurrences (including inside branches) - let mut k = body_start; - let mut return_types: Vec = Vec::new(); - while k < body_end { - if matches!(tokens[k], T::Return) { - // capture expression until next top-level `;` relative to paren/brace depth of this expression - let mut e = k + 1; - let mut expr_depth = 0i32; - let mut last = e; - while e < body_end { - match &tokens[e] { - T::LParen | T::LBracket | T::LBrace => expr_depth += 1, - T::RParen | T::RBracket | T::RBrace => expr_depth -= 1, - T::Semicolon if expr_depth == 0 => break, - _ => {} - } - last = e; - e += 1; - } - if last > k { - let expr_tokens = &tokens[k + 1..=last]; - if !expr_tokens.is_empty() { - if let Ok(expr) = ast::Parser::new(expr_tokens).parse() { - let mut checker = typ::TypeChecker::new_strict(); - if let Ok(ret_ty) = checker.infer_resolved_type(&expr) { - return_types.push(ret_ty); - } - } - } - } - // Advance past this statement terminator if present - k = e + 1; - continue; - } - k += 1; - } - - if !return_types.is_empty() { - // Deduplicate by display string for stable union label - use std::collections::BTreeMap; - let mut by_key: BTreeMap = BTreeMap::new(); - for t in return_types { - by_key.entry(t.display()).or_insert(t); - } - let parts: Vec = by_key.into_keys().collect(); - let label = if parts.len() == 1 { - format!(" -> {}", parts[0]) - } else { - format!(" -> {}", parts.join(" | ")) - }; - - // Place hint right after the parameter list, at the end of ')' - if rparen_idx < spans.len() { - let sp = &spans[rparen_idx]; - let pos = Position::new(sp.end.line - 1, sp.end.column.saturating_sub(1)); - if pos.line >= range.start.line && pos.line <= range.end.line { - hints.push(InlayHint { - position: pos, - label: InlayHintLabel::from(label), - kind: Some(InlayHintKind::TYPE), - text_edits: None, - tooltip: None, - padding_left: Some(true), - padding_right: Some(false), - data: None, - }); - } - } - } - i = j + 1; + hints.push(InlayHint { + position, + label: InlayHintLabel::from(format!(" -> {rendered}")), + kind: Some(InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: Some(true), + padding_right: Some(false), + data: None, + }); } hints } @@ -584,7 +379,7 @@ impl LkAnalyzer { .into_iter() .map(|module| (module.name, module.root)) .collect(); - self.missing_packages = graph.missing.into_iter().collect(); + self.missing_packages = graph.missing.into_iter().map(|missing| missing.name).collect(); } else { self.package_modules.clear(); self.missing_packages.clear(); @@ -954,6 +749,22 @@ impl LkAnalyzer { Ok(entry) } + /// A diagnostic covering the first line, for errors that carry no usable span. + fn first_line_diagnostic(content: &str, message: String) -> Diagnostic { + Diagnostic::new( + Range::new( + Position::new(0, 0), + Position::new(0, content.lines().next().map_or(0, |line| line.len() as u32)), + ), + Some(DiagnosticSeverity::ERROR), + None, + Some("lk".to_string()), + message, + None, + None, + ) + } + /// Analyze LK code and return diagnostics, symbols, and identifier roots pub fn analyze(&mut self, content: &str) -> AnalysisResult { let mut result = AnalysisResult { @@ -1028,6 +839,20 @@ impl LkAnalyzer { if !nad.is_empty() { result.diagnostics.extend(nad); } + + // Type-check the expression itself. The statement path gets this from + // `collect_type_diagnostics` below; this branch parsed the whole document + // as one expression, so it is the only place that checks it. Checking the + // already-parsed `expr` rather than re-parsing the source keeps `analyze` + // to a single tokenize+parse. + if result.diagnostics.is_empty() { + let mut checker = TypeChecker::new_strict(); + if let Err(err) = expr.type_check(&mut checker) { + result + .diagnostics + .push(Self::first_line_diagnostic(content, err.to_string())); + } + } } Err(expr_err) => { // Attempt expression-level recovery to surface multiple errors for pure expressions @@ -1115,16 +940,22 @@ impl LkAnalyzer { // Add precise use diagnostics using tokens/spans self.add_import_diagnostics(tokens, spans, &mut result); - // Run type checking to surface semantic diagnostics (e.g., numeric operand errors) + // Diagnostics from the document's one type check. The + // expansion path differs only in which token stream the + // ranges are resolved against — the errors are the same + // ones, produced once. + let document_types = token_entry.document_types(content); let type_diags = match expansion { Some(expansion) => Self::collect_type_diagnostics( - &expansion.program, + &document_types.errors, &expansion.source.tokens, &expansion.source.spans, content, Some(&expansion.source.origins), ), - None => Self::collect_type_diagnostics(program, tokens, spans, content, None), + None => { + Self::collect_type_diagnostics(&document_types.errors, tokens, spans, content, None) + } }; if !type_diags.is_empty() { result.diagnostics.extend(type_diags); @@ -1234,44 +1065,6 @@ impl LkAnalyzer { } } - // Run strict type checking when parsing succeeded to surface semantic diagnostics - if result.diagnostics.is_empty() { - if let Ok((tokens, spans)) = Tokenizer::tokenize_enhanced_with_spans(content) { - let mut parser = StmtParser::new_with_spans(&tokens, &spans); - if let Ok(program) = parser.parse_program_with_enhanced_errors(content) { - let has_complex_items = program - .statements - .iter() - .any(|stmt| matches!(stmt_without_attributes(stmt), Stmt::Import(_) | Stmt::Function { .. })); - if has_complex_items { - // Skip type checking when imports/functions are present since additional context is required. - // TODO: enrich analyzer with module resolution to support complex programs. - self.dedup_diagnostics(&mut result.diagnostics); - return result; - } - let mut checker = TypeChecker::new_strict(); - if let Err(err) = program.type_check(&mut checker) { - let mut diag = Diagnostic::new( - Range::new(Position::new(0, 0), Position::new(0, 0)), - Some(DiagnosticSeverity::ERROR), - None, - Some("lk".to_string()), - err.to_string(), - None, - None, - ); - if diag.range.end.line == 0 && diag.range.end.character == 0 { - diag.range = Range::new( - Position::new(0, 0), - Position::new(0, content.lines().next().map_or(0, |l| l.len() as u32)), - ); - } - result.diagnostics.push(diag); - } - } - } - } - // Deduplicate diagnostics by range and message to reduce noise self.dedup_diagnostics(&mut result.diagnostics); diff --git a/lsp/src/analyzer/mod.rs b/lsp/src/analyzer/mod.rs index f9699d06..bde8688e 100644 --- a/lsp/src/analyzer/mod.rs +++ b/lsp/src/analyzer/mod.rs @@ -15,7 +15,7 @@ use lk_core::{ token, token::{Span, Tokenizer}, typ, - typ::TypeChecker, + typ::{ObservedBinding, TypeChecker}, val, }; use lk_core::{stmt::NamedParamDecl, util::fast_map::FastHashMap}; @@ -60,11 +60,91 @@ pub(crate) struct TokenCacheEntry { project_dependencies: Arc>, project_dependency_fingerprint: macro_system::ProcMacroDependencyFingerprint, named_param_decls: OnceCell>>>, + document_types: OnceCell>, program_expansion: OnceCell, program_ast: OnceCell>, expr_ast: OnceCell>, } +/// A type as the reader should see it, or `None` when there is nothing to say. +/// +/// The checker's unresolved type variables are internal numbering — `'T0`, +/// `'T14`. Showing them is worse than showing nothing: `let double : ('T0) -> Int` +/// asks the reader to decode a solver detail, and two unrelated bindings can +/// even display the *same* `'T14` because the variable is shared, which reads as +/// a relationship that is not there. +/// +/// So a variable renders as `_` — `(_) -> Int` still says the return type — and +/// a type that is nothing *but* a variable produces no hint at all. +pub(crate) fn readable_type(ty: &val::Type) -> Option { + if matches!(ty, val::Type::Variable(_)) { + return None; + } + let rendered = ty.display(); + if !rendered.contains('\'') { + return Some(rendered); + } + // `Type::display` writes a variable as `'name`, and no other type spelling + // contains an apostrophe. + // + // The substitute is `_`, which is now also how `Type::Unknown` — the + // read-only container view, `List<_>` — is written. Deliberately the same: + // a hint is read, not parsed, and both say the one thing the reader needs, + // that nothing here pins this type. Dropping the hint instead was tried + // (2026-08-06) and is worse — `test_hints_never_show_solver_type_variables` + // pins the case it loses, a lambda whose parameter is open and whose return + // is known (`(_) -> Int`), which is most of what these hints are for. + static TYPE_VARIABLE: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| regex::Regex::new(r"'[A-Za-z_][A-Za-z0-9_]*").expect("valid regex")); + Some(TYPE_VARIABLE.replace_all(&rendered, "_").into_owned()) +} + +/// What one program-wide type check yields, cached per document revision. +#[derive(Debug, Default)] +pub(crate) struct DocumentTypes { + /// Every binding, with the type it was bound to and where it was written. + pub(crate) bindings: Vec, + /// Each top-level function's inferred return type, by name. + pub(crate) function_returns: HashMap, + pub(crate) errors: Vec, +} + +/// A type error, kept in a form that outlives the check that produced it. +/// +/// `anyhow::Error` is not `Clone`, so it cannot live in a shared cache; the +/// parts a diagnostic is built from can. `TypeError` carries the expression and +/// the expected/actual pair that decide where the squiggle goes. +#[derive(Debug, Clone)] +pub(crate) struct RecordedTypeError { + pub(crate) typed: Option, + /// The span of an error raised as a `ParseError` rather than a `TypeError`. + /// + /// A `let` whose annotation disagrees with its value is reported that way, + /// and it already carries the statement's position — the diagnostic just + /// never read it, and fell back to highlighting the first line of the file. + pub(crate) span: Option, + pub(crate) message: String, +} + +impl RecordedTypeError { + fn from_error(error: &anyhow::Error) -> Self { + let typed = error.downcast_ref::().cloned(); + let span = typed + .as_ref() + .and_then(|type_error| type_error.span.clone()) + .or_else(|| { + error + .downcast_ref::() + .and_then(|parse_error| parse_error.span.clone()) + }); + Self { + typed, + span, + message: error.to_string(), + } + } +} + #[derive(Debug, Clone)] struct CachedProgramExpansion { expansion: Arc, @@ -87,6 +167,7 @@ impl TokenCacheEntry { project_dependencies: Arc::new(project_dependencies), project_dependency_fingerprint, named_param_decls: OnceCell::new(), + document_types: OnceCell::new(), program_expansion: OnceCell::new(), program_ast: OnceCell::new(), expr_ast: OnceCell::new(), @@ -104,6 +185,43 @@ impl TokenCacheEntry { .cloned() } + /// Everything one program-wide type check produces, for this revision. + /// + /// One check per document, shared by every feature that wants a type — + /// diagnostics, inlay hints, hover, completion. They used to run their own: + /// the same file was checked twice per analysis, which is the thing + /// `analyze` was cleaned up for one commit before this cache existed. + /// + /// `type_check_collecting` rather than `type_check`, so one bad statement + /// takes neither the diagnostics nor the types of the rest of the file. + fn document_types(&self, content: &str) -> Arc { + self.document_types + .get_or_init(|| { + let Ok(program) = self.parse_program_arc(content) else { + return Arc::new(DocumentTypes::default()); + }; + let mut checker = TypeChecker::new_strict(); + checker.observe_bindings(); + // The same seeding `lk check` does. Without it the editor knows + // strictly less about the file than the compiler does: every + // name from `use { f } from "lib";` reads as `Any`. + if let Some(base_dir) = self.parse_options.base_dir.as_deref() { + typ::seed_imported_signatures(&program, base_dir, &mut checker); + } + let errors = program + .type_check_collecting(&mut checker) + .iter() + .map(RecordedTypeError::from_error) + .collect(); + Arc::new(DocumentTypes { + bindings: checker.take_observations(), + function_returns: checker.function_return_types().into_iter().collect(), + errors, + }) + }) + .clone() + } + fn parse_program_expansion_arc( &self, content: &str, diff --git a/lsp/src/analyzer/tests.rs b/lsp/src/analyzer/tests.rs index 6b261d8e..6a50dbb7 100644 --- a/lsp/src/analyzer/tests.rs +++ b/lsp/src/analyzer/tests.rs @@ -1,7 +1,6 @@ use super::*; use lk_core::expr; use lk_core::macro_system::{ProcMacroProcessConfig, ProcMacroProviders}; -use lk_core::util::fast_map::FastHashMap; use lk_core::val::{HeapStore, HeapValue, LiteralVal, RuntimeVal, ShortStr, TypedMap}; use std::{fs, path::PathBuf, time::Duration}; use tower_lsp::lsp_types::{ @@ -35,7 +34,7 @@ fn string_map(heap: &mut HeapStore, entries: impl IntoIterator::from(key), value)) - .collect::>(); + .collect::>(); RuntimeVal::Obj(heap.alloc(HeapValue::Map(TypedMap::StringMixed(entries)))) } @@ -544,17 +543,270 @@ fn test_validate_semantic_tokens_rejects_bad_ranges_and_legend_indexes() { #[test] fn test_type_inlay_hints_let_and_define() { - let analyzer = LkAnalyzer::new(); + let mut analyzer = LkAnalyzer::new(); let src = r#" let x = 1; y := 1.0; "#; - let mut hints = analyzer.compute_type_inlay_hints(src, full_range(src)); - hints.extend(analyzer.compute_define_type_hints(src, full_range(src))); + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); assert!(!hints.is_empty(), "expected type hints for let/define, got none"); assert!(hints.iter().all(|h| h.kind == Some(InlayHintKind::TYPE))); } +fn hint_labels(hints: &[InlayHint]) -> Vec { + hints + .iter() + .map(|hint| match &hint.label { + tower_lsp::lsp_types::InlayHintLabel::String(label) => label.clone(), + _ => String::new(), + }) + .collect() +} + +#[test] +fn test_type_hints_cover_bindings_that_name_other_things() { + let mut analyzer = LkAnalyzer::new(); + let src = "fn greet(name: String) -> String {\n return name;\n}\nlet who = greet(\"lk\");\n"; + + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); + + // A fresh per-expression checker has never heard of `greet`, so this hint + // could not exist before the types came from one document-wide check. + assert!( + hint_labels(&hints).iter().any(|label| label == ": String"), + "expected `who: String`, got {:?}", + hint_labels(&hints) + ); +} + +#[test] +fn test_type_hints_use_stdlib_signatures() { + let mut analyzer = LkAnalyzer::new(); + let src = "use string;\nlet parts = string.split(\"a,b\", \",\");\n"; + + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); + + assert!( + hint_labels(&hints).iter().any(|label| label == ": List"), + "expected the declared return type of string.split, got {:?}", + hint_labels(&hints) + ); +} + +#[test] +fn test_return_type_hints_come_from_the_checked_signature() { + let mut analyzer = LkAnalyzer::new(); + let src = "fn greet(name: String) -> String {\n return name;\n}\nfn call_it() {\n return greet(\"lk\");\n}\n"; + + let hints = analyzer.compute_function_return_type_hints(src, full_range(src)); + + // `call_it` returns whatever `greet` returns. Deriving that from the token + // stream in a fresh checker — what this used to do — could not know `greet`. + assert!( + hint_labels(&hints).iter().any(|label| label.trim() == "-> String"), + "expected `-> String` for call_it, got {:?}", + hint_labels(&hints) + ); + // `greet` says its own return type, so it gets no hint. + assert_eq!( + hint_labels(&hints).len(), + 1, + "an annotated function needs no hint: {:?}", + hint_labels(&hints) + ); +} + +#[test] +fn test_hints_never_show_solver_type_variables() { + let mut analyzer = LkAnalyzer::new(); + // The shape from closure.lk: the parameter type is undetermined, the return + // type is not. Rendering the whole thing gave `('T0) -> Int`. + let src = "let double = |x| x * 2;\nlet plain = 7;\n"; + + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); + let labels = hint_labels(&hints); + + assert!( + !labels.iter().any(|label| label.contains('\'')), + "a solver variable reached the editor: {labels:?}" + ); + assert!( + labels.iter().any(|label| label == ": Int"), + "the known type should still be shown: {labels:?}" + ); + assert!( + labels.iter().any(|label| label.contains("(_)")), + "an unknown parameter should read as `_`, not disappear: {labels:?}" + ); +} + +#[test] +fn test_type_hints_use_imported_signatures() { + let dir = unique_tmp_dir("imported_signature_hints"); + fs::create_dir_all(&dir).expect("create temp dir"); + fs::write( + dir.join("lib.lk"), + "fn greet(name: String) -> String {\n return name;\n}\n", + ) + .expect("write dependency"); + + let mut analyzer = LkAnalyzer::new(); + analyzer.set_base_dir(dir.clone()); + let src = "use { greet } from \"lib\";\nlet who = greet(\"lk\");\n"; + + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); + + // `lk check` has always seeded these signatures; the editor used to know + // strictly less about the file than the compiler did. + assert!( + hint_labels(&hints).iter().any(|label| label == ": String"), + "expected the imported function's declared return type, got {:?}", + hint_labels(&hints) + ); + let _ = fs::remove_dir_all(&dir); +} + +/// The editor must not cry wolf about a type that lives in another file. +/// +/// An unknown type name is a diagnostic now, and a type crosses a module +/// boundary by its bare name — `use * as L from "./lib"; fn f() -> Row` names +/// `Row`, not `L.Row`. Only *functions* used to be seeded from an imported +/// file, so the checker had never heard of `Row`; while unknown names were +/// silently accepted that cost nothing, and the moment they became an error it +/// would have put a red squiggle under correct code. +#[test] +fn test_imported_types_are_not_reported_as_unknown() { + let dir = unique_tmp_dir("imported_type_diagnostics"); + fs::create_dir_all(&dir).expect("create temp dir"); + fs::write( + dir.join("lib.lk"), + "struct Row { id: Int } +type Id = Int; +trait Shown { fn show(self) -> Int; } +fn mk(v: Int) -> Row { return Row { id: v }; } +", + ) + .expect("write dependency"); + + let mut analyzer = LkAnalyzer::new(); + analyzer.set_base_dir(dir.clone()); + let src = "use * as L from \"lib\";\n\ + fn pass(v: Int) -> Row { return L.mk(v); }\n\ + fn ident(v: Id) -> Id { return v; }\n"; + + let result = analyzer.analyze(src); + let unknown: Vec<&String> = result + .diagnostics + .iter() + .map(|diagnostic| &diagnostic.message) + .filter(|message| message.contains("Unknown type")) + .collect(); + assert!( + unknown.is_empty(), + "imported types should be known to the editor, got {unknown:?}" + ); + + // …and a name nothing declares is still reported, so this is not vacuous. + let typo = analyzer.analyze("let x: Strng = \"a\";\n"); + assert!( + typo.diagnostics + .iter() + .any(|diagnostic| diagnostic.message.contains("Unknown type 'Strng'")), + "a typo should still be reported, got {:?}", + typo.diagnostics.iter().map(|d| &d.message).collect::>() + ); + let _ = fs::remove_dir_all(&dir); +} + +#[test] +fn test_type_hints_use_namespace_import_members() { + let dir = unique_tmp_dir("namespace_import_hints"); + fs::create_dir_all(&dir).expect("create temp dir"); + fs::write( + dir.join("lib.lk"), + "fn greet(name: String) -> String {\n return name;\n}\n", + ) + .expect("write dependency"); + + let mut analyzer = LkAnalyzer::new(); + analyzer.set_base_dir(dir.clone()); + + // Both spellings bind a namespace whose members are reached as `lib.greet`. + for src in [ + "use \"lib\";\nlet who = lib.greet(\"lk\");\n", + "use * as lib from \"lib\";\nlet who = lib.greet(\"lk\");\n", + ] { + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); + assert!( + hint_labels(&hints).iter().any(|label| label == ": String"), + "expected the namespace member's declared return type for {src:?}, got {:?}", + hint_labels(&hints) + ); + } + let _ = fs::remove_dir_all(&dir); +} + +#[test] +fn test_one_bad_statement_does_not_cost_the_rest_their_hints() { + let mut analyzer = LkAnalyzer::new(); + let src = "let bad: Int = \"x\";\nlet good = 41 + 1;\n"; + + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); + + assert!( + hint_labels(&hints).iter().any(|label| label == ": Int"), + "the statement after a type error still has a type, got {:?}", + hint_labels(&hints) + ); +} + +#[test] +fn test_type_error_points_at_the_offending_statement() { + let mut analyzer = create_analyzer(); + // Two statements whose expressions are token-identical. Locating the error + // by hunting the token stream finds the first `1`; the error is in the + // second statement. + let result = analyzer.analyze("let a: Int = 1;\nlet b: Bool = 1;\n"); + + let diagnostic = result + .diagnostics + .iter() + .find(|diagnostic| diagnostic.message.contains("Type mismatch")) + .unwrap_or_else(|| panic!("expected a type mismatch, got {:?}", result.diagnostics)); + assert_eq!( + diagnostic.range.start.line, 1, + "the error belongs to line 2, got {:?}", + diagnostic.range + ); +} + +#[test] +fn test_expression_document_is_type_checked_once() { + let mut analyzer = create_analyzer(); + // A document that parses as a single expression never reaches the statement + // path, so its type check lives in the expression branch of `analyze`. + // + // The sample used to be `1 ? 2 : 3`, which was an error only because the + // ternary demanded a `Bool` condition where the `if` statement accepted any + // truthy value. That divergence is gone — LK's rule is truthiness — so the + // sample is now an expression that is ill-typed for a reason unrelated to + // conditions. + let result = analyzer.analyze("\"a\" - 1"); + + assert_eq!( + result.diagnostics.len(), + 1, + "expected exactly one type diagnostic, got {:?}", + result.diagnostics.iter().map(|d| &d.message).collect::>() + ); + assert_eq!(result.diagnostics[0].severity, Some(DiagnosticSeverity::ERROR)); + assert!( + result.diagnostics[0].message.contains("Type Error"), + "unexpected message: {}", + result.diagnostics[0].message + ); +} + #[test] fn test_business_workload_should_run_infers_from_string_calls() { let mut analyzer = create_analyzer(); @@ -582,8 +834,10 @@ fn test_unconstrained_implicit_any_diagnostic_points_to_parameter() { assert_eq!(result.diagnostics.len(), 1); let diag = &result.diagnostics[0]; - assert_eq!(diag.severity, Some(DiagnosticSeverity::ERROR)); - assert_eq!(diag.code, Some(NumberOrString::String("lk_type_error".to_string()))); + // A warning, not an error: the compiler accepts this program and runs it, + // and only `lk check --strict` reports the finding at all. + assert_eq!(diag.severity, Some(DiagnosticSeverity::WARNING)); + assert_eq!(diag.code, Some(NumberOrString::String("lk_type_lint".to_string()))); assert_eq!( diag.message, "Function 'should_run' infers implicit Any for parameter 'name'; add explicit annotations" @@ -609,7 +863,7 @@ fn test_macro_generated_type_diagnostic_includes_origin_stack() { let diagnostic = result .diagnostics .iter() - .find(|diag| diag.message.contains("must by numeric types")) + .find(|diag| diag.message.contains("must be numeric types")) .expect("expected numeric type diagnostic"); assert!(diagnostic.message.contains("Macro origin stack:")); assert!(diagnostic.message.contains("bad_numeric")); @@ -1055,3 +1309,78 @@ fn full_range(s: &str) -> Range { let end_col = s.lines().last().map(|l| l.len() as u32).unwrap_or(0); Range::new(Position::new(0, 0), Position::new(end_line, end_col)) } + +/// The editor's outline shows what the writer wrote. +/// +/// A parse-time desugar binds a temporary — `a?.m()` and `expr!` both do — and +/// those temporaries were listed in the document outline beside the real +/// variables, as `__optcall0` and `__unwrap1`. They could not be filtered by +/// name either: `__optcall0` is a name a program may legitimately spell. They +/// are minted with a `$` now, which no source identifier can contain, and the +/// resolver keeps them out of the list tools read. +#[test] +fn the_outline_lists_only_the_writers_variables() { + fn flatten(symbols: &[tower_lsp::lsp_types::DocumentSymbol], out: &mut Vec) { + for symbol in symbols { + out.push(symbol.name.clone()); + if let Some(children) = &symbol.children { + flatten(children, out); + } + } + } + + let mut analyzer = LkAnalyzer::new(); + let src = "let m = {\"a\": \"xy\"};\nlet n = m.get(\"a\")?.len();\nlet v = n!;\n"; + let result = analyzer.analyze(src); + let mut names = Vec::new(); + flatten(&result.symbols, &mut names); + + for written in ["m", "n", "v"] { + assert!( + names.iter().any(|name| name == written), + "{written} should be listed: {names:?}" + ); + } + assert!( + !names.iter().any(|name| name.contains('$')), + "a desugar's temporary is not the writer's variable: {names:?}" + ); +} + +/// The editor and the compiler agree about what is an error. +/// +/// The analyzer type-checks with the *strict* checker, which adds the +/// implicit-`Any` lint that `lk check` only reports under `--strict`. Rendering +/// everything it says as `ERROR` meant a program the compiler accepts, and that +/// runs, showed a red error in every LSP client — three of this repository's own +/// examples among them. A lint is advice: it stays, as a warning, under its own +/// code. +#[test] +fn an_implicit_any_lint_is_a_warning_and_a_real_error_is_not() { + let mut analyzer = LkAnalyzer::new(); + + let lint = analyzer.analyze("fn take(xs) {\n return xs;\n}\n"); + let lints: Vec<_> = lint + .diagnostics + .iter() + .filter(|d| d.message.contains("implicit Any")) + .collect(); + assert_eq!(lints.len(), 1, "expected the lint: {:?}", lint.diagnostics); + assert_eq!(lints[0].severity, Some(DiagnosticSeverity::WARNING)); + assert_eq!( + lints[0].code, + Some(tower_lsp::lsp_types::NumberOrString::String("lk_type_lint".to_string())) + ); + + // A genuine type error keeps its severity — the point is the distinction, + // not silencing the checker. + let broken = analyzer.analyze("let a: Int = \"text\";\n"); + assert!( + broken + .diagnostics + .iter() + .any(|d| d.severity == Some(DiagnosticSeverity::ERROR)), + "a real type error must stay an error: {:?}", + broken.diagnostics + ); +} diff --git a/lsp/src/bench_test.rs b/lsp/src/bench_test.rs index 777abeec..54299472 100644 --- a/lsp/src/bench_test.rs +++ b/lsp/src/bench_test.rs @@ -73,6 +73,7 @@ mod bench_tests { trigger: lk_completion::CompletionTrigger::Invoked, session_source: None, base_dir: None, + known_types: None, }; let start = Instant::now(); diff --git a/lsp/src/editor_grammar_test.rs b/lsp/src/editor_grammar_test.rs new file mode 100644 index 00000000..2d2f5b6b --- /dev/null +++ b/lsp/src/editor_grammar_test.rs @@ -0,0 +1,152 @@ +//! The editor grammars keep their own copy of the language's type names. +//! +//! Three copies, historically, and they disagreed: machine ints (`u8`, `usize`, +//! …) were in the language for a month while neither grammar highlighted them, +//! and completion's receiver table listed a `Str` that the language has never +//! had. Nobody did anything wrong — a copy nobody can check is a copy that +//! drifts. +//! +//! So the copies are checked here, against `lk_values`, which is the one that +//! decides. A name added to the language and not to a grammar fails this; a +//! name in a grammar that the language does not have fails it too. + +#[cfg(test)] +mod tests { + use lk_core::val::{IntKind, CONTAINER_TYPE_NAMES, NUMBER_TYPE_NAME, PRIMITIVE_TYPES, TYPE_SPELLINGS}; + use std::collections::BTreeSet; + use std::path::{Path, PathBuf}; + + /// Names the tree-sitter grammar legitimately does not list as builtins. + /// + /// Each of these takes a type parameter (`Set`), so it needs a rule of + /// its own beside `list_type`/`map_type` rather than a bare keyword in + /// `primitive_type` — putting it there would make `Set` fail to parse. + /// Until someone writes those rules they fall through to `type_identifier`, + /// which still highlights as a type, just not as a builtin one. + /// + /// `List` and `Map` are absent from this list because they *do* have rules. + const TREE_SITTER_NOT_YET_BUILTIN: &[&str] = &["Set", "Tuple", "Task", "Channel", "Box", "Boxed"]; + + fn repo_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .map(Path::to_path_buf) + .expect("lsp crate has a parent directory") + } + + /// Every type name the language spells, builtin and machine-int alike. + fn language_type_names() -> BTreeSet { + PRIMITIVE_TYPES + .iter() + .map(|(name, _)| (*name).to_string()) + .chain(CONTAINER_TYPE_NAMES.iter().map(|name| (*name).to_string())) + .chain(IntKind::ALL.iter().map(|kind| kind.name().to_string())) + // Second spellings are types too, as far as a reader and a + // highlighter are concerned: `f64` names the same type as `Float`, + // and leaving it out of the grammars would render it as an + // identifier. + .chain(TYPE_SPELLINGS.iter().map(|(name, _)| (*name).to_string())) + .chain(core::iter::once(NUMBER_TYPE_NAME.to_string())) + .collect() + } + + /// The quoted words inside `primitive_type: $ => choice(...)`. + fn tree_sitter_primitive_names(grammar: &str) -> BTreeSet { + let start = grammar + .find("primitive_type: $ => choice(") + .expect("grammar.js declares primitive_type"); + let rest = &grammar[start..]; + let end = rest.find(')').expect("primitive_type choice is closed"); + rest[..end] + .split('\'') + .skip(1) + .step_by(2) + .map(ToString::to_string) + .collect() + } + + /// The alternatives of every `support.type.primitive.lk` match pattern. + fn textmate_type_names(grammar: &str) -> BTreeSet { + let mut names = BTreeSet::new(); + for section in grammar.split("support.type.primitive.lk").skip(1) { + let Some(open) = section.find("(") else { continue }; + let Some(close) = section[open..].find(")") else { + continue; + }; + for name in section[open + 1..open + close].split('|') { + let name = name.trim(); + if !name.is_empty() { + names.insert(name.to_string()); + } + } + } + assert!(!names.is_empty(), "found no type patterns in the TextMate grammar"); + names + } + + #[test] + fn type_name_lists_agree() { + let root = repo_root(); + let grammar_js = std::fs::read_to_string(root.join("ecosystem/tree-sitter-lk/grammar.js")) + .expect("read tree-sitter grammar"); + let tm_language = std::fs::read_to_string(root.join("ecosystem/vsc-ext/lsp/syntaxes/lk.tmLanguage.json")) + .expect("read TextMate grammar"); + + let language = language_type_names(); + + // TextMate is pure highlighting — it can and should carry every name. + let textmate = textmate_type_names(&tm_language); + assert_eq!( + language.difference(&textmate).collect::>(), + Vec::<&String>::new(), + "the TextMate grammar does not highlight these language types" + ); + assert_eq!( + textmate.difference(&language).collect::>(), + Vec::<&String>::new(), + "the TextMate grammar highlights these as types, but the language has no such type" + ); + + // tree-sitter drives parsing, so a parameterised name cannot be a bare + // keyword here; those are listed as not-yet-builtin with a rationale. + let expected_tree_sitter: BTreeSet = language + .iter() + .filter(|name| !TREE_SITTER_NOT_YET_BUILTIN.contains(&name.as_str())) + // `List`/`Map` have rules of their own. + .filter(|name| name.as_str() != "List" && name.as_str() != "Map") + .cloned() + .collect(); + let tree_sitter = tree_sitter_primitive_names(&grammar_js); + assert_eq!( + expected_tree_sitter.difference(&tree_sitter).collect::>(), + Vec::<&String>::new(), + "grammar.js does not know these language types — add them to `primitive_type` \ + and re-run `tree-sitter generate`, or list them in TREE_SITTER_NOT_YET_BUILTIN" + ); + assert_eq!( + tree_sitter.difference(&expected_tree_sitter).collect::>(), + Vec::<&String>::new(), + "grammar.js lists these as builtin types, but the language has no such type" + ); + } + + #[test] + fn the_generated_parser_is_not_stale() { + // `src/parser.c` is a committed build product. A `primitive_type` name + // present in the grammar but absent from the generated parser means + // somebody edited grammar.js without re-running `tree-sitter generate`, + // and the editor is still parsing with the old rules. + let root = repo_root(); + let grammar_js = std::fs::read_to_string(root.join("ecosystem/tree-sitter-lk/grammar.js")) + .expect("read tree-sitter grammar"); + let generated = std::fs::read_to_string(root.join("ecosystem/tree-sitter-lk/src/grammar.json")) + .expect("read generated grammar.json"); + + for name in tree_sitter_primitive_names(&grammar_js) { + assert!( + generated.contains(&format!("\"value\": \"{name}\"")), + "`{name}` is in grammar.js but not in the generated parser — run `tree-sitter generate`" + ); + } + } +} diff --git a/lsp/src/inlay_hint_test.rs b/lsp/src/inlay_hint_test.rs index 7a021a7f..07dea3bc 100644 --- a/lsp/src/inlay_hint_test.rs +++ b/lsp/src/inlay_hint_test.rs @@ -162,9 +162,8 @@ mod inlay_hint_tests { let x = 1; y := 1.0; "#; - let analyzer = LkAnalyzer::new(); - let mut hints = analyzer.compute_type_inlay_hints(src, full_range(src)); - hints.extend(analyzer.compute_define_type_hints(src, full_range(src))); + let mut analyzer = LkAnalyzer::new(); + let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); assert!(!hints.is_empty(), "expected type hints for let/define, got none"); assert!(hints.iter().all(|h| h.kind == Some(InlayHintKind::TYPE))); let labels: Vec = hints @@ -183,7 +182,7 @@ mod inlay_hint_tests { let x: Int = 1; let y = 2; "#; - let analyzer = LkAnalyzer::new(); + let mut analyzer = LkAnalyzer::new(); let hints = analyzer.compute_type_inlay_hints(src, full_range(src)); // Should only hint for y, not for the annotated x let labels: Vec = hints @@ -233,9 +232,8 @@ mod inlay_hint_tests { // Collect both parameter and type hints as the server would before filtering let mut combined: Vec = compute_inlay_hints(src, full_range(src)); - let analyzer = LkAnalyzer::new(); + let mut analyzer = LkAnalyzer::new(); combined.extend(analyzer.compute_type_inlay_hints(src, full_range(src))); - combined.extend(analyzer.compute_define_type_hints(src, full_range(src))); assert!(!combined.is_empty(), "expected mixed inlay hints present"); @@ -286,7 +284,7 @@ mod inlay_hint_tests { } fn consts() { return 42; } "#; - let analyzer = LkAnalyzer::new(); + let mut analyzer = LkAnalyzer::new(); let hints = analyzer.compute_function_return_type_hints(src, full_range(src)); assert!(!hints.is_empty(), "expected function return type hints"); // Should include TYPE kind hints with labels like " -> Int" (at least for the const function) @@ -312,7 +310,7 @@ mod inlay_hint_tests { } } "#; - let analyzer = LkAnalyzer::new(); + let mut analyzer = LkAnalyzer::new(); let hints = analyzer.compute_function_return_type_hints(src, full_range(src)); assert!(!hints.is_empty(), "expected function return type hint for union"); let labs = labels(&hints); diff --git a/lsp/src/lib.rs b/lsp/src/lib.rs index 9f259eee..cb9062b0 100644 --- a/lsp/src/lib.rs +++ b/lsp/src/lib.rs @@ -1,3 +1,19 @@ +//! The LK language server, as a library the binary is a shell over. +//! +//! `main.rs` used to declare `mod analyzer; mod server;` of its own, so the +//! whole crate compiled **twice** — once as this lib, once inside the binary. +//! Besides the build time, it made the lib's dead-code analysis wrong: every +//! `analyzer` method whose only caller lives in `server` looked unused from +//! here, and `-D warnings` (which CI sets) failed on one of them. pub mod analyzer; +pub mod server; pub use analyzer::LkAnalyzer; +pub use server::compute_inlay_hints; + +#[cfg(test)] +mod bench_test; +#[cfg(test)] +mod editor_grammar_test; +#[cfg(test)] +mod inlay_hint_test; diff --git a/lsp/src/main.rs b/lsp/src/main.rs index b9912439..d64a6f7d 100644 --- a/lsp/src/main.rs +++ b/lsp/src/main.rs @@ -1,14 +1,6 @@ -mod analyzer; -mod server; - -#[cfg(test)] -mod bench_test; -#[cfg(test)] -mod inlay_hint_test; - -pub use server::compute_inlay_hints; +//! The `lk-lsp` binary: a shell over the crate's own library (see `lib.rs`). #[tokio::main] async fn main() -> anyhow::Result<()> { - server::run().await + lk_lsp::server::run().await } diff --git a/lsp/src/server/analysis.rs b/lsp/src/server/analysis.rs index 4ce929bc..72226991 100644 --- a/lsp/src/server/analysis.rs +++ b/lsp/src/server/analysis.rs @@ -180,14 +180,15 @@ impl LkLanguageServer { (doc.content.to_string(), off) }; - let (tokens, spans, ast_macro_origins) = { + let (tokens, spans, ast_macro_origins, document_types) = { if let Ok(mut analyzer) = self.analyzer.lock() { match analyzer.tokenize_with_spans_cached(&content) { Ok(entry) => { let tokens = entry.tokens.clone(); let spans = entry.spans.clone(); let ast_macro_origins = analyzer.ast_macro_origins(&content); - (tokens, spans, ast_macro_origins) + let document_types = analyzer.document_types_for(&content); + (tokens, spans, ast_macro_origins, document_types) } Err(_) => return None, } @@ -214,6 +215,7 @@ impl LkLanguageServer { idx, &ast_macro_origins, &package_modules, + &document_types.bindings, )); } @@ -813,6 +815,85 @@ fn parse_options_for_uri(uri: &Url) -> syntax::ParseOptions { options } +/// Reorder `candidates` so the declaration a cursor at `cursor_offset` can see +/// comes first. +/// +/// A declaration is visible when the innermost `{ … }` that encloses *it* also +/// encloses the cursor; among those, the nearest enclosing block wins, which is +/// what shadowing means. Attributing a declaration to any block that happens to +/// contain it is not enough — a function body contains both `x`s below, so the +/// inner one would win even after its own block closed: +/// +/// ```lk +/// let x = 1; +/// if true { let x = 2; println(x); } // ← this one sees `x = 2` +/// println(x); // ← and this one sees `x = 1` +/// ``` +fn innermost_visible_first( + candidates: Vec, + tokens: &[token::Token], + spans: &[token::Span], + cursor_offset: usize, +) -> Vec { + if candidates.len() < 2 { + return candidates; + } + // Every `{ … }` range in the file. An unclosed one runs to the end, so a + // half-written buffer still resolves. + let mut open: Vec = Vec::new(); + let mut blocks: Vec<(usize, usize)> = Vec::new(); + for (index, tok) in tokens.iter().enumerate() { + let Some(span) = spans.get(index) else { continue }; + match tok { + token::Token::LBrace => open.push(span.start.offset), + token::Token::RBrace => { + if let Some(start) = open.pop() { + blocks.push((start, span.end.offset)); + } + } + _ => {} + } + } + blocks.extend(open.into_iter().map(|start| (start, usize::MAX))); + + // The innermost block containing `offset`, as its width — narrower is + // nearer. `None` means "outside every block", which is the widest scope. + let innermost = |offset: usize| -> Option<(usize, usize)> { + blocks + .iter() + .filter(|(start, end)| *start <= offset && offset <= *end) + .min_by_key(|(start, end)| end.saturating_sub(*start)) + .copied() + }; + + let cursor_block = innermost(cursor_offset); + let mut visible: Vec = Vec::new(); + let mut rest: Vec = Vec::new(); + for span in candidates { + let declared_before = span.start.offset <= cursor_offset; + let block = innermost(span.start.offset); + // Visible when the declaration's own block also holds the cursor: + // either they share a block, or the declaration sits outside every + // block (top level) and so encloses everything. + let sees_cursor = match (block, cursor_block) { + (None, _) => true, + (Some(declared), Some(at_cursor)) => declared.0 <= at_cursor.0 && at_cursor.1 <= declared.1, + (Some(_), None) => false, + }; + if declared_before && sees_cursor { + visible.push(span); + } else { + rest.push(span); + } + } + // Nearest declaration first among the visible ones — that is the shadowing + // rule, since an inner block's declaration always comes later in the file + // than the outer one it shadows. + visible.sort_by_key(|span| core::cmp::Reverse(span.start.offset)); + visible.extend(rest); + visible +} + fn definition_location_in_program( program: &stmt::Program, tokens: &[token::Token], @@ -864,6 +945,17 @@ fn definition_location_in_program( } } } + // The declaration the cursor can actually see, innermost first. + // + // This used to take `candidate_spans.first()` — the earliest declaration + // with a matching name, whatever scope it was in — so under shadowing + // go-to-definition always landed on the outermost binding: + // + // ```lk + // let x = 1; + // if true { let x = 2; println(x); } // ← jumped to `let x = 1` + // ``` + let candidate_spans = innermost_visible_first(candidate_spans, tokens, spans, cursor_offset); if let Some(sp) = candidate_spans.first() { let range = Range::new( Position::new(sp.start.line - 1, sp.start.column - 1), @@ -1177,6 +1269,29 @@ mod tests { assert_eq!(on_qualifier, None); } + /// Go-to-definition used to answer the *earliest* declaration with a + /// matching name, so on a shadowed binding it always jumped past the one + /// the reader is standing in. + #[test] + fn definition_resolves_the_innermost_binding_in_scope() { + let uri = Url::parse("file:///shadow.lk").expect("uri"); + let content = "fn outer() {\n let x = 1;\n if true {\n let x = 2;\n println(x);\n }\n println(x);\n}\n"; + let (tokens, spans) = token::Tokenizer::tokenize_enhanced_with_spans(content).expect("tokens"); + let program = stmt::stmt_parser::StmtParser::new_with_spans(&tokens, &spans) + .parse_program_with_enhanced_errors(content) + .expect("program"); + + let inside_the_block = content.find("println(x);\n }").expect("inner use") + 8; + let after_the_block = content.rfind("println(x)").expect("outer use") + 8; + + let inner = definition_location_in_program(&program, &tokens, &spans, "x", inside_the_block, &uri); + let outer = definition_location_in_program(&program, &tokens, &spans, "x", after_the_block, &uri); + + // `let x = 2` is line 3, `let x = 1` is line 1 (both 0-based). + assert_eq!(inner.map(|location| location.range.start.line), Some(3)); + assert_eq!(outer.map(|location| location.range.start.line), Some(1)); + } + #[test] fn plain_symbol_at_position_uses_only_the_current_token() { let content = "let doubled = mathlib.double(n);\n"; diff --git a/lsp/src/server/completion.rs b/lsp/src/server/completion.rs index b4e71be5..9ab7d3f8 100644 --- a/lsp/src/server/completion.rs +++ b/lsp/src/server/completion.rs @@ -18,12 +18,22 @@ impl LkLanguageServer { let content = doc.content.to_string(); let cursor_char = position_to_char_idx(&doc.content, position); let base_dir = uri.to_file_path().ok().and_then(|mut path| path.pop().then_some(path)); + // From the document-wide check the analyzer already ran and cached. A + // half-typed line will not parse, and then this is empty and the engine + // falls back to reading token shapes. + let known_types = self + .analyzer + .lock() + .ok() + .map(|mut analyzer| analyzer.binding_types(&content)) + .unwrap_or_default(); Some(completion_response_for_source( &self.completion_engine, &content, cursor_char, completion_trigger_from_lsp(context), base_dir.as_deref(), + Some(&known_types), )) } } @@ -34,6 +44,7 @@ pub(crate) fn completion_response_for_source( cursor_char: usize, trigger: CompletionTrigger, base_dir: Option<&std::path::Path>, + known_types: Option<&std::collections::HashMap>, ) -> CompletionResponse { let cursor = char_to_byte_idx(content, cursor_char); let result = engine.complete_with_metadata(CompletionRequest { @@ -43,6 +54,7 @@ pub(crate) fn completion_response_for_source( trigger, session_source: None, base_dir, + known_types, }); let items = result .candidates @@ -66,6 +78,7 @@ pub(crate) fn completion_items_for_source( cursor_char: usize, trigger: CompletionTrigger, base_dir: Option<&std::path::Path>, + known_types: Option<&std::collections::HashMap>, ) -> Vec { completion_items_from_response(completion_response_for_source( engine, @@ -73,6 +86,7 @@ pub(crate) fn completion_items_for_source( cursor_char, trigger, base_dir, + known_types, )) } @@ -110,7 +124,7 @@ fn completion_items_for_source_invoked( cursor_char: usize, base_dir: Option<&std::path::Path>, ) -> Vec { - completion_items_for_source(engine, content, cursor_char, CompletionTrigger::Invoked, base_dir) + completion_items_for_source(engine, content, cursor_char, CompletionTrigger::Invoked, base_dir, None) } fn completion_item(content: &str, candidate: CompletionCandidate) -> CompletionItem { @@ -205,8 +219,14 @@ mod tests { let engine = lk_completion::CompletionEngine::new().unwrap(); let content = "if should_run(\"gcd_batch\") {}\nif should_run(\"\") {}"; let cursor = content.rfind("\"\"").unwrap() + 1; - let items = - completion_items_for_source(&engine, content, cursor, CompletionTrigger::TriggerCharacter('"'), None); + let items = completion_items_for_source( + &engine, + content, + cursor, + CompletionTrigger::TriggerCharacter('"'), + None, + None, + ); let item = items .iter() .find(|item| item.label == "gcd_batch") @@ -228,6 +248,7 @@ mod tests { content.chars().count(), CompletionTrigger::Incomplete, None, + None, ); assert!(items.iter().any(|item| item.label == "should_run")); } @@ -242,6 +263,7 @@ mod tests { content.chars().count(), CompletionTrigger::TriggerCharacter('{'), None, + None, ); let CompletionResponse::List(list) = response else { panic!("expected incomplete completion list"); diff --git a/lsp/src/server/handlers.rs b/lsp/src/server/handlers.rs index 0b46eef1..6d3852c1 100644 --- a/lsp/src/server/handlers.rs +++ b/lsp/src/server/handlers.rs @@ -250,14 +250,28 @@ impl LanguageServer for LkLanguageServer { } async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) { + let mut cleared_any = false; for change in params.changes { if let Ok(path) = change.uri.to_file_path() { let affected = self.workspace_cache.invalidate_changed_path_dependents(&path); for (affected_uri, version) in clear_cached_document_artifacts(&self.documents, &affected) { + cleared_any = true; self.schedule_diagnostics_and_warmup(affected_uri, version, 0).await; } } } + + if cleared_any { + // Diagnostics are pushed, so re-scheduling them above is enough. + // Inlay hints and semantic tokens are *pulled*: dropping the cached + // ones only decides what the next request computes, and nothing has + // told the editor to make one. A dependency's edit does not change + // this document's version either, so the editor has no reason of its + // own to ask again — the hints would sit there showing types read + // out of a file that has since changed. + let _ = self.client.inlay_hint_refresh().await; + let _ = self.client.semantic_tokens_refresh().await; + } } async fn did_close(&self, params: DidCloseTextDocumentParams) { @@ -594,6 +608,8 @@ impl LanguageServer for LkLanguageServer { let mut signatures: Vec = Vec::new(); // Built-ins and selected stdlib functions/meta-methods + // Globals first — they are functions, not methods, and belong to no + // receiver. match func_name.as_str() { "print" => signatures.push(sig( "print(fmt, ...args)", @@ -610,130 +626,20 @@ impl LanguageServer for LkLanguageServer { ["message"].as_slice(), "Global function - raise runtime error", )), - // iter module - "enumerate" => signatures.push(sig( - "enumerate(list)", - ["list"].as_slice(), - "iter: Add 0-based index to each element; returns list of [index, value]", - )), - "range" => { - signatures.push(sig( - "range(end)", - ["end"].as_slice(), - "iter: Generate [0, 1, ..., end-1]", - )); - signatures.push(sig( - "range(start, end)", - ["start", "end"].as_slice(), - "iter: Generate [start, ..., end) with step 1", - )); - signatures.push(sig( - "range(start, end, step)", - ["start", "end", "step"].as_slice(), - "iter: Generate arithmetic progression with given step (nonzero)", - )); - } - "zip" => signatures.push(sig( - "zip(list1, list2)", - ["list1", "list2"].as_slice(), - "iter: Pair elements into [a[i], b[i]] up to the shortest length", - )), - "take" => signatures.push(sig( - "take(list, n)", - ["list", "n"].as_slice(), - "iter: First n elements (n <= 0 returns [])", - )), - "skip" => signatures.push(sig( - "skip(list, n)", - ["list", "n"].as_slice(), - "iter: Elements after skipping first n (n <= 0 returns original)", - )), - "chain" => signatures.push(sig( - "chain(list1, list2)", - ["list1", "list2"].as_slice(), - "iter: Concatenate two lists", - )), - "flatten" => signatures.push(sig( - "flatten(list)", - ["list"].as_slice(), - "iter: Flatten one nesting level (non-lists pass through)", - )), - "unique" => signatures.push(sig( - "unique(list)", - ["list"].as_slice(), - "iter: Stable de-duplicate preserving first occurrences", - )), - "chunk" => signatures.push(sig( - "chunk(list, size)", - ["list", "size"].as_slice(), - "iter: Split into chunks of positive size", - )), - // list meta-methods and module functions (common ones) - "map" => { - signatures.push(sig( - "map(list, func)", - ["list", "func(value)"].as_slice(), - "Apply function to each element; returns transformed list", - )); - signatures.push(sig( - "list.map(func)", - ["func(value)"].as_slice(), - "Meta-method variant of map", - )); - } - "filter" => { - signatures.push(sig( - "filter(list, predicate)", - ["list", "predicate(value)"].as_slice(), - "Keep elements where predicate returns true (nil/false treated as false)", - )); - signatures.push(sig( - "list.filter(predicate)", - ["predicate(value)"].as_slice(), - "Meta-method variant of filter", - )); - } - "reduce" => { - signatures.push(sig( - "reduce(list, init, func)", - ["list", "init", "func(acc, value)"].as_slice(), - "Fold elements into an accumulator", - )); - signatures.push(sig( - "list.reduce(init, func)", - ["init", "func(acc, value)"].as_slice(), - "Meta-method variant of reduce", - )); - } - "push" => signatures.push(sig( - "push(list, value)", - ["list", "value"].as_slice(), - "Return a new list with value appended", - )), - "concat" => signatures.push(sig( - "concat(list, other)", - ["list", "other"].as_slice(), - "Concatenate two lists", - )), - "join" => signatures.push(sig( - "join(list, delimiter)", - ["list", "delimiter"].as_slice(), - "Join list of strings with delimiter", - )), - "get" => signatures.push(sig( - "get(list, index)", - ["list", "index"].as_slice(), - "Safe index access; returns value or nil", - )), - "first" => signatures.push(sig("first(list)", ["list"].as_slice(), "First element or nil")), - "last" => signatures.push(sig("last(list)", ["list"].as_slice(), "Last element or nil")), - "len" => signatures.push(sig( - "len(value)", - ["value"].as_slice(), - "Length of list/map/string (where applicable)", - )), _ => {} } + // Built-in methods, rendered from the one table that declares them + // (`lk_core::typ::BUILTIN_METHODS`). + // + // This used to be a hand-written arm per method, covering ten of the + // sixty-odd and describing `take(list, n)` as "n <= 0 returns []" — + // which stopped being true when a negative count started raising, and + // which nothing would have caught, because a help string has no reader + // that can disagree with it. + signatures.extend(builtin_method_signatures(&func_name)); + // The module spelling of the same operations (`iter.take(xs, 2)`), + // from the signature the `#[stdlib_export]` macro generated. + signatures.extend(stdlib_export_signatures(&func_name)); // Prefer AST-based scan for user-defined functions to reflect named parameter blocks if let Ok(mut analyzer) = self.analyzer.lock() { @@ -972,15 +878,9 @@ impl LanguageServer for LkLanguageServer { } if want_types { // Tokenize once and reuse across individual computations - if let Ok((tokens, spans)) = Tokenizer::tokenize_enhanced_with_spans(&content) { - let analyzer = LkAnalyzer::new_light(); - let mut h1 = analyzer.compute_type_inlay_hints_from_tokens(&tokens, &spans, range); - let mut h2 = analyzer.compute_define_type_hints_from_tokens(&tokens, &spans, range); - let mut h3 = analyzer.compute_function_return_type_hints_from_tokens(&tokens, &spans, range); - hints.append(&mut h1); - hints.append(&mut h2); - hints.append(&mut h3); - } + let mut analyzer = LkAnalyzer::new_light(); + hints.append(&mut analyzer.compute_type_inlay_hints(&content, range)); + hints.append(&mut analyzer.compute_function_return_type_hints(&content, range)); } hints }) @@ -1211,6 +1111,82 @@ impl LanguageServer for LkLanguageServer { } } +/// Signature help for a built-in method name, one entry per receiver that has +/// it — `len` belongs to five of them, and which one the user meant is not +/// knowable from the name alone. +fn builtin_method_signatures(name: &str) -> Vec { + lk_core::typ::BUILTIN_METHODS + .iter() + .filter(|declared| declared.name == name) + .map(|declared| { + let receiver = builtin_receiver_label(declared.receiver); + let last = declared.params.len().saturating_sub(1); + let params: Vec = declared + .params + .iter() + .enumerate() + .map(|(index, param)| { + // `...` on the repeating tail, the spelling `#[stdlib_export]` + // uses for the same thing (`...values: Any`). + if declared.variadic && index == last { + format!("...{}: {}", param.name, param.ty) + } else if param.optional { + format!("{}?: {}", param.name, param.ty) + } else { + format!("{}: {}", param.name, param.ty) + } + }) + .collect(); + let label = format!( + "{}.{}({}) -> {}", + receiver.to_lowercase(), + name, + params.join(", "), + declared.returns + ); + let param_refs: Vec<&str> = params.iter().map(String::as_str).collect(); + sig(&label, param_refs.as_slice(), declared.docs) + }) + .collect() +} + +fn builtin_receiver_label(kind: lk_core::typ::BuiltinReceiverKind) -> &'static str { + use lk_core::typ::BuiltinReceiverKind::*; + match kind { + List => "List", + Bytes => "Bytes", + Slice => "Slice", + Map => "Map", + Set => "Set", + Str => "String", + } +} + +/// Signature help for a bare stdlib export name (`take` → `iter.take(...)`), +/// taken from the catalog the export macro generates. +fn stdlib_export_signatures(name: &str) -> Vec { + let catalog = lk_stdlib::stdlib_catalog(); + let mut out = Vec::new(); + for module in &catalog.modules { + for export in &module.exports { + if export.name != name { + continue; + } + let Some(signature) = export.signature.as_deref() else { + continue; + }; + let params: Vec<&str> = signature + .split_once('(') + .and_then(|(_, rest)| rest.rsplit_once(')').map(|(inner, _)| inner)) + .filter(|inner| !inner.is_empty()) + .map(|inner| inner.split(", ").collect()) + .unwrap_or_default(); + out.push(sig(signature, params.as_slice(), export.docs.as_deref().unwrap_or(""))); + } + } + out +} + #[cfg(test)] mod tests { use super::*; diff --git a/lsp/src/server/hover.rs b/lsp/src/server/hover.rs index adefe3f4..3ab1072c 100644 --- a/lsp/src/server/hover.rs +++ b/lsp/src/server/hover.rs @@ -5,6 +5,7 @@ use std::path::{Path, PathBuf}; use lk_core::{ macro_system::AstMacroOrigin, token::{Span, Token}, + typ::ObservedBinding, }; use once_cell::sync::Lazy; use regex::Regex; @@ -50,6 +51,10 @@ pub(crate) struct LkDocIndex { pub(crate) decls: Vec, } +#[allow( + clippy::too_many_arguments, + reason = "every one is a distinct slice of the analysis the caller already has" +)] pub(crate) fn document_hover( content: &str, uri: &Url, @@ -58,11 +63,15 @@ pub(crate) fn document_hover( idx: usize, ast_macro_origins: &[AstMacroOrigin], package_modules: &HashMap, + bindings: &[ObservedBinding], ) -> Hover { let index = scan_lk_docs(content); if let Some(hover) = declaration_hover(content, uri, tokens, spans, idx, &index) { return hover; } + if let Some(hover) = binding_type_hover(tokens, spans, idx, bindings) { + return hover; + } if let Some(hover) = package_doc_hover(tokens, idx, package_modules) { return hover; } @@ -132,6 +141,40 @@ fn render_decl_markdown(decl: &LkDecl, content: &str, uri: &Url, index: &LkDocIn out } +/// The type of the binding the cursor is on. +/// +/// Hover had no access to types at all: it read declaration lines out of the +/// source text and looked names up in the stdlib catalog, which says nothing +/// about a local. The document's type check knows, and now that it is recorded +/// per binding with a position, hovering a name can answer with it. +/// +/// A name can be bound more than once — different scopes, or a rebinding — so +/// the nearest binding *at or before* the cursor wins, which is the one whose +/// type the cursor's occurrence actually has. +fn binding_type_hover(tokens: &[Token], spans: &[Span], idx: usize, bindings: &[ObservedBinding]) -> Option { + let Token::Id(name) = tokens.get(idx)? else { + return None; + }; + let cursor = spans.get(idx)?; + let binding = bindings + .iter() + .filter(|binding| &binding.name == name) + .filter(|binding| binding.span.start.offset <= cursor.start.offset) + .max_by_key(|binding| binding.span.start.offset) + // A use *above* the binding — a function body reading a top-level + // `const` declared below it — still has that binding's type. + .or_else(|| bindings.iter().find(|binding| &binding.name == name))?; + + // Hover answers even when the type is only partly known — unlike a hint, + // there is a question here that deserves an answer — but the solver's + // variable numbering is not part of it. + let rendered = crate::analyzer::readable_type(&binding.ty).unwrap_or_else(|| "_".to_string()); + Some(markdown_hover( + format!("```lk\n{}: {}\n```", binding.name, rendered), + Some(lsp_range_from_span(cursor)), + )) +} + fn ast_macro_origin_hover(span: Option<&Span>, origins: &[AstMacroOrigin]) -> Option { let span = span?; let origin = origins @@ -737,6 +780,37 @@ struct User { id: Int, name: String } assert!(rendered.contains("[Go to Int](command:lk.openLocation?")); } + #[test] + fn hovering_a_binding_answers_with_its_checked_type() { + use lk_core::token::Tokenizer; + + let uri = Url::parse("file:///tmp/test.lk").expect("uri"); + let content = "let total = 1 + 2;\nprintln(total);\n"; + let (tokens, spans) = Tokenizer::tokenize_enhanced_with_spans(content).expect("tokenize"); + + let mut analyzer = crate::analyzer::LkAnalyzer::new(); + let bindings = analyzer.document_types_for(content).bindings.clone(); + + // The `total` inside `println`, not the one being bound. + let use_idx = tokens + .iter() + .enumerate() + .filter(|(_, token)| matches!(token, Token::Id(name) if name == "total")) + .map(|(idx, _)| idx) + .next_back() + .expect("a use of total"); + + let hover = document_hover(content, &uri, &tokens, &spans, use_idx, &[], &HashMap::new(), &bindings); + let HoverContents::Markup(markup) = hover.contents else { + panic!("expected markdown hover"); + }; + assert!( + markup.value.contains("total: Int"), + "hover should carry the checked type, got: {}", + markup.value + ); + } + #[test] fn stdlib_function_hover_renders_markdown_signature_docs_and_links() { let uri = Url::parse("file:///tmp/test.lk").expect("uri"); @@ -834,6 +908,7 @@ struct User { id: Int } debug_idx, &expanded.ast_macro_origins, &HashMap::new(), + &[], ); let HoverContents::Markup(markup) = hover.contents else { panic!("expected markdown hover"); diff --git a/lsp/src/server/workspace_cache.rs b/lsp/src/server/workspace_cache.rs index c89e2add..7aa03648 100644 --- a/lsp/src/server/workspace_cache.rs +++ b/lsp/src/server/workspace_cache.rs @@ -20,7 +20,6 @@ use lk_core::{ ProcMacroDependencyGraph, ProcMacroProviders, }, package::{PackageGraph, LOCK_FILE, MANIFEST_FILE}, - token::Tokenizer, }; use super::{inlay_hints::compute_inlay_hints_with_margin, utils::compute_content_hash}; @@ -237,7 +236,7 @@ impl WorkspaceCache { .into_iter() .map(|module| (module.name, module.root)) .collect(); - let missing = graph.missing.into_iter().collect(); + let missing = graph.missing.into_iter().map(|missing| missing.name).collect(); if let Ok(mut ctx) = self.package_context.lock() { ctx.modules = modules; ctx.missing = missing; @@ -349,12 +348,9 @@ fn compute_full_inlay_hints(content: &str) -> Vec { } let range = full_range(content); let mut hints = compute_inlay_hints_with_margin(content, range, 0); - if let Ok((tokens, spans)) = Tokenizer::tokenize_enhanced_with_spans(content) { - let analyzer = LkAnalyzer::new_light(); - hints.extend(analyzer.compute_type_inlay_hints_from_tokens(&tokens, &spans, range)); - hints.extend(analyzer.compute_define_type_hints_from_tokens(&tokens, &spans, range)); - hints.extend(analyzer.compute_function_return_type_hints_from_tokens(&tokens, &spans, range)); - } + let mut analyzer = LkAnalyzer::new_light(); + hints.extend(analyzer.compute_type_inlay_hints(content, range)); + hints.extend(analyzer.compute_function_return_type_hints(content, range)); hints } diff --git a/lsp/tests/integration_test.rs b/lsp/tests/integration_test.rs index 84ac9e66..87f95afa 100644 --- a/lsp/tests/integration_test.rs +++ b/lsp/tests/integration_test.rs @@ -341,6 +341,7 @@ impl TestLanguageServer { } T::Str(s) => format!("String literal: \"{}\"", s), T::Int(i) => format!("Integer: {}", i), + T::UInt { value, radix } => format!("Integer: {}", lk_core::token::render_radix(*value, *radix)), T::Float(f) => format!("Float: {}", f), T::Bool(b) => format!("Boolean: {}", b), T::Nil => "Nil literal".to_string(), @@ -350,6 +351,7 @@ impl TestLanguageServer { T::Let => "Keyword: let".to_string(), T::Break => "Keyword: break".to_string(), T::Continue => "Keyword: continue".to_string(), + T::Defer => "Keyword: defer".to_string(), T::Return => "Keyword: return".to_string(), T::Struct => "Keyword: struct".to_string(), T::Fn => "Keyword: fn".to_string(), @@ -367,6 +369,9 @@ impl TestLanguageServer { T::And => "Operator: &&".to_string(), T::Or => "Operator: ||".to_string(), T::Not => "Operator: !".to_string(), + // Not an operator: the grammar gives it no meaning, and it exists so + // a `macro_rules!` can mark its internal rules the way Rust's do. + T::At => "Macro internal-rule marker: @".to_string(), T::In => "Operator: in".to_string(), T::Assign => "Operator: =".to_string(), T::Add => "Operator: +".to_string(), @@ -375,6 +380,10 @@ impl TestLanguageServer { T::Div => "Operator: /".to_string(), T::Mod => "Operator: %".to_string(), T::BitAnd => "Operator: &".to_string(), + T::BitXor => "Operator: ^".to_string(), + T::BitAndAssign => "Operator: &=".to_string(), + T::BitOrAssign => "Operator: |=".to_string(), + T::BitXorAssign => "Operator: ^=".to_string(), T::BitNot => "Operator: ~".to_string(), T::Dot => "Accessor: .".to_string(), T::Colon => "Symbol: :".to_string(), diff --git a/lsp/tests/perf_latency_test.rs b/lsp/tests/perf_latency_test.rs index c724d880..07240ddd 100644 --- a/lsp/tests/perf_latency_test.rs +++ b/lsp/tests/perf_latency_test.rs @@ -1,3 +1,26 @@ +//! Wall-clock budgets for the LSP's user-facing operations. +//! +//! **Every test here is `#[ignore]`d**, and that is not a decoy: a wall-clock +//! assertion is a *performance* gate, and this repository runs those alone +//! (`bench/run_workload_bench.sh` for the language, the `lsp-latency` job for +//! these). Inside `cargo test --workspace` they share the machine with every +//! other test binary, and contention makes them fail for reasons that have +//! nothing to do with the code: `analyze(complex program)` measures 1.16ms +//! here and failed a workspace run at 11.99ms against a 10ms budget. +//! +//! `fastest_of_five` below defeats a *single* interrupted sample; it cannot +//! defeat contention that lasts through all five, which is what a parallel +//! workspace run is. Scaling the budgets by a machine-speed probe was tried +//! and rejected: the probe's own spread on one machine was 3.6x, which turns a +//! 10ms budget into a 184ms one — a number that cannot fail, which is exactly +//! what the comment on `fastest_of_five` says these assertions must not be. +//! +//! Run them with: +//! +//! ```sh +//! cargo test -p lk-lsp --test perf_latency_test -- --ignored --test-threads=1 +//! ``` + use lk_lsp::LkAnalyzer; use std::{ fs, @@ -17,6 +40,30 @@ fn assert_under(label: &str, dur: Duration, max: Duration) { assert!(dur <= max, "{} exceeded budget: {:?} > {:?}", label, dur, max); } +/// The fastest of five runs of `work`. +/// +/// Two reasons, and the second is why the budgets below are what they are. +/// +/// **Noise.** These tests run on whatever core the scheduler gives them, +/// alongside every other test in the binary. Contention, page faults and +/// frequency scaling can only make a sample *slower*, so the minimum is the +/// closest one to the work being measured. A single sample under a tight +/// budget is a coin flip — a lesson `compiling_many_functions_stays_linear` +/// taught by failing once inside `cargo test --workspace` and passing five +/// times on its own. +/// +/// **Meaning.** With the noise gone the budget can be *tight*, and it has to +/// be: these six assertions were 67x to **2381x** above what they measure +/// (`semantic_tokens(example workspace main)` took 21µs against a 50ms limit). +/// A budget three orders of magnitude above the measurement cannot fail, so it +/// says nothing — a ten-fold LSP slowdown, which is the difference between an +/// editor that feels instant and one that does not, passed every one of them. +/// Each `max` below is ~10x the slowest observed minimum on 2026-08-01, so a +/// 10x regression is caught and ordinary machine-to-machine variation is not. +fn fastest_of_five(mut work: impl FnMut() -> Duration) -> Duration { + (0..5).map(|_| work()).min().expect("five samples") +} + fn collect_lk_files(dir: &Path, out: &mut Vec) { for entry in fs::read_dir(dir).expect("read directory") { let entry = entry.expect("read directory entry"); @@ -30,19 +77,25 @@ fn collect_lk_files(dir: &Path, out: &mut Vec) { } #[test] +#[ignore = "wall-clock budget: run alone (the latency job), not inside `cargo test --workspace`"] fn test_analyze_small_expression_latency() { - let mut analyzer = LkAnalyzer::new(); let src = "req.user.role == 'admin' && req.user.id > 0"; - let start = Instant::now(); - let _res = analyzer.analyze(src); - let elapsed = start.elapsed(); + // A fresh analyzer per sample: reusing one would measure whatever it + // cached, and the cold path is the one a keystroke hits. + let elapsed = fastest_of_five(|| { + let mut analyzer = LkAnalyzer::new(); + let start = Instant::now(); + let _res = analyzer.analyze(src); + start.elapsed() + }); - // Debug builds vary; keep threshold generous but meaningful - assert_under("analyze(small expr)", elapsed, Duration::from_millis(10)); + // Observed 0.12ms (debug, 2026-08-01). + assert_under("analyze(small expr)", elapsed, Duration::from_micros(1_500)); } #[test] +#[ignore = "wall-clock budget: run alone (the latency job), not inside `cargo test --workspace`"] fn test_analyze_complex_program_latency() { let mut analyzer = LkAnalyzer::new(); let program = r#" @@ -81,10 +134,12 @@ fn test_analyze_complex_program_latency() { let elapsed = start.elapsed(); // Keep threshold generous for debug builds - assert_under("analyze(complex program)", elapsed, Duration::from_millis(100)); + // Observed 0.94ms (debug, 2026-08-01). + assert_under("analyze(complex program)", elapsed, Duration::from_millis(10)); } #[test] +#[ignore = "wall-clock budget: run alone (the latency job), not inside `cargo test --workspace`"] fn test_semantic_tokens_large_document_latency() { let analyzer = LkAnalyzer::new(); // Generate a moderately large document (~1000 lines) @@ -96,16 +151,20 @@ fn test_semantic_tokens_large_document_latency() { doc.push_str("if (x >= 2 && x <= 10) { return x }\n"); } - let start = Instant::now(); let tokens = analyzer.generate_semantic_tokens(&doc); - let elapsed = start.elapsed(); - - // Ensure we produced some tokens and kept time under a relaxed budget assert!(!tokens.is_empty(), "semantic tokens should not be empty"); - assert_under("semantic_tokens(large doc)", elapsed, Duration::from_millis(1500)); + let elapsed = fastest_of_five(|| { + let start = Instant::now(); + analyzer.generate_semantic_tokens(&doc); + start.elapsed() + }); + + // Observed 3.3ms (debug, 2026-08-01). + assert_under("semantic_tokens(large doc)", elapsed, Duration::from_millis(35)); } #[test] +#[ignore = "wall-clock budget: run alone (the latency job), not inside `cargo test --workspace`"] fn test_analyze_example_workspace_main_latency() { let root = repo_root().join("examples/lk-example-workspace"); let app_src = root.join("apps/demo/src"); @@ -114,40 +173,51 @@ fn test_analyze_example_workspace_main_latency() { let mut analyzer = LkAnalyzer::new(); analyzer.set_base_dir(app_src); - let start = Instant::now(); let res = analyzer.analyze(&src); - let elapsed = start.elapsed(); + let elapsed = fastest_of_five(|| { + let start = Instant::now(); + analyzer.analyze(&src); + start.elapsed() + }); let messages: Vec<&str> = res.diagnostics.iter().map(|diag| diag.message.as_str()).collect(); assert!( !messages.iter().any(|msg| msg.contains("Unknown module")), "example workspace imports should resolve; diagnostics: {messages:?}" ); - assert_under("analyze(example workspace main)", elapsed, Duration::from_millis(100)); + // Observed 1.0ms (debug, 2026-08-01). + assert_under("analyze(example workspace main)", elapsed, Duration::from_millis(12)); } #[test] +#[ignore = "wall-clock budget: run alone (the latency job), not inside `cargo test --workspace`"] fn test_semantic_tokens_example_workspace_latency() { let main_path = repo_root().join("examples/lk-example-workspace/apps/demo/src/main.lk"); let src = fs::read_to_string(&main_path).expect("read example workspace main.lk"); let analyzer = LkAnalyzer::new(); - let start = Instant::now(); let tokens = analyzer.generate_semantic_tokens(&src); - let elapsed = start.elapsed(); - assert!( !tokens.is_empty(), "example workspace semantic tokens should not be empty" ); + let elapsed = fastest_of_five(|| { + let start = Instant::now(); + analyzer.generate_semantic_tokens(&src); + start.elapsed() + }); + + // Observed 16µs (debug, 2026-08-01). The floor is 1ms rather than 10x that: + // below it the timer's own granularity is a visible part of the number. assert_under( "semantic_tokens(example workspace main)", elapsed, - Duration::from_millis(50), + Duration::from_millis(1), ); } #[test] +#[ignore = "wall-clock budget: run alone (the latency job), not inside `cargo test --workspace`"] fn test_semantic_tokens_example_workspace_all_files_are_valid_and_fast() { let root = repo_root().join("examples/lk-example-workspace"); let mut files = Vec::new(); @@ -156,11 +226,15 @@ fn test_semantic_tokens_example_workspace_all_files_are_valid_and_fast() { assert!(!files.is_empty(), "example workspace should contain .lk files"); let analyzer = LkAnalyzer::new(); - let start = Instant::now(); - for file in &files { - let src = fs::read_to_string(file).expect("read example workspace lk file"); - let tokens = analyzer.generate_semantic_tokens(&src); - let summary = analyzer.validate_semantic_tokens(&src, &tokens); + // Read once: the budget is for the analyzer, and leaving the file reads + // inside it would have measured the page cache. + let sources: Vec = files + .iter() + .map(|file| fs::read_to_string(file).expect("read example workspace lk file")) + .collect(); + for (file, src) in files.iter().zip(&sources) { + let tokens = analyzer.generate_semantic_tokens(src); + let summary = analyzer.validate_semantic_tokens(src, &tokens); assert!( summary.valid, "invalid semantic tokens for {}: {:?}", @@ -168,11 +242,19 @@ fn test_semantic_tokens_example_workspace_all_files_are_valid_and_fast() { summary.errors ); } - let elapsed = start.elapsed(); + let elapsed = fastest_of_five(|| { + let start = Instant::now(); + for src in &sources { + analyzer.generate_semantic_tokens(src); + } + start.elapsed() + }); + + // Observed 56µs (debug, 2026-08-01). assert_under( "semantic_tokens(example workspace all files)", elapsed, - Duration::from_millis(100), + Duration::from_millis(1), ); } diff --git a/lsp/tests/stdlib_completion_test.rs b/lsp/tests/stdlib_completion_test.rs index dec16020..be8ae20e 100644 --- a/lsp/tests/stdlib_completion_test.rs +++ b/lsp/tests/stdlib_completion_test.rs @@ -5,7 +5,7 @@ fn test_stdlib_modules_listed() { let analyzer = &mut LkAnalyzer::new(); let modules = analyzer.list_stdlib_modules(); // Ensure key stdlib modules are registered - for m in ["math", "string", "datetime", "os", "io", "net", "bytes", "slice"] { + for m in ["math", "string", "datetime", "os", "io", "net", "bytes"] { assert!(modules.contains(&m.to_string()), "missing module: {}", m); } } diff --git a/lsp/tests/type_diagnostic_test.rs b/lsp/tests/type_diagnostic_test.rs index 063bfb6c..c62d36c6 100644 --- a/lsp/tests/type_diagnostic_test.rs +++ b/lsp/tests/type_diagnostic_test.rs @@ -15,7 +15,7 @@ fn reports_numeric_operand_diagnostic() { .any(|d| d.severity == Some(DiagnosticSeverity::ERROR))); let messages: Vec<&str> = analysis.diagnostics.iter().map(|d| d.message.as_str()).collect(); assert!( - messages.iter().any(|m| m.contains("must by numeric types")), + messages.iter().any(|m| m.contains("must be numeric types")), "expected numeric diagnostic in {:?}", messages ); diff --git a/scripts/aot_coverage.sh b/scripts/aot_coverage.sh index 8ca3fbd8..55e7954d 100755 --- a/scripts/aot_coverage.sh +++ b/scripts/aot_coverage.sh @@ -2,7 +2,7 @@ # AOT native-lowering coverage scan (M4.2): tries a native `lk compile` on every # example and tallies the Unsupported reasons, so "deep coverage" work stays # data-driven. Usage: -# cargo build -p lk-cli --features aot && bash scripts/aot_coverage.sh +# bash scripts/aot_coverage.sh # builds the compiler it scans with # Output: per-file OK/FAIL lines on stdout, reason ranking on stderr. # # Gate mode (used by CI): `AOT_COVERAGE_REQUIRE_FULL=1` makes the script exit @@ -13,6 +13,16 @@ # must be listed explicitly in `AOT_COVERAGE_ALLOW` (comma-separated paths), # never dropped silently. set -u +# The compiler under test is built here rather than assumed. A *missing* binary +# is loud — every compile fails and the count goes to zero — but a *stale* one +# is silent: it reports full coverage for a compiler that never contained the +# change being scanned, which is exactly how a fix once got credit for lowering +# it had not done. `cargo build` is a no-op when nothing changed, so the only +# cost is honesty. An explicit `LK_BIN` is used verbatim: that names a specific +# binary, and whether it matches the tree is the caller's business. +if [ -z "${LK_BIN:-}" ]; then + cargo build -p lk-cli --features aot || exit 1 +fi LK_BIN="${LK_BIN:-./target/debug/lk}" REQUIRE_FULL="${AOT_COVERAGE_REQUIRE_FULL:-0}" ALLOW="${AOT_COVERAGE_ALLOW:-}" @@ -31,7 +41,11 @@ tmp_bin="$(mktemp)" trap 'rm -f "$reasons_file" "$tmp_bin"' EXIT stale_allow="" -for f in examples/syntax/*.lk examples/stdlib/*.lk examples/general/*.lk; do +# The bench corpus belongs in the scan for a reason of its own: the bench script +# compiles it with a plain `lk compile`, which happily falls back. A workload +# that stopped lowering would be measured as "AOT" while running the VM bundle — +# the perf numbers would be wrong and nothing would say so. +for f in examples/syntax/*.lk examples/stdlib/*.lk examples/general/*.lk bench/workloads_business_algorithms.lk; do total=$((total + 1)) out=$("$LK_BIN" compile "$f" --output "$tmp_bin" 2>&1) if [ $? -eq 0 ]; then diff --git a/scripts/build_lkrt_asan.sh b/scripts/build_lkrt_asan.sh index 67866036..9e7d313d 100755 --- a/scripts/build_lkrt_asan.sh +++ b/scripts/build_lkrt_asan.sh @@ -47,13 +47,12 @@ fi # once. (No sanitizer flags here — instrumenting `lkrt` is the point; partial # instrumentation is fine, mismatched toolchains are not.) cargo +nightly build \ - -p lk-api \ - --features ffi \ + -p lk-api-cabi \ --release \ --target "$TARGET" \ --target-dir "$TARGET_DIR" 1>&2 -API_LIB="$TARGET_DIR/$TARGET/release/liblk_api.a" +API_LIB="$TARGET_DIR/$TARGET/release/liblk_api_cabi.a" if [ ! -f "$API_LIB" ]; then echo "error: expected $API_LIB after the build" >&2 exit 1 diff --git a/scripts/debug-vscode-lsp.sh b/scripts/debug-vscode-lsp.sh index 2ea19a25..7453598e 100755 --- a/scripts/debug-vscode-lsp.sh +++ b/scripts/debug-vscode-lsp.sh @@ -7,32 +7,19 @@ EXAMPLES="$ROOT/examples/lk-example-workspace" SERVER="$ROOT/target/debug/lk-lsp" USER_DATA_DIR="${LK_VSCODE_USER_DATA_DIR:-$(mktemp -d "${TMPDIR:-/tmp}/lk-vscode-lsp.XXXXXX")}" -find_code_bin() { - if [[ -n "${CODE_BIN:-}" ]]; then - printf '%s\n' "$CODE_BIN" - return - fi +# shellcheck source=lib/vscode_cli.sh +. "$ROOT/scripts/lib/vscode_cli.sh" - if command -v code >/dev/null 2>&1; then - command -v code +find_code_bin() { + local found + # launchable: this opens an Extension Development Host window, which the + # server-side code-server CLI cannot do. + found="$(lk_vscode_cli launchable)" + if [[ -n "$found" ]]; then + printf '%s\n' "$found" return fi - local candidates=( - "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" - "$HOME/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" - "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code" - "$HOME/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code" - ) - - local candidate - for candidate in "${candidates[@]}"; do - if [[ -x "$candidate" ]]; then - printf '%s\n' "$candidate" - return - fi - done - echo "error: VS Code CLI 'code' not found" >&2 echo "Set CODE_BIN to the VS Code CLI path, for example:" >&2 echo " CODE_BIN=\"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code\" make debug-lsp-ext" >&2 diff --git a/scripts/install_vsix.sh b/scripts/install_vsix.sh new file mode 100755 index 00000000..d781102b --- /dev/null +++ b/scripts/install_vsix.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Install a packaged VSIX into every VS Code-family editor found on this +# machine (see scripts/lib/vscode_cli.sh for how they are found). +# +# Usage: scripts/install_vsix.sh [PATH_TO_VSIX] +# Defaults to the newest VSIX under ecosystem/vsc-ext/lsp. +# +# Environment: +# VSCODE_CLI / CODE_BIN install with this CLI only +# LK_VSIX_TIMEOUT per-attempt timeout in seconds (default 180) +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck source=lib/vscode_cli.sh +. "$ROOT/scripts/lib/vscode_cli.sh" + +vsix="${1:-}" +if [ -z "$vsix" ]; then + vsix="$(ls -t "$ROOT"/ecosystem/vsc-ext/lsp/*.vsix 2>/dev/null | head -n 1 || true)" +fi +if [ -z "$vsix" ] || [ ! -f "$vsix" ]; then + echo "install_vsix: no VSIX found; run 'make vsix' first" >&2 + exit 1 +fi +# VS Code's CLI resolves a relative path against its own cwd, not ours. +case "$vsix" in + /*) ;; + *) vsix="$(cd "$(dirname "$vsix")" && pwd)/$(basename "$vsix")" ;; +esac + +run_cli() { + if command -v timeout >/dev/null 2>&1; then + timeout "${LK_VSIX_TIMEOUT:-180}" "$@" + else + "$@" + fi +} + +candidates="$(lk_vscode_cli_candidates)" +if [ -z "$candidates" ]; then + cat >&2 < Extensions > ... > Install from VSIX... > + $vsix +Or point the installer at a CLI: + make install-vsix VSCODE_CLI=/path/to/code +EOF + exit 1 +fi + +products="$(printf '%s\n' "$candidates" | cut -f1 | awk '!seen[$0]++')" +installed=() +failed=() + +for product in $products; do + ok=0 + while IFS=$'\t' read -r cand_product cand_path; do + [ "$cand_product" = "$product" ] || continue + echo "==> $product: $cand_path" + # --force so an already-installed same-version extension is replaced + # instead of refused. + if run_cli "$cand_path" --install-extension "$vsix" --force; then + installed+=("$product ($cand_path)") + ok=1 + break + fi + echo " failed, trying the next candidate for $product" >&2 + done <<<"$candidates" + [ "$ok" = 1 ] || failed+=("$product") +done + +echo +[ ${#installed[@]} -eq 0 ] || printf 'installed: %s\n' "${installed[@]}" +[ ${#failed[@]} -eq 0 ] || printf 'not installed: %s (no working CLI; install the VSIX from its UI)\n' "${failed[@]}" >&2 + +if [ ${#installed[@]} -eq 0 ]; then + echo "install_vsix: every candidate failed for $vsix" >&2 + exit 1 +fi diff --git a/scripts/install_zed_ext.sh b/scripts/install_zed_ext.sh new file mode 100755 index 00000000..15049162 --- /dev/null +++ b/scripts/install_zed_ext.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Best-effort Zed extension step of `make install`. +# +# Zed has no CLI for installing an extension from a directory — dev extensions +# are loaded from the UI, and Zed builds the wasm and the grammar itself. So +# this script cannot install anything; what it can do is detect Zed, check the +# two things that make the load fail silently later (a missing lk-lsp, a +# placeholder grammar commit), and print the exact path to load. It never fails +# the build: a machine without Zed is not a broken install. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +EXT_DIR="$ROOT/ecosystem/zed-ext" + +find_zed() { + local candidate + if command -v zed >/dev/null 2>&1; then + command -v zed + return + fi + for candidate in \ + "/Applications/Zed.app/Contents/MacOS/cli" \ + "$HOME/Applications/Zed.app/Contents/MacOS/cli" \ + "$HOME/.local/bin/zed" \ + "/usr/bin/zed" \ + "/usr/local/bin/zed" \ + "/var/lib/flatpak/exports/bin/dev.zed.Zed" \ + "$HOME/.local/share/flatpak/exports/bin/dev.zed.Zed"; do + if [ -x "$candidate" ]; then + printf '%s\n' "$candidate" + return + fi + done +} + +zed_bin="$(find_zed || true)" +if [ -z "$zed_bin" ]; then + echo "zed: not detected, skipping the Zed extension" + exit 0 +fi + +echo "zed: found $zed_bin" + +commit="$(sed -n 's/^commit = "\(.*\)"/\1/p' "$EXT_DIR/extension.toml" | head -n 1)" +if ! printf '%s' "$commit" | grep -qE '^[0-9a-f]{40}$'; then + echo "zed: WARNING grammar commit in extension.toml is '$commit', not a published SHA." + echo "zed: Zed clones that commit to build the grammar, so syntax highlighting" + echo "zed: will fail to build until 'make zed-ext-release-check' passes." +fi + +cat < "zed: install dev extension" +zed: 3. Choose $EXT_DIR +zed: The extension finds lk-lsp on PATH / in ~/.cargo/bin, which 'make install-lsp' just populated. +EOF diff --git a/scripts/lib/vscode_cli.sh b/scripts/lib/vscode_cli.sh new file mode 100644 index 00000000..0054153d --- /dev/null +++ b/scripts/lib/vscode_cli.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash +# Shared discovery of VS Code-family CLIs, used by install_vsix.sh and +# debug-vscode-lsp.sh. +# +# Two things make this more than `command -v code`: +# +# 1. Remote windows. Under WSL / SSH remote / devcontainers the extension has +# to be installed on the *remote* side (that is where lk-lsp runs), so the +# remote server's own CLI is preferred over anything on PATH. VS Code's +# integrated terminal puts `remote-cli/code` on PATH, but a plain WSL shell +# does not, and `remote-cli/code` only works while a window is attached +# (it talks over $VSCODE_IPC_HOOK_CLI). The server ships a second CLI — +# `server/bin/code-server` — that installs offline into +# ~/.vscode-server/extensions with no window at all, so both are emitted +# and the caller tries them in order. +# 2. Forks and channels. VS Code, Insiders, VSCodium, Cursor and Windsurf are +# separate installs with separate extension directories; "install +# everywhere" means one install per product, not one install total. +# +# Entry point: lk_vscode_cli_candidates, which prints "productpath" lines, +# best candidate first, deduplicated by path. + +lk_vscode_os() { + if [ -z "${_LK_VSCODE_OS:-}" ]; then + case "$(uname -s)" in + Darwin) _LK_VSCODE_OS=macos ;; + Linux) _LK_VSCODE_OS=linux ;; + MINGW* | MSYS* | CYGWIN*) _LK_VSCODE_OS=windows ;; + *) _LK_VSCODE_OS=unknown ;; + esac + fi + printf '%s\n' "$_LK_VSCODE_OS" +} + +# _lk_vscode_emit PRODUCT PATH — print the candidate if it looks runnable. +_lk_vscode_emit() { + local product="$1" path="$2" + [ -n "$path" ] || return 0 + if [ -x "$path" ] || { [ "$(lk_vscode_os)" = windows ] && [ -f "$path" ]; }; then + printf '%s\t%s\n' "$product" "$path" + fi +} + +# Remote server installs: ~/.vscode-server & friends. +_lk_vscode_remote_candidates() { + local entry root product bin_dir cli + for entry in \ + "$HOME/.vscode-server:vscode" \ + "$HOME/.vscode-server-insiders:vscode-insiders" \ + "$HOME/.vscodium-server:vscodium" \ + "$HOME/.cursor-server:cursor" \ + "$HOME/.windsurf-server:windsurf"; do + root="${entry%:*}" + product="${entry##*:}" + [ -d "$root" ] || continue + # Newest server build first; both the current (cli/servers/*) and the older + # (bin/*) layouts. Capped so a long-lived machine with a dozen stale server + # versions does not turn one failure into a dozen timeouts. + while IFS= read -r bin_dir; do + [ -d "$bin_dir" ] || continue + local remote_cli='' server_cli='' + for cli in "$bin_dir"/remote-cli/*; do + if [ -x "$cli" ]; then + remote_cli="$cli" + break + fi + done + # code-server can install extensions offline but cannot open a window, so + # it is not a candidate for launch-mode callers. + if [ "${_lk_vscode_launch_only:-0}" != 1 ] && [ -x "$bin_dir/code-server" ]; then + server_cli="$bin_dir/code-server" + fi + # remote-cli needs an attached window; prefer it only when one is there. + if [ -n "${VSCODE_IPC_HOOK_CLI:-}" ]; then + _lk_vscode_emit "$product" "$remote_cli" + _lk_vscode_emit "$product" "$server_cli" + else + _lk_vscode_emit "$product" "$server_cli" + _lk_vscode_emit "$product" "$remote_cli" + fi + done < <( + { + ls -1dt "$root"/cli/servers/*/server/bin 2>/dev/null + ls -1dt "$root"/bin/*/bin 2>/dev/null + } | head -n 4 + ) + done +} + +_lk_vscode_path_candidates() { + local entry name product found + for entry in \ + "code:vscode" \ + "code-insiders:vscode-insiders" \ + "codium:vscodium" \ + "vscodium:vscodium" \ + "code-oss:code-oss" \ + "cursor:cursor" \ + "windsurf:windsurf"; do + name="${entry%:*}" + product="${entry##*:}" + found="$(command -v "$name" 2>/dev/null)" || continue + _lk_vscode_emit "$product" "$found" + done +} + +_lk_vscode_macos_candidates() { + local dir entry app product + for dir in "/Applications" "$HOME/Applications"; do + for entry in \ + "Visual Studio Code.app/Contents/Resources/app/bin/code:vscode" \ + "Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code:vscode-insiders" \ + "Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders:vscode-insiders" \ + "VSCodium.app/Contents/Resources/app/bin/codium:vscodium" \ + "Cursor.app/Contents/Resources/app/bin/cursor:cursor" \ + "Windsurf.app/Contents/Resources/app/bin/windsurf:windsurf"; do + app="${entry%:*}" + product="${entry##*:}" + _lk_vscode_emit "$product" "$dir/$app" + done + done +} + +_lk_vscode_linux_candidates() { + local entry path product + for entry in \ + "/usr/share/code/bin/code:vscode" \ + "/usr/lib/code/bin/code:vscode" \ + "/opt/visual-studio-code/bin/code:vscode" \ + "/snap/bin/code:vscode" \ + "/var/lib/flatpak/exports/bin/com.visualstudio.code:vscode" \ + "$HOME/.local/share/flatpak/exports/bin/com.visualstudio.code:vscode" \ + "/usr/share/code-insiders/bin/code-insiders:vscode-insiders" \ + "/snap/bin/code-insiders:vscode-insiders" \ + "/usr/share/codium/bin/codium:vscodium" \ + "/opt/vscodium-bin/bin/codium:vscodium" \ + "/var/lib/flatpak/exports/bin/com.vscodium.codium:vscodium" \ + "$HOME/.local/share/flatpak/exports/bin/com.vscodium.codium:vscodium" \ + "/usr/share/code-oss/bin/code-oss:code-oss" \ + "/usr/lib/code-oss/bin/code-oss:code-oss" \ + "/opt/cursor/bin/cursor:cursor" \ + "/usr/share/cursor/bin/cursor:cursor" \ + "/opt/windsurf/bin/windsurf:windsurf"; do + path="${entry%:*}" + product="${entry##*:}" + _lk_vscode_emit "$product" "$path" + done +} + +_lk_vscode_windows_candidates() { + local roots=() root entry rel product + # In Git Bash/MSYS these are Windows paths (C:\Users\...); cygpath makes them + # usable from the shell, and bash can execute .cmd wrappers directly. + for root in "${LOCALAPPDATA:-}" "${ProgramFiles:-}" "${ProgramW6432:-}" "${PROGRAMFILES:-}"; do + [ -n "$root" ] || continue + if command -v cygpath >/dev/null 2>&1; then + root="$(cygpath -u "$root" 2>/dev/null || printf '%s' "$root")" + fi + roots+=("$root") + done + roots+=("/c/Program Files" "/c/Program Files (x86)") + for root in "${roots[@]}"; do + [ -d "$root" ] || continue + for entry in \ + "Programs/Microsoft VS Code/bin/code.cmd:vscode" \ + "Microsoft VS Code/bin/code.cmd:vscode" \ + "Programs/Microsoft VS Code Insiders/bin/code-insiders.cmd:vscode-insiders" \ + "Microsoft VS Code Insiders/bin/code-insiders.cmd:vscode-insiders" \ + "Programs/VSCodium/bin/codium.cmd:vscodium" \ + "VSCodium/bin/codium.cmd:vscodium" \ + "Programs/cursor/resources/app/bin/cursor.cmd:cursor" \ + "Programs/Windsurf/bin/windsurf.cmd:windsurf"; do + rel="${entry%:*}" + product="${entry##*:}" + _lk_vscode_emit "$product" "$root/$rel" + done + done +} + +# lk_vscode_cli_candidates [launchable] +# Prints "productpath", best first, deduplicated by path. Pass "launchable" +# to exclude CLIs that can install extensions but cannot open a window. +lk_vscode_cli_candidates() { + local _lk_vscode_launch_only=0 + [ "${1:-}" = launchable ] && _lk_vscode_launch_only=1 + { + # An explicit override is the whole answer: do not fan out to other editors + # when the caller named one. + local override="${VSCODE_CLI:-${CODE_BIN:-}}" + if [ -n "$override" ]; then + if [ -x "$override" ] || command -v "$override" >/dev/null 2>&1; then + printf '%s\t%s\n' "override" "$override" + else + printf 'lk: VSCODE_CLI/CODE_BIN is set to %s, which is not executable\n' "$override" >&2 + fi + else + _lk_vscode_remote_candidates + _lk_vscode_path_candidates + case "$(lk_vscode_os)" in + macos) _lk_vscode_macos_candidates ;; + linux) _lk_vscode_linux_candidates ;; + windows) _lk_vscode_windows_candidates ;; + esac + fi + } | awk -F'\t' '!seen[$2]++' +} + +# First candidate only — for callers that just need one CLI (debug host). +lk_vscode_cli() { + lk_vscode_cli_candidates "${1:-}" | head -n 1 | cut -f2 +} diff --git a/scripts/prune_target.sh b/scripts/prune_target.sh new file mode 100755 index 00000000..4c79de90 --- /dev/null +++ b/scripts/prune_target.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Reclaim `target/`. Cargo has no stable garbage collector: every time a crate's +# fingerprint changes it writes a *new* artifact into `target//deps` +# and leaves the old one there forever. This workspace measured 190GB of +# `target/debug`, of which 41 generations of one archive were 19GB and 3996 +# incremental session directories were 65GB. +# +# Everything here is safe to delete at any time. Cargo treats a missing output +# as "not built" and rebuilds it; nothing under `target/` is a source of truth. +# The only cost is build time. +# +# scripts/prune_target.sh # incremental dirs + artifacts unused for 14 days +# scripts/prune_target.sh --days 3 # more aggressive age cutoff +# scripts/prune_target.sh --keep-incremental +# scripts/prune_target.sh --dry-run +# +# `cargo clean --gc` does this properly, but it is nightly-only as of 1.90. + +set -euo pipefail + +DAYS=14 +KEEP_INCREMENTAL=0 +DRY_RUN=0 + +while [ $# -gt 0 ]; do + case "$1" in + --days) + DAYS="${2:?--days needs a value}" + shift 2 + ;; + --keep-incremental) + KEEP_INCREMENTAL=1 + shift + ;; + --dry-run | -n) + DRY_RUN=1 + shift + ;; + -h | --help) + sed -n '2,20p' "$0" | sed 's/^# \?//' + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + exit 2 + ;; + esac +done + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +TARGET="${CARGO_TARGET_DIR:-$ROOT/target}" + +if [ ! -d "$TARGET" ]; then + echo "no target directory at $TARGET" + exit 0 +fi + +before=$(du -sk "$TARGET" | cut -f1) + +remove() { + if [ "$DRY_RUN" = 1 ]; then + printf ' would remove %s\n' "$1" + else + rm -rf -- "$1" + fi +} + +# Incremental state is per-session and rebuilt from scratch; the directories are +# not shared between fingerprints, so a stale one is never read again. +if [ "$KEEP_INCREMENTAL" = 0 ]; then + count=0 + while IFS= read -r dir; do + remove "$dir" + count=$((count + 1)) + done < <(find "$TARGET" -maxdepth 3 -type d -name incremental) + echo "incremental: $count director$([ "$count" = 1 ] && echo y || echo ies)" +fi + +# Age-based, not "keep newest N per crate": cargo hashes the *fingerprint*, not +# a version, so two live artifacts of the same crate (different feature sets, +# different targets) are both current. Access time is what distinguishes a +# generation still being linked from one abandoned by a config change — but +# `relatime` only updates atime once a day, so this uses mtime, which for a +# cargo artifact is its build time. +found=0 +while IFS= read -r file; do + remove "$file" + found=$((found + 1)) +done < <(find "$TARGET" -type f \ + \( -name '*.rlib' -o -name '*.rmeta' -o -name '*.a' -o -name '*.so' -o -name '*.dylib' \) \ + -mtime "+$DAYS") +echo "artifacts older than ${DAYS}d: $found" + +if [ "$DRY_RUN" = 1 ]; then + echo "(dry run — nothing removed)" + exit 0 +fi + +after=$(du -sk "$TARGET" | cut -f1) +awk -v b="$before" -v a="$after" \ + 'BEGIN { printf "target/: %.1f GB -> %.1f GB (reclaimed %.1f GB)\n", b/1048576, a/1048576, (b-a)/1048576 }' diff --git a/scripts/verify.sh b/scripts/verify.sh new file mode 100755 index 00000000..64c59ba7 --- /dev/null +++ b/scripts/verify.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +# +# Runs the gates and answers with an exit code. +# +# The reason this exists: every gate here already exits non-zero when it fails, +# and every one of them also prints something. Reading the printout is not the +# same as reading the status — a test target that fails to *compile* prints +# `error[E0308]` and no failure keyword at all, so a filter looking for +# "FAILED" or "failures:" sees an empty result and reads as green. That +# happened, for four rounds, and every "all green" reported in them was false. +# +# So: each gate runs, its status is captured, and the summary at the end is the +# statuses. Nothing here parses output to decide anything. +# +# Usage: +# scripts/verify.sh # the gates a change has to pass +# scripts/verify.sh --fast # skips the slow ones (gc_stress, fuzz, perf, …) +# scripts/verify.sh --list # names the gates and exits +# +# Not covered here, because they need a toolchain or emulator this script +# cannot assume: the QEMU bare-metal smokes (thumbv7em, aarch64), the wasm32 +# playground build, the Zed extension check, miri, and the ASan/UBSan +# differential runs. `.github/workflows/` is the full set. +# +# The AOT gates need the `aot` feature, which is on by default for lk-cli. + +set -u -o pipefail + +FAST=0 +for arg in "$@"; do + case "$arg" in + --fast) FAST=1 ;; + --list) + printf '%s\n' fmt lk_fmt artifacts clippy tests coverage sweep no_std gc_stress verify_fuzz sweep_hybrid fuzz perf + exit 0 + ;; + *) + echo "unknown argument: $arg" >&2 + exit 2 + ;; + esac +done + +cd "$(dirname "$0")/.." || exit 2 + +NAMES=() +STATUSES=() + +# Runs one gate, keeping its status. Output goes to the terminal as it happens — +# a gate that hangs should be visible, not buffered. +gate() { + local name="$1" + shift + echo + echo "=== $name" + "$@" + local status=$? + NAMES+=("$name") + STATUSES+=("$status") + if [ "$status" -ne 0 ]; then + echo "=== $name FAILED (exit $status)" + fi + return 0 +} + +gate fmt cargo fmt --check + +# `lk fmt --check` over every `.lk` in the repo. It shipped as a CI feature +# with no workflow running it, and 36 of 97 files were then not in the shape +# the tool produces — including the ones it is demonstrated on. Needs the +# binary, so it builds one first. +lk_fmt_shape() { + cargo build -p lk-cli || return 1 + ./target/debug/lk fmt --check +} +gate lk_fmt lk_fmt_shape + +# `lk compile foo.lk` writes `foo` — extensionless, so no suffix pattern in +# `.gitignore` reaches it and `git add -A` after a compile takes it. Two 20MB +# binaries reached `main` that way. This catches a `git add -f` past the rule. +no_tracked_artifacts() { + local found + found=$(git ls-files examples bench | grep -v '\.' || true) + if [ -n "$found" ]; then + echo "tracked files with no extension under examples/ or bench/ — build artifacts?" >&2 + echo "$found" >&2 + return 1 + fi + return 0 +} +gate artifacts no_tracked_artifacts + +# `--all-targets`, like CI: without it clippy never lints test code, which is +# most of the code added in a normal change. +gate clippy cargo clippy --workspace --all-targets --all-features -- -D warnings +gate tests cargo test --workspace --all-features +gate coverage env AOT_COVERAGE_REQUIRE_FULL=1 bash scripts/aot_coverage.sh +gate sweep bash scripts/vm_native_sweep.sh + +no_std_targets() { + local failed=0 + for crate in lk-core lk-values lkrt; do + if ! cargo build -p "$crate" --target thumbv7em-none-eabi --no-default-features; then + echo "no_std build failed: $crate" >&2 + failed=1 + fi + done + return "$failed" +} +gate no_std no_std_targets + +if [ "$FAST" -eq 0 ]; then + # Every GC safepoint collects, so a value the host holds without rooting it + # is freed under the holder. The failure it catches is a wrong answer, not a + # crash — `json_process.lk` returning the wrong thing is what found it. + gate gc_stress env LK_GC_STRESS=1 cargo test -p lk-core -p lk-stdlib -p lk-cli + # The artifact decoder against random bytes: a `.lkm` is an untrusted input + # to `lk FILE.lkm`, and the verifier is what stands between a corrupt one + # and the executor. + gate verify_fuzz env LK_FUZZ_CASES=20000 cargo test -p lk-core verify_fuzz + # The *shipping* configuration. Every other AOT gate pins `LK_AOT_HYBRID=0` + # — the pure-native measurement is what they are for — so until this existed + # nothing swept the arrangement a user gets by default: hybrid on, fallback + # allowed. Slow for the same reason the pure pass is (a link per program), + # which is why it sits with the fuzz and the perf run rather than in + # `--fast`. + gate sweep_hybrid bash scripts/vm_native_sweep.sh --hybrid + # The generative differential fuzz is not part of `cargo test --workspace` + # (its own CI workflow runs it), and it is the only gate that *combines* + # features. 300 cases is the floor the native-lowering count is stable at. + gate fuzz env LK_FUZZ_CASES=300 LK_FUZZ_SEED=4242 \ + cargo test -p lk-cli --test aot_fuzz_differential_test + # Performance is a hard PR gate. This runs it; reading the geometric mean is + # still the human's job, because "regressed" is a comparison against a base + # this script does not have. + gate perf env RUN_AOT=0 RUNS=3 EXTRA_RUNS=5 BENCH_PROGRESS=0 BENCH_TIMEOUT=60 \ + bash bench/run_workload_bench.sh +fi + +echo +echo "=== summary" +failed=0 +for index in "${!NAMES[@]}"; do + status="${STATUSES[$index]}" + if [ "$status" -eq 0 ]; then + printf ' ok %s\n' "${NAMES[$index]}" + else + printf ' FAIL %s (exit %s)\n' "${NAMES[$index]}" "$status" + failed=1 + fi +done +exit "$failed" diff --git a/scripts/vm_native_sweep.sh b/scripts/vm_native_sweep.sh new file mode 100755 index 00000000..f94ed606 --- /dev/null +++ b/scripts/vm_native_sweep.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# Run every example and bench program under both executors and compare stdout. +# +# What only this catches: a program that compiles *and* answers differently. +# `scripts/aot_coverage.sh` measures whether a program lowers natively and says +# nothing about the answer; the differential test suites compare a pinned corpus +# of small cases. Between them sits "lowers fine, wrong answer, and no case in +# the corpus has that shape" — which is where the `Bytes` global miscompile and +# the typed-list rebuild both lived. +# +# This lived in `/tmp` as a hand-written loop for months, which meant it was +# re-typed from memory after every tmpfs sweep and its expected counts lived in +# a commit message. It is a gate; it belongs in the repo. +# +# bash scripts/vm_native_sweep.sh # compare, print a summary +# SWEEP_REQUIRE="identical=78 diverged=1" … # fail unless the counts match +# +# Today: identical=78 diverged=1 fallback=0 over 79 programs. +# +# One divergence is expected today: `bench/workloads_business_algorithms.lk` is +# nondeterministic (it prints timings), so it differs run to run under either +# executor. `SWEEP_ALLOW_DIVERGED` names the files allowed to differ. +set -uo pipefail + +cd "$(dirname "$0")/.." +LK=${LK_BIN:-./target/debug/lk} +ALLOW=${SWEEP_ALLOW_DIVERGED:-bench/workloads_business_algorithms.lk} +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +if [ ! -x "$LK" ]; then + echo "no $LK — build it with \`cargo build -p lk-cli --features aot\`" >&2 + exit 1 +fi + +# `--hybrid` sweeps the default configuration instead of the pure-native one. +HYBRID=0 +for arg in "$@"; do + case "$arg" in + --hybrid) HYBRID=1 ;; + esac +done + +identical=0 +diverged=0 +fallback=0 +diverged_files="" + +# `examples/_references` holds *other languages'* sources, so it is not a set of +# LK programs at all. Nothing else is excluded by hand: a program that cannot be +# compiled counts as `fallback`, which is a number worth watching rather than a +# name worth maintaining. The workspace example's app is the current one — its +# package imports do not lower, and it is multi-file so the Tier 0 bundle +# refuses it too. +for src in $(git ls-files 'examples/**/*.lk' 'bench/*.lk' | + grep -v '^examples/_references/' | sort); do + vm_out=$("$LK" "$src" 2>&1) + out_bin="$WORK/$(echo "$src" | tr / _)" + # `LK_AOT_NO_FALLBACK=1` on the *compile*: without it a program that does + # not lower falls back to the Tier 0 bundle, which embeds the interpreter — + # comparing that against the VM compares the VM with itself, and it costs a + # full Rust link per file to learn nothing. + if [ "$HYBRID" = 1 ]; then + # The *shipping* configuration: hybrid on, fallback allowed. Every other + # gate pins hybrid off, so nothing swept the arrangement a user actually + # gets — a program whose helper rides the bridge, or that fell back to + # the Tier 0 bundle, was compared against the VM by nothing at all. + # + # A Tier 0 bundle *is* the interpreter, so agreeing proves little there; + # the value is in the bridged programs, and the two cannot be told apart + # from outside without reading the build log. + if ! LK_AOT_HYBRID=1 "$LK" compile "$src" --output "$out_bin" >/dev/null 2>&1; then + fallback=$((fallback + 1)) + echo "COMPILE-FAILED $src" + continue + fi + native_out=$(cd "$(dirname "$src")" && "$out_bin" 2>&1) + if [ "$vm_out" = "$native_out" ]; then + identical=$((identical + 1)) + else + diverged=$((diverged + 1)) + diverged_files="$diverged_files $src" + case " $ALLOW " in + *" $src "*) echo "DIVERGED (allowed) $src" ;; + *) echo "DIVERGED $src" ;; + esac + fi + continue + fi + if ! LK_AOT_NO_FALLBACK=1 "$LK" compile "$src" --output "$out_bin" >/dev/null 2>&1; then + fallback=$((fallback + 1)) + echo "FALLBACK $src" + continue + fi + # Run from the source's directory: a program that reads a relative path + # must find the same files either way. `$out_bin` is absolute (mktemp -d), + # so the `cd` does not reach it. + native_out=$(cd "$(dirname "$src")" && LK_AOT_NO_FALLBACK=1 "$out_bin" 2>&1) + if [ "$vm_out" = "$native_out" ]; then + identical=$((identical + 1)) + else + diverged=$((diverged + 1)) + diverged_files="$diverged_files $src" + case " $ALLOW " in + *" $src "*) echo "DIVERGED (allowed) $src" ;; + *) echo "DIVERGED $src" ;; + esac + fi +done + +echo "identical=$identical diverged=$diverged fallback=$fallback" + +# Unexpected divergence is a failure even when the totals were not pinned: a +# file that is not on the allow list has no business differing. +status=0 +for src in $diverged_files; do + case " $ALLOW " in + *" $src "*) ;; + *) + echo "::error file=$src::stdout differs between the VM and the native build" + status=1 + ;; + esac +done + +if [ -n "${SWEEP_REQUIRE:-}" ]; then + actual="identical=$identical diverged=$diverged" + if [ "$actual" != "$SWEEP_REQUIRE" ]; then + echo "::error::expected \"$SWEEP_REQUIRE\", got \"$actual\"" >&2 + status=1 + fi +fi +exit $status diff --git a/stdlib/Cargo.toml b/stdlib/Cargo.toml index 850be21c..952cce58 100644 --- a/stdlib/Cargo.toml +++ b/stdlib/Cargo.toml @@ -8,7 +8,14 @@ license = "Apache-2.0" [lib] name = "lk_stdlib" -crate-type = ["rlib", "staticlib"] +# `rlib` only. A `staticlib` crate-type is emitted on *every* build of the +# crate, and for this one that archive is ~480MB (the whole dependency graph's +# objects plus debuginfo) — 19GB of stale generations had accumulated under +# `target/debug/deps`. Nothing ever linked it: the AOT driver links +# `liblkrt_cabi.a` and `liblk_api.a`, and both of those come from crates that +# exist to produce them. Same rule as `lkrt-cabi`: a staticlib belongs to a +# crate whose job is the staticlib. +crate-type = ["rlib"] [features] default = [] @@ -33,7 +40,6 @@ lk-stdlib-path = { path = "crates/path" } lk-stdlib-process = { path = "crates/process" } lk-stdlib-random = { path = "crates/random" } lk-stdlib-regex = { path = "crates/regex" } -lk-stdlib-slice = { path = "crates/slice" } lk-stdlib-stream = { path = "crates/stream" } lk-stdlib-string = { path = "crates/string" } lk-stdlib-task = { path = "crates/task" } @@ -49,3 +55,13 @@ tracing = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } toml = { workspace = true } + +[dev-dependencies] +# `RuntimeVal`'s `PartialEq` exists for test code only — see the `testing` +# feature in lk-core. +lk-core = { path = "../core", features = ["testing"] } +# Test-only, for the host-conformance check in `src/host_parity_test.rs`: the +# two alternative hosts are asked what they know, rather than being described by +# a second list that can drift. Neither links into the shipped `stdlib`. +lk-stdlib-bare = { path = "bare" } +lk-stdlib-web = { path = "web" } diff --git a/stdlib/bare/Cargo.toml b/stdlib/bare/Cargo.toml index 4afa1867..d59f5b54 100644 --- a/stdlib/bare/Cargo.toml +++ b/stdlib/bare/Cargo.toml @@ -14,14 +14,13 @@ crate-type = ["rlib"] # Every module is opt-in: on an MCU flash is the scarce resource, so a # board should pay only for what it imports. `default` is the full # no_std-capable set; pick a subset with `default-features = false`. -default = ["bytes", "encoding", "hash", "iter", "math", "slice", "string"] +default = ["bytes", "encoding", "hash", "iter", "math", "string"] bytes = ["dep:lk-stdlib-bytes"] # json/base64/hex only; yaml/toml/url need std. encoding = ["dep:lk-stdlib-encoding"] hash = ["dep:lk-stdlib-hash"] iter = ["dep:lk-stdlib-iter"] math = ["dep:lk-stdlib-math"] -slice = ["dep:lk-stdlib-slice"] string = ["dep:lk-stdlib-string"] [dependencies] @@ -36,5 +35,4 @@ lk-stdlib-encoding = { path = "../crates/encoding", default-features = false, op lk-stdlib-hash = { path = "../crates/hash", default-features = false, optional = true } lk-stdlib-iter = { path = "../crates/iter", default-features = false, optional = true } lk-stdlib-math = { path = "../crates/math", default-features = false, optional = true } -lk-stdlib-slice = { path = "../crates/slice", default-features = false, optional = true } lk-stdlib-string = { path = "../crates/string", default-features = false, optional = true } diff --git a/stdlib/bare/src/lib.rs b/stdlib/bare/src/lib.rs index ef74c8e9..e00ebb29 100644 --- a/stdlib/bare/src/lib.rs +++ b/stdlib/bare/src/lib.rs @@ -17,7 +17,6 @@ extern crate alloc; use alloc::boxed::Box; -use alloc::string::{String, ToString}; use anyhow::{Result, anyhow}; use lk_core::{ @@ -26,7 +25,6 @@ use lk_core::{ val::RuntimeVal, vm::{NativeArgs, NativeEntry, NativeRuntime, RuntimeExport}, }; -use lk_stdlib_common::runtime_native::runtime_display_value; /// Where `print`/`println` go. A plain `fn` pointer rather than a closure so /// the slot is `const`-initialisable and needs no allocation before `main`. @@ -67,15 +65,13 @@ const BARE_MODULES: &[fn(&mut ModuleRegistry) -> Result<()>] = &[ lk_stdlib_iter::register, #[cfg(feature = "math")] lk_stdlib_math::register, - #[cfg(feature = "slice")] - lk_stdlib_slice::register, #[cfg(feature = "string")] lk_stdlib_string::register, ]; /// Modules that exist in LK but cannot be backed by anything on bare metal. /// Kept explicit so the error names the reason rather than the symptom. -const UNSUPPORTED_MODULES: &[&str] = &[ +pub const UNSUPPORTED_MODULES: &[&str] = &[ "chan", "datetime", "env", "fs", "http", "io", "net", "os", "path", "process", "random", "regex", "stream", "task", "time", "uuid", ]; @@ -97,10 +93,56 @@ pub fn register_bare_stdlib_globals(registry: &mut ModuleRegistry) { full_state "panic" => panic, NativeEntry::VARIADIC, full_state "assert" => assert, NativeEntry::VARIADIC, full_state "assert_eq" => assert_eq, NativeEntry::VARIADIC, + full_state "assert_ne" => assert_ne, NativeEntry::VARIADIC, + // `error`, which is what a `catch` catches. + // + // Not a module: a host may leave `fs` out and a program importing it + // is told so, by name. This is a global the language's own error + // handling is written in terms of, and without it every + // `try { error(…) } catch` that `bare-metal-x86`'s interpreter ran + // failed at run time — after the program had been parsed and + // accepted — with a stage code that says only "it raised". + full_state "error" => lk_stdlib_common::language::error, NativeEntry::VARIADIC, + // Present and refusing, rather than absent — see `unavailable`. + full_state "spawn" => spawn, 1, + full_state "chan" => chan, NativeEntry::VARIADIC, + full_state "send" => send, 2, + full_state "recv" => recv, 1, ], ); } +/// The concurrency globals, present and refusing by name. +/// +/// `chan` is already in `UNSUPPORTED_MODULES`, so `use chan` answers "not +/// available on bare metal". `spawn(f)` answered "undefined function `spawn`" — +/// the same absence, reported as if the program had a typo. There is one task on +/// this host and no way to make a second, so these cannot work; what they can do +/// is say which of the two problems the reader has. +fn unavailable(name: &str) -> Result { + Err(anyhow!("`{name}` is not available on bare metal: there is one task")) +} + +fn spawn(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("spawn") +} + +fn chan(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("chan") +} + +fn send(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("send") +} + +fn recv(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("recv") +} + +fn assert_ne(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + lk_stdlib_common::language::assert_ne(args, runtime) +} + pub fn register_bare_stdlib_modules(registry: &mut ModuleRegistry) -> Result<()> { for register in BARE_MODULES { register(registry)?; @@ -112,122 +154,31 @@ pub fn register_bare_stdlib_modules(registry: &mut ModuleRegistry) -> Result<()> } fn print(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - emit(&format_variadic(args.as_slice(), runtime)?); + emit(&lk_stdlib_common::language::format_variadic(args.as_slice(), runtime)?); Ok(RuntimeVal::Nil) } fn println(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let mut text = format_variadic(args.as_slice(), runtime)?; + let mut text = lk_stdlib_common::language::format_variadic(args.as_slice(), runtime)?; text.push('\n'); emit(&text); Ok(RuntimeVal::Nil) } fn panic(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let message = if args.is_empty() { - "panic".to_string() - } else { - format_variadic(args.as_slice(), runtime)? - }; - Err(anyhow!("{message}")) + lk_stdlib_common::language::panic(args, runtime) } +// `assert`/`assert_eq`/`assert_ne`/`panic` are the same on every host — an +// assertion is arithmetic on values, and only `print` needs to know where +// output goes. They were written out three times and had drifted three ways; +// see `lk_stdlib_common::language`. fn assert(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let Some(condition) = values.first() else { - return Err(anyhow!("assert expects at least 1 argument")); - }; - if truthy(condition) { - return Ok(RuntimeVal::Nil); - } - match values.get(1) { - Some(message) => Err(anyhow!("assertion failed: {}", display(message, runtime)?)), - None => Err(anyhow!("assertion failed")), - } + lk_stdlib_common::language::assert(args, runtime) } fn assert_eq(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - if values.len() < 2 { - return Err(anyhow!("assert_eq expects at least 2 arguments")); - } - if values[0] == values[1] { - return Ok(RuntimeVal::Nil); - } - let actual = display(&values[0], runtime)?; - let expected = display(&values[1], runtime)?; - Err(anyhow!("assertion failed: expected {expected}, got {actual}")) -} - -fn truthy(value: &RuntimeVal) -> bool { - !matches!(value, RuntimeVal::Nil | RuntimeVal::Bool(false)) -} - -fn display(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result { - runtime_display_value(value, runtime.heap()) -} - -/// `println("{} of {}", a, b)`-style formatting: a leading string argument acts -/// as a template whose `{}` holes consume the rest, and anything left over is -/// appended space-separated. Without a leading string, all arguments are simply -/// joined by spaces. -fn format_variadic(args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { - let Some((first, rest)) = args.split_first() else { - return Ok(String::new()); - }; - - let Some(template) = string_maybe(first, runtime)? else { - return join_with_spaces(args, runtime); - }; - - let mut out = String::with_capacity(template.len() + rest.len() * 8); - let mut chars = template.chars().peekable(); - let mut next_arg = 0usize; - while let Some(ch) = chars.next() { - if ch == '{' && chars.peek() == Some(&'}') { - chars.next(); - match rest.get(next_arg) { - Some(value) => { - out.push_str(&display(value, runtime)?); - next_arg += 1; - } - None => out.push_str("{}"), - } - } else { - out.push(ch); - } - } - for value in &rest[next_arg.min(rest.len())..] { - out.push(' '); - out.push_str(&display(value, runtime)?); - } - Ok(out) -} - -/// A string argument may be inline (`ShortStr`) or on the heap — only short -/// ones are inline, so matching just `ShortStr` silently fails to treat any -/// realistic format string as a template. -fn string_maybe(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result> { - Ok(match value { - RuntimeVal::ShortStr(value) => Some(value.as_str().to_string()), - RuntimeVal::Obj(handle) => match runtime.heap().get(*handle) { - Some(lk_core::val::HeapValue::String(value)) => Some(value.to_string()), - Some(_) => None, - None => return Err(anyhow!("heap object {} out of bounds", handle.index())), - }, - _ => None, - }) -} - -fn join_with_spaces(args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { - let mut out = String::new(); - for (index, value) in args.iter().enumerate() { - if index > 0 { - out.push(' '); - } - out.push_str(&display(value, runtime)?); - } - Ok(out) + lk_stdlib_common::language::assert_eq(args, runtime) } #[derive(Debug)] diff --git a/stdlib/common/src/language.rs b/stdlib/common/src/language.rs new file mode 100644 index 00000000..192aa9b7 --- /dev/null +++ b/stdlib/common/src/language.rs @@ -0,0 +1,329 @@ +//! The globals the *language* is written in terms of, whatever the host is. +//! +//! Everything else in the standard library is a module a program asks for by +//! name, and a host that cannot back one says so — `use fs` on bare metal +//! answers "not available on bare metal". `error` is not that: it is the global +//! `catch` catches, and a host without it turns every raising program into +//! "undefined function" at run time, after the parser and the type checker have +//! both approved it. `bare-metal-x86`'s interpreter answered exactly that, and +//! the browser playground did too — both build their global list by hand, and +//! both lists were written before `error` was. +//! +//! Nothing here needs an OS: these call through the VM's own machinery and +//! allocate from its heap, both of which a bare host has. + +use alloc::sync::Arc; + +use anyhow::{Result, anyhow}; +use lk_core::val::RuntimeVal; +use lk_core::vm::{NativeArgs, NativeRuntime}; + +/// `error(value)` — raise, carrying `value` itself where it can be carried. +/// +/// A raised heap value has to survive the collection that can happen at any +/// native-call safepoint while the error unwinds, so it is pinned as a GC root +/// until a `catch` binds it. A primitive is `Copy` and needs no pinning; a +/// host with no full VM state cannot pin at all, and falls back to the rendered +/// message — the value is lost, the report is not. +pub fn error(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + if let [value] = args.as_slice() { + let value = *value; + // Rendered up front: once the error unwinds out of the program, the heap + // it points into is gone and there is nothing left to render. + let rendered = joined_display(args.as_slice(), runtime)?; + let carry_first_class = if matches!(value, RuntimeVal::Obj(_)) { + match runtime.state_ctx_module_mut() { + Some((state, _, _)) => { + state.set_pending_raise_root(Some(value)); + true + } + None => false, + } + } else { + true + }; + if carry_first_class { + return Err(anyhow!(lk_core::vm::LkRaisedValue { + value, + rendered: Arc::::from(rendered.as_str()), + })); + } + return Err(anyhow!("{rendered}")); + } + let message = if args.is_empty() { + alloc::string::String::from("error") + } else { + joined_display(args.as_slice(), runtime)? + }; + Err(anyhow!("{message}")) +} + +/// Values joined with spaces, each rendered the way the language renders it. +/// +/// Through [`display`], which asks the value's `show` — the same question the +/// `{}` path asks. It used to go straight to `runtime_display_value`, so +/// +/// ```text +/// println("{}", p) → P! (the impl) +/// println(p) → P{a:1} (the raw struct) +/// ``` +/// +/// — one value, two renderings, decided by whether a template happened to be +/// there. +fn joined_display(values: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { + let mut out = alloc::string::String::new(); + for (index, value) in values.iter().enumerate() { + if index > 0 { + out.push(' '); + } + let piece = display(value, runtime)?; + out.push_str(&piece); + } + Ok(out) +} + +/// `assert`/`assert_eq`/`assert_ne`/`panic` — the same on every host. +/// +/// These were written out three times, once per host, and the copies had +/// drifted in three ways at once: `assert_eq` compared *handles* on web and +/// bare (so it failed on any string past seven bytes), `panic` was a Rust +/// `panic!` on the desktop and a catchable error on the other two, and +/// `assert_ne`'s failure message differed. None of that is a platform +/// difference — an assertion is arithmetic on values, and only `print` needs to +/// know where output goes. +/// +/// The message text matters as much as the outcome: a program can `catch` a +/// failed assertion and read it. +pub fn assert(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + expect_assert_args(args, ASSERT_ARITY.0 as usize, ASSERT_ARITY.1 as usize, "assert")?; + let values = args.as_slice(); + if truthy(&values[0]) { + return Ok(RuntimeVal::Nil); + } + let message = match values.get(1) { + Some(message) => alloc::format!("assertion failed: {}", display(message, runtime)?), + None => alloc::string::String::from("assertion failed"), + }; + Err(anyhow!("{message}")) +} + +pub fn assert_eq(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + expect_assert_args( + args, + ASSERT_PAIR_ARITY.0 as usize, + ASSERT_PAIR_ARITY.1 as usize, + "assert_eq", + )?; + let values = args.as_slice(); + if crate::runtime_native::runtime_values_equal(&values[0], &values[1], runtime.heap())? { + return Ok(RuntimeVal::Nil); + } + let actual = display(&values[0], runtime)?; + let expected = display(&values[1], runtime)?; + let mut message = alloc::format!("assertion failed: expected {expected}, got {actual}"); + append_note(&mut message, values.get(2), runtime)?; + Err(anyhow!("{message}")) +} + +pub fn assert_ne(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + expect_assert_args( + args, + ASSERT_PAIR_ARITY.0 as usize, + ASSERT_PAIR_ARITY.1 as usize, + "assert_ne", + )?; + let values = args.as_slice(); + if !crate::runtime_native::runtime_values_equal(&values[0], &values[1], runtime.heap())? { + return Ok(RuntimeVal::Nil); + } + // Names the value, which "values should not be equal" did not — and which + // is the whole reason to read a failed assertion. + let rendered = display(&values[0], runtime)?; + let mut message = alloc::format!("assertion failed: expected something other than {rendered}"); + append_note(&mut message, values.get(2), runtime)?; + Err(anyhow!("{message}")) +} + +/// `panic(msg...)` — stop, and do not let `catch` intervene. +/// +/// A [`lk_core::vm::LkPanic`], never Rust's `panic!`: unwinding the *host* +/// works on a desktop, is an unrecoverable trap in wasm, and has no unwinder at +/// all on bare metal. +pub fn panic(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + let message = if args.is_empty() { + alloc::string::String::from("panic") + } else { + joined_display(args.as_slice(), runtime)? + }; + Err(anyhow!(lk_core::vm::LkPanic { + message: alloc::sync::Arc::::from(message.as_str()), + })) +} + +/// Only nil and false are falsy — the VM's rule (`truthy_unchecked`). +fn truthy(value: &RuntimeVal) -> bool { + !matches!(value, RuntimeVal::Nil | RuntimeVal::Bool(false)) +} + +/// How a value prints: the user's `show` if its type has one, else the +/// language's own rendering. +/// +/// `show` is a *language* rule — `impl Show for Rect` decides what +/// `print(rect)` says — and it lived in the desktop host alone. The other two +/// rendered the raw struct, so the same program printed `Rect(3x4)` on a +/// desktop and `Rect{h:4,w:3}` in the browser and on bare metal. +pub fn display(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result { + if let Some(shown) = display_via_show(value, runtime)? { + return Ok(shown); + } + crate::runtime_native::runtime_display_value(value, runtime.heap()) +} + +/// The `show` implementation for this value's type, called — or `None` when +/// there is no such type, no such impl, or no context to dispatch through. +fn display_via_show(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result> { + let RuntimeVal::Obj(handle) = value else { + return Ok(None); + }; + let Some(lk_core::val::HeapValue::Object(object)) = runtime.heap().get(*handle) else { + return Ok(None); + }; + let type_name = alloc::string::String::from(object.type_name().as_ref()); + // The declaring module is the other half of the receiver's type identity; + // read it before the mutable borrow below takes the heap. + let receiver_scope = lk_core::vm::receiver_type_scope(value, runtime.heap()); + let Some((state, ctx, module)) = runtime.state_ctx_module_mut() else { + return Ok(None); + }; + let Some(ctx) = ctx else { + return Ok(None); + }; + let Some(impl_ref) = ctx.trait_method(&receiver_scope, &type_name, "show").cloned() else { + return Ok(None); + }; + let result = lk_core::vm::call_trait_method( + &impl_ref, + lk_core::vm::TraitMethodRef { + type_name: &type_name, + method: "show", + }, + value, + None, + state, + module, + Some(ctx), + )?; + Ok(match result { + RuntimeVal::ShortStr(value) => Some(alloc::string::String::from(value.as_str())), + RuntimeVal::Obj(handle) => match state.heap().get(handle) { + Some(lk_core::val::HeapValue::String(value)) => Some(alloc::string::String::from(value.as_ref())), + _ => None, + }, + _ => None, + }) +} + +fn append_note( + message: &mut alloc::string::String, + note: Option<&RuntimeVal>, + runtime: &mut NativeRuntime<'_>, +) -> Result<()> { + if let Some(note) = note { + message.push_str(" - "); + message.push_str(&display(note, runtime)?); + } + Ok(()) +} + +/// How many arguments each assertion takes. +/// +/// Public because the registration says the same thing to the type checker, and +/// it says it *from here* — the numbers used to live only inside the check +/// below, which is why `lk check` passed `assert(true, "a", "b")`. +pub const ASSERT_ARITY: (u16, u16) = (1, 2); +/// `assert_eq` / `assert_ne`: two values, and an optional note. +pub const ASSERT_PAIR_ARITY: (u16, u16) = (2, 3); + +fn expect_assert_args(args: NativeArgs<'_>, min: usize, max: usize, name: &str) -> Result<()> { + if args.has_named() { + return Err(anyhow!("{name}() does not accept named arguments")); + } + let len = args.len(); + if (min..=max).contains(&len) { + Ok(()) + } else if min == max { + Err(anyhow!("{name}() expects exactly {min} arguments")) + } else { + Err(anyhow!("{name}() expects {min} or {max} arguments")) + } +} + +/// `print`/`println`'s argument rendering — one implementation, three hosts. +/// +/// The first argument is a template when it is a string: each `{}` takes the +/// next argument, a `{}` with nothing left stays literal, and arguments past +/// the last `{}` are appended space-separated. A first argument that is not a +/// string means there is no template, so everything is joined with spaces. +/// +/// This was written out three times. The copies agreed on all of that and +/// disagreed on one line — the leading space when the template renders empty: +/// +/// ```text +/// print("", 1, 2) desktop and web: "1 2" bare: " 1 2" +/// ``` +/// +/// Only `print` itself is a platform difference (where the bytes go). What the +/// bytes *are* is the language's, and belongs here. +pub fn format_variadic(args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { + let Some((first, rest)) = args.split_first() else { + return Ok(alloc::string::String::new()); + }; + let Some(template) = string_maybe(first, runtime)? else { + return joined_display(args, runtime); + }; + + let mut out = alloc::string::String::with_capacity(template.len() + rest.len() * 8); + let mut chars = template.chars().peekable(); + let mut next_arg = 0usize; + while let Some(ch) = chars.next() { + if ch == '{' && chars.peek() == Some(&'}') { + chars.next(); + match rest.get(next_arg) { + Some(value) => { + out.push_str(&display(value, runtime)?); + next_arg += 1; + } + // More holes than arguments: the hole stays, rather than + // silently closing over nothing. + None => out.push_str("{}"), + } + } else { + out.push(ch); + } + } + // More arguments than holes: append them, separated as they would be with + // no template at all. No separator before the first if there is nothing to + // separate it from — the line the three copies disagreed on. + for value in rest.iter().skip(next_arg) { + if !out.is_empty() { + out.push(' '); + } + out.push_str(&display(value, runtime)?); + } + Ok(out) +} + +/// The string a value is, or `None` when it is not a string. +/// +/// A template is a template only if the first argument *is* one; `print(1, 2)` +/// has no template and joins. +fn string_maybe(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result> { + Ok(match value { + RuntimeVal::ShortStr(value) => Some(alloc::string::String::from(value.as_str())), + RuntimeVal::Obj(handle) => match runtime.heap().get(*handle) { + Some(lk_core::val::HeapValue::String(value)) => Some(alloc::string::String::from(value.as_ref())), + _ => None, + }, + _ => None, + }) +} diff --git a/stdlib/common/src/lib.rs b/stdlib/common/src/lib.rs index b0b1c069..d49f8710 100644 --- a/stdlib/common/src/lib.rs +++ b/stdlib/common/src/lib.rs @@ -21,6 +21,7 @@ use alloc::{ vec::Vec, }; +pub mod language; pub mod metadata; pub mod resource; pub mod runtime_native; @@ -70,6 +71,39 @@ use lk_core::{ val::{HeapStore, HeapValue, RuntimeVal, TypedList}, }; +/// A duration argument, in milliseconds. +/// +/// Rejects a negative **before** truncating, which is where the spellings of +/// this one operation used to disagree — four answers for the same call: +/// +/// | | `-1` | `-0.5` | +/// | --- | --- | --- | +/// | `time.sleep` | `Duration::from_millis(-1 as u64)` — a 584-million-year sleep | returned at once | +/// | `task.sleep` | refused | returned at once | +/// | `time.timeout` / `time.after` | a timer that never fires, silently | — | +/// +/// The `-0.5` column is the reason the check has to come first: `task.sleep` +/// *had* a `< 0` guard, and it ran after `as i64` had already turned the value +/// into `0`. +pub fn duration_millis(value: &RuntimeVal, name: &str) -> anyhow::Result { + let ms = match value { + RuntimeVal::Int(ms) => *ms as f64, + RuntimeVal::Float(ms) => *ms, + other => { + return Err(anyhow::anyhow!( + "{name} expects a numeric argument, got {:?}", + other.kind() + )); + } + }; + if ms < 0.0 { + return Err(anyhow::anyhow!( + "{name} expects a non-negative duration in milliseconds, got {ms}" + )); + } + Ok(ms as i64) +} + pub fn typed_list_from_values(values: Vec, heap: &HeapStore) -> TypedList { if values.is_empty() { return TypedList::Mixed(values); @@ -132,3 +166,37 @@ pub fn runtime_string_value(value: &str, heap: &mut HeapStore) -> RuntimeVal { RuntimeVal::Obj(heap.alloc(HeapValue::String(Arc::::from(value)))) } } + +#[cfg(test)] +mod duration_tests { + use super::*; + + /// A negative duration is refused, and refused *before* truncation. + /// + /// One operation had four answers: `time.sleep(-1)` cast to `u64` and slept + /// for 584 million years, `task.sleep(-1)` refused, `time.timeout(-1)` and + /// `time.after(-1)` armed a timer that never fires — and every one of them + /// accepted `-0.5`, because the only `< 0` check ran after `as i64` had + /// already turned it into `0`. + #[test] + fn a_duration_cannot_be_negative() { + for value in [RuntimeVal::Int(-1), RuntimeVal::Float(-0.5), RuntimeVal::Float(-1e-9)] { + let err = duration_millis(&value, "time.sleep()").expect_err("refused"); + assert!( + err.to_string().contains("non-negative duration in milliseconds"), + "{err}" + ); + } + assert_eq!( + duration_millis(&RuntimeVal::Int(0), "x").expect("zero is a duration"), + 0 + ); + assert_eq!(duration_millis(&RuntimeVal::Int(7), "x").expect("positive"), 7); + // Truncation toward zero is unchanged for the values that are allowed. + assert_eq!( + duration_millis(&RuntimeVal::Float(0.9), "x").expect("sub-millisecond"), + 0 + ); + assert!(duration_millis(&RuntimeVal::Bool(true), "x").is_err()); + } +} diff --git a/stdlib/common/src/metadata.rs b/stdlib/common/src/metadata.rs index 2ddac422..d715a0ca 100644 --- a/stdlib/common/src/metadata.rs +++ b/stdlib/common/src/metadata.rs @@ -89,6 +89,14 @@ pub struct StdlibModuleMetadata { pub name: &'static str, pub docs: Option<&'static str>, pub callables: &'static [StdlibCallableMetadata], + /// The same exports' declared types, for the type checker. + /// + /// Kept beside `callables` rather than inside them because the two cross + /// different boundaries: `callables` is consumed inside the standard + /// library (catalog, hover, lowering keys), while this is handed to + /// `lk_core`'s checker — which is also why it holds type *text* instead of + /// `Type`. Both come out of one `#[stdlib_export]`, so they cannot drift. + pub signatures: &'static [lk_core::typ::StdlibCallableSig], } impl StdlibModuleMetadata { @@ -96,8 +104,14 @@ impl StdlibModuleMetadata { name: &'static str, docs: Option<&'static str>, callables: &'static [StdlibCallableMetadata], + signatures: &'static [lk_core::typ::StdlibCallableSig], ) -> Self { - Self { name, docs, callables } + Self { + name, + docs, + callables, + signatures, + } } } @@ -136,7 +150,9 @@ macro_rules! stdlib_module_metadata { ), )* ]; - $crate::metadata::StdlibModuleMetadata::new(stringify!($module), None, CALLABLES) + // No signatures: this macro declares return *kinds* for lowering, not + // parameter types, so it has nothing to tell the type checker. + $crate::metadata::StdlibModuleMetadata::new(stringify!($module), None, CALLABLES, &[]) }}; } @@ -206,6 +222,11 @@ pub fn register_stdlib_module_metadata(metadata: StdlibModuleMetadata) -> Result } } registry.modules.push(metadata); + // Hand the declared types to the type checker here rather than at each call + // site: registration is the one path every module takes, nested ones + // included, so a module cannot be typed by the checker and absent from the + // catalog or the reverse. + lk_core::typ::register_stdlib_signatures(metadata.signatures); Ok(()) } diff --git a/stdlib/common/src/runtime_native.rs b/stdlib/common/src/runtime_native.rs index b50856d6..8efe1507 100644 --- a/stdlib/common/src/runtime_native.rs +++ b/stdlib/common/src/runtime_native.rs @@ -1,6 +1,5 @@ use alloc::sync::Arc; use anyhow::{Result, anyhow, bail}; -use core::fmt::Write as _; // From `alloc` directly, not `lk_core::compat::prelude`: feature // unification can give lk-core `std` while this crate stays no_std, and // then that prelude does not exist. @@ -16,7 +15,6 @@ use alloc::{ }; use lk_core::{ module::{RuntimeNativeExport, RuntimeValueExport}, - util::fast_map::fast_hash_map_new, val::{ CallableValue, HeapStore, HeapValue, RuntimeMapKey, RuntimeSet, RuntimeVal, ShortStr, TypedList, TypedMap, de, }, @@ -53,7 +51,7 @@ pub fn module_export( namespaces: &[(&'static str, RuntimeExport)], ) -> Result { let mut heap = HeapStore::new(); - let mut map = fast_hash_map_new(); + let mut map = lk_core::util::value_map::value_map_new(); for native in natives { let value = RuntimeVal::Obj(heap.alloc(HeapValue::Callable(CallableValue::RuntimeNative { name: Arc::::from(native.name), @@ -122,200 +120,235 @@ pub fn runtime_string_value(value: &str, heap: &mut HeapStore) -> RuntimeVal { } } -pub fn runtime_display_value(value: &RuntimeVal, heap: &HeapStore) -> Result { - match value { - RuntimeVal::Nil => Ok("nil".to_string()), - RuntimeVal::Bool(value) => Ok(value.to_string()), - RuntimeVal::Int(value) => Ok(value.to_string()), - RuntimeVal::Float(value) => Ok(value.to_string()), - RuntimeVal::ShortStr(value) => Ok(value.as_str().to_string()), - RuntimeVal::Obj(handle) => { - let value = heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?; - runtime_display_heap_value(value, heap) +/// Value equality, shared by everything that needs it. +/// +/// There were four copies of this question in the tree. The one in +/// `stdlib/web` — which backs `assert_eq` in the browser playground — was +/// `left == right`, the *derived* `PartialEq` on `RuntimeVal`. That derive +/// means two different things for the two variants: structural for a +/// `ShortStr`, and **handle identity** for an `Obj`. So in the playground +/// +/// ```text +/// assert_eq("ab", "ab") passed +/// assert_eq("abcdefghij", "abcdefghij") failed +/// ``` +/// +/// — the seven-byte inline limit of `ShortStr` deciding whether an assertion +/// held. `ShortStr` is not the bug: it is a small-string optimisation that made +/// half the cases accidentally right. Take it away and the derive is uniformly +/// wrong instead of intermittently. +pub fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore) -> Result { + Ok(match (left, right) { + (RuntimeVal::Nil, RuntimeVal::Nil) => true, + (RuntimeVal::Bool(left), RuntimeVal::Bool(right)) => left == right, + (RuntimeVal::Int(left), RuntimeVal::Int(right)) => left == right, + (RuntimeVal::Float(left), RuntimeVal::Float(right)) => left == right, + (RuntimeVal::Int(left), RuntimeVal::Float(right)) => *left as f64 == *right, + (RuntimeVal::Float(left), RuntimeVal::Int(right)) => *left == *right as f64, + (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) if left == right => true, + (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) => { + let left = heap + .get(*left) + .ok_or_else(|| anyhow!("heap object {} out of bounds", left.index()))?; + let right = heap + .get(*right) + .ok_or_else(|| anyhow!("heap object {} out of bounds", right.index()))?; + heap_values_equal(left, right, heap)? } - } + _ => match ( + runtime_value_to_string(left, heap)?, + runtime_value_to_string(right, heap)?, + ) { + (Some(left), Some(right)) => left == right, + _ => false, + }, + }) } - -fn runtime_display_heap_value(value: &HeapValue, heap: &HeapStore) -> Result { - match value { - HeapValue::String(value) => Ok(value.to_string()), - HeapValue::Bytes(value) => Ok(format!("", value.len())), - HeapValue::List(values) => runtime_display_list(values, heap), - HeapValue::Map(values) => runtime_display_map(values, heap), - HeapValue::Set(values) => runtime_display_set(values), - HeapValue::Callable(value) => Ok(runtime_display_callable(value)), - HeapValue::Object(value) => { - let mut out = value.type_name().to_string(); - append_display_entries( - &mut out, - value - .fields - .iter() - .map(|(key, value)| Ok((key.to_string(), runtime_display_value(value, heap)?))), - )?; - Ok(out) - } - other => Ok(format!("<{}>", other.type_name())), - } +fn heap_values_equal(left: &HeapValue, right: &HeapValue, heap: &HeapStore) -> Result { + Ok(match (left, right) { + (HeapValue::String(left), HeapValue::String(right)) => left == right, + (HeapValue::List(left), HeapValue::List(right)) => typed_lists_equal(left, right, heap)?, + (HeapValue::Map(left), HeapValue::Map(right)) => typed_maps_equal(left, right, heap)?, + (HeapValue::Set(left), HeapValue::Set(right)) => runtime_sets_equal(left, right), + _ => false, + }) } - -fn runtime_display_set(values: &RuntimeSet) -> Result { - let mut out = String::from("Set("); - out.push('['); - let mut first = true; - let mut entries = values.entries().map(runtime_display_map_key).collect::>(); - entries.sort(); - for key in entries { - push_display_sep(&mut out, &mut first); - out.push_str(&key); - } - out.push(']'); - out.push(')'); - Ok(out) +fn runtime_sets_equal(left: &RuntimeSet, right: &RuntimeSet) -> bool { + left.len() == right.len() && left.entries().all(|key| right.contains(key)) } - -fn runtime_display_callable(value: &CallableValue) -> String { - match value { - CallableValue::Closure { - function_index, - captures, - } => format!("", function_index, captures.len()), - CallableValue::RuntimeNative { name, arity, .. } => { - if *arity == lk_core::vm::NativeEntry::VARIADIC { - format!("", name) - } else { - format!("", name, arity) - } - } - CallableValue::Runtime(function) => { - format!( - "", - function.display_signature(), - function.capture_count() - ) +fn typed_lists_equal(left: &TypedList, right: &TypedList, heap: &HeapStore) -> Result { + if left.len() != right.len() { + return Ok(false); + } + match (left, right) { + (TypedList::Int(left), TypedList::Int(right)) => return Ok(left == right), + (TypedList::Float(left), TypedList::Float(right)) => return Ok(left == right), + (TypedList::Bool(left), TypedList::Bool(right)) => return Ok(left == right), + (TypedList::String(left), TypedList::String(right)) => return Ok(left == right), + _ => {} + } + for index in 0..left.len() { + if !typed_list_items_equal(left, index, right, index, heap)? { + return Ok(false); } } + Ok(true) } - -fn runtime_display_list(values: &TypedList, heap: &HeapStore) -> Result { - let mut out = String::from("["); - let mut first = true; - match values { - TypedList::Mixed(values) => { - for value in values { - push_display_sep(&mut out, &mut first); - out.push_str(&runtime_display_value(value, heap)?); +fn runtime_value_equals_string(value: &RuntimeVal, expected: &str, heap: &HeapStore) -> Result { + Ok(match value { + RuntimeVal::ShortStr(value) => value.as_str() == expected, + RuntimeVal::Obj(handle) => matches!( + heap.get(*handle) + .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?, + HeapValue::String(value) if value.as_ref() == expected + ), + _ => false, + }) +} +fn typed_maps_equal(left: &TypedMap, right: &TypedMap, heap: &HeapStore) -> Result { + if left.len() != right.len() { + return Ok(false); + } + match left { + TypedMap::Mixed(entries) => { + for (key, value) in entries { + if !typed_map_value_equal(right, key, value, heap)? { + return Ok(false); + } } } - TypedList::Int(values) => { - for value in values { - push_display_sep(&mut out, &mut first); - write!(&mut out, "{value}").expect("write to String cannot fail"); + TypedMap::StringMixed(entries) => { + for (key, value) in entries { + let key = RuntimeMapKey::from_shared(key.clone()); + if !typed_map_value_equal(right, &key, value, heap)? { + return Ok(false); + } } } - TypedList::Float(values) => { - for value in values { - push_display_sep(&mut out, &mut first); - write!(&mut out, "{value}").expect("write to String cannot fail"); + TypedMap::StringInt(entries) => { + for (key, value) in entries { + let key = RuntimeMapKey::from_shared(key.clone()); + if !typed_map_value_equal(right, &key, &RuntimeVal::Int(*value), heap)? { + return Ok(false); + } } } - TypedList::Bool(values) => { - for value in values { - push_display_sep(&mut out, &mut first); - write!(&mut out, "{value}").expect("write to String cannot fail"); + TypedMap::StringFloat(entries) => { + for (key, value) in entries { + let key = RuntimeMapKey::from_shared(key.clone()); + if !typed_map_value_equal(right, &key, &RuntimeVal::Float(*value), heap)? { + return Ok(false); + } } } - TypedList::String(values) => { - for value in values { - push_display_sep(&mut out, &mut first); - out.push_str("e_string(value)); + TypedMap::StringBool(entries) => { + for (key, value) in entries { + let key = RuntimeMapKey::from_shared(key.clone()); + if !typed_map_value_equal(right, &key, &RuntimeVal::Bool(*value), heap)? { + return Ok(false); + } } } } - out.push(']'); - Ok(out) + Ok(true) } -fn runtime_display_map(values: &TypedMap, heap: &HeapStore) -> Result { - let mut out = String::new(); - match values { - TypedMap::Mixed(entries) => append_display_entries( - &mut out, - entries - .iter() - .map(|(key, value)| Ok((runtime_display_map_key(key), runtime_display_value(value, heap)?))), - )?, - TypedMap::StringMixed(entries) => append_display_entries( - &mut out, - entries - .iter() - .map(|(key, value)| Ok((quote_string(key), runtime_display_value(value, heap)?))), - )?, - TypedMap::StringInt(entries) => append_display_entries( - &mut out, - entries - .iter() - .map(|(key, value)| Ok((quote_string(key), value.to_string()))), - )?, - TypedMap::StringFloat(entries) => append_display_entries( - &mut out, - entries - .iter() - .map(|(key, value)| Ok((quote_string(key), value.to_string()))), - )?, - TypedMap::StringBool(entries) => append_display_entries( - &mut out, - entries - .iter() - .map(|(key, value)| Ok((quote_string(key), value.to_string()))), - )?, +fn runtime_value_to_string(value: &RuntimeVal, heap: &HeapStore) -> Result>> { + match value { + RuntimeVal::ShortStr(value) => Ok(Some(Arc::::from(value.as_str()))), + RuntimeVal::Obj(handle) => match heap + .get(*handle) + .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? + { + HeapValue::String(value) => Ok(Some(value.clone())), + _ => Ok(None), + }, + _ => Ok(None), } - Ok(out) } - -fn runtime_display_map_key(key: &RuntimeMapKey) -> String { - match key { - RuntimeMapKey::Nil => "nil".to_string(), - RuntimeMapKey::Bool(value) => value.to_string(), - RuntimeMapKey::Int(value) => value.to_string(), - RuntimeMapKey::ShortStr(value) => quote_string(value.as_str()), - RuntimeMapKey::String(value) => quote_string(value), - RuntimeMapKey::Obj(value) => format!("", value.index()), +fn typed_list_items_equal( + left: &TypedList, + left_index: usize, + right: &TypedList, + right_index: usize, + heap: &HeapStore, +) -> Result { + match (left, right) { + (TypedList::Mixed(left), TypedList::Mixed(right)) => { + runtime_values_equal(&left[left_index], &right[right_index], heap) + } + (TypedList::Mixed(left), TypedList::String(right)) => { + runtime_value_equals_string(&left[left_index], &right[right_index], heap) + } + (TypedList::String(left), TypedList::Mixed(right)) => { + runtime_value_equals_string(&right[right_index], &left[left_index], heap) + } + (TypedList::Int(left), _) => { + typed_list_runtime_item_equal(RuntimeVal::Int(left[left_index]), right, right_index, heap) + } + (TypedList::Float(left), _) => { + typed_list_runtime_item_equal(RuntimeVal::Float(left[left_index]), right, right_index, heap) + } + (TypedList::Bool(left), _) => { + typed_list_runtime_item_equal(RuntimeVal::Bool(left[left_index]), right, right_index, heap) + } + (TypedList::String(left), _) => typed_list_string_item_equal(&left[left_index], right, right_index, heap), + (TypedList::Mixed(left), _) => typed_list_runtime_item_equal(left[left_index], right, right_index, heap), } } -fn append_display_entries(out: &mut String, entries: impl IntoIterator>) -> Result<()> { - out.push('{'); - let mut first = true; - for entry in entries { - let (key, value) = entry?; - push_display_sep(out, &mut first); - out.push_str(&key); - out.push(':'); - out.push_str(&value); - } - out.push('}'); - Ok(()) +fn typed_map_value_equal( + right: &TypedMap, + key: &RuntimeMapKey, + left_value: &RuntimeVal, + heap: &HeapStore, +) -> Result { + let Some(right_value) = right.get(key) else { + return Ok(false); + }; + runtime_values_equal(left_value, &right_value, heap) } -fn push_display_sep(out: &mut String, first: &mut bool) { - if *first { - *first = false; - } else { - out.push(','); +fn typed_list_runtime_item_equal( + value: RuntimeVal, + right: &TypedList, + right_index: usize, + heap: &HeapStore, +) -> Result { + match right { + TypedList::Mixed(right) => runtime_values_equal(&value, &right[right_index], heap), + TypedList::Int(right) => runtime_values_equal(&value, &RuntimeVal::Int(right[right_index]), heap), + TypedList::Float(right) => runtime_values_equal(&value, &RuntimeVal::Float(right[right_index]), heap), + TypedList::Bool(right) => runtime_values_equal(&value, &RuntimeVal::Bool(right[right_index]), heap), + TypedList::String(right) => runtime_value_equals_string(&value, &right[right_index], heap), + } +} +fn typed_list_string_item_equal( + left: &Arc, + right: &TypedList, + right_index: usize, + heap: &HeapStore, +) -> Result { + match right { + TypedList::Mixed(right) => runtime_value_equals_string(&right[right_index], left, heap), + TypedList::String(right) => Ok(left == &right[right_index]), + _ => Ok(false), } } -fn quote_string(value: &str) -> String { - format!("{value:?}") +/// How a value looks — `lk_core::vm::runtime_display_value`, which is the one +/// rendering there is. +/// +/// This crate used to hold it and the VM had its own, so `println` and the REPL +/// showed the same value differently. `show` dispatch is a layer above, in +/// `language::display`: it calls user code, which a renderer cannot. +pub fn runtime_display_value(value: &RuntimeVal, heap: &HeapStore) -> Result { + lk_core::vm::runtime_display_value(value, heap) } #[cfg(test)] mod tests { use alloc::sync::Arc; - use lk_core::util::fast_map::fast_hash_map_from_iter; use super::*; use lk_core::val::TypedMap; @@ -324,12 +357,12 @@ mod tests { fn runtime_display_formats_typed_containers_without_val_containers() { let mut heap = HeapStore::new(); let nested = RuntimeVal::Obj(heap.alloc(HeapValue::List(TypedList::Int(vec![1, 2])))); - let map = RuntimeVal::Obj( - heap.alloc(HeapValue::Map(TypedMap::StringMixed(fast_hash_map_from_iter([ + let map = RuntimeVal::Obj(heap.alloc(HeapValue::Map(TypedMap::StringMixed( + lk_core::util::value_map::value_map_from_iter([ (Arc::::from("items"), nested), (Arc::::from("ok"), RuntimeVal::Bool(true)), - ])))), - ); + ]), + )))); let output = runtime_display_value(&map, &heap).expect("display"); diff --git a/stdlib/crates/bytes/src/lib.rs b/stdlib/crates/bytes/src/lib.rs index c666c11f..36261ee5 100644 --- a/stdlib/crates/bytes/src/lib.rs +++ b/stdlib/crates/bytes/src/lib.rs @@ -21,7 +21,7 @@ use alloc::sync::Arc; use anyhow::{Result, anyhow, bail}; use lk_core::{ - val::{HeapStore, HeapValue, RuntimeVal, TypedList}, + val::{HeapStore, HeapValue, RuntimeVal}, vm::{NativeArgs, NativeRuntime}, }; @@ -29,7 +29,7 @@ pub mod runtime_native { pub use lk_stdlib_common::runtime_native::*; } -use crate::runtime_native::{runtime_string_arg, runtime_string_value}; +use crate::runtime_native::runtime_string_arg; #[derive(Debug, Default, lk_stdlib_common::StdlibModule)] #[stdlib_module(name = "bytes", docs = "Byte buffer helpers")] @@ -61,151 +61,123 @@ pub fn runtime_bytes_or_string_arg(value: &RuntimeVal, heap: &HeapStore, context #[lk_stdlib_common::stdlib_exports] impl BytesModule { - #[stdlib_export(name = "from_list", params(values: List), returns = Bytes)] + /// `xs.to_bytes()`, spelled as a constructor. + /// + /// The body is the method's, as everywhere else in this module: a module + /// function whose first parameter is the receiver **is** the method, and + /// two bodies for one operation is how `bytes.slice(b, 3, 1)` came to raise + /// while `b.slice(3, 1)` answered an empty window. + #[stdlib_export(name = "from_list", params(values: List<_>), returns = Bytes)] fn from_list(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = byte_list_arg(args.get(0).expect("checked arity"), runtime.heap(), "bytes.from_list()")?; - Ok(runtime_bytes_value(values, runtime.heap_mut())) + forward("to_bytes", args, runtime) } + /// `s.bytes()`, spelled as a constructor — one operation, and now one body, + /// even though the two spellings live in different modules. #[stdlib_export(name = "from_string", params(value: String), returns = Bytes)] fn from_string(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = runtime_string_arg( - args.get(0).expect("checked arity"), - runtime.heap(), - "bytes.from_string()", - )?; - Ok(runtime_bytes_value( - Arc::<[u8]>::from(value.as_bytes()), - runtime.heap_mut(), - )) + forward("bytes", args, runtime) } #[stdlib_export(name = "len", params(value: Bytes), returns = Int)] fn len(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = runtime_bytes_arg(args.get(0).expect("checked arity"), runtime.heap(), "bytes.len()")?; - Ok(RuntimeVal::Int(value.len() as i64)) + forward("len", args, runtime) } #[stdlib_export(name = "is_empty", params(value: Bytes), returns = Bool)] fn is_empty(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = runtime_bytes_arg(args.get(0).expect("checked arity"), runtime.heap(), "bytes.is_empty()")?; - Ok(RuntimeVal::Bool(value.is_empty())) + forward("is_empty", args, runtime) } #[stdlib_export(name = "get", params(value: Bytes, index: Int), returns = Int?)] fn get(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let bytes = runtime_bytes_arg(&values[0], runtime.heap(), "bytes.get()")?; - let index = usize_arg(&values[1], "bytes.get() index")?; - Ok(bytes - .get(index) - .copied() - .map(|value| RuntimeVal::Int(value as i64)) - .unwrap_or(RuntimeVal::Nil)) + forward("get", args, runtime) } - #[stdlib_export(name = "slice", params(value: Bytes, start: Int, end?: Int), returns = Bytes)] + #[stdlib_export(name = "first", params(value: Bytes), returns = Int?)] + fn first(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("first", args, runtime) + } + + #[stdlib_export(name = "last", params(value: Bytes), returns = Int?)] + fn last(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("last", args, runtime) + } + + #[stdlib_export(name = "contains", params(value: Bytes, byte: Int), returns = Bool)] + fn contains(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("contains", args, runtime) + } + + #[stdlib_export(name = "index_of", params(value: Bytes, byte: Int), returns = Int?)] + fn index_of(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("index_of", args, runtime) + } + + #[stdlib_export(name = "sum", params(value: Bytes), returns = Int)] + fn sum(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("sum", args, runtime) + } + + #[stdlib_export(name = "min", params(value: Bytes), returns = Int?)] + fn min(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("min", args, runtime) + } + + #[stdlib_export(name = "max", params(value: Bytes), returns = Int?)] + fn max(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("max", args, runtime) + } + + #[stdlib_export(name = "take", params(value: Bytes, count: Int), returns = Bytes)] + fn take(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("take", args, runtime) + } + + #[stdlib_export(name = "skip", params(value: Bytes, count: Int), returns = Bytes)] + fn skip(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("skip", args, runtime) + } + + /// Window positions: negative counts from the end and out of range clamps, + /// including a reversed window, which is empty rather than a raise. The + /// module used to refuse `end < start` while the method answered `Bytes([])` + /// — the last surviving difference between the two spellings. + #[stdlib_export(name = "slice", params(value: Bytes, start: Int, end?: Int), named(start, end), returns = Bytes)] fn slice(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - if args.len() != 2 && args.len() != 3 { - bail!("bytes.slice() expects 2 or 3 arguments: bytes, start[, end]"); - } - let values = args.as_slice(); - let bytes = runtime_bytes_arg(&values[0], runtime.heap(), "bytes.slice()")?; - let start = usize_arg(&values[1], "bytes.slice() start")?.min(bytes.len()); - let end = if let Some(value) = values.get(2) { - usize_arg(value, "bytes.slice() end")?.min(bytes.len()) - } else { - bytes.len() - }; - if end < start { - bail!("bytes.slice() end must be greater than or equal to start"); - } - let slice = bytes[start..end].to_vec(); - Ok(runtime_bytes_value(slice, runtime.heap_mut())) + forward("slice", args, runtime) } #[stdlib_export(name = "to_list", params(value: Bytes), returns = List)] fn to_list(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let bytes = runtime_bytes_arg(args.get(0).expect("checked arity"), runtime.heap(), "bytes.to_list()")?; - let list = TypedList::Int(bytes.iter().copied().map(i64::from).collect()); - Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(list)))) + forward("to_list", args, runtime) } #[stdlib_export(name = "to_string_utf8", params(value: Bytes), returns = String, docs = "Decodes bytes as UTF-8 and raises an error for invalid input.")] fn to_string_utf8(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let bytes = runtime_bytes_arg( - args.get(0).expect("checked arity"), - runtime.heap(), - "bytes.to_string_utf8()", - )?; - let value = core::str::from_utf8(&bytes).map_err(|err| anyhow!("bytes are not valid UTF-8: {err}"))?; - Ok(runtime_string_value(value, runtime.heap_mut())) + forward("to_string_utf8", args, runtime) } #[stdlib_export(name = "to_string_lossy", params(value: Bytes), returns = String)] fn to_string_lossy(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let bytes = runtime_bytes_arg( - args.get(0).expect("checked arity"), - runtime.heap(), - "bytes.to_string_lossy()", - )?; - Ok(runtime_string_value( - &String::from_utf8_lossy(&bytes), - runtime.heap_mut(), - )) - } - - #[stdlib_export(name = "concat", params(left: Bytes, right: Bytes), returns = Bytes)] - fn concat(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let left = runtime_bytes_arg(&values[0], runtime.heap(), "bytes.concat() first argument")?; - let right = runtime_bytes_arg(&values[1], runtime.heap(), "bytes.concat() second argument")?; - let mut out = Vec::with_capacity(left.len() + right.len()); - out.extend_from_slice(&left); - out.extend_from_slice(&right); - Ok(runtime_bytes_value(out, runtime.heap_mut())) - } - - #[stdlib_export(name = "eq", params(left: Bytes, right: Bytes), returns = Bool)] - fn eq(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let left = runtime_bytes_arg(&values[0], runtime.heap(), "bytes.eq() first argument")?; - let right = runtime_bytes_arg(&values[1], runtime.heap(), "bytes.eq() second argument")?; - Ok(RuntimeVal::Bool(left == right)) + forward("to_string_lossy", args, runtime) } -} -fn byte_list_arg(value: &RuntimeVal, heap: &HeapStore, context: &str) -> Result> { - let RuntimeVal::Obj(handle) = value else { - bail!("{context} expects a list of bytes"); - }; - let list = match heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::List(list) => list, - other => bail!("{context} expects a list of bytes, got {}", other.type_name()), - }; - match list { - TypedList::Int(values) => values.iter().map(|value| checked_byte(*value, context)).collect(), - TypedList::Mixed(values) => values - .iter() - .map(|value| match value { - RuntimeVal::Int(value) => checked_byte(*value, context), - other => bail!("{context} expects Int items, got {:?}", other.kind()), - }) - .collect(), - _ => bail!("{context} expects Int items"), + #[stdlib_export(name = "concat", params(left: Bytes, right: Bytes), named(right), returns = Bytes)] + fn concat(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("concat", args, runtime) } } -fn checked_byte(value: i64, context: &str) -> Result { - u8::try_from(value).map_err(|_| anyhow!("{context} expects byte values in 0..=255, got {value}")) -} - -fn usize_arg(value: &RuntimeVal, context: &str) -> Result { - match value { - RuntimeVal::Int(value) if *value >= 0 => Ok(*value as usize), - other => bail!("{context} expects a non-negative integer, got {:?}", other.kind()), - } +/// The module spelling of a method: the receiver written first. +/// +/// See `lk_stdlib_string::forward` for why this shape rather than a second +/// body — this module is the one that proved the point twice, with `get` and +/// then with `slice`. +fn forward(method: &'static str, args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + let values = args.as_slice(); + let Some((receiver, rest)) = values.split_first() else { + bail!("bytes.{method} expects its receiver as the first argument"); + }; + lk_core::vm::core_call_method_windowed(*receiver, method, rest, runtime) } diff --git a/stdlib/crates/chan/src/lib.rs b/stdlib/crates/chan/src/lib.rs index 95a00af9..cc37c030 100644 --- a/stdlib/crates/chan/src/lib.rs +++ b/stdlib/crates/chan/src/lib.rs @@ -20,8 +20,113 @@ pub mod runtime_native { #[stdlib_module(name = "chan", docs = "Channel operations for inter-task communication")] pub struct ChannelModule; +/// Creates a channel — the implementation behind both `chan.new(…)` and the +/// bare `chan(…)` global. +/// +/// Both spellings exist because importing the module *shadows* the global: +/// after `use chan;` the name is the module, so `chan(3)` stopped being a call +/// at all and there was no way left to make a channel. One implementation, two +/// names, and the module is now complete on its own. +/// `chan(capacity[, type])` — a capacity, and an optional type hint. +/// +/// Public because the registration tells the type checker the same numbers, and +/// they are these ones. +pub const CHAN_ARITY: (u16, u16) = (1, 2); + +pub fn create_channel_value(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + if args.len() < CHAN_ARITY.0 as usize || args.len() > CHAN_ARITY.1 as usize { + bail!("chan() expects 1 or 2 arguments: capacity[, type_str]"); + } + let values = args.as_slice(); + let capacity = match &values[0] { + RuntimeVal::Int(value) => *value, + RuntimeVal::Float(value) => *value as i64, + other => bail!("chan() capacity must be numeric, got {:?}", other.kind()), + }; + let inner_type = if values.len() == 2 { + match &values[1] { + RuntimeVal::Nil => lk_core::val::Type::Nil, + value => { + let text = runtime_native::runtime_string_arg(value, runtime.heap(), "chan() type")?; + lk_core::val::Type::parse(text.as_ref()).unwrap_or(lk_core::val::Type::Nil) + } + } + } else { + lk_core::val::Type::Nil + }; + // `0` is *unbuffered*, as it is in every channel API a reader has seen — + // not unbounded, which is what it used to mean here. The runtime's mpsc has + // no true rendezvous form, so `0` takes the smallest bound it offers. + if capacity < 0 { + bail!("chan() capacity cannot be negative, got {capacity}"); + } + let channel_id = runtime + .async_runtime() + .with(|runtime| runtime.create_channel(Some((capacity as usize).max(1)))) + .map_err(|error| anyhow!("Failed to create channel: {error}"))?; + Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::Channel(Arc::new( + ChannelValue { + id: channel_id, + capacity: Some(capacity), + inner_type, + }, + ))))) +} + +/// Blocking send — the implementation behind both `chan.send(c, v)` and the +/// bare `send(c, v)` global. +/// +/// Returns Nil on delivery and raises a catchable error once the channel is +/// closed (v2 error model: failures raise, they don't return status values — +/// Go's panic-on-closed-send). +pub fn blocking_send_value(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>, name: &str) -> Result { + let values = args.as_slice(); + if values.len() != 2 { + bail!("{name} expects 2 arguments: channel, value"); + } + let channel = channel_arg(&values[0], runtime.heap(), name)?; + let value = RuntimePayload::copy_from_value(&values[1], runtime.heap())?; + let sent = runtime + .async_runtime() + .with(|rt| rt.block_on(rt.guard_blocking("send", rt.send_async(channel.id, value)))) + .map_err(|error| anyhow!("Send operation failed: {error}"))?; + if !sent { + bail!("send on closed channel"); + } + Ok(RuntimeVal::Nil) +} + +/// Blocking receive — the implementation behind both `chan.recv(c)` and the +/// bare `recv(c)` global. +/// +/// Returns the value; raises a catchable error once the channel is closed and +/// drained (no `[ok, value]` pairs — a consume-until-closed loop wraps itself +/// in try/catch, or polls `chan.is_closed`/`chan.try_recv`). +pub fn blocking_recv_value(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>, name: &str) -> Result { + let values = args.as_slice(); + if values.len() != 1 { + bail!("{name} expects 1 argument: channel"); + } + let channel = channel_arg(&values[0], runtime.heap(), name)?; + let (ok, value) = runtime + .async_runtime() + .with(|rt| rt.block_on(rt.guard_blocking("recv", rt.recv_async(channel.id)))) + .map_err(|error| anyhow!("Receive operation failed: {error}"))?; + if !ok { + bail!("receive on closed channel"); + } + value.into_value(runtime.heap_mut()) +} + #[lk_stdlib_common::stdlib_exports(module = "chan", runtime_builtins = true)] impl ChannelModule { + /// `chan.new(capacity[, type])` — the module spelling of the `chan(…)` + /// global, and the only one reachable after `use chan;`. + #[stdlib_export(name = "new", params(capacity: Int, type?: String), returns = Channel)] + fn new_channel(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + create_channel_value(args, runtime) + } + #[stdlib_export(name = "close", params(channel: Channel), returns = Nil)] fn close(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let channel = channel_arg(args.get(0).expect("checked arity"), runtime.heap(), "chan.close()")?; @@ -58,15 +163,32 @@ impl ChannelModule { Ok(RuntimeVal::Bool(closed)) } + /// `chan.send(c, v)` — blocking send, the module spelling of the `send` + /// global. The module had `try_send` but not this, so `use chan;` produced a + /// channel you could only poll: the blocking half was reachable only through + /// an unqualified global. + #[stdlib_export(name = "send", params(channel: Channel, value: Any), returns = Nil)] + fn send(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + blocking_send_value(args, runtime, "chan.send()") + } + + /// `chan.recv(c)` — blocking receive, the module spelling of the `recv` + /// global. + #[stdlib_export(name = "recv", params(channel: Channel), returns = Any)] + fn recv(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + blocking_recv_value(args, runtime, "chan.recv()") + } + #[stdlib_export(name = "try_send", params(channel: Channel, value: Any), returns = Bool)] fn try_send(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); let channel = channel_arg(&values[0], runtime.heap(), "chan.try_send()")?; let value = RuntimePayload::copy_from_value(&values[1], runtime.heap())?; + // Propagated, not wrapped: the closed-channel wording is the + // language's and lives in `rt::try_send`. let sent = runtime .async_runtime() - .with(|runtime| runtime.try_send(channel.id, value)) - .map_err(|err| anyhow!("Failed to send to channel: {err}"))?; + .with(|runtime| runtime.try_send(channel.id, value))?; Ok(RuntimeVal::Bool(sent)) } @@ -148,6 +270,12 @@ mod tests { assert!(matches!(function, NativeFunction::Plain(_))); assert_ne!(arity, lk_core::vm::NativeEntry::VARIADIC); } + // `new` alone is variadic: its element type is optional, and an + // optional parameter means the call site can supply fewer arguments + // than the declaration lists. + let (arity, function) = chan_native("new")?; + assert!(matches!(function, NativeFunction::Plain(_))); + assert_eq!(arity, lk_core::vm::NativeEntry::VARIADIC); Ok(()) } @@ -199,4 +327,31 @@ mod tests { assert_eq!(received, RuntimeVal::Nil); Ok(()) } + + /// Importing the module shadows the bare `chan(…)` global — they share the + /// name — so before `chan.new` existed, `use chan;` left no way at all to + /// create a channel. + #[test] + fn chan_new_creates_a_channel_and_rejects_a_negative_capacity() -> Result<()> { + let mut ctx = VmContext::new_without_core_vm_builtins(); + let mut state = RuntimeModuleState::default(); + + let created = call("new", &[RuntimeVal::Int(3)], &mut state, &mut ctx)?; + assert_eq!( + call("capacity", std::slice::from_ref(&created), &mut state, &mut ctx)?, + RuntimeVal::Int(3) + ); + + // Zero is *unbuffered*, and it is a capacity like any other — not the + // unbounded queue it used to mean. + let unbuffered = call("new", &[RuntimeVal::Int(0)], &mut state, &mut ctx)?; + assert_eq!( + call("capacity", std::slice::from_ref(&unbuffered), &mut state, &mut ctx)?, + RuntimeVal::Int(0) + ); + + let error = call("new", &[RuntimeVal::Int(-1)], &mut state, &mut ctx).expect_err("negative capacity"); + assert!(error.to_string().contains("cannot be negative"), "{error}"); + Ok(()) + } } diff --git a/stdlib/crates/datetime/src/lib.rs b/stdlib/crates/datetime/src/lib.rs index 428c74b2..3f1adbe3 100644 --- a/stdlib/crates/datetime/src/lib.rs +++ b/stdlib/crates/datetime/src/lib.rs @@ -30,12 +30,22 @@ impl DateTimeModule { Ok(runtime_string_value(&formatted, runtime.heap_mut())) } + /// The inverse of [`format`] — **whatever `format` can produce**. + /// + /// It used to try only `NaiveDateTime`, which requires a date *and* a time, + /// so the pair could not round-trip: `format(t, "%Y-%m-%d")` gives + /// `1970-01-02` and parsing that back with the same format string answered + /// "input is not enough for unique date and time". A format string is the + /// caller's description of the text on both sides; the two directions have + /// to agree about what it describes. + /// + /// Date-only text is midnight UTC; time-only text is that time on the epoch + /// day — the same defaults `format` drops when it omits the other half. #[stdlib_export(name = "parse", params(value: String, format: String), returns = Int)] fn parse(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let datetime = runtime_string_arg(args.get(0).expect("checked arity"), runtime.heap(), "parse")?; let format = runtime_string_arg(args.get(1).expect("checked arity"), runtime.heap(), "parse")?; - let naive = chrono::NaiveDateTime::parse_from_str(datetime.as_ref(), format.as_ref()) - .map_err(|err| anyhow!("failed to parse datetime: {err}"))?; + let naive = parse_naive(datetime.as_ref(), format.as_ref())?; let dt = chrono::DateTime::::from_naive_utc_and_offset(naive, chrono::Utc); Ok(RuntimeVal::Int(dt.timestamp())) } @@ -106,3 +116,29 @@ fn timestamp_arg(value: &RuntimeVal, name: &str) -> Result { fn utc_datetime(timestamp: i64) -> Result> { chrono::DateTime::::from_timestamp(timestamp, 0).ok_or_else(|| anyhow!("invalid timestamp")) } + +/// `value` read against `format`, accepting the three shapes `format` can +/// write: a full datetime, a date alone, or a time alone. +/// +/// Tried in that order. The error names the format rather than repeating +/// chrono's phrasing, which described its own parser's internal requirement +/// ("input is not enough for unique date and time") — a sentence about a +/// library the program never mentioned. +fn parse_naive(value: &str, format: &str) -> Result { + if let Ok(naive) = chrono::NaiveDateTime::parse_from_str(value, format) { + return Ok(naive); + } + // A date with no time is midnight, which is what `format` dropped. + if let Ok(date) = chrono::NaiveDate::parse_from_str(value, format) { + return Ok(date.and_time(chrono::NaiveTime::MIN)); + } + // A time with no date is that time on the epoch day, the other half of the + // same rule. + if let Ok(time) = chrono::NaiveTime::parse_from_str(value, format) { + let epoch = chrono::DateTime::::from_timestamp(0, 0) + .ok_or_else(|| anyhow!("invalid timestamp"))? + .date_naive(); + return Ok(epoch.and_time(time)); + } + Err(anyhow!("`{value}` does not match the format `{format}`")) +} diff --git a/stdlib/crates/encoding/src/lib.rs b/stdlib/crates/encoding/src/lib.rs index 55a19d80..f29e20a6 100644 --- a/stdlib/crates/encoding/src/lib.rs +++ b/stdlib/crates/encoding/src/lib.rs @@ -24,7 +24,7 @@ use anyhow::bail; use anyhow::{Result, anyhow}; use base64::Engine as _; #[cfg(feature = "std")] -use lk_core::util::fast_map::fast_hash_map_new; +use lk_core::util::value_map::value_map_new; #[cfg(feature = "std")] use lk_core::val::{HeapValue, TypedMap}; use lk_core::{ @@ -67,6 +67,19 @@ impl JsonModule { fn parse(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { parse_format(args, runtime, "encoding.json.parse", de::Format::Json) } + + /// The other half of `parse`. Without it a script could read a config and + /// change it but not write it back — two thirds of the most ordinary task + /// there is. + #[stdlib_export(params(value: Value), returns = String)] + fn stringify(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + write_format( + args, + runtime, + "encoding.json.stringify", + lk_core::val::ser::to_json_string, + ) + } } #[cfg(feature = "std")] @@ -81,6 +94,16 @@ impl YamlModule { fn parse(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { parse_format(args, runtime, "encoding.yaml.parse", de::Format::Yaml) } + + #[stdlib_export(params(value: Value), returns = String)] + fn stringify(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + write_format( + args, + runtime, + "encoding.yaml.stringify", + lk_core::val::ser::to_yaml_string, + ) + } } #[cfg(feature = "std")] @@ -95,6 +118,31 @@ impl TomlModule { fn parse(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { parse_format(args, runtime, "encoding.toml.parse", de::Format::Toml) } + + #[stdlib_export(params(value: Value), returns = String)] + fn stringify(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + write_format( + args, + runtime, + "encoding.toml.stringify", + lk_core::val::ser::to_toml_string, + ) + } +} + +/// The `stringify` half of `parse_format`: one argument in, text out. +fn write_format( + args: NativeArgs<'_>, + runtime: &mut NativeRuntime<'_>, + name: &str, + write: fn(&RuntimeVal, &lk_core::val::HeapStore) -> Result, +) -> Result { + if args.len() != 1 { + return Err(anyhow!("{name}(value) requires 1 argument")); + } + let text = + write(args.get(0).expect("checked arity"), runtime.heap()).map_err(|error| anyhow!("{name}: {error}"))?; + Ok(runtime_string_value(&text, runtime.heap_mut())) } #[derive(Debug, Default, lk_stdlib_common::StdlibModule)] @@ -174,7 +222,7 @@ impl UrlEncodingModule { "encoding.url.encode_component value", )?; Ok(runtime_string_value( - &url::form_urlencoded::byte_serialize(value.as_bytes()).collect::(), + &percent_encode_component(value.as_ref()), runtime.heap_mut(), )) } @@ -197,7 +245,7 @@ impl UrlEncodingModule { runtime.heap(), "encoding.url.query_parse value", )?; - let mut map = fast_hash_map_new(); + let mut map = value_map_new(); for (key, value) in url::form_urlencoded::parse(value.as_bytes()) { map.insert( Arc::::from(key.as_ref()), @@ -224,6 +272,38 @@ impl UrlEncodingModule { } } +/// Percent-encodes a URI **component**: everything outside the unreserved set +/// becomes `%XX`. +/// +/// The other direction of [`percent_decode_component`], written here rather than +/// taken from a crate so the pair is one implementation's two directions. It used +/// to be `form_urlencoded::byte_serialize`, which is *form* encoding — a space +/// becomes `+` — while the decoder only ever undid `%XX`. So the pair did not +/// round-trip: `decode_component(encode_component("a b"))` was `"a+b"`. +/// +/// Form encoding is what a query body wants, and `query_stringify` / +/// `query_parse` are that pair; they use `form_urlencoded` on both sides and are +/// unaffected. A *component* keeps `+` as the literal `+` it is, which is also +/// what `encodeURIComponent` / `decodeURIComponent` do. +/// +/// The unreserved set is `encodeURIComponent`'s: `A-Za-z0-9-_.!~*'()`. +#[cfg(feature = "std")] +fn percent_encode_component(value: &str) -> String { + fn unreserved(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'!' | b'~' | b'*' | b'\'' | b'(' | b')') + } + let mut out = String::with_capacity(value.len()); + for &byte in value.as_bytes() { + if unreserved(byte) { + out.push(byte as char); + } else { + out.push('%'); + out.push_str(&alloc::format!("{byte:02X}")); + } + } + out +} + /// Only used by the `url` child, which is std-only. #[cfg(feature = "std")] fn percent_decode_component(value: &str) -> Result { @@ -274,3 +354,41 @@ fn string_map_arg(value: &RuntimeVal, runtime: &NativeRuntime<'_>, context: &str _ => bail!("{context} expects string map"), } } + +#[cfg(all(test, feature = "std"))] +mod component_tests { + use super::{percent_decode_component, percent_encode_component}; + + /// The pair's two directions have to agree with each other before they agree + /// with anything else. They did not: the encoder was `form_urlencoded`'s + /// *form* encoding (a space becomes `+`) while the decoder only ever undid + /// `%XX`, so `decode(encode("a b"))` was `"a+b"`. + #[test] + fn a_component_round_trips() { + for original in [ + "a b&c=d", + "", + "plain", + "+literal+", + "100%", + "héllo", + "a/b?c#d", + "~*'()!-_.", + ] { + let encoded = percent_encode_component(original); + let decoded = percent_decode_component(&encoded).expect("own output decodes"); + assert_eq!(decoded, original, "round trip of {original:?} through {encoded:?}"); + } + } + + /// A space is `%20`, and `+` is the literal `+` — `encodeURIComponent`'s + /// rule. Form encoding is what a query body wants, and `query_stringify` / + /// `query_parse` are that pair, on `form_urlencoded` at both ends. + #[test] + fn a_component_is_not_form_encoded() { + assert_eq!(percent_encode_component("a b"), "a%20b"); + assert_eq!(percent_decode_component("a+b").expect("valid"), "a+b"); + // The unreserved set survives untouched. + assert_eq!(percent_encode_component("aZ09-_.!~*'()"), "aZ09-_.!~*'()"); + } +} diff --git a/stdlib/crates/env/src/lib.rs b/stdlib/crates/env/src/lib.rs index 1243de59..9487248e 100644 --- a/stdlib/crates/env/src/lib.rs +++ b/stdlib/crates/env/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::Result; +use lk_core::util::value_map::value_map_new; use lk_core::{ - util::fast_map::fast_hash_map_new, val::{HeapValue, RuntimeVal, TypedMap}, vm::{NativeArgs, NativeRuntime}, }; @@ -46,7 +46,7 @@ impl EnvModule { #[stdlib_export(name = "vars", params(), returns = Map, docs = "Returns all environment variables as a map.")] fn vars(_args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let mut map = fast_hash_map_new(); + let mut map = value_map_new(); for (key, value) in std::env::vars_os() { let key = key.to_string_lossy(); let value = value.to_string_lossy(); diff --git a/stdlib/crates/fs/src/lib.rs b/stdlib/crates/fs/src/lib.rs index 7876dadd..84f055bd 100644 --- a/stdlib/crates/fs/src/lib.rs +++ b/stdlib/crates/fs/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::{Result, anyhow}; +use lk_core::util::value_map::value_map_new; use lk_core::{ - util::fast_map::fast_hash_map_new, val::{HeapStore, HeapValue, RuntimeVal, TypedList, TypedMap}, vm::{NativeArgs, NativeRuntime}, }; @@ -74,7 +74,7 @@ impl FsModule { fn metadata(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let path = path_arg(args.get(0).expect("checked arity"), runtime, "fs.metadata path")?; let meta = std::fs::metadata(path.as_ref()).map_err(|err| anyhow!("failed to stat '{}': {err}", path))?; - let mut map = fast_hash_map_new(); + let mut map = value_map_new(); map.insert(Arc::::from("len"), RuntimeVal::Int(meta.len() as i64)); map.insert(Arc::::from("is_file"), RuntimeVal::Bool(meta.is_file())); map.insert(Arc::::from("is_dir"), RuntimeVal::Bool(meta.is_dir())); @@ -87,7 +87,7 @@ impl FsModule { )) } - #[stdlib_export(params(path: String), returns = List[String])] + #[stdlib_export(params(path: String), returns = List)] fn read_dir(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let path = path_arg(args.get(0).expect("checked arity"), runtime, "fs.read_dir path")?; let mut entries = Vec::new(); @@ -138,7 +138,7 @@ impl FsModule { remove_path(path.as_ref(), |path| std::fs::remove_dir_all(path)) } - #[stdlib_export(params(from: String, to: String), returns = Bool)] + #[stdlib_export(params(from: String, to: String), named(to), returns = Bool)] fn rename(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let from = path_arg(args.get(0).expect("checked arity"), runtime, "fs.rename from")?; let to = path_arg(args.get(1).expect("checked arity"), runtime, "fs.rename to")?; @@ -146,7 +146,7 @@ impl FsModule { Ok(RuntimeVal::Bool(true)) } - #[stdlib_export(params(from: String, to: String), returns = Int)] + #[stdlib_export(params(from: String, to: String), named(to), returns = Int)] fn copy(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let from = path_arg(args.get(0).expect("checked arity"), runtime, "fs.copy from")?; let to = path_arg(args.get(1).expect("checked arity"), runtime, "fs.copy to")?; diff --git a/stdlib/crates/http/src/lib.rs b/stdlib/crates/http/src/lib.rs index 0f4c0e4f..1f767a73 100644 --- a/stdlib/crates/http/src/lib.rs +++ b/stdlib/crates/http/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::{Result, anyhow, bail}; +use lk_core::util::value_map::value_map_new; use lk_core::{ - util::fast_map::fast_hash_map_new, val::{HeapValue, RuntimeVal, TypedMap}, vm::{NativeArgs, NativeRuntime}, }; @@ -16,7 +16,7 @@ pub struct HttpModule; #[lk_stdlib_common::stdlib_exports(module = "http")] impl HttpModule { - #[stdlib_export(params(method: String, url: String, opts?: Map), returns = Map, docs = "Sends an HTTP request and returns a response map.")] + #[stdlib_export(params(method: String, url: String, opts?: Map<_, _>), returns = Map, docs = "Sends an HTTP request and returns a response map.")] fn request(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { if args.len() < 2 || args.len() > 3 { bail!("http.request() expects 2 or 3 arguments: method, url[, opts]"); @@ -31,7 +31,7 @@ impl HttpModule { send_request(method.as_ref(), url.as_ref(), opts, None, runtime) } - #[stdlib_export(params(url: String, opts?: Map), returns = Map)] + #[stdlib_export(params(url: String, opts?: Map<_, _>), returns = Map)] fn get(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { if args.is_empty() || args.len() > 2 { bail!("http.get() expects 1 or 2 arguments: url[, opts]"); @@ -40,7 +40,7 @@ impl HttpModule { send_request("GET", url.as_ref(), args.get(1), None, runtime) } - #[stdlib_export(params(url: String, body: Bytes | String, opts?: Map), returns = Map)] + #[stdlib_export(params(url: String, body: Bytes | String, opts?: Map<_, _>), returns = Map)] fn post(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { if args.len() < 2 || args.len() > 3 { bail!("http.post() expects 2 or 3 arguments: url, body[, opts]"); @@ -79,7 +79,7 @@ fn send_request( fn response_map(response: ureq::Response, runtime: &mut NativeRuntime<'_>) -> Result { let status = response.status() as i64; - let mut headers = fast_hash_map_new(); + let mut headers = value_map_new(); for name in response.headers_names() { if let Some(value) = response.header(&name) { headers.insert(Arc::::from(name), runtime_string_value(value, runtime.heap_mut())); @@ -94,7 +94,7 @@ fn response_map(response: ureq::Response, runtime: &mut NativeRuntime<'_>) -> Re bail!("http response body exceeds {MAX_BODY_BYTES} bytes"); } let headers = RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::Map(TypedMap::StringMixed(headers)))); - let mut map = fast_hash_map_new(); + let mut map = value_map_new(); map.insert(Arc::::from("status"), RuntimeVal::Int(status)); map.insert(Arc::::from("headers"), headers); map.insert(Arc::::from("body"), runtime_bytes_value(body, runtime.heap_mut())); diff --git a/stdlib/crates/iter/src/lib.rs b/stdlib/crates/iter/src/lib.rs index feba3459..4651d070 100644 --- a/stdlib/crates/iter/src/lib.rs +++ b/stdlib/crates/iter/src/lib.rs @@ -17,15 +17,10 @@ use alloc::{ vec::Vec, }; -use alloc::sync::Arc; - use anyhow::{Result, anyhow, bail}; use lk_core::{ - val::{CallableValue, HeapStore, HeapValue, RuntimeMapKey, RuntimeVal, ShortStr, TypedList, TypedMap}, - vm::{ - NativeArgs, NativeEntry, NativeFunction, NativeRuntime, call_runtime_callable_runtime, - call_runtime_value_runtime, - }, + val::{HeapStore, HeapValue, RuntimeVal, TypedList}, + vm::{NativeArgs, NativeRuntime}, }; pub mod runtime_native { @@ -39,88 +34,46 @@ pub struct IterModule; #[lk_stdlib_common::stdlib_exports(module = "iter")] impl IterModule { - #[stdlib_export(params(values: List, f: Fn), returns = List, kind = "full_state")] + // `List | Slice | Bytes`, because a window and a `Bytes` have elements too and + // this forwards to the method that reads them. Only the exports whose result + // does *not* depend on which sequence came in can widen: `take` on a `Bytes` + // answers `Bytes`, which no single declared return type can say. + #[stdlib_export(params(values: List<_> | Slice<_> | Bytes, f: Fn), returns = List, kind = "full_state")] fn map(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let input = list_snapshot_arg(&values[0], runtime.heap(), "iter.map first argument")?; - let mut out = Vec::with_capacity(input.len()); - // Accumulated callback results are host-held Rust values: pin each one - // or a GC inside the next callback frees it (host_roots discipline). - let mark = runtime.host_roots_mark(); - let run = input.for_each_item(|item| { - let value = item.into_runtime_value(runtime.heap_mut()); - let result = call_callable(&values[1], &[value], runtime, "iter.map second argument")?; - runtime.host_root_push(result); - out.push(result); - Ok(()) - }); - runtime.host_roots_truncate(mark); - run?; - runtime_list(out, runtime.heap_mut()) + forward("map", args, runtime) } - #[stdlib_export(params(values: List, predicate: Fn), returns = List, kind = "full_state")] + #[stdlib_export(params(values: List<_>, predicate: Fn), returns = List, kind = "full_state")] fn filter(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let input = list_snapshot_arg(&values[0], runtime.heap(), "iter.filter first argument")?; - let mut out = Vec::with_capacity(input.len()); - // Kept items can be fresh heap objects materialized off the snapshot - // (long strings) — pin them across the remaining predicate callbacks. - let mark = runtime.host_roots_mark(); - let run = input.for_each_item(|item| { - let value = item.into_runtime_value(runtime.heap_mut()); - let keep = call_callable( - &values[1], - core::slice::from_ref(&value), - runtime, - "iter.filter second argument", - )?; - if truthy(&keep) { - runtime.host_root_push(value); - out.push(value); - } - Ok(()) - }); - runtime.host_roots_truncate(mark); - run?; - runtime_list(out, runtime.heap_mut()) + forward("filter", args, runtime) + } + + // The three reductions, forwarded like the rest: the module spelling is the + // method with the receiver written first, and a method that had no module + // spelling would be the kind of half-surface this module exists to avoid. + #[stdlib_export(params(values: List<_> | Slice<_> | Bytes), returns = Any, kind = "full_state")] + fn min(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("min", args, runtime) + } + + #[stdlib_export(params(values: List<_> | Slice<_> | Bytes), returns = Any, kind = "full_state")] + fn max(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("max", args, runtime) + } + + #[stdlib_export(params(values: List<_> | Slice<_> | Bytes), returns = Any, kind = "full_state")] + fn sum(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("sum", args, runtime) } - #[stdlib_export(params(values: List, initial: Any, f: Fn), returns = Any, kind = "full_state")] + #[stdlib_export(params(values: List<_> | Slice<_> | Bytes, initial: Any, f: Fn), returns = Any, kind = "full_state")] fn reduce(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let input = list_snapshot_arg(&values[0], runtime.heap(), "iter.reduce first argument")?; - let mut acc = values[1]; - input.for_each_item(|item| { - let value = item.into_runtime_value(runtime.heap_mut()); - let previous = core::mem::replace(&mut acc, RuntimeVal::Nil); - // Pin the accumulator only for the callback that consumes it - // (per-iteration mark/truncate keeps `host_roots` O(1)). - let iteration_mark = runtime.host_roots_mark(); - runtime.host_root_push(previous); - let result = call_callable(&values[2], &[previous, value], runtime, "iter.reduce third argument"); - runtime.host_roots_truncate(iteration_mark); - acc = result?; - Ok(()) - })?; - Ok(acc) + forward("reduce", args, runtime) } - #[stdlib_export(params(values: List), returns = List)] + #[stdlib_export(params(values: List<_>), returns = List, kind = "full_state")] fn enumerate(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let input = list_snapshot_arg(&args.as_slice()[0], runtime.heap(), "iter.enumerate")?; - let mut out = Vec::with_capacity(input.len()); - let mut index = 0usize; - input.for_each_item(|item| { - let value = item.into_runtime_value(runtime.heap_mut()); - out.push(runtime_list( - vec![RuntimeVal::Int(index as i64), value], - runtime.heap_mut(), - )?); - index += 1; - Ok(()) - })?; - runtime_list(out, runtime.heap_mut()) + forward("enumerate", args, runtime) } #[stdlib_export(params(stop: Int; start: Int, stop: Int, step?: Int), returns = List)] @@ -158,120 +111,78 @@ impl IterModule { )) } - #[stdlib_export(params(left: List, right: List), returns = List)] + #[stdlib_export(params(left: List<_>, right: List<_>), named(right), returns = List, kind = "full_state")] fn zip(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let pairs = { - let left = typed_list_arg_ref(&values[0], runtime.heap(), "iter.zip first argument")?; - let right = typed_list_arg_ref(&values[1], runtime.heap(), "iter.zip second argument")?; - let count = left.len().min(right.len()); - let mut pairs = Vec::with_capacity(count); - for index in 0..count { - let left = typed_list_item_snapshot(left, index).expect("index bounded by count"); - let right = typed_list_item_snapshot(right, index).expect("index bounded by count"); - pairs.push((left, right)); - } - pairs - }; - let mut out = Vec::with_capacity(pairs.len()); - for (left, right) in pairs { - let left = left.into_runtime_value(runtime.heap_mut()); - let right = right.into_runtime_value(runtime.heap_mut()); - out.push(runtime_list(vec![left, right], runtime.heap_mut())?); - } - runtime_list(out, runtime.heap_mut()) + forward("zip", args, runtime) } - #[stdlib_export(params(values: List, count: Int), returns = List)] + #[stdlib_export(params(values: List<_>, count: Int), returns = List, kind = "full_state")] fn take(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let n = count_arg(&values[1], "iter.take count")?; - list_slice(&values[0], runtime.heap_mut(), 0, Some(n), "iter.take first argument") + forward("take", args, runtime) } - #[stdlib_export(params(values: List, count: Int), returns = List)] + #[stdlib_export(params(values: List<_>, count: Int), returns = List, kind = "full_state")] fn skip(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let n = count_arg(&values[1], "iter.skip count")?; - list_slice(&values[0], runtime.heap_mut(), n, None, "iter.skip first argument") + forward("skip", args, runtime) } - #[stdlib_export(params(left: List, right: List), returns = List)] + #[stdlib_export(params(left: List<_>, right: List<_>), named(right), returns = List, kind = "full_state")] fn chain(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let plan = typed_list_concat_preserving_backing( - typed_list_arg_ref(&values[0], runtime.heap(), "iter.chain first argument")?, - typed_list_arg_ref(&values[1], runtime.heap(), "iter.chain second argument")?, - ); - let list = plan.into_typed(runtime.heap_mut()); - Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(list)))) + forward("chain", args, runtime) } - #[stdlib_export(params(values: List), returns = List)] + #[stdlib_export(params(values: List<_>), returns = List, kind = "full_state")] fn flatten(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let plan = flatten_typed_list( - typed_list_arg_ref(&args.as_slice()[0], runtime.heap(), "iter.flatten")?, - runtime.heap(), - )?; - let list = plan.into_typed(runtime.heap_mut()); - Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(list)))) + forward("flatten", args, runtime) } - #[stdlib_export(params(values: List), returns = List)] + #[stdlib_export(params(values: List<_>), returns = List, kind = "full_state")] fn unique(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let input = typed_list_arg_ref(&args.as_slice()[0], runtime.heap(), "iter.unique")?; - let list = unique_typed_list(input, runtime.heap()); - Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(list)))) + forward("unique", args, runtime) } - #[stdlib_export(params(values: List, size: Int), returns = List)] + #[stdlib_export(params(values: List<_>, size: Int), returns = List, kind = "full_state")] fn chunk(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let size = count_arg(&values[1], "iter.chunk size")?; - if size == 0 { - bail!("iter.chunk size must be positive"); - } - let chunks = { - let input = typed_list_arg_ref(&values[0], runtime.heap(), "iter.chunk first argument")?; - let mut chunks = Vec::new(); - for start in (0..input.len()).step_by(size) { - chunks.push(typed_list_slice(input, start, Some(size))); - } - chunks - }; - let mut out = Vec::with_capacity(chunks.len()); - for chunk in chunks { - out.push(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(chunk)))); - } - runtime_list(out, runtime.heap_mut()) + forward("chunk", args, runtime) } - #[stdlib_export(params(values: List), returns = Any)] + /// `iter.next(xs)` is `xs.first()` — the name is the iterator vocabulary, + /// the operation is the list one. + #[stdlib_export(params(values: List<_> | Slice<_> | Bytes), returns = Any, kind = "full_state")] fn next(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - first_list_item(&args.as_slice()[0], runtime.heap_mut(), "iter.next") + forward("first", args, runtime) } - #[stdlib_export(params(values: List), returns = List)] + #[stdlib_export(params(values: List<_>), returns = List)] fn collect(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let input = typed_list_arg_ref(&args.as_slice()[0], runtime.heap(), "iter.collect")?; - let input = copy_typed_list(input); - Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(input)))) - } -} - -fn maybe_typed_list_arg_ref<'a>(value: &RuntimeVal, heap: &'a HeapStore) -> Result> { - let RuntimeVal::Obj(handle) = value else { - return Ok(None); + // The one export with no method behind it: a list *is* the iterator + // here, so "collect" means "copy", and no list method spells that. + let copied = { + let input = typed_list_arg_ref(&args.as_slice()[0], runtime.heap(), "iter.collect")?; + input.window(0, input.len()) + }; + Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(copied)))) + } +} + +/// Call the built-in method `method` on the first argument, passing the rest. +/// +/// Every `iter.f(xs, ...)` above is defined as `xs.f(...)`, which is the whole +/// point of this module now: the module form is a *spelling* of the method +/// form, not a second implementation of it. The two used to be written out +/// separately — 14 exports' worth of snapshotting, truthiness, host-root +/// pinning and result-list construction, each of which had to be kept in step +/// with `core_methods` by whoever remembered. They agreed, when this was +/// written, on every case tested; `take(-1)` was the one that did not, and it +/// took a deliberate comparison to find. +fn forward(method: &'static str, args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + let values = args.as_slice(); + let Some((receiver, rest)) = values.split_first() else { + bail!("iter.{method} expects a list as its first argument"); }; - let value = heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?; - match value { - HeapValue::List(list) => Ok(Some(list)), - _ => Ok(None), - } + lk_core::vm::core_call_method_windowed(*receiver, method, rest, runtime) } - +/// The one argument check every export shares: the receiver must be a list. fn typed_list_arg_ref<'a>(value: &RuntimeVal, heap: &'a HeapStore, context: &str) -> Result<&'a TypedList> { let RuntimeVal::Obj(handle) = value else { bail!("{context} expects a list"); @@ -285,1099 +196,9 @@ fn typed_list_arg_ref<'a>(value: &RuntimeVal, heap: &'a HeapStore, context: &str } } -fn list_snapshot_arg(value: &RuntimeVal, heap: &HeapStore, context: &str) -> Result { - Ok(RuntimeListSnapshot::from_typed(typed_list_arg_ref( - value, heap, context, - )?)) -} - -fn copy_typed_list(list: &TypedList) -> TypedList { - match list { - TypedList::Mixed(values) => TypedList::Mixed(copy_slice(values)), - TypedList::Int(values) => TypedList::Int(copy_slice(values)), - TypedList::Float(values) => TypedList::Float(copy_slice(values)), - TypedList::Bool(values) => TypedList::Bool(copy_slice(values)), - TypedList::String(values) => TypedList::String(copy_slice(values)), - } -} - -fn copy_slice(values: &[T]) -> Vec { - let mut out = Vec::with_capacity(values.len()); - out.extend_from_slice(values); - out -} - -fn first_list_item(value: &RuntimeVal, heap: &mut HeapStore, context: &str) -> Result { - let RuntimeVal::Obj(handle) = value else { - bail!("{context} expects a list"); - }; - let value = heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?; - let HeapValue::List(list) = value else { - bail!("{context} expects a list"); - }; - let string = match list { - TypedList::Mixed(values) => return Ok(values.first().cloned().unwrap_or(RuntimeVal::Nil)), - TypedList::Int(values) => return Ok(values.first().copied().map(RuntimeVal::Int).unwrap_or(RuntimeVal::Nil)), - TypedList::Float(values) => { - return Ok(values - .first() - .copied() - .map(RuntimeVal::Float) - .unwrap_or(RuntimeVal::Nil)); - } - TypedList::Bool(values) => return Ok(values.first().copied().map(RuntimeVal::Bool).unwrap_or(RuntimeVal::Nil)), - TypedList::String(values) => { - let Some(value) = values.first() else { - return Ok(RuntimeVal::Nil); - }; - if let Some(short) = ShortStr::new(value) { - return Ok(RuntimeVal::ShortStr(short)); - } - value.clone() - } - }; - Ok(RuntimeVal::Obj(heap.alloc(HeapValue::String(string)))) -} - -enum RuntimeListItemSnapshot { - Value(RuntimeVal), - String(Arc), -} - -impl RuntimeListItemSnapshot { - fn into_runtime_value(self, heap: &mut HeapStore) -> RuntimeVal { - match self { - Self::Value(value) => value, - Self::String(value) => { - if let Some(short) = ShortStr::new(&value) { - RuntimeVal::ShortStr(short) - } else { - RuntimeVal::Obj(heap.alloc(HeapValue::String(value))) - } - } - } - } -} - -fn typed_list_item_snapshot(list: &TypedList, index: usize) -> Option { - Some(match list { - TypedList::Mixed(values) => RuntimeListItemSnapshot::Value(*values.get(index)?), - TypedList::Int(values) => RuntimeListItemSnapshot::Value(RuntimeVal::Int(*values.get(index)?)), - TypedList::Float(values) => RuntimeListItemSnapshot::Value(RuntimeVal::Float(*values.get(index)?)), - TypedList::Bool(values) => RuntimeListItemSnapshot::Value(RuntimeVal::Bool(*values.get(index)?)), - TypedList::String(values) => RuntimeListItemSnapshot::String(Arc::clone(values.get(index)?)), - }) -} - -fn list_slice( - value: &RuntimeVal, - heap: &mut HeapStore, - start: usize, - limit: Option, - context: &str, -) -> Result { - let RuntimeVal::Obj(handle) = value else { - bail!("{context} expects a list"); - }; - let list = { - let value = heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?; - let HeapValue::List(list) = value else { - bail!("{context} expects a list"); - }; - typed_list_slice(list, start, limit) - }; - Ok(RuntimeVal::Obj(heap.alloc(HeapValue::List(list)))) -} - -fn typed_list_slice(list: &TypedList, start: usize, limit: Option) -> TypedList { - let len = list.len(); - let start = start.min(len); - let end = limit.map_or(len, |limit| start.saturating_add(limit).min(len)); - match list { - TypedList::Mixed(values) => TypedList::Mixed(copy_slice(&values[start..end])), - TypedList::Int(values) => TypedList::Int(copy_slice(&values[start..end])), - TypedList::Float(values) => TypedList::Float(copy_slice(&values[start..end])), - TypedList::Bool(values) => TypedList::Bool(copy_slice(&values[start..end])), - TypedList::String(values) => TypedList::String(copy_slice(&values[start..end])), - } -} - -enum RuntimeListSnapshot { - Mixed(Vec), - Int(Vec), - Float(Vec), - Bool(Vec), - String(Vec>), -} - -impl RuntimeListSnapshot { - fn from_typed(list: &TypedList) -> Self { - match list { - TypedList::Mixed(values) => Self::Mixed(copy_slice(values)), - TypedList::Int(values) => Self::Int(copy_slice(values)), - TypedList::Float(values) => Self::Float(copy_slice(values)), - TypedList::Bool(values) => Self::Bool(copy_slice(values)), - TypedList::String(values) => Self::String(copy_slice(values)), - } - } - - fn len(&self) -> usize { - match self { - Self::Mixed(values) => values.len(), - Self::Int(values) => values.len(), - Self::Float(values) => values.len(), - Self::Bool(values) => values.len(), - Self::String(values) => values.len(), - } - } - - fn for_each_item(self, mut f: impl FnMut(RuntimeListItemSnapshot) -> Result<()>) -> Result<()> { - match self { - Self::Mixed(values) => { - for value in values { - f(RuntimeListItemSnapshot::Value(value))?; - } - } - Self::Int(values) => { - for value in values { - f(RuntimeListItemSnapshot::Value(RuntimeVal::Int(value)))?; - } - } - Self::Float(values) => { - for value in values { - f(RuntimeListItemSnapshot::Value(RuntimeVal::Float(value)))?; - } - } - Self::Bool(values) => { - for value in values { - f(RuntimeListItemSnapshot::Value(RuntimeVal::Bool(value)))?; - } - } - Self::String(values) => { - for value in values { - f(RuntimeListItemSnapshot::String(value))?; - } - } - } - Ok(()) - } - - fn into_typed(self) -> TypedList { - match self { - Self::Mixed(values) => TypedList::Mixed(values), - Self::Int(values) => TypedList::Int(values), - Self::Float(values) => TypedList::Float(values), - Self::Bool(values) => TypedList::Bool(values), - Self::String(values) => TypedList::String(values), - } - } - - fn append_to_mixed_output(self, out: &mut Vec, heap: &mut HeapStore) { - match self { - Self::Mixed(values) => out.extend(values), - Self::Int(values) => out.extend(values.into_iter().map(RuntimeVal::Int)), - Self::Float(values) => out.extend(values.into_iter().map(RuntimeVal::Float)), - Self::Bool(values) => out.extend(values.into_iter().map(RuntimeVal::Bool)), - Self::String(values) => out.extend(values.into_iter().map(|value| { - if let Some(short) = ShortStr::new(&value) { - RuntimeVal::ShortStr(short) - } else { - RuntimeVal::Obj(heap.alloc(HeapValue::String(value))) - } - })), - } - } -} - -enum ListConcatPlan { - Ready(TypedList), - Mixed { - left: RuntimeListSnapshot, - right: RuntimeListSnapshot, - }, -} - -impl ListConcatPlan { - fn into_typed(self, heap: &mut HeapStore) -> TypedList { - match self { - Self::Ready(list) => list, - Self::Mixed { left, right } => { - let mut values = Vec::with_capacity(left.len() + right.len()); - left.append_to_mixed_output(&mut values, heap); - right.append_to_mixed_output(&mut values, heap); - TypedList::Mixed(values) - } - } - } -} - -fn typed_list_concat_preserving_backing(left: &TypedList, right: &TypedList) -> ListConcatPlan { - match (left, right) { - (TypedList::Int(left), TypedList::Int(right)) => { - ListConcatPlan::Ready(TypedList::Int(copy_concat(left, right))) - } - (TypedList::Float(left), TypedList::Float(right)) => { - ListConcatPlan::Ready(TypedList::Float(copy_concat(left, right))) - } - (TypedList::Bool(left), TypedList::Bool(right)) => { - ListConcatPlan::Ready(TypedList::Bool(copy_concat(left, right))) - } - (TypedList::String(left), TypedList::String(right)) => { - ListConcatPlan::Ready(TypedList::String(copy_concat(left, right))) - } - (left, right) => ListConcatPlan::Mixed { - left: RuntimeListSnapshot::from_typed(left), - right: RuntimeListSnapshot::from_typed(right), - }, - } -} - -fn copy_concat(left: &[T], right: &[T]) -> Vec { - let mut out = Vec::with_capacity(left.len() + right.len()); - out.extend_from_slice(left); - out.extend_from_slice(right); - out -} - -enum FlattenItem { - List(RuntimeListSnapshot), - Value(RuntimeVal), -} - -enum FlattenPlan { - Ready(TypedList), - Items(Vec), -} - -impl FlattenPlan { - fn into_typed(self, heap: &mut HeapStore) -> TypedList { - match self { - Self::Ready(list) => list, - Self::Items(items) => flatten_items_into_typed(items, heap), - } - } -} - -fn flatten_typed_list(input: &TypedList, heap: &HeapStore) -> Result { - let TypedList::Mixed(values) = input else { - return Ok(FlattenPlan::Ready(RuntimeListSnapshot::from_typed(input).into_typed())); - }; - let mut items = Vec::with_capacity(values.len()); - for value in values.iter() { - if let Some(list) = maybe_typed_list_arg_ref(value, heap)? { - items.push(FlattenItem::List(RuntimeListSnapshot::from_typed(list))); - } else { - items.push(FlattenItem::Value(*value)); - } - } - Ok(FlattenPlan::Items(items)) -} - -fn flatten_items_into_typed(items: Vec, heap: &mut HeapStore) -> TypedList { - let mut typed_out: Option = None; - let mut mixed_out: Option> = None; - for item in items { - match item { - FlattenItem::List(list) => { - if let Some(out) = mixed_out.as_mut() { - list.append_to_mixed_output(out, heap); - } else { - typed_out = Some(match typed_out.take() { - Some(current) => concat_list_snapshots(current, list, heap), - None => list, - }); - } - } - FlattenItem::Value(value) => { - let out = mixed_out.get_or_insert_with(|| { - let Some(list) = typed_out.take() else { - return Vec::new(); - }; - let mut values = Vec::with_capacity(list.len()); - list.append_to_mixed_output(&mut values, heap); - values - }); - out.push(value); - } - } - } - match mixed_out { - Some(values) => crate::typed_list_from_values(values, heap), - None => typed_out - .map(RuntimeListSnapshot::into_typed) - .unwrap_or_else(|| TypedList::Mixed(Vec::new())), - } -} - -fn concat_list_snapshots( - left: RuntimeListSnapshot, - right: RuntimeListSnapshot, - heap: &mut HeapStore, -) -> RuntimeListSnapshot { - match (left, right) { - (RuntimeListSnapshot::Int(left), RuntimeListSnapshot::Int(right)) => { - RuntimeListSnapshot::Int(copy_concat_owned(left, right)) - } - (RuntimeListSnapshot::Float(left), RuntimeListSnapshot::Float(right)) => { - RuntimeListSnapshot::Float(copy_concat_owned(left, right)) - } - (RuntimeListSnapshot::Bool(left), RuntimeListSnapshot::Bool(right)) => { - RuntimeListSnapshot::Bool(copy_concat_owned(left, right)) - } - (RuntimeListSnapshot::String(left), RuntimeListSnapshot::String(right)) => { - RuntimeListSnapshot::String(copy_concat_owned(left, right)) - } - (left, right) => { - let mut values = Vec::with_capacity(left.len() + right.len()); - left.append_to_mixed_output(&mut values, heap); - right.append_to_mixed_output(&mut values, heap); - RuntimeListSnapshot::Mixed(values) - } - } -} - -fn copy_concat_owned(left: Vec, right: Vec) -> Vec { - let mut out = Vec::with_capacity(left.len() + right.len()); - out.extend(left); - out.extend(right); - out -} - -fn unique_typed_list(input: &TypedList, heap: &HeapStore) -> TypedList { - match input { - TypedList::Mixed(values) => unique_mixed_values(values, heap), - TypedList::Int(values) => TypedList::Int(unique_copy_values(values)), - TypedList::Float(values) => TypedList::Float(unique_copy_values(values)), - TypedList::Bool(values) => TypedList::Bool(unique_copy_values(values)), - TypedList::String(values) => TypedList::String(unique_arc_values(values)), - } -} - -fn unique_mixed_values(values: &[RuntimeVal], heap: &HeapStore) -> TypedList { - let mut out: Vec = Vec::with_capacity(values.len()); - for value in values { - if !out.iter().any(|existing| runtime_values_equal(existing, value, heap)) { - out.push(*value); - } - } - crate::typed_list_from_values(out, heap) -} - -fn unique_copy_values(values: &[T]) -> Vec -where - T: Copy + PartialEq, -{ - let mut out = Vec::with_capacity(values.len()); - for value in values.iter().copied() { - if !out.contains(&value) { - out.push(value); - } - } - out -} - -fn unique_arc_values(values: &[alloc::sync::Arc]) -> Vec> { - let mut out = Vec::with_capacity(values.len()); - for value in values { - if !out - .iter() - .any(|existing: &alloc::sync::Arc| existing.as_ref() == value.as_ref()) - { - out.push(Arc::clone(value)); - } - } - out -} - -fn runtime_list(values: Vec, heap: &mut HeapStore) -> Result { - let list = crate::typed_list_from_values(values, heap); - Ok(RuntimeVal::Obj(heap.alloc(HeapValue::List(list)))) -} - fn int_arg(value: &RuntimeVal, context: &str) -> Result { match value { RuntimeVal::Int(value) => Ok(*value), _ => Err(anyhow!("{context} must be an integer")), } } - -fn count_arg(value: &RuntimeVal, context: &str) -> Result { - let value = int_arg(value, context)?; - if value < 0 { - bail!("{context} must be non-negative"); - } - usize::try_from(value).map_err(|_| anyhow!("{context} is too large")) -} - -fn truthy(value: &RuntimeVal) -> bool { - !matches!(value, RuntimeVal::Nil | RuntimeVal::Bool(false)) -} - -fn call_callable( - callable_value: &RuntimeVal, - args: &[RuntimeVal], - runtime: &mut NativeRuntime<'_>, - context: &str, -) -> Result { - let RuntimeVal::Obj(handle) = callable_value else { - bail!("{context} must be callable"); - }; - - enum IterCallableTarget { - Runtime(Arc), - Closure, - RuntimeNative { arity: u16, function: NativeFunction }, - } - - let target = match runtime - .heap() - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::Callable(CallableValue::Runtime(function)) => IterCallableTarget::Runtime(Arc::clone(function)), - HeapValue::Callable(CallableValue::Closure { .. }) => IterCallableTarget::Closure, - HeapValue::Callable(CallableValue::RuntimeNative { arity, function, .. }) => { - IterCallableTarget::RuntimeNative { - arity: *arity, - function: function.clone(), - } - } - _ => bail!("{context} must be callable"), - }; - - match target { - IterCallableTarget::Runtime(function) => { - let (heap, ctx) = runtime.heap_ctx_mut(); - call_runtime_callable_runtime(function.as_ref(), args, heap, ctx) - } - IterCallableTarget::Closure => { - if let Some((state, ctx, module)) = runtime.state_ctx_module_mut() { - return call_runtime_value_runtime(RuntimeVal::Obj(*handle), args, state, module, ctx); - } - bail!("{context} closure requires active RuntimeModuleState") - } - IterCallableTarget::RuntimeNative { arity, function } => { - let entry = NativeEntry { - name: context.to_string(), - arity, - function, - }; - if !entry.accepts_arity(args.len() as u16) { - bail!("{context} expects {arity} arguments, got {}", args.len()); - } - call_runtime_native_entry(&entry, args, runtime) - } - } -} - -fn call_runtime_native_entry( - entry: &NativeEntry, - args: &[RuntimeVal], - runtime: &mut NativeRuntime<'_>, -) -> Result { - match &entry.function { - NativeFunction::Plain(function) | NativeFunction::Context(function) | NativeFunction::FullState(function) => { - function(NativeArgs::new(args), runtime) - } - NativeFunction::Closure(function) => function(NativeArgs::new(args), runtime), - } -} - -fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore) -> bool { - if left == right { - return true; - } - let (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) = (left, right) else { - return false; - }; - let (Some(left), Some(right)) = (heap.get(*left), heap.get(*right)) else { - return false; - }; - match (left, right) { - (HeapValue::String(left), HeapValue::String(right)) => left == right, - (HeapValue::List(left), HeapValue::List(right)) => runtime_lists_equal(left, right, heap), - (HeapValue::Map(left), HeapValue::Map(right)) => runtime_maps_equal(left, right, heap), - _ => false, - } -} - -fn runtime_maps_equal(left: &TypedMap, right: &TypedMap, heap: &HeapStore) -> bool { - if left.len() != right.len() { - return false; - } - match left { - TypedMap::Mixed(entries) => entries - .iter() - .all(|(key, value)| runtime_map_value_equal(right, key, value, heap)), - TypedMap::StringMixed(entries) => entries - .iter() - .all(|(key, value)| runtime_map_value_equal(right, &RuntimeMapKey::String(key.clone()), value, heap)), - TypedMap::StringInt(entries) => entries.iter().all(|(key, value)| { - runtime_map_value_equal( - right, - &RuntimeMapKey::String(key.clone()), - &RuntimeVal::Int(*value), - heap, - ) - }), - TypedMap::StringFloat(entries) => entries.iter().all(|(key, value)| { - runtime_map_value_equal( - right, - &RuntimeMapKey::String(key.clone()), - &RuntimeVal::Float(*value), - heap, - ) - }), - TypedMap::StringBool(entries) => entries.iter().all(|(key, value)| { - runtime_map_value_equal( - right, - &RuntimeMapKey::String(key.clone()), - &RuntimeVal::Bool(*value), - heap, - ) - }), - } -} - -fn runtime_map_value_equal(right: &TypedMap, key: &RuntimeMapKey, left: &RuntimeVal, heap: &HeapStore) -> bool { - right - .get(key) - .is_some_and(|right| runtime_values_equal(left, &right, heap)) -} - -fn runtime_lists_equal(left: &TypedList, right: &TypedList, heap: &HeapStore) -> bool { - if left.len() != right.len() { - return false; - } - match (left, right) { - (TypedList::Int(left), TypedList::Int(right)) => return left == right, - (TypedList::Float(left), TypedList::Float(right)) => return left == right, - (TypedList::Bool(left), TypedList::Bool(right)) => return left == right, - (TypedList::String(left), TypedList::String(right)) => return left == right, - _ => {} - } - (0..left.len()).all(|index| runtime_list_items_equal(left, index, right, index, heap)) -} - -fn runtime_list_items_equal( - left: &TypedList, - left_index: usize, - right: &TypedList, - right_index: usize, - heap: &HeapStore, -) -> bool { - match (left, right) { - (TypedList::Mixed(left), TypedList::Mixed(right)) => { - runtime_values_equal(&left[left_index], &right[right_index], heap) - } - (TypedList::Mixed(left), TypedList::String(right)) => { - runtime_value_equals_string(&left[left_index], &right[right_index], heap) - } - (TypedList::String(left), TypedList::Mixed(right)) => { - runtime_value_equals_string(&right[right_index], &left[left_index], heap) - } - (TypedList::Int(left), _) => { - runtime_list_runtime_item_equal(RuntimeVal::Int(left[left_index]), right, right_index, heap) - } - (TypedList::Float(left), _) => { - runtime_list_runtime_item_equal(RuntimeVal::Float(left[left_index]), right, right_index, heap) - } - (TypedList::Bool(left), _) => { - runtime_list_runtime_item_equal(RuntimeVal::Bool(left[left_index]), right, right_index, heap) - } - (TypedList::String(left), _) => runtime_list_string_item_equal(&left[left_index], right, right_index, heap), - (TypedList::Mixed(left), _) => runtime_list_runtime_item_equal(left[left_index], right, right_index, heap), - } -} - -fn runtime_list_runtime_item_equal(left: RuntimeVal, right: &TypedList, right_index: usize, heap: &HeapStore) -> bool { - match right { - TypedList::Mixed(right) => runtime_values_equal(&left, &right[right_index], heap), - TypedList::Int(right) => runtime_values_equal(&left, &RuntimeVal::Int(right[right_index]), heap), - TypedList::Float(right) => runtime_values_equal(&left, &RuntimeVal::Float(right[right_index]), heap), - TypedList::Bool(right) => runtime_values_equal(&left, &RuntimeVal::Bool(right[right_index]), heap), - TypedList::String(right) => runtime_value_equals_string(&left, &right[right_index], heap), - } -} - -fn runtime_list_string_item_equal(left: &Arc, right: &TypedList, right_index: usize, heap: &HeapStore) -> bool { - match right { - TypedList::Mixed(right) => runtime_value_equals_string(&right[right_index], left, heap), - TypedList::String(right) => left == &right[right_index], - _ => false, - } -} - -fn runtime_value_equals_string(value: &RuntimeVal, expected: &str, heap: &HeapStore) -> bool { - match value { - RuntimeVal::ShortStr(value) => value.as_str() == expected, - RuntimeVal::Obj(handle) => { - matches!(heap.get(*handle), Some(HeapValue::String(value)) if value.as_ref() == expected) - } - _ => false, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use lk_core::vm::ModuleResolver; - use lk_core::vm::ProgramExec; - use lk_core::{ - stmt::stmt_parser::StmtParser, - token::Tokenizer, - vm::{NativeFunction, ProgramResult, RuntimeModuleState, VmContext}, - }; - fn run(source: &str) -> Result { - let tokens = Tokenizer::tokenize(source)?; - let mut parser = StmtParser::new(&tokens); - let program = parser.parse_program()?; - - let mut registry = lk_core::module::ModuleRegistry::new(); - registry.register_module("iter", Box::new(IterModule::new()))?; - let resolver = Arc::new(ModuleResolver::with_registry(registry)); - let mut env = VmContext::new().with_resolver(resolver); - program.execute_with_ctx(&mut env) - } - - fn run_value(source: &str) -> Result { - Ok(*run(source)?.first_return()) - } - - fn expect_list(value: &RuntimeVal, heap: &HeapStore) -> Vec { - let RuntimeVal::Obj(handle) = value else { - panic!("expected runtime list object"); - }; - let Some(HeapValue::List(list)) = heap.get(*handle) else { - panic!("expected runtime list heap value"); - }; - match list { - TypedList::Mixed(values) => values.clone(), - TypedList::Int(values) => values.iter().copied().map(RuntimeVal::Int).collect(), - TypedList::Float(values) => values.iter().copied().map(RuntimeVal::Float).collect(), - TypedList::Bool(values) => values.iter().copied().map(RuntimeVal::Bool).collect(), - TypedList::String(values) => values - .iter() - .map(|value| RuntimeVal::ShortStr(lk_core::val::ShortStr::new(value).expect("short test string"))) - .collect(), - } - } - - fn expect_return_list(result: &ProgramResult) -> Vec { - expect_list(result.first_return(), result.state.heap()) - } - - fn iter_native(name: &str) -> Result<(u16, NativeFunction)> { - crate::runtime_native::runtime_native_export(&IterModule::new(), name) - } - - #[test] - fn iter_exports_use_runtime_native_abi() -> Result<()> { - for name in ["map", "filter", "reduce"] { - let (_, function) = iter_native(name)?; - assert!(matches!(function, NativeFunction::FullState(_))); - } - for name in [ - "enumerate", - "range", - "zip", - "take", - "skip", - "chain", - "flatten", - "unique", - "chunk", - "next", - "collect", - ] { - let (_, function) = iter_native(name)?; - assert!(matches!(function, NativeFunction::Plain(_))); - } - Ok(()) - } - - #[test] - fn iter_sequence_ops_run_on_exec() -> Result<()> { - assert_eq!( - expect_return_list(&run("use iter; return iter.range(0, 6, 2);")?), - vec![RuntimeVal::Int(0), RuntimeVal::Int(2), RuntimeVal::Int(4)] - ); - let result = run("use iter; return iter.zip([1,2], [\"a\",\"b\",\"c\"]);")?; - let zipped = expect_return_list(&result); - assert_eq!(zipped.len(), 2); - assert_eq!( - expect_list(&zipped[0], result.state.heap()), - vec![ - RuntimeVal::Int(1), - RuntimeVal::ShortStr(lk_core::val::ShortStr::new("a").expect("short")) - ] - ); - assert_eq!( - expect_list(&zipped[1], result.state.heap()), - vec![ - RuntimeVal::Int(2), - RuntimeVal::ShortStr(lk_core::val::ShortStr::new("b").expect("short")) - ] - ); - assert_eq!( - expect_return_list(&run( - "use iter; return iter.chain(iter.take([1,2,3], 2), iter.skip([4,5,6], 1));" - )?), - vec![ - RuntimeVal::Int(1), - RuntimeVal::Int(2), - RuntimeVal::Int(5), - RuntimeVal::Int(6) - ] - ); - Ok(()) - } - - #[test] - fn iter_list_shape_ops_run_on_exec() -> Result<()> { - assert_eq!( - expect_return_list(&run( - "use iter; let a = [1,2]; let b = [3]; let c = [4]; return iter.flatten([a,b,c]);" - )?), - vec![ - RuntimeVal::Int(1), - RuntimeVal::Int(2), - RuntimeVal::Int(3), - RuntimeVal::Int(4) - ] - ); - assert_eq!( - expect_return_list(&run("use iter; return iter.unique([1,1,2,2,3]);")?), - vec![RuntimeVal::Int(1), RuntimeVal::Int(2), RuntimeVal::Int(3)] - ); - let result = run("use iter; return iter.chunk([1,2,3,4,5], 2);")?; - let chunks = expect_return_list(&result); - assert_eq!(chunks.len(), 3); - assert_eq!( - expect_list(&chunks[0], result.state.heap()), - vec![RuntimeVal::Int(1), RuntimeVal::Int(2)] - ); - assert_eq!( - expect_list(&chunks[1], result.state.heap()), - vec![RuntimeVal::Int(3), RuntimeVal::Int(4)] - ); - assert_eq!(expect_list(&chunks[2], result.state.heap()), vec![RuntimeVal::Int(5)]); - Ok(()) - } - - #[test] - fn iter_higher_order_ops_call_runtime_closures() -> Result<()> { - assert_eq!( - expect_return_list(&run("use iter; return iter.map([1,2,3], fn(x) => x * 2);")?), - vec![RuntimeVal::Int(2), RuntimeVal::Int(4), RuntimeVal::Int(6)] - ); - assert_eq!( - expect_return_list(&run("use iter; return iter.filter([1,2,3,4], fn(x) => x % 2 == 0);")?), - vec![RuntimeVal::Int(2), RuntimeVal::Int(4)] - ); - assert_eq!( - run_value("use iter; return iter.reduce([1,2,3], 0, fn(acc, x) => acc + x);")?, - RuntimeVal::Int(6) - ); - Ok(()) - } - - #[test] - fn iter_map_materializes_long_string_items_lazily_for_callback() -> Result<()> { - fn fail_on_first(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { - bail!("stop after first item"); - } - - let (_, function) = iter_native("map")?; - let NativeFunction::FullState(function) = function else { - panic!("map must use FullState RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let input = state.heap_mut().alloc(HeapValue::List(TypedList::String(vec![ - Arc::::from("long-map-first"), - Arc::::from("long-map-second"), - ]))); - let callback = state - .heap_mut() - .alloc(HeapValue::Callable(CallableValue::RuntimeNative { - name: Arc::::from("fail_on_first"), - arity: 1, - function: NativeFunction::Plain(fail_on_first), - })); - let args = [RuntimeVal::Obj(input), RuntimeVal::Obj(callback)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let err = function(NativeArgs::new(&args), &mut runtime).expect_err("callback should fail"); - - assert!(err.to_string().contains("stop after first item")); - assert_eq!(runtime.heap().len(), 3); - Ok(()) - } - - #[test] - fn iter_direct_runtime_call_preserves_typed_lists() -> Result<()> { - let (_, function) = iter_native("range")?; - let NativeFunction::Plain(function) = function else { - panic!("range must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let args = [RuntimeVal::Int(1), RuntimeVal::Int(4)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - let result = function(NativeArgs::new(&args), &mut runtime)?; - assert_eq!( - expect_list(&result, runtime.heap()), - vec![RuntimeVal::Int(1), RuntimeVal::Int(2), RuntimeVal::Int(3)] - ); - Ok(()) - } - - #[test] - fn iter_take_skip_slice_typed_string_lists_without_materializing_items() -> Result<()> { - let long = Arc::::from("long-string-value"); - for (name, args) in [ - ("take", [RuntimeVal::Nil, RuntimeVal::Int(1)]), - ("skip", [RuntimeVal::Nil, RuntimeVal::Int(1)]), - ] { - let (_, function) = iter_native(name)?; - let NativeFunction::Plain(function) = function else { - panic!("{name} must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let list = state.heap_mut().alloc(HeapValue::List(TypedList::String(vec![ - Arc::clone(&long), - Arc::::from("tail"), - ]))); - let mut args = args; - args[0] = RuntimeVal::Obj(list); - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let result = function(NativeArgs::new(&args), &mut runtime)?; - - let RuntimeVal::Obj(handle) = result else { - panic!("expected list result"); - }; - let Some(HeapValue::List(TypedList::String(values))) = runtime.heap().get(handle) else { - panic!("expected typed string list result"); - }; - assert_eq!(values.len(), 1); - assert_eq!(runtime.heap().len(), 2); - } - Ok(()) - } - - #[test] - fn iter_chain_preserves_typed_string_backing_without_materializing_items() -> Result<()> { - let (_, function) = iter_native("chain")?; - let NativeFunction::Plain(function) = function else { - panic!("chain must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let left = state - .heap_mut() - .alloc(HeapValue::List(TypedList::String(vec![Arc::::from( - "long-left-value", - )]))); - let right = state - .heap_mut() - .alloc(HeapValue::List(TypedList::String(vec![Arc::::from( - "long-right-value", - )]))); - let args = [RuntimeVal::Obj(left), RuntimeVal::Obj(right)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let result = function(NativeArgs::new(&args), &mut runtime)?; - - let RuntimeVal::Obj(handle) = result else { - panic!("expected list result"); - }; - let Some(HeapValue::List(TypedList::String(values))) = runtime.heap().get(handle) else { - panic!("expected typed string list result"); - }; - assert_eq!(values.len(), 2); - assert_eq!(runtime.heap().len(), 3); - Ok(()) - } - - #[test] - fn iter_chunk_preserves_typed_string_backing_without_materializing_items() -> Result<()> { - let (_, function) = iter_native("chunk")?; - let NativeFunction::Plain(function) = function else { - panic!("chunk must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let input = state.heap_mut().alloc(HeapValue::List(TypedList::String(vec![ - Arc::::from("long-one-value"), - Arc::::from("long-two-value"), - Arc::::from("long-three-value"), - ]))); - let args = [RuntimeVal::Obj(input), RuntimeVal::Int(2)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let result = function(NativeArgs::new(&args), &mut runtime)?; - - let RuntimeVal::Obj(outer) = result else { - panic!("expected outer list"); - }; - let Some(HeapValue::List(TypedList::Mixed(chunks))) = runtime.heap().get(outer) else { - panic!("expected mixed outer list"); - }; - assert_eq!(chunks.len(), 2); - for chunk in chunks { - let RuntimeVal::Obj(handle) = chunk else { - panic!("expected chunk list object"); - }; - assert!(matches!( - runtime.heap().get(*handle), - Some(HeapValue::List(TypedList::String(_))) - )); - } - assert_eq!(runtime.heap().len(), 4); - Ok(()) - } - - #[test] - fn iter_zip_materializes_only_used_long_string_items() -> Result<()> { - let (_, function) = iter_native("zip")?; - let NativeFunction::Plain(function) = function else { - panic!("zip must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let left = state.heap_mut().alloc(HeapValue::List(TypedList::String(vec![ - Arc::::from("long-left-used"), - Arc::::from("long-left-unused"), - ]))); - let right = state - .heap_mut() - .alloc(HeapValue::List(TypedList::String(vec![Arc::::from( - "long-right-used", - )]))); - let args = [RuntimeVal::Obj(left), RuntimeVal::Obj(right)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let result = function(NativeArgs::new(&args), &mut runtime)?; - - let RuntimeVal::Obj(outer) = result else { - panic!("expected outer list"); - }; - let Some(HeapValue::List(TypedList::Mixed(pairs))) = runtime.heap().get(outer) else { - panic!("expected mixed outer list"); - }; - assert_eq!(pairs.len(), 1); - let RuntimeVal::Obj(pair) = pairs[0] else { - panic!("expected pair list"); - }; - let Some(HeapValue::List(TypedList::String(pair_values))) = runtime.heap().get(pair) else { - panic!("expected typed string pair list"); - }; - assert_eq!(pair_values.len(), 2); - assert_eq!(runtime.heap().len(), 6); - Ok(()) - } - - #[test] - fn iter_collect_preserves_typed_string_backing_without_materializing_items() -> Result<()> { - let (_, function) = iter_native("collect")?; - let NativeFunction::Plain(function) = function else { - panic!("collect must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let input = state.heap_mut().alloc(HeapValue::List(TypedList::String(vec![ - Arc::::from("long-collect-one"), - Arc::::from("long-collect-two"), - ]))); - let args = [RuntimeVal::Obj(input)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let result = function(NativeArgs::new(&args), &mut runtime)?; - - let RuntimeVal::Obj(handle) = result else { - panic!("expected list result"); - }; - let Some(HeapValue::List(TypedList::String(values))) = runtime.heap().get(handle) else { - panic!("expected typed string list result"); - }; - assert_eq!(values.len(), 2); - assert_eq!(runtime.heap().len(), 2); - Ok(()) - } - - #[test] - fn iter_flatten_preserves_nested_typed_string_backing_without_materializing_items() -> Result<()> { - let (_, function) = iter_native("flatten")?; - let NativeFunction::Plain(function) = function else { - panic!("flatten must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let first = state - .heap_mut() - .alloc(HeapValue::List(TypedList::String(vec![Arc::::from( - "long-flatten-one", - )]))); - let second = state - .heap_mut() - .alloc(HeapValue::List(TypedList::String(vec![Arc::::from( - "long-flatten-two", - )]))); - let outer = state.heap_mut().alloc(HeapValue::List(TypedList::Mixed(vec![ - RuntimeVal::Obj(first), - RuntimeVal::Obj(second), - ]))); - let args = [RuntimeVal::Obj(outer)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let result = function(NativeArgs::new(&args), &mut runtime)?; - - let RuntimeVal::Obj(handle) = result else { - panic!("expected list result"); - }; - let Some(HeapValue::List(TypedList::String(values))) = runtime.heap().get(handle) else { - panic!("expected typed string list result"); - }; - assert_eq!(values.len(), 2); - assert_eq!(runtime.heap().len(), 4); - Ok(()) - } - - #[test] - fn iter_unique_preserves_typed_string_backing_without_materializing_items() -> Result<()> { - let (_, function) = iter_native("unique")?; - let NativeFunction::Plain(function) = function else { - panic!("unique must use plain RuntimeNative"); - }; - let mut state = RuntimeModuleState::default(); - let input = state.heap_mut().alloc(HeapValue::List(TypedList::String(vec![ - Arc::::from("long-unique-one"), - Arc::::from("long-unique-one"), - Arc::::from("long-unique-two"), - ]))); - let args = [RuntimeVal::Obj(input)]; - let mut runtime = NativeRuntime::new(&mut state, None, None); - - let result = function(NativeArgs::new(&args), &mut runtime)?; - - let RuntimeVal::Obj(handle) = result else { - panic!("expected list result"); - }; - let Some(HeapValue::List(TypedList::String(values))) = runtime.heap().get(handle) else { - panic!("expected typed string list result"); - }; - assert_eq!(values.len(), 2); - assert_eq!(runtime.heap().len(), 2); - Ok(()) - } - - #[test] - fn iter_collect_and_next_accept_lists_only() -> Result<()> { - assert_eq!(run_value("use iter; return iter.next([7,8]);")?, RuntimeVal::Int(7)); - assert_eq!(run_value("use iter; return iter.next([]);")?, RuntimeVal::Nil); - assert_eq!( - expect_return_list(&run("use iter; return iter.collect([1,2]);")?), - vec![RuntimeVal::Int(1), RuntimeVal::Int(2)] - ); - Ok(()) - } -} diff --git a/stdlib/crates/math/src/lib.rs b/stdlib/crates/math/src/lib.rs index c668ec1f..f7ec6bf8 100644 --- a/stdlib/crates/math/src/lib.rs +++ b/stdlib/crates/math/src/lib.rs @@ -32,7 +32,6 @@ mod seed; use float::FloatExt as _; use anyhow::{Result, anyhow, bail}; -use lk_core::compat::collections::HashSet; use lk_core::{ val::RuntimeVal, vm::{NativeArgs, NativeRuntime}, @@ -53,7 +52,7 @@ pub struct MathModule; #[stdlib_value("epsilon" => RuntimeVal::Float(f64::EPSILON))] impl MathModule { #[stdlib_export(params(value: Int, min?: Int = 0, max?: Int = 100), named(min, max), returns = Int)] - fn clamp(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + fn clamp(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { let pos = args.as_slice(); if pos.is_empty() { bail!("clamp() requires at least the value argument"); @@ -62,31 +61,24 @@ impl MathModule { bail!("clamp() takes at most 3 positional arguments: value, min, max"); } + // `min:` / `max:` never reach here as *named* arguments: the + // `named(min, max)` declaration makes the export wrapper fold them + // into their positional slots first, so this reads one shape. The + // wrapper is also what rejects a duplicate or an unknown name — the + // hand-written loop that used to do it here was the only copy, so + // every other `named(...)` function was silently accepting both. let value = int_arg(&pos[0], "clamp() first argument (value)")?; - let mut min = if pos.len() >= 2 { + let min = if pos.len() >= 2 { int_arg(&pos[1], "clamp() second argument (min)")? } else { 0 }; - let mut max = if pos.len() >= 3 { + let max = if pos.len() >= 3 { int_arg(&pos[2], "clamp() third argument (max)")? } else { 100 }; - let mut seen = HashSet::with_capacity(args.named_len()); - args.try_for_each_named(runtime.heap(), |name, value| { - if !seen.insert(name.to_string()) { - bail!("clamp() received duplicate named argument '{}'", name); - } - match name { - "min" => min = int_arg(value, "clamp() named 'min'")?, - "max" => max = int_arg(value, "clamp() named 'max'")?, - other => bail!("clamp() does not accept named argument '{}'", other), - } - Ok(()) - })?; - if min > max { bail!("clamp() requires 'min' to be less than or equal to 'max'"); } @@ -102,7 +94,11 @@ impl MathModule { #[stdlib_export(params(value: Int | Float), returns = Int | Float)] fn abs(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { match args.as_slice()[0] { - RuntimeVal::Int(value) => Ok(RuntimeVal::Int(value.abs())), + // Wrapping, because that is the language's rule for Int overflow + // (`docs/semantics.md`) and `abs(Int::MIN)` is exactly that: there + // is no positive `Int::MIN`. `i64::abs` panicked instead, so + // `math.abs` on one value took the process down. + RuntimeVal::Int(value) => Ok(RuntimeVal::Int(value.wrapping_abs())), RuntimeVal::Float(value) => Ok(RuntimeVal::Float(value.abs())), _ => bail!("abs() argument must be a number"), } @@ -156,7 +152,7 @@ impl MathModule { unary_float(args, "atan()", f64::atan) } - #[stdlib_export(name = "atan2", params(y: Number, x: Number), returns = Float)] + #[stdlib_export(name = "atan2", params(y: Number, x: Number), named(x), returns = Float)] fn atan2_(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); let y = number_arg(&values[0], "atan2() first argument")?; @@ -184,7 +180,7 @@ impl MathModule { unary_float(args, "exp()", f64::exp) } - #[stdlib_export(params(base: Number, exponent: Number), returns = Float)] + #[stdlib_export(params(base: Number, exponent: Number), named(exponent), returns = Float)] fn pow(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); let base = number_arg(&values[0], "pow() first argument")?; diff --git a/stdlib/crates/math/src/seed.rs b/stdlib/crates/math/src/seed.rs index 10fde739..56dcefc3 100644 --- a/stdlib/crates/math/src/seed.rs +++ b/stdlib/crates/math/src/seed.rs @@ -10,13 +10,17 @@ pub fn next() -> u64 { let mut seed = load(); if seed == 0 { - // TODO(pre-existing): unreachable in practice — the state starts at - // `INITIAL` and xorshift never produces 0 from a non-zero one, so the - // std build's clock reseed below has never actually run. Starting the - // state at 0 instead would make `math.random()` differ per process, - // which is probably what was intended, but that is a behaviour change - // to the std build and does not belong in the no_std port. Decide it - // separately. + // The first call on a host: take the state from the clock. + // + // This used to be unreachable. The state started at `INITIAL` and + // xorshift never produces 0 from a non-zero one, so the reseed never + // ran and **`math.random()` returned the same sequence in every + // process** — the three numbers a program printed on Monday were the + // three it printed on Tuesday. A function called `random` may not do + // that. + // + // Bare metal keeps `INITIAL`: it has no clock, and a fixed stream is + // the honest answer there rather than a pretence. seed = reseed(); } seed ^= seed << 13; @@ -26,14 +30,20 @@ pub fn next() -> u64 { seed.wrapping_add(bump_counter() as u64) } -/// Chosen once so a zeroed state still yields a usable stream. +/// The bare-metal starting state: no clock to seed from, so the stream is fixed +/// and says so. A host starts at 0 instead, which is what makes the first call +/// reseed. +#[cfg(not(feature = "std"))] const INITIAL: u64 = 0x12345678_9ABCDEF0; #[cfg(feature = "std")] mod imp { use core::sync::atomic::{AtomicU64, Ordering}; - static SEED: AtomicU64 = AtomicU64::new(super::INITIAL); + /// Zero means "not seeded yet", which is what sends the first call through + /// `reseed`. Xorshift never *produces* zero from a non-zero state, so this + /// value cannot recur once the stream is running. + static SEED: AtomicU64 = AtomicU64::new(0); pub(super) fn load() -> u64 { SEED.load(Ordering::Relaxed) diff --git a/stdlib/crates/net/src/udp.rs b/stdlib/crates/net/src/udp.rs index 5a679731..a26e923e 100644 --- a/stdlib/crates/net/src/udp.rs +++ b/stdlib/crates/net/src/udp.rs @@ -1,11 +1,11 @@ use anyhow::{Result, anyhow, bail}; +use lk_core::util::value_map::value_map_new; use lk_core::{ rt::RuntimePayload, - util::fast_map::fast_hash_map_new, val::{HeapStore, HeapValue, ResourceHandle, RuntimeMapKey, RuntimeVal, TypedMap}, vm::{NativeArgs, NativeRuntime}, }; -use std::{net::UdpSocket, sync::Arc}; +use std::net::UdpSocket; use crate::{ bytes::{runtime_bytes_or_string_arg, runtime_bytes_value}, @@ -131,9 +131,11 @@ fn spawn_task( fn recv_result_value(data: Vec, addr: String, heap: &mut HeapStore) -> RuntimeVal { let data = runtime_bytes_value(data, heap); let addr = runtime_string_value(&addr, heap); - let mut fields = fast_hash_map_new(); - fields.insert(RuntimeMapKey::String(Arc::::from("data")), data); - fields.insert(RuntimeMapKey::String(Arc::::from("addr")), addr); + let mut fields = value_map_new(); + // `from_text`, not the `String` variant: four characters key inline, and + // a lookup built from the text would not have found this one. + fields.insert(RuntimeMapKey::from_text("data"), data); + fields.insert(RuntimeMapKey::from_text("addr"), addr); RuntimeVal::Obj(heap.alloc(HeapValue::Map(TypedMap::Mixed(fields)))) } diff --git a/stdlib/crates/path/src/lib.rs b/stdlib/crates/path/src/lib.rs index 76354a0e..2a733c8a 100644 --- a/stdlib/crates/path/src/lib.rs +++ b/stdlib/crates/path/src/lib.rs @@ -70,13 +70,26 @@ impl PathModule { #[stdlib_export(name = "normalize", params(path: String), returns = String)] fn normalize(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let path = string_arg(args.get(0).expect("checked arity"), runtime, "path.normalize path")?; + let path = Path::new(path.as_ref()); + // A `..` that cannot be popped is kept only when the path is + // *relative*, where it still means something: `a/../../b` really does + // go up past where it started, so `../b` is the answer. Above a root it + // means nothing — `/..` is `/` on every filesystem — and keeping it + // produced `/../a`, a path that normalizes to itself forever. + let rooted = path.has_root(); let mut out = std::path::PathBuf::new(); - for component in Path::new(path.as_ref()).components() { + for component in path.components() { use std::path::Component; match component { Component::CurDir => {} Component::ParentDir => { - if !out.pop() { + // Only a *named* component is what `..` cancels. Popping + // whatever was last meant one `..` ate another: + // `normalize("../..")` answered the empty string, so two + // levels up became none at all. + if matches!(out.components().next_back(), Some(Component::Normal(_))) { + out.pop(); + } else if !rooted { out.push(component.as_os_str()); } } diff --git a/stdlib/crates/process/src/lib.rs b/stdlib/crates/process/src/lib.rs index 38b5f08d..594d5313 100644 --- a/stdlib/crates/process/src/lib.rs +++ b/stdlib/crates/process/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::{Result, anyhow, bail}; +use lk_core::util::value_map::value_map_new; use lk_core::{ - util::fast_map::fast_hash_map_new, val::{HeapValue, RuntimeVal, TypedList, TypedMap}, vm::{NativeArgs, NativeRuntime}, }; @@ -48,7 +48,7 @@ impl ProcessModule { std::process::exit(code); } - #[stdlib_export(name = "status", params(cmd: String, args?: List[String]), returns = Int)] + #[stdlib_export(name = "status", params(cmd: String, args?: List), returns = Int)] fn status(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (cmd, argv) = command_args(args, runtime, "process.status()")?; let status = Command::new(cmd.as_ref()) @@ -58,7 +58,7 @@ impl ProcessModule { Ok(RuntimeVal::Int(status.code().unwrap_or(-1) as i64)) } - #[stdlib_export(name = "output", params(cmd: String, args?: List[String]), returns = Map)] + #[stdlib_export(name = "output", params(cmd: String, args?: List), returns = Map)] fn output(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (cmd, argv) = command_args(args, runtime, "process.output()")?; let output = Command::new(cmd.as_ref()) @@ -68,7 +68,7 @@ impl ProcessModule { output_map(output, runtime) } - #[stdlib_export(name = "output_string", params(cmd: String, args?: List[String]), returns = String)] + #[stdlib_export(name = "output_string", params(cmd: String, args?: List), returns = String)] fn output_string(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (cmd, argv) = command_args(args, runtime, "process.output_string()")?; let output = Command::new(cmd.as_ref()) @@ -81,7 +81,7 @@ impl ProcessModule { } fn output_map(output: std::process::Output, runtime: &mut NativeRuntime<'_>) -> Result { - let mut map = fast_hash_map_new(); + let mut map = value_map_new(); map.insert( Arc::::from("status"), RuntimeVal::Int(output.status.code().unwrap_or(-1) as i64), diff --git a/stdlib/crates/random/src/lib.rs b/stdlib/crates/random/src/lib.rs index 40739650..f8e85bfe 100644 --- a/stdlib/crates/random/src/lib.rs +++ b/stdlib/crates/random/src/lib.rs @@ -15,7 +15,7 @@ pub struct RandomModule; #[lk_stdlib_common::stdlib_exports(module = "random")] impl RandomModule { - #[stdlib_export(name = "int", params(min: Int, max: Int), returns = Int)] + #[stdlib_export(name = "int", params(min: Int, max: Int), named(max), returns = Int)] fn int(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { let min = int_arg(args.get(0).expect("checked arity"), "random.int min")?; let max = int_arg(args.get(1).expect("checked arity"), "random.int max")?; @@ -57,7 +57,7 @@ impl RandomModule { Ok(runtime_bytes_value(data, runtime.heap_mut())) } - #[stdlib_export(name = "choice", params(values: List), returns = Any)] + #[stdlib_export(name = "choice", params(values: List<_>), returns = Any)] fn choice(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let values = list_values(args.get(0).expect("checked arity"), runtime, "random.choice list")?; if values.is_empty() { @@ -67,7 +67,7 @@ impl RandomModule { Ok(values[index]) } - #[stdlib_export(name = "shuffle", params(values: List), returns = List)] + #[stdlib_export(name = "shuffle", params(values: List<_>), returns = List)] fn shuffle(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let mut values = list_values(args.get(0).expect("checked arity"), runtime, "random.shuffle list")?; for i in (1..values.len()).rev() { diff --git a/stdlib/crates/regex/src/lib.rs b/stdlib/crates/regex/src/lib.rs index a6ad7d60..4eda8c1a 100644 --- a/stdlib/crates/regex/src/lib.rs +++ b/stdlib/crates/regex/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::{Result, anyhow}; +use lk_core::util::value_map::value_map_new; use lk_core::{ - util::fast_map::fast_hash_map_new, val::{HeapValue, RuntimeVal, TypedList, TypedMap}, vm::{NativeArgs, NativeRuntime}, }; @@ -17,13 +17,13 @@ pub struct RegexModule; #[lk_stdlib_common::stdlib_exports(module = "regex")] impl RegexModule { - #[stdlib_export(name = "is_match", params(pattern: String, text: String), returns = Bool)] + #[stdlib_export(name = "is_match", params(text: String, pattern: String), named(pattern), returns = Bool)] fn is_match(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (regex, text) = regex_text(args, runtime, "regex.is_match()")?; Ok(RuntimeVal::Bool(regex.is_match(text.as_ref()))) } - #[stdlib_export(name = "find", params(pattern: String, text: String), returns = Map?)] + #[stdlib_export(name = "find", params(text: String, pattern: String), named(pattern), returns = Map?)] fn find(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (regex, text) = regex_text(args, runtime, "regex.find()")?; Ok(match regex.find(text.as_ref()) { @@ -32,7 +32,7 @@ impl RegexModule { }) } - #[stdlib_export(name = "find_all", params(pattern: String, text: String), returns = List)] + #[stdlib_export(name = "find_all", params(text: String, pattern: String), named(pattern), returns = List)] fn find_all(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (regex, text) = regex_text(args, runtime, "regex.find_all()")?; let values = regex @@ -43,7 +43,7 @@ impl RegexModule { Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(list)))) } - #[stdlib_export(name = "captures", params(pattern: String, text: String), returns = List?)] + #[stdlib_export(name = "captures", params(text: String, pattern: String), named(pattern), returns = List?)] fn captures(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (regex, text) = regex_text(args, runtime, "regex.captures()")?; let Some(captures) = regex.captures(text.as_ref()) else { @@ -60,14 +60,19 @@ impl RegexModule { Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(list)))) } - #[stdlib_export(name = "replace", params(pattern: String, text: String, replacement: String), returns = String)] + #[stdlib_export( + name = "replace", + params(text: String, pattern: String, replacement: String), + named(pattern, replacement), + returns = String + )] fn replace(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let regex = cached_regex(args.get(0).expect("checked arity"), runtime, "regex.replace pattern")?; let text = runtime_string_arg( - args.get(1).expect("checked arity"), + args.get(0).expect("checked arity"), runtime.heap(), "regex.replace text", )?; + let regex = cached_regex(args.get(1).expect("checked arity"), runtime, "regex.replace pattern")?; let replacement = runtime_string_arg( args.get(2).expect("checked arity"), runtime.heap(), @@ -79,7 +84,7 @@ impl RegexModule { )) } - #[stdlib_export(name = "split", params(pattern: String, text: String), returns = List)] + #[stdlib_export(name = "split", params(text: String, pattern: String), named(pattern), returns = List)] fn split(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (regex, text) = regex_text(args, runtime, "regex.split()")?; let values = regex.split(text.as_ref()).map(Arc::::from).collect::>(); @@ -90,8 +95,8 @@ impl RegexModule { } fn regex_text(args: NativeArgs<'_>, runtime: &NativeRuntime<'_>, name: &str) -> Result<(regex::Regex, Arc)> { - let regex = cached_regex(args.get(0).expect("checked arity"), runtime, name)?; - let text = runtime_string_arg(args.get(1).expect("checked arity"), runtime.heap(), name)?; + let text = runtime_string_arg(args.get(0).expect("checked arity"), runtime.heap(), name)?; + let regex = cached_regex(args.get(1).expect("checked arity"), runtime, name)?; Ok((regex, text)) } @@ -116,7 +121,7 @@ fn cached_regex(value: &RuntimeVal, runtime: &NativeRuntime<'_>, context: &str) } fn match_map(text: &str, start: usize, end: usize, runtime: &mut NativeRuntime<'_>) -> RuntimeVal { - let mut map = fast_hash_map_new(); + let mut map = value_map_new(); map.insert(Arc::::from("text"), runtime_string_value(text, runtime.heap_mut())); map.insert(Arc::::from("start"), RuntimeVal::Int(start as i64)); map.insert(Arc::::from("end"), RuntimeVal::Int(end as i64)); diff --git a/stdlib/crates/slice/Cargo.toml b/stdlib/crates/slice/Cargo.toml deleted file mode 100644 index cc72c3cd..00000000 --- a/stdlib/crates/slice/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "lk-stdlib-slice" -description = "LK standard library slice module" -version = "0.1.3" -edition = "2024" -authors = ["lollipopkit "] -license = "Apache-2.0" - -[features] -default = ["std"] -# Off => no_std + alloc, so this module can be offered on bare metal. -std = ["lk-core/std", "lk-stdlib-common/std", "anyhow/std"] - -[dependencies] -lk-core = { path = "../../../core", default-features = false } -lk-stdlib-common = { path = "../../common", default-features = false } -anyhow = { version = "1", default-features = false } - diff --git a/stdlib/crates/slice/src/lib.rs b/stdlib/crates/slice/src/lib.rs deleted file mode 100644 index 97381c6e..00000000 --- a/stdlib/crates/slice/src/lib.rs +++ /dev/null @@ -1,238 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate alloc; - -// From `alloc` directly, not `lk_core::compat::prelude`: feature -// unification can give lk-core `std` while this crate stays no_std, and -// then that prelude does not exist. What alloc provides does not depend -// on anyone else's features. -#[cfg(not(feature = "std"))] -#[allow(unused_imports)] -use alloc::{ - borrow::ToOwned, - boxed::Box, - format, - string::{String, ToString}, - vec, - vec::Vec, -}; - -use alloc::sync::Arc; - -use anyhow::{Result, anyhow, bail}; -use lk_core::{ - val::{HeapStore, HeapValue, RuntimeVal, SliceKind, SliceValue, TypedList}, - vm::{NativeArgs, NativeRuntime}, -}; - -pub mod runtime_native { - pub use lk_stdlib_common::runtime_native::*; -} -pub use lk_stdlib_common::typed_list_from_values; - -use crate::runtime_native::{runtime_string_arg, runtime_string_value}; - -/// Byte-oriented slices over lists and strings. -/// -/// `slice.from_string()` and `slice.sub()` operate on byte offsets. String slices -/// may split a multibyte UTF-8 character; `slice.to_string()` validates the byte -/// range and returns an error when the selected range is not valid UTF-8. -#[derive(Debug, Default, lk_stdlib_common::StdlibModule)] -#[stdlib_module(name = "slice", docs = "Byte-oriented slices over lists and strings")] -pub struct SliceModule; - -#[lk_stdlib_common::stdlib_exports(module = "slice")] -impl SliceModule { - #[stdlib_export(params(list: List), returns = Slice)] - fn from_list(source: RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result { - let len = list_arg(&source, runtime.heap(), "slice.from_list()")?.len(); - Ok(slice_value(source, SliceKind::List, 0, len, runtime.heap_mut())) - } - - #[stdlib_export(params(text: String), returns = Slice)] - fn from_string(source: RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result { - let text = runtime_string_arg(&source, runtime.heap(), "slice.from_string()")?; - Ok(slice_value( - source, - SliceKind::String, - 0, - text.len(), - runtime.heap_mut(), - )) - } - - #[stdlib_export(params(slice: Slice), returns = Int)] - fn len(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - Ok(RuntimeVal::Int( - slice_arg(args.get(0).expect("checked arity"), runtime.heap(), "slice.len()")?.len as i64, - )) - } - - #[stdlib_export(params(slice: Slice), returns = Bool)] - fn is_empty(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - Ok(RuntimeVal::Bool( - slice_arg(args.get(0).expect("checked arity"), runtime.heap(), "slice.is_empty()")?.len == 0, - )) - } - - #[stdlib_export(params(slice: Slice, index: Int), returns = Any)] - fn get(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let slice = slice_arg(&values[0], runtime.heap(), "slice.get()")?; - let index = usize_arg(&values[1], "slice.get() index")?; - if index >= slice.len { - return Ok(RuntimeVal::Nil); - } - match slice.kind { - SliceKind::List => { - let item = { - let list = list_arg(&slice.source, runtime.heap(), "slice.get() source")?; - list_item(list, slice.start + index) - }; - Ok(item - .map(|item| item.into_runtime(runtime.heap_mut())) - .unwrap_or(RuntimeVal::Nil)) - } - SliceKind::String => { - let text = runtime_string_arg(&slice.source, runtime.heap(), "slice.get() source")?; - let Some(byte) = text.as_bytes().get(slice.start + index) else { - return Ok(RuntimeVal::Nil); - }; - Ok(RuntimeVal::Int(*byte as i64)) - } - } - } - - #[stdlib_export(params(slice: Slice, start: Int, end?: Int), returns = Slice)] - fn sub(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - if args.len() != 2 && args.len() != 3 { - bail!("slice.sub() expects 2 or 3 arguments: slice, start[, end]"); - } - let values = args.as_slice(); - let slice = slice_arg(&values[0], runtime.heap(), "slice.sub()")?; - let start = usize_arg(&values[1], "slice.sub() start")?.min(slice.len); - let end = if let Some(end) = values.get(2) { - usize_arg(end, "slice.sub() end")?.min(slice.len) - } else { - slice.len - }; - let len = end.saturating_sub(start); - Ok(slice_value( - slice.source, - slice.kind, - slice.start + start, - len, - runtime.heap_mut(), - )) - } - - #[stdlib_export(params(slice: Slice), returns = List)] - fn to_list(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let slice = slice_arg(args.get(0).expect("checked arity"), runtime.heap(), "slice.to_list()")?; - let values = match slice.kind { - SliceKind::List => { - let items = { - let list = list_arg(&slice.source, runtime.heap(), "slice.to_list() source")?; - (0..slice.len) - .filter_map(|index| list_item(list, slice.start + index)) - .collect::>() - }; - items - .into_iter() - .map(|item| item.into_runtime(runtime.heap_mut())) - .collect() - } - SliceKind::String => { - let text = runtime_string_arg(&slice.source, runtime.heap(), "slice.to_list() source")?; - text.as_bytes()[slice.start..slice.start + slice.len] - .iter() - .copied() - .map(|byte| RuntimeVal::Int(byte as i64)) - .collect() - } - }; - let list = crate::typed_list_from_values(values, runtime.heap()); - Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::List(list)))) - } - - #[stdlib_export(params(slice: Slice), returns = String)] - fn to_string(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let slice = slice_arg(args.get(0).expect("checked arity"), runtime.heap(), "slice.to_string()")?; - match slice.kind { - SliceKind::String => { - let text = runtime_string_arg(&slice.source, runtime.heap(), "slice.to_string() source")?; - let bytes = &text.as_bytes()[slice.start..slice.start + slice.len]; - let value = - core::str::from_utf8(bytes).map_err(|_| anyhow!("slice.to_string() range is not valid UTF-8"))?; - Ok(runtime_string_value(value, runtime.heap_mut())) - } - SliceKind::List => bail!("slice.to_string() expects a string slice"), - } - } -} - -fn slice_value(source: RuntimeVal, kind: SliceKind, start: usize, len: usize, heap: &mut HeapStore) -> RuntimeVal { - RuntimeVal::Obj(heap.alloc(HeapValue::Slice(Arc::new(SliceValue { - source, - kind, - start, - len, - })))) -} - -fn slice_arg(value: &RuntimeVal, heap: &HeapStore, context: &str) -> Result> { - let RuntimeVal::Obj(handle) = value else { - bail!("{context} expects a Slice"); - }; - match heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::Slice(slice) => Ok(slice.clone()), - other => bail!("{context} expects a Slice, got {}", other.type_name()), - } -} - -fn list_arg<'a>(value: &RuntimeVal, heap: &'a HeapStore, context: &str) -> Result<&'a TypedList> { - let RuntimeVal::Obj(handle) = value else { - bail!("{context} expects a list"); - }; - match heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::List(list) => Ok(list), - other => bail!("{context} expects a list, got {}", other.type_name()), - } -} - -enum ListItem { - Runtime(RuntimeVal), - String(Arc), -} - -impl ListItem { - fn into_runtime(self, heap: &mut HeapStore) -> RuntimeVal { - match self { - Self::Runtime(value) => value, - Self::String(value) => runtime_string_value(&value, heap), - } - } -} - -fn list_item(list: &TypedList, index: usize) -> Option { - match list { - TypedList::Mixed(values) => values.get(index).cloned().map(ListItem::Runtime), - TypedList::Int(values) => values.get(index).copied().map(RuntimeVal::Int).map(ListItem::Runtime), - TypedList::Float(values) => values.get(index).copied().map(RuntimeVal::Float).map(ListItem::Runtime), - TypedList::Bool(values) => values.get(index).copied().map(RuntimeVal::Bool).map(ListItem::Runtime), - TypedList::String(values) => values.get(index).cloned().map(ListItem::String), - } -} - -fn usize_arg(value: &RuntimeVal, context: &str) -> Result { - match value { - RuntimeVal::Int(value) if *value >= 0 => Ok(*value as usize), - other => bail!("{context} expects a non-negative integer, got {:?}", other.kind()), - } -} diff --git a/stdlib/crates/stream/src/lib.rs b/stdlib/crates/stream/src/lib.rs index 6a502579..c1ba3d4b 100644 --- a/stdlib/crates/stream/src/lib.rs +++ b/stdlib/crates/stream/src/lib.rs @@ -409,14 +409,14 @@ impl StreamCursor for ChannelCursor { #[lk_stdlib_common::stdlib_exports(module = "stream")] impl StreamModule { - #[stdlib_export(params(values: List), returns = Stream)] + #[stdlib_export(params(values: List<_>), returns = Stream)] fn from_list(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let values = list_arg_ref(&args.as_slice()[0], runtime.heap(), "stream.from_list argument")?; let values = copy_typed_list(values); create_stream(StreamSpec::FromList(Arc::new(values)), Type::Any, runtime.heap_mut()) } - #[stdlib_export(params(start: Int, end?: Int, step?: Int), returns = Stream)] + #[stdlib_export(params(start: Int, end?: Int, step?: Int), named(end, step), returns = Stream)] fn range(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); let (start, end, step) = match values { @@ -511,7 +511,7 @@ impl StreamModule { create_stream(StreamSpec::Skip { upstream, n }, Type::Any, runtime.heap_mut()) } - #[stdlib_export(params(left: Stream, right: Stream), returns = Stream)] + #[stdlib_export(params(left: Stream, right: Stream), named(right), returns = Stream)] fn chain(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); let left = get_stream_spec(stream_id_arg(&values[0], runtime.heap(), "stream.chain left")?)?; @@ -553,7 +553,14 @@ impl StreamModule { next_block_cursor(cursor_id, timeout_ms, runtime) } - #[stdlib_export(params(cursor: Stream | Cursor, limit?: Int, timeout_ms?: Int), returns = List, kind = "full_state")] + // A count and a duration, both `Int`: swapping them is silent, and one of + // them is measured in milliseconds — which only the name says. + #[stdlib_export( + params(cursor: Stream | Cursor, limit?: Int, timeout_ms?: Int), + named(limit, timeout_ms), + returns = List, + kind = "full_state" + )] fn collect_block(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let (cursor_id, limit, timeout_ms) = cursor_limit_timeout(args.as_slice(), runtime, "stream.collect_block")?; collect_block_cursor(cursor_id, limit, timeout_ms, runtime) @@ -883,11 +890,13 @@ fn ensure_runtime_callable(value: &RuntimeVal, runtime: &NativeRuntime<'_>, cont } } +/// `context` is `&'static str` because it becomes the entry's name, which is +/// borrowed rather than allocated; every caller passes a literal. fn call_runtime_callable_value( callable: &RuntimeVal, args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>, - context: &str, + context: &'static str, ) -> Result { let RuntimeVal::Obj(handle) = callable else { bail!("{context} must be a runtime callable"); @@ -930,7 +939,7 @@ fn call_runtime_callable_value( } StreamCallableTarget::RuntimeNative { arity, function } => { let entry = NativeEntry { - name: context.to_string(), + name: std::borrow::Cow::Borrowed(context), arity, function, }; diff --git a/stdlib/crates/string/src/lib.rs b/stdlib/crates/string/src/lib.rs index e8a0db95..8db22ea0 100644 --- a/stdlib/crates/string/src/lib.rs +++ b/stdlib/crates/string/src/lib.rs @@ -17,9 +17,6 @@ use alloc::{ vec::Vec, }; -use alloc::sync::Arc; -use lk_core::compat::collections::HashSet; - use anyhow::{Result, anyhow, bail}; use lk_core::{ val::{HeapStore, HeapValue, RuntimeVal, TypedList}, @@ -30,7 +27,7 @@ pub mod runtime_native { pub use lk_stdlib_common::runtime_native::*; } -use crate::runtime_native::{runtime_display_value, runtime_string_arg, runtime_string_value}; +use crate::runtime_native::{runtime_string_arg, runtime_string_value}; #[derive(Debug, Default, lk_stdlib_common::StdlibModule)] #[stdlib_module(name = "string", docs = "String manipulation functions")] @@ -40,151 +37,78 @@ pub struct StringModule; impl StringModule { #[stdlib_export(params(text: String), returns = Int)] fn len(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "len()")?; - Ok(RuntimeVal::Int(value.len() as i64)) + forward("len", args, runtime) } #[stdlib_export(params(text: String), returns = String)] fn lower(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "lower()")?; - Ok(runtime_string_value(&value.to_lowercase(), runtime.heap_mut())) + forward("lower", args, runtime) } #[stdlib_export(params(text: String), returns = String)] fn upper(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "upper()")?; - Ok(runtime_string_value(&value.to_uppercase(), runtime.heap_mut())) + forward("upper", args, runtime) } #[stdlib_export(params(text: String), returns = String)] fn trim(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "trim()")?; - Ok(runtime_string_value(value.trim(), runtime.heap_mut())) + forward("trim", args, runtime) } #[stdlib_export(params(text: String, prefix: String), returns = Bool)] fn starts_with(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, prefix) = two_strings(args, runtime, "starts_with()")?; - Ok(RuntimeVal::Bool(value.starts_with(prefix.as_ref()))) + forward("starts_with", args, runtime) } #[stdlib_export(params(text: String, suffix: String), returns = Bool)] fn ends_with(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, suffix) = two_strings(args, runtime, "ends_with()")?; - Ok(RuntimeVal::Bool(value.ends_with(suffix.as_ref()))) + forward("ends_with", args, runtime) } #[stdlib_export(params(text: String, needle: String), returns = Bool)] fn contains(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, needle) = two_strings(args, runtime, "contains()")?; - Ok(RuntimeVal::Bool(value.contains(needle.as_ref()))) - } - - #[stdlib_export(params(text: String, pattern?: String, with?: String, all?: Bool), named(pattern, with, all), returns = String)] + forward("contains", args, runtime) + } + + /// `replace(text, pattern, with, all = true)`. + /// + /// `all` used to default to whether the *call* spelled its arguments by + /// name: `replace("aaa", "a", "b")` answered `"bbb"` and + /// `replace("aaa", pattern: "a", with: "b")` answered `"baa"` — the same + /// arguments, a different answer, decided by punctuation. Naming an + /// argument is supposed to mean exactly what passing it positionally + /// means, so there is one default now, and it is the positional one. + #[stdlib_export( + params(text: String, pattern: String, with: String, all?: Bool = true), + named(pattern, with, all), + returns = String + )] fn replace(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let pos = args.as_slice(); - if pos.is_empty() { - bail!("replace() requires at least the source string as the first argument"); - } - if pos.len() > 4 { - bail!("replace() received too many positional arguments (expected at most 4)"); - } - - let source = runtime_string_arg(&pos[0], runtime.heap(), "replace() first argument")?; - let mut pattern = None; - let mut with = None; - let mut all_flag = None; - let mut used_named_core = false; - - if pos.len() >= 2 { - pattern = Some(runtime_string_arg( - &pos[1], - runtime.heap(), - "replace() second argument (pattern)", - )?); - } - if pos.len() >= 3 { - with = Some(runtime_string_arg( - &pos[2], - runtime.heap(), - "replace() third argument (with)", - )?); - } - if pos.len() >= 4 { - all_flag = Some(bool_arg(&pos[3], "replace() fourth argument (all flag)")?); - } - - let mut seen = HashSet::with_capacity(args.named_len()); - args.try_for_each_named(runtime.heap(), |name, value| { - if !seen.insert(name.to_string()) { - bail!("replace() received duplicate named argument '{}'", name); - } - match name { - "pattern" => { - pattern = Some(runtime_string_arg(value, runtime.heap(), "replace() named 'pattern'")?); - used_named_core = true; - } - "with" => { - with = Some(runtime_string_arg(value, runtime.heap(), "replace() named 'with'")?); - used_named_core = true; - } - "all" => all_flag = Some(bool_arg(value, "replace() named 'all'")?), - other => bail!("replace() does not accept named argument '{}'", other), - } - Ok(()) - })?; - - let pattern = pattern.ok_or_else(|| { - anyhow!("replace() requires a pattern string (provide it positionally or via named 'pattern')") - })?; - let with = with.ok_or_else(|| { - anyhow!("replace() requires a replacement string (provide it positionally or via named 'with')") - })?; - let all = all_flag.unwrap_or(!used_named_core); - let result = if all { - source.replace(pattern.as_ref(), with.as_ref()) - } else { - source.replacen(pattern.as_ref(), with.as_ref(), 1) - }; - Ok(runtime_string_value(&result, runtime.heap_mut())) - } - - #[stdlib_export(params(text: String, start: Int, end: Int), returns = String)] - fn substring(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let value = runtime_string_arg(&values[0], runtime.heap(), "substring() first argument")?; - let start = usize_arg(&values[1], "substring() second argument")?; - let length = usize_arg(&values[2], "substring() third argument")?; - if start > value.len() { - bail!("substring() start index out of bounds"); - } - // `saturating_add`, not `+`: `usize` is 32-bit on the bare-metal - // targets, where two large `Int` arguments overflow it. In release - // that wraps to a small `end`, and `value[start..end]` with - // `end < start` panics — which on an MCU means a halt, not a message. - let end = core::cmp::min(start.saturating_add(length), value.len()); - Ok(runtime_string_value(&value[start..end], runtime.heap_mut())) - } - - #[stdlib_export(params(text: String, separator: String), returns = List[String])] + forward("replace", args, runtime) + } + + /// `s.slice(start[, end])`, spelled as a function. + /// + /// **Start and end**, not start and length. This was `substring(s, start, + /// length)` — the one place in the language where a window was given a + /// count, so `xs.slice(1, 3)` and `substring(s, 1, 3)` cut different + /// windows out of the same two numbers. The method form is gone; this is + /// what it became. + /// + /// Character positions, and never a panic: byte slicing halted on a + /// multi-byte boundary, which on an MCU is a halt rather than a message. + /// Out of range clamps. + #[stdlib_export(params(text: String, start: Int, end?: Int), named(start, end), returns = String)] + fn slice(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("slice", args, runtime) + } + + #[stdlib_export(params(text: String, separator: String), returns = List)] fn split(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, delimiter) = two_strings(args, runtime, "split()")?; - let mut parts = Vec::new(); - if delimiter.is_empty() { - for value in value.chars() { - parts.push(Arc::::from(value.to_string())); - } - } else { - for value in value.split(delimiter.as_ref()) { - parts.push(Arc::::from(value)); - } - } - Ok(RuntimeVal::Obj( - runtime.heap_mut().alloc(HeapValue::List(TypedList::String(parts))), - )) + forward("split", args, runtime) } - #[stdlib_export(params(values: List, separator: String), returns = String)] + #[stdlib_export(params(values: List<_>, separator: String), returns = String)] fn join(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); let strings = string_list_arg(&values[0], runtime.heap(), "join() first argument")?; @@ -197,282 +121,257 @@ impl StringModule { #[stdlib_export(params(text: String), returns = String)] fn reverse(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "reverse()")?; - let mut reversed = String::new(); - for value in value.chars().rev() { - reversed.push(value); - } - Ok(runtime_string_value(&reversed, runtime.heap_mut())) + forward("reverse", args, runtime) } #[stdlib_export(params(text: String, count: Int), returns = String)] fn repeat(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let value = runtime_string_arg(&values[0], runtime.heap(), "repeat() first argument")?; - let count = int_arg(&values[1], "repeat() second argument")?; - if count < 0 { - bail!("repeat() count must be non-negative"); - } - Ok(runtime_string_value(&value.repeat(count as usize), runtime.heap_mut())) + forward("repeat", args, runtime) } - #[stdlib_export(name = "char", params(text: String, index: Int), returns = String?)] - fn char_at(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let value = runtime_string_arg(&values[0], runtime.heap(), "char() first argument")?; - let index = usize_arg(&values[1], "char() second argument")?; - Ok(value.chars().nth(index).map_or(RuntimeVal::Nil, |value| { - runtime_string_value(&value.to_string(), runtime.heap_mut()) - })) + /// `s.get(i)` — the character at a position, `nil` out of range. + /// + /// This was `string.char_at`, and before that `string.char`, while the + /// element accessor every other sequence carrier spells is `get` + /// (`xs.get(i)`, `bytes.get(b, i)`, and `s.get(i)` here). A third name for + /// it also meant a third rule: `char_at` refused a negative index, where + /// `s[-1]`, `s.get(-1)` and the native `str.char_at` symbol all count back + /// from the end. `byte_at` keeps its name because it answers a different + /// thing — a byte, not an element. + #[stdlib_export(params(text: String, index: Int), returns = String?)] + fn get(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("get", args, runtime) } - #[stdlib_export(name = "byte", params(text: String, index: Int), returns = Int?)] + #[stdlib_export(params(text: String), returns = String?)] + fn first(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("first", args, runtime) + } + + #[stdlib_export(params(text: String), returns = String?)] + fn last(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("last", args, runtime) + } + + #[stdlib_export(params(text: String, count: Int), returns = String)] + fn take(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("take", args, runtime) + } + + #[stdlib_export(params(text: String, count: Int), returns = String)] + fn skip(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("skip", args, runtime) + } + + #[stdlib_export(params(text: String), returns = Bytes)] + fn bytes(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("bytes", args, runtime) + } + + #[stdlib_export(params(text: String, index: Int), returns = Int?)] fn byte_at(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let value = runtime_string_arg(&values[0], runtime.heap(), "byte() first argument")?; - let index = usize_arg(&values[1], "byte() second argument")?; - Ok(value - .as_bytes() - .get(index) - .map_or(RuntimeVal::Nil, |value| RuntimeVal::Int(*value as i64))) + forward("byte_at", args, runtime) } - #[stdlib_export(params(text: String), returns = List[String])] + #[stdlib_export(params(text: String), returns = List)] fn chars(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "chars()")?; - let mut chars = Vec::new(); - for value in value.chars() { - chars.push(Arc::::from(value.to_string())); - } - Ok(RuntimeVal::Obj( - runtime.heap_mut().alloc(HeapValue::List(TypedList::String(chars))), - )) + forward("chars", args, runtime) } + /// `s.index_of(needle)`, spelled as a function, plus an optional position + /// to start looking from — which the method form has no room for. + /// + /// This was `find`. The sequence surface calls it `index_of` everywhere + /// else, and a module function that is a spelling of a method should not + /// need a second name. #[stdlib_export(params(text: String, needle: String, start?: Int), returns = Int?)] - fn find(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - if args.len() != 2 && args.len() != 3 { - bail!("find() takes 2 or 3 arguments: string, pattern[, start]"); - } - let values = args.as_slice(); - let value = runtime_string_arg(&values[0], runtime.heap(), "find() first argument")?; - let pattern = runtime_string_arg(&values[1], runtime.heap(), "find() second argument")?; - let start = if values.len() == 3 { - usize_arg(&values[2], "find() third argument")? - } else { - 0 - }; - if start > value.len() { - return Ok(RuntimeVal::Nil); - } - Ok(value[start..] - .find(pattern.as_ref()) - .map_or(RuntimeVal::Nil, |index| RuntimeVal::Int((start + index) as i64))) + fn index_of(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + forward("index_of", args, runtime) } #[stdlib_export(params(text: String), returns = Bool)] fn is_empty(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "is_empty()")?; - Ok(RuntimeVal::Bool(value.is_empty())) + forward("is_empty", args, runtime) } + /// `"{} and {}".format(a, b)` — the receiver is the template. #[stdlib_export(params(template: String, ...values: Any), returns = String)] fn format(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { if args.is_empty() { bail!("format() requires at least 1 argument (format string)"); } - let values = args.as_slice(); - let fmt = runtime_string_arg(&values[0], runtime.heap(), "format() first argument")?; - let rest = &values[1..]; - let mut out = String::with_capacity(fmt.len()); - let mut chars = fmt.chars().peekable(); - let mut arg_index = 0usize; - while let Some(ch) = chars.next() { - if ch == '{' && chars.peek() == Some(&'}') { - chars.next(); - if arg_index < rest.len() { - out.push_str(&runtime_display_value(&rest[arg_index], runtime.heap())?); - arg_index += 1; - } else { - out.push_str("{}"); - } - } else { - out.push(ch); - } - } - if arg_index < rest.len() { - if !out.is_empty() { - out.push(' '); - } - for (index, value) in rest[arg_index..].iter().enumerate() { - if index > 0 { - out.push(' '); - } - out.push_str(&runtime_display_value(value, runtime.heap())?); - } - } - Ok(runtime_string_value(&out, runtime.heap_mut())) - } - - #[stdlib_export(params(text: String, chars: String), returns = String?)] + forward("format", args, runtime) + } + + /// Removes every leading and trailing character that is in `chars`. + /// + /// The parameter has always been named `chars` — a *set* — but the body + /// stripped the whole string as a prefix, and only if that failed as a + /// suffix, once: + /// + /// ```text + /// strip("--a--", "-") → "-a--" + /// ``` + /// + /// One end, one occurrence, and `nil` when neither matched. `strip_prefix` + /// and `strip_suffix` next door are the once-each operations; this one is + /// what its name and its parameter both said it was, and it always has an + /// answer. + #[stdlib_export(params(text: String, chars: String), returns = String)] fn strip(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, pattern) = two_strings(args, runtime, "strip()")?; - Ok(value - .strip_prefix(pattern.as_ref()) - .or_else(|| value.strip_suffix(pattern.as_ref())) - .map_or(RuntimeVal::Nil, |s| runtime_string_value(s, runtime.heap_mut()))) + forward("strip", args, runtime) } #[stdlib_export(params(text: String, prefix: String), returns = String?)] fn strip_prefix(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, prefix) = two_strings(args, runtime, "strip_prefix()")?; - Ok(value - .strip_prefix(prefix.as_ref()) - .map_or(RuntimeVal::Nil, |s| runtime_string_value(s, runtime.heap_mut()))) + forward("strip_prefix", args, runtime) } #[stdlib_export(params(text: String, suffix: String), returns = String?)] fn strip_suffix(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, suffix) = two_strings(args, runtime, "strip_suffix()")?; - Ok(value - .strip_suffix(suffix.as_ref()) - .map_or(RuntimeVal::Nil, |s| runtime_string_value(s, runtime.heap_mut()))) + forward("strip_suffix", args, runtime) } #[stdlib_export(params(text: String, needle: String), returns = Int)] fn count(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let (value, pattern) = two_strings(args, runtime, "count()")?; - if pattern.is_empty() { - // Count empty pattern matches between each char + at start and end - return Ok(RuntimeVal::Int(value.len() as i64 + 1)); - } - Ok(RuntimeVal::Int(value.matches(pattern.as_ref()).count() as i64)) + forward("count", args, runtime) } #[stdlib_export(params(text: String, width: Int, pad?: String), returns = String)] fn pad_left(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - if args.len() < 2 || args.len() > 3 { - bail!("pad_left() takes 2 or 3 arguments: string, width[, fill]"); - } - let values = args.as_slice(); - let value = runtime_string_arg(&values[0], runtime.heap(), "pad_left() string")?; - let width = usize_arg(&values[1], "pad_left() width")?; - let fill = if values.len() >= 3 { - let f = runtime_string_arg(&values[2], runtime.heap(), "pad_left() fill")?; - if f.is_empty() { - bail!("pad_left() fill must not be empty"); - } - f.to_string() - } else { - " ".to_string() - }; - if width <= value.len() { - return Ok(runtime_string_value(value.as_ref(), runtime.heap_mut())); - } - let needed = width - value.len(); - let pad = fill.repeat(needed / fill.len() + 1); - let padded = format!("{}{}", &pad[pad.len() - needed..], value.as_ref()); - Ok(runtime_string_value(&padded, runtime.heap_mut())) + forward("pad_left", args, runtime) } #[stdlib_export(params(text: String, width: Int, pad?: String), returns = String)] fn pad_right(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - if args.len() < 2 || args.len() > 3 { - bail!("pad_right() takes 2 or 3 arguments: string, width[, fill]"); - } + forward("pad_right", args, runtime) + } + + /// A number out of a String — or out of another number. + /// + /// The String case is the reason this exists: until it did, LK could read a + /// config file, split a CSV or take an argument and had **no way at all** to + /// turn `"42"` into `42`. `to_int` was the one name that looked like the + /// answer and refused a String outright. + /// + /// Two failure kinds, deliberately different: + /// + /// - **Text that is not a number → `nil`.** "Is this line a number?" is a + /// question about input, not a program error, so it answers with a value: + /// `line.trim()` then `?? 0` or `!` — the same shape as `index_of`. + /// - **A Float with no Int → raise.** NaN, the infinities and anything + /// outside `i64` are program errors; Rust's `as` would hand back `0` or + /// `i64::MAX`, which is a wrong answer dressed as a right one. + /// + /// Surrounding whitespace is trimmed: a line read from a file carries its + /// newline, and `"42\n"` is the same answer as `"42"` to every reader. + /// `base` (2–36) reads the digits in another radix; the sign may lead it + /// (`"-ff"`, base 16). + #[stdlib_export(params(value: String | Number | Bool, base?: Int), returns = Int?)] + fn to_int(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); - let value = runtime_string_arg(&values[0], runtime.heap(), "pad_right() string")?; - let width = usize_arg(&values[1], "pad_right() width")?; - let fill = if values.len() >= 3 { - let f = runtime_string_arg(&values[2], runtime.heap(), "pad_right() fill")?; - if f.is_empty() { - bail!("pad_right() fill must not be empty"); + if values.len() > 2 { + bail!("to_int() takes 1 or 2 arguments: value[, base]"); + } + let base = match values.get(1) { + Some(value) => { + let base = int_arg(value, "to_int() base")?; + if !(2..=36).contains(&base) { + bail!("to_int() base must be between 2 and 36, got {base}"); + } + base as u32 } - f.to_string() - } else { - " ".to_string() + None => 10, }; - if width <= value.len() { - return Ok(runtime_string_value(value.as_ref(), runtime.heap_mut())); - } - let needed = width - value.len(); - let pad = fill.repeat(needed / fill.len() + 1); - let padded = format!("{}{}", value.as_ref(), &pad[..needed]); - Ok(runtime_string_value(&padded, runtime.heap_mut())) - } - - #[stdlib_export(params(value: Number | Bool), returns = Int)] - fn to_int(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { - match &args.as_slice()[0] { - RuntimeVal::Int(v) => Ok(RuntimeVal::Int(*v)), - RuntimeVal::Float(v) => Ok(RuntimeVal::Int(*v as i64)), - RuntimeVal::Bool(v) => Ok(RuntimeVal::Int(if *v { 1 } else { 0 })), - _ => bail!("to_int() argument must be a number or bool"), + match &values[0] { + RuntimeVal::Int(value) => Ok(RuntimeVal::Int(*value)), + RuntimeVal::Float(value) => Ok(RuntimeVal::Int(float_to_int(*value)?)), + RuntimeVal::Bool(value) => Ok(RuntimeVal::Int(i64::from(*value))), + other => { + let text = runtime_string_arg(other, runtime.heap(), "to_int() first argument")?; + Ok(match i64::from_str_radix(text.trim(), base) { + Ok(value) => RuntimeVal::Int(value), + Err(_) => RuntimeVal::Nil, + }) + } } } - #[stdlib_export(params(value: Number | Bool), returns = Float)] - fn to_float(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + /// The `Float` half of [`to_int`], with the same split: unparseable text is + /// `nil`, everything else converts. `"nan"`, `"inf"` and `"-inf"` parse — + /// they are Float values, unlike for `to_int`. + #[stdlib_export(params(value: String | Number | Bool), returns = Float?)] + fn to_float(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { match &args.as_slice()[0] { - RuntimeVal::Float(v) => Ok(RuntimeVal::Float(*v)), - RuntimeVal::Int(v) => Ok(RuntimeVal::Float(*v as f64)), - RuntimeVal::Bool(v) => Ok(RuntimeVal::Float(if *v { 1.0 } else { 0.0 })), - _ => bail!("to_float() argument must be a number or bool"), + RuntimeVal::Float(value) => Ok(RuntimeVal::Float(*value)), + RuntimeVal::Int(value) => Ok(RuntimeVal::Float(*value as f64)), + RuntimeVal::Bool(value) => Ok(RuntimeVal::Float(if *value { 1.0 } else { 0.0 })), + other => { + let text = runtime_string_arg(other, runtime.heap(), "to_float() first argument")?; + Ok(match text.trim().parse::() { + Ok(value) => RuntimeVal::Float(value), + Err(_) => RuntimeVal::Nil, + }) + } } } #[stdlib_export(params(text: String), returns = String)] fn title(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "title()")?; - let mut result = String::with_capacity(value.len()); - let mut capitalize_next = true; - for ch in value.chars() { - if ch.is_whitespace() { - capitalize_next = true; - result.push(ch); - } else if capitalize_next { - for c in ch.to_uppercase() { - result.push(c); - } - capitalize_next = false; - } else { - for c in ch.to_lowercase() { - result.push(c); - } - } - } - Ok(runtime_string_value(&result, runtime.heap_mut())) + forward("title", args, runtime) } #[stdlib_export(params(text: String), returns = String)] fn capitalize(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let value = one_string(args, runtime, "capitalize()")?; - let mut chars = value.chars(); - let mut result = String::with_capacity(value.len()); - if let Some(first) = chars.next() { - for c in first.to_uppercase() { - result.push(c); - } - } - for ch in chars { - for c in ch.to_lowercase() { - result.push(c); - } - } - Ok(runtime_string_value(&result, runtime.heap_mut())) + forward("capitalize", args, runtime) } } -fn one_string(args: NativeArgs<'_>, runtime: &NativeRuntime<'_>, name: &str) -> Result> { - runtime_string_arg(&args.as_slice()[0], runtime.heap(), name) +/// The module spelling of a method: the receiver written first. +/// +/// `string.upper(s)` **is** `s.upper()`, and this is what makes that true by +/// construction rather than by two bodies agreeing. They did not agree: the +/// module refused a negative `slice` start while the method counted from the +/// end (the language's own rule), `split(s, "")` answered `["a","b","c"]` here +/// and `["","a","b","c",""]` there, and `byte_at(s, -1)` raised here and +/// answered nil there. Three different answers for three spellings of one +/// question is what two implementations buy. +/// +/// `iter` was built this way for exactly this reason — see the note on +/// `core_call_method_windowed`. +fn forward(method: &'static str, args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { + let values = args.as_slice(); + let Some((receiver, rest)) = values.split_first() else { + bail!("string.{method} expects a string as its first argument"); + }; + lk_core::vm::core_call_method_windowed(*receiver, method, rest, runtime) } -fn two_strings(args: NativeArgs<'_>, runtime: &NativeRuntime<'_>, name: &str) -> Result<(Arc, Arc)> { - let values = args.as_slice(); - Ok(( - runtime_string_arg(&values[0], runtime.heap(), name)?, - runtime_string_arg(&values[1], runtime.heap(), name)?, - )) +/// A Float as an Int, or a raise. +/// +/// `as i64` answers `0` for NaN and saturates at the ends — a wrong number that +/// looks like a right one. LK's rule for a value with no meaning is to fail +/// loudly (`math.sqrt(-4.0)` does), so this does. +fn float_to_int(value: f64) -> Result { + if value.is_nan() { + bail!("to_int() cannot convert NaN to an Int"); + } + if value.is_infinite() { + bail!("to_int() cannot convert {value} to an Int"); + } + // Half-open against the powers of two, not against `i64::MIN`/`MAX` as + // floats: `i64::MAX as f64` rounds *up* to 2^63, so comparing against it + // would admit a value one step past the end. No `trunc`/`powi` — neither + // exists without std, and neither is needed: every representable f64 + // outside this range truncates to something outside it too (the spacing up + // there is 2048), so the cast below is exact for everything that passes. + const MIN: f64 = -9_223_372_036_854_775_808.0; // -2^63 + const LIMIT: f64 = 9_223_372_036_854_775_808.0; // 2^63 + if !(MIN..LIMIT).contains(&value) { + bail!("to_int() cannot convert {value} to an Int: it is outside the Int range"); + } + // Truncates toward zero, which is what `to_int(3.99)` means. + Ok(value as i64) } fn int_arg(value: &RuntimeVal, context: &str) -> Result { @@ -482,21 +381,6 @@ fn int_arg(value: &RuntimeVal, context: &str) -> Result { } } -fn usize_arg(value: &RuntimeVal, context: &str) -> Result { - let value = int_arg(value, context)?; - if value < 0 { - bail!("{context} must be non-negative"); - } - Ok(value as usize) -} - -fn bool_arg(value: &RuntimeVal, context: &str) -> Result { - match value { - RuntimeVal::Bool(value) => Ok(*value), - _ => Err(anyhow!("{context} must be a boolean")), - } -} - fn string_list_arg(value: &RuntimeVal, heap: &HeapStore, context: &str) -> Result> { let RuntimeVal::Obj(handle) = value else { bail!("{context} must be a list"); diff --git a/stdlib/crates/task/src/lib.rs b/stdlib/crates/task/src/lib.rs index 7337ea38..243fb693 100644 --- a/stdlib/crates/task/src/lib.rs +++ b/stdlib/crates/task/src/lib.rs @@ -4,6 +4,7 @@ // `stdlib::register_stdlib_concurrency_globals` are migrated separately. use anyhow::{Result, anyhow, bail}; +use lk_core::util::value_map::value_map_new; use lk_core::{ val::{HeapStore, HeapValue, RuntimeVal, TaskValue}, vm::{NativeArgs, NativeRuntime}, @@ -27,7 +28,13 @@ impl TaskModule { let value = runtime .async_runtime() .with(|rt| rt.block_on(rt.join_task(task.id))) - .map_err(|err| anyhow!("Failed to await task: {err}"))?; + // The cause, unwrapped: a task that raised `modulo by zero` has to + // say that and not `Failed to await task: modulo by zero`, or the + // same failure reads differently depending on whether it crossed a + // task boundary. See the error-text ruling in `docs/semantics.md`. + // A raise that crossed the task boundary arrives detached from the + // heap it was built in; this is where it comes back into one. + .map_err(|error| lk_core::rt::RaisedPayload::reattach(error, runtime.heap_mut()))?; value.into_value(runtime.heap_mut()) } @@ -42,15 +49,26 @@ impl TaskModule { } } - #[stdlib_export(name = "join_all", params(...tasks: Task), returns = List)] + /// Awaits several tasks and answers their values, in the order given. + /// + /// Takes either the tasks themselves or **one list of them**. The list form + /// is the point: tasks are collected in a loop, the language has no spread + /// operator, and variadic-only meant there was no way at all to join a + /// number of tasks the program did not know when it was written — which is + /// what `join_all` is for. + #[stdlib_export(name = "join_all", params(...tasks: Any), returns = List)] fn join_all(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let mut values = Vec::with_capacity(args.len()); - for arg in args.as_slice() { + let tasks = match args.as_slice() { + [single] => task_list_arg(single, runtime.heap()), + many => many.to_vec(), + }; + let mut values = Vec::with_capacity(tasks.len()); + for arg in &tasks { let task = task_arg(arg, runtime.heap(), "task.join_all()")?; let value = runtime .async_runtime() .with(|rt| rt.block_on(rt.join_task(task.id))) - .map_err(|err| anyhow!("Failed to await task: {err}"))?; + .map_err(|error| lk_core::rt::RaisedPayload::reattach(error, runtime.heap_mut()))?; values.push(value.into_value(runtime.heap_mut())?); } let list = crate::typed_list_from_values(values, runtime.heap()); @@ -67,7 +85,7 @@ impl TaskModule { .async_runtime() .with(|rt| Ok(rt.stats())) .map_err(|err| anyhow!("Failed to read runtime stats: {err}"))?; - let mut map = lk_core::util::fast_map::fast_hash_map_new(); + let mut map = value_map_new(); map.insert( Arc::::from("active_tasks"), RuntimeVal::Int(stats.active_tasks as i64), @@ -89,10 +107,7 @@ impl TaskModule { #[stdlib_export(name = "sleep", params(ms: Int | Float), returns = Nil)] fn sleep(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let duration_ms = numeric_millis(args.get(0).expect("checked arity"), "task.sleep()")?; - if duration_ms < 0 { - bail!("task.sleep() duration must be non-negative"); - } + let duration_ms = lk_stdlib_common::duration_millis(args.get(0).expect("checked arity"), "task.sleep()")?; runtime .async_runtime() .with(|rt| { @@ -106,6 +121,22 @@ impl TaskModule { } } +/// The elements of a list argument, or the argument itself when it is not one. +/// +/// Lets `join_all` take `[t1, t2]` as well as `t1, t2` without a second export. +fn task_list_arg(value: &RuntimeVal, heap: &HeapStore) -> Vec { + let RuntimeVal::Obj(handle) = value else { + return vec![*value]; + }; + match heap.get(*handle) { + // Only a mixed list can hold tasks; a typed one (`List`, …) + // cannot, so it is handed on whole and reported as the wrong argument + // rather than dissolved into a row of nils. + Some(HeapValue::List(lk_core::val::TypedList::Mixed(values))) => values.clone(), + _ => vec![*value], + } +} + fn task_arg(value: &RuntimeVal, heap: &HeapStore, name: &str) -> Result> { let RuntimeVal::Obj(handle) = value else { bail!("{name} expects a Task argument"); @@ -119,14 +150,6 @@ fn task_arg(value: &RuntimeVal, heap: &HeapStore, name: &str) -> Result Result { - match value { - RuntimeVal::Int(value) => Ok(*value), - RuntimeVal::Float(value) => Ok(*value as i64), - other => Err(anyhow!("{name} expects a numeric argument, got {:?}", other.kind())), - } -} - #[cfg(test)] mod tests { use super::*; @@ -194,6 +217,42 @@ mod tests { Ok(()) } + /// Tasks are collected in a loop, into a list, and the language has no + /// spread operator — so variadic-only meant a number of tasks the program + /// did not know when it was written could not be joined at all. + /// + /// Asserted through the *rejection*, because a synthetic task is not + /// registered with the async runtime and joining one cannot succeed here: + /// a mixed list of tasks gets past the argument check and fails on the + /// join, while a typed list — which cannot hold tasks — is rejected as the + /// wrong argument. Those two outcomes are only distinguishable if the list + /// was unwrapped. + #[test] + fn task_join_all_takes_one_list_of_tasks_as_well_as_the_tasks() -> Result<()> { + let mut state = RuntimeModuleState::default(); + let task = resolved_task(RuntimeVal::Int(7), state.heap_mut()); + let tasks = RuntimeVal::Obj( + state + .heap_mut() + .alloc(HeapValue::List(lk_core::val::TypedList::Mixed(vec![task]))), + ); + let ints = RuntimeVal::Obj( + state + .heap_mut() + .alloc(HeapValue::List(lk_core::val::TypedList::Int(vec![1, 2]))), + ); + + let unwrapped = call("join_all", &[tasks], &mut state).expect_err("no live runtime here"); + assert!( + !unwrapped.to_string().contains("expects a Task argument"), + "a list of tasks must be unwrapped, not rejected: {unwrapped}" + ); + + let rejected = call("join_all", &[ints], &mut state).expect_err("a List holds no tasks"); + assert!(rejected.to_string().contains("expects a Task argument"), "{rejected}"); + Ok(()) + } + #[test] fn task_join_all_empty_returns_empty_list() -> Result<()> { let mut state = RuntimeModuleState::default(); diff --git a/stdlib/crates/time/src/lib.rs b/stdlib/crates/time/src/lib.rs index e090cb91..f417d34c 100644 --- a/stdlib/crates/time/src/lib.rs +++ b/stdlib/crates/time/src/lib.rs @@ -23,7 +23,7 @@ pub struct TimeModule; impl TimeModule { #[stdlib_export(name = "sleep", params(ms: Int | Float), returns = Nil)] fn sleep(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let duration_ms = numeric_millis(args.get(0).expect("checked arity"), "time.sleep()")?; + let duration_ms = lk_stdlib_common::duration_millis(args.get(0).expect("checked arity"), "time.sleep()")?; runtime .async_runtime() .with(|runtime| { @@ -38,14 +38,14 @@ impl TimeModule { #[stdlib_export(name = "timeout", params(ms: Int | Float), returns = Channel)] fn timeout(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let duration_ms = numeric_millis(args.get(0).expect("checked arity"), "time.timeout()")?; + let duration_ms = lk_stdlib_common::duration_millis(args.get(0).expect("checked arity"), "time.timeout()")?; let channel_id = spawn_timer(&runtime.async_runtime(), duration_ms, RuntimeVal::Nil)?; Ok(runtime_channel(channel_id, 1, Type::Nil, runtime)) } #[stdlib_export(name = "after", params(ms: Int | Float), returns = Channel)] fn after(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let duration_ms = numeric_millis(args.get(0).expect("checked arity"), "time.after()")?; + let duration_ms = lk_stdlib_common::duration_millis(args.get(0).expect("checked arity"), "time.after()")?; let channel_id = spawn_timer(&runtime.async_runtime(), duration_ms, RuntimeVal::Int(epoch_millis()))?; Ok(runtime_channel(channel_id, 1, Type::Int, runtime)) } @@ -55,7 +55,7 @@ impl TimeModule { Ok(RuntimeVal::Int(epoch_millis())) } - #[stdlib_export(name = "since", params(start_ms: Int | Float, end_ms: Int | Float), returns = Int)] + #[stdlib_export(name = "since", params(start_ms: Int | Float, end_ms: Int | Float), named(end_ms), returns = Int)] fn since(args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { let values = args.as_slice(); let start = numeric_millis(&values[0], "time.since()")?; @@ -64,6 +64,11 @@ impl TimeModule { } } +/// An *instant* in milliseconds, of either sign. +/// +/// Distinct from `lk_stdlib_common::duration_millis`, which is a *duration* and +/// must be non-negative: `time.since(start, end)` takes two points on a clock, +/// and their difference is the thing with a direction. fn numeric_millis(value: &RuntimeVal, name: &str) -> Result { match value { RuntimeVal::Int(ms) => Ok(*ms), @@ -73,7 +78,12 @@ fn numeric_millis(value: &RuntimeVal, name: &str) -> Result { } fn epoch_millis() -> i64 { - SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64 + // A clock set before 1970 answers `Err`, and unwrapping it aborted the + // process — every `time.*` call, on a machine whose clock is merely wrong. + // Zero is the epoch, which is what a pre-epoch clock is closest to. + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_or(0, |elapsed| elapsed.as_millis() as i64) } fn runtime_channel(id: u64, capacity: i64, inner_type: Type, runtime: &mut NativeRuntime<'_>) -> RuntimeVal { @@ -127,12 +137,26 @@ mod tests { function(NativeArgs::new(args), &mut runtime) } + /// Registered arity follows the declaration: a member with `named(...)` + /// registers variadic, because a named argument occupies no positional + /// slot and the generated precheck — which knows the names — checks the + /// bounds instead. #[test] fn time_exports_use_runtime_native() -> Result<()> { for name in ["sleep", "timeout", "after", "now", "since"] { let (arity, function) = time_native(name)?; assert!(matches!(function, NativeFunction::Plain(_))); - assert_ne!(arity, lk_core::vm::NativeEntry::VARIADIC); + let path = format!("time.{name}"); + let nameable = TimeModule::stdlib_metadata() + .signatures + .iter() + .find(|signature| signature.path == path) + .is_some_and(|signature| signature.params.iter().any(|param| param.named)); + if nameable { + assert_eq!(arity, lk_core::vm::NativeEntry::VARIADIC, "{name} declares named(...)"); + } else { + assert_ne!(arity, lk_core::vm::NativeEntry::VARIADIC, "{name}"); + } } Ok(()) } diff --git a/stdlib/macros/src/lib.rs b/stdlib/macros/src/lib.rs index 77dd3fd8..941c3704 100644 --- a/stdlib/macros/src/lib.rs +++ b/stdlib/macros/src/lib.rs @@ -63,6 +63,7 @@ fn expand_stdlib_exports(args: StdlibExportsArgs, impl_item: &mut ItemImpl) -> R let register_fn = format_ident!("register"); let mut exports = Vec::new(); let mut metadata_exports = Vec::new(); + let mut signature_exports = Vec::new(); let mut wrapper_functions = Vec::new(); let mut value_exports = Vec::new(); @@ -78,7 +79,7 @@ fn expand_stdlib_exports(args: StdlibExportsArgs, impl_item: &mut ItemImpl) -> R let params = export .params .ok_or_else(|| syn::Error::new_spanned(fn_ident, "missing export params(...)"))?; - let arity = params.arity(); + let arity = params.arity(&export.named); let arity_tokens = arity.tokens(); let returns = export .returns @@ -121,6 +122,16 @@ fn expand_stdlib_exports(args: StdlibExportsArgs, impl_item: &mut ItemImpl) -> R #docs_tokens, ) }); + let (signature_params, single_arity) = params.signature_param_tokens(&export.named); + let returns_text = &returns.display; + signature_exports.push(quote! { + ::lk_core::typ::StdlibCallableSig { + path: concat!(#module_ident, ".", #name), + params: #signature_params, + returns: #returns_text, + single_arity: #single_arity, + } + }); } for attr in impl_item.attrs.iter() { @@ -137,7 +148,7 @@ fn expand_stdlib_exports(args: StdlibExportsArgs, impl_item: &mut ItemImpl) -> R let params = export .params .ok_or_else(|| syn::Error::new_spanned(attr, "impl-level stdlib_export requires params(...)"))?; - let arity = params.arity(); + let arity = params.arity(&export.named); let arity_tokens = arity.tokens(); let returns = export .returns @@ -167,6 +178,16 @@ fn expand_stdlib_exports(args: StdlibExportsArgs, impl_item: &mut ItemImpl) -> R #docs_tokens, ) }); + let (signature_params, single_arity) = params.signature_param_tokens(&export.named); + let returns_text = &returns.display; + signature_exports.push(quote! { + ::lk_core::typ::StdlibCallableSig { + path: concat!(#module_ident, ".", #name), + params: #signature_params, + returns: #returns_text, + single_arity: #single_arity, + } + }); } impl_item.attrs.retain(|attr| !attr.path().is_ident("stdlib_export")); @@ -250,10 +271,14 @@ fn expand_stdlib_exports(args: StdlibExportsArgs, impl_item: &mut ItemImpl) -> R const CALLABLES: &[::lk_stdlib_common::metadata::StdlibCallableMetadata] = &[ #(#metadata_exports),* ]; + const SIGNATURES: &[::lk_core::typ::StdlibCallableSig] = &[ + #(#signature_exports),* + ]; ::lk_stdlib_common::metadata::StdlibModuleMetadata::new( #module_ident, <#self_ty>::stdlib_module_docs(), CALLABLES, + SIGNATURES, ) } } @@ -396,7 +421,7 @@ fn export_function_path( wrapper: Some(wrapper), }); } - let arity = params.arity(); + let arity = params.arity(named); let Arity::Fixed(expected_arity) = arity else { return Err(syn::Error::new_spanned( fn_ident, @@ -588,10 +613,20 @@ impl ParamList { Ok(Self { signatures }) } - fn arity(&self) -> Arity { + fn arity(&self, named: &[String]) -> Arity { if self.signatures.len() != 1 { return Arity::Variadic; } + // A named argument does not occupy a positional slot, so a call that + // uses one supplies fewer than the declaration lists. The VM checks a + // `Fixed` arity against the positional count *before* the export runs, + // so declaring one here would reject `substring(s, start: 2, length: 3)` + // as "expects 3 positional arguments, got 1" — which is what it did. + // The real bounds are checked by the generated precheck, which knows + // about the names. + if !named.is_empty() { + return Arity::Variadic; + } let signature = &self.signatures[0]; if signature .params @@ -604,6 +639,34 @@ impl ParamList { } } + /// The declared parameter types, as `lk_core`'s checker wants them. + /// + /// The second element is false when this declaration has no single type to + /// give: more than one parameter list, or a variadic tail. Handing the + /// checker one arm of an overload would make calls to the other arms fail, + /// so it is told nothing and falls back to inference. + fn signature_param_tokens(&self, named: &[String]) -> (proc_macro2::TokenStream, bool) { + let single_arity = self.signatures.len() == 1 && !self.signatures[0].params.iter().any(|param| param.variadic); + let params: &[ParamSpec] = if single_arity { &self.signatures[0].params } else { &[] }; + let entries = params.iter().map(|param| { + let name = ¶m.name; + let ty = ¶m.ty; + let optional = param.optional; + let is_named = named.iter().any(|entry| entry == ¶m.name); + let has_default = param.default.is_some(); + quote! { + ::lk_core::typ::StdlibParamSig { + name: #name, + ty: #ty, + optional: #optional, + named: #is_named, + has_default: #has_default, + } + } + }); + (quote!(&[#(#entries),*]), single_arity) + } + fn signature(&self, name: &str, returns: &str) -> String { self.signatures .iter() @@ -662,12 +725,126 @@ impl ParamList { })?; } }; + let merge_tokens = self.named_merge_tokens(&named.iter().copied().collect::>(), display_name); quote! { let __lk_stdlib_export_arg_len = args.len(); if !(#(#checks)||*) { ::anyhow::bail!("{} takes {}", #display_name, #expected); } #named_tokens + #merge_tokens + } + } + + /// Code that folds the named arguments back into the positional slots the + /// body reads. + /// + /// Without this, `named(...)` only *validates* which names are accepted — + /// the value never reaches `args.as_slice()`, so a body that reads + /// `values[2]` sees nothing and reports "expects 3 positional arguments, + /// got 1". That is why two of two hundred and forty exports used named + /// parameters: adopting one meant hand-writing fifteen to forty lines of + /// merging in the body, which `math.clamp` and `string.replace` both did. + /// + /// A slot that neither the call nor a declared default fills is an error + /// naming the parameter — which is the whole point of having named it. + fn named_merge_tokens(&self, named: &[&str], display_name: &str) -> proc_macro2::TokenStream { + // One parameter list only: with two, a name does not identify a slot. + let Some(signature) = self.signatures.first() else { + return quote!(); + }; + if named.is_empty() || self.signatures.len() != 1 || signature.params.iter().any(|param| param.variadic) { + return quote!(); + } + let count = signature.params.len(); + let arms = signature + .params + .iter() + .enumerate() + .filter(|&(_index, param)| named.contains(¶m.name.as_str())) + .map(|(index, param)| { + let name = param.name.as_str(); + quote!(#name => { + if __lk_slots[#index].is_some() { + ::anyhow::bail!("{} received duplicate named argument '{}'", #display_name, #name); + } + __lk_slots[#index] = ::core::option::Option::Some(*__lk_named_value); + }) + }); + // Defaults are declared as source text (`min?: Int = 0`), and the ones + // that can fill a gap are the literals — which is all any of them are. + let fills = signature.params.iter().map(|param| { + let name = param.name.as_str(); + match param.default.as_deref().and_then(default_literal_tokens) { + Some(literal) => quote!((::core::option::Option::Some(#literal), #name)), + None => quote!((::core::option::Option::None, #name)), + } + }); + quote! { + // A fixed buffer, not a `Vec`: the slot count is known here, and + // some of these crates are `no_std` without `alloc` in scope. + let mut __lk_buf: [::lk_core::val::RuntimeVal; #count] = + [::lk_core::val::RuntimeVal::Nil; #count]; + let mut __lk_merged = false; + let mut __lk_len: usize = 0; + if args.named_len() > 0 { + let mut __lk_slots: [::core::option::Option<::lk_core::val::RuntimeVal>; #count] = + [::core::option::Option::None; #count]; + args.try_for_each_named(runtime.heap(), |__lk_named_name, __lk_named_value| { + match __lk_named_name { + #(#arms)* + // Not a name this function declares. The type checker + // rejects it for a call written in source, but this is + // the only guard for one built any other way — and + // ignoring it silently is how a typo becomes a default. + __lk_other => ::anyhow::bail!( + "{} does not accept named argument '{}'", + #display_name, + __lk_other + ), + } + Ok(()) + })?; + let __lk_decl: [(::core::option::Option<::lk_core::val::RuntimeVal>, &str); #count] = + [#(#fills),*]; + for (__lk_index, __lk_value) in args.as_slice().iter().enumerate() { + __lk_buf[__lk_index] = *__lk_value; + __lk_len = __lk_index + 1; + } + for __lk_slot in 0..#count { + let ::core::option::Option::Some(__lk_value) = __lk_slots[__lk_slot] else { + continue; + }; + if __lk_slot < __lk_len { + ::anyhow::bail!( + "{} received '{}' both positionally and by name", + #display_name, + __lk_decl[__lk_slot].1 + ); + } + // Slots between the last positional argument and this one + // take their declared default, or say which is missing. + while __lk_len < __lk_slot { + let (__lk_default, __lk_name) = __lk_decl[__lk_len]; + let ::core::option::Option::Some(__lk_default) = __lk_default else { + ::anyhow::bail!( + "{} needs '{}' — give it positionally or by name", + #display_name, + __lk_name + ); + }; + __lk_buf[__lk_len] = __lk_default; + __lk_len += 1; + } + __lk_buf[__lk_len] = __lk_value; + __lk_len += 1; + __lk_merged = true; + } + } + // The named arguments ride along: a body that reads them by name + // (`string.replace` decides its `all` default that way) must still + // find them after the merge. + let args = if __lk_merged { args.with_values(&__lk_buf[..__lk_len]) } else { args }; } } @@ -851,6 +1028,27 @@ fn split_top_level(source: &str, separator: char) -> Vec { out } +/// A declared default (`min?: Int = 0`) as a `RuntimeVal` the merge can place. +/// +/// Only literals: a default is written to be read, and every one in the +/// standard library is a number, a bool or a string. Anything else returns +/// `None`, which makes that slot one the caller has to fill — an error naming +/// the parameter rather than a value nobody wrote. +fn default_literal_tokens(text: &str) -> Option { + let text = text.trim(); + if text == "true" || text == "false" { + let value: bool = text == "true"; + return Some(quote!(::lk_core::val::RuntimeVal::Bool(#value))); + } + if let Ok(value) = text.parse::() { + return Some(quote!(::lk_core::val::RuntimeVal::Int(#value))); + } + if let Ok(value) = text.parse::() { + return Some(quote!(::lk_core::val::RuntimeVal::Float(#value))); + } + None +} + fn normalize_type_display(source: &str) -> String { source .split_whitespace() diff --git a/stdlib/src/bytes_test.rs b/stdlib/src/bytes_test.rs index 28053257..3451c8ff 100644 --- a/stdlib/src/bytes_test.rs +++ b/stdlib/src/bytes_test.rs @@ -40,7 +40,7 @@ mod tests { && bytes.to_list(bytes.slice(c, 1, 4)) == [66, 67, 100] && bytes.to_string_utf8(c) == "ABCde" && bytes.to_string_lossy(bytes.from_list([255])) != "" - && bytes.eq(a, bytes.from_list([65, 66, 67])); + && a == bytes.from_list([65, 66, 67]); "#; let result = run(source)?; @@ -59,13 +59,66 @@ mod tests { let err = run("use bytes; return bytes.from_list([\"x\"]);").expect_err("non-int item should fail"); assert!(err.to_string().contains("expects Int items")); + + // The method spelling of the same constructor, and the same refusal. + let err = run("return [256].to_bytes();").expect_err("256 is outside u8 range"); + assert!(err.to_string().contains("0..=255")); } + /// A reversed window is empty, not a refusal — the rule every other + /// sequence reads by, and the one the *method* spelling always followed. + /// + /// `bytes.slice(b, 2, 1)` used to raise while `b.slice(2, 1)` answered + /// `Bytes([])`: the same call, two bodies, two answers. The module forwards + /// to the method now, so there is one answer and it is the clamping one + /// (`"abcde".slice(-1, -3)` and `xs.slice(-1, -3)` are empty too). #[test] - fn bytes_slice_rejects_end_before_start() { - let err = run("use bytes; return bytes.slice(bytes.from_string(\"abc\"), 2, 1);") - .expect_err("end before start should fail"); - assert!(err.to_string().contains("end must be greater than or equal to start")); + fn a_reversed_window_is_empty_on_both_spellings() -> Result<()> { + let source = r#" + use bytes; + let b = bytes.from_string("abc"); + return bytes.slice(b, 2, 1) == b.slice(2, 1) + && bytes.len(bytes.slice(b, 2, 1)) == 0; + "#; + assert_eq!(run(source)?.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// Every `bytes` member answers exactly what its method spelling answers — + /// by construction, because the module forwards. + /// + /// Five members had two bodies (`len`, `is_empty`, `get`, `slice`, + /// `to_list`), and `slice` had already drifted. Three more existed only as + /// module functions and ten only as methods, so most of this surface could + /// not even be *compared* until both spellings existed. + #[test] + fn every_module_spelling_answers_what_the_method_answers() -> Result<()> { + let source = r#" + use bytes; + let b = bytes.from_string("abcde"); + return bytes.len(b) == b.len() + && bytes.is_empty(b) == b.is_empty() + && bytes.get(b, -1) == b.get(-1) + && bytes.get(b, 99) == b.get(99) + && bytes.first(b) == b.first() + && bytes.last(b) == b.last() + && bytes.contains(b, 98) == b.contains(98) + && bytes.index_of(b, 98) == b.index_of(98) + && bytes.sum(b) == b.sum() + && bytes.min(b) == b.min() + && bytes.max(b) == b.max() + && bytes.take(b, 2) == b.take(2) + && bytes.skip(b, 2) == b.skip(2) + && bytes.slice(b, 1, 3) == b.slice(1, 3) + && bytes.to_list(b) == b.to_list() + && bytes.to_string_utf8(b) == b.to_string_utf8() + && bytes.to_string_lossy(b) == b.to_string_lossy() + && bytes.concat(b, b) == b.concat(b) + && bytes.from_string("xy") == "xy".bytes() + && bytes.from_list([1, 2]) == [1, 2].to_bytes(); + "#; + assert_eq!(run(source)?.first_return(), &RuntimeVal::Bool(true)); + Ok(()) } #[test] @@ -88,7 +141,7 @@ mod tests { file.close(reader); fs.remove_file("{path}"); fs.remove_file(text_path); - return bytes.eq(raw, data) && text == "hello"; + return raw == data && text == "hello"; "# ); diff --git a/stdlib/src/chan_semantics_test.rs b/stdlib/src/chan_semantics_test.rs index 7d064cff..0bbe8b05 100644 --- a/stdlib/src/chan_semantics_test.rs +++ b/stdlib/src/chan_semantics_test.rs @@ -75,8 +75,73 @@ mod tests { ); } + /// Every closed-channel refusal reads the same, whichever spelling raised + /// it — and the same as the native runtime's. + /// + /// A caught message is printed output, so three spellings of one operation + /// is three answers: `chan.try_send` decorated the runtime's error into + /// "Failed to send to channel: Channel is closed" while `send`, `chan.send` + /// and `chan.try_recv` all said the short form. `lkrt::chan` raises the + /// short form too, so the decorated one was also a VM/native divergence. + #[test] + fn every_closed_channel_refusal_reads_the_same() { + let program = r#" + use chan; + let c = chan.new(1); + chan.close(c); + let out = []; + out = out.push(try { "${chan.try_send(c, 1)}" } catch e { "${e}" }); + out = out.push(try { "${chan.send(c, 1)}" } catch e { "${e}" }); + out = out.push(try { "${send(c, 1)}" } catch e { "${e}" }); + out = out.push(try { "${chan.try_recv(c)}" } catch e { "${e}" }); + out = out.push(try { "${chan.recv(c)}" } catch e { "${e}" }); + out = out.push(try { "${recv(c)}" } catch e { "${e}" }); + return out; + "#; + let result = run(program).expect("program runs"); + let rendered = lk_core::vm::display_runtime_value(result.first_return(), result.state.heap()); + assert_eq!( + rendered, + "[\"send on closed channel\",\"send on closed channel\",\"send on closed channel\",\ + \"receive on closed channel\",\"receive on closed channel\",\"receive on closed channel\"]" + ); + } + /// `try_recv`: value when ready, nil when empty (not an error) — postfix /// `!` turns "must have a value" into an assertion. + /// The module is usable on its own: `use chan;` shadows the `chan` global, + /// and the blocking pair used to exist only as unqualified `send`/`recv`, + /// so an imported channel could only be polled. + #[test] + fn the_module_spells_the_blocking_pair_too() { + assert_true( + r#" + use chan; + let c = chan.new(2); + chan.send(c, 41); + chan.send(c, 42); + return chan.recv(c) == 41 && chan.recv(c) == 42 && chan.len(c) == 0; + "#, + ); + } + + /// `0` is unbuffered, not unbounded: one value fits, the second does not. + /// lkrt read the retired rule (`<= 0` unbounded) and answered `true` twice. + #[test] + fn capacity_zero_is_unbuffered_and_negative_raises() { + assert_true( + r#" + use chan; + let c = chan.new(0); + let first = chan.try_send(c, 1); + let second = chan.try_send(c, 2); + let negative = try { chan.new(-1); false } catch e { true }; + // `capacity` reports what was asked for, not the queue's bound. + return first && !second && chan.len(c) == 1 && negative && chan.capacity(c) == 0; + "#, + ); + } + #[test] fn try_recv_yields_value_or_nil_and_pairs_with_unwrap() { assert_true( @@ -121,4 +186,42 @@ mod tests { "#, ); } + + /// Calling an imported module is a **check-time** error that says what to + /// do about it. + /// + /// `use chan;` binds the module over the `chan()` global — a documented + /// sharp edge — and a module is a map of its members, so `chan(1)` calls a + /// Map. Three answers for one program until now: the VM raised at run time + /// ("this value is not a function: it is a Map"), the native backend + /// ignored the import and called the builtin constructor, and `lk check` + /// said nothing at all. The checker knows what the import bound, so it is + /// the one that answers — before either engine runs. + #[test] + fn calling_an_imported_module_says_what_it_is() { + let error = run("use chan;\nlet c = chan(1);\n").expect_err("a module is not callable"); + let text = format!("{error:#}"); + assert!(text.contains("not a function"), "unexpected error: {text}"); + assert!(text.contains("module"), "and point at how a program gets here: {text}"); + assert!( + text.contains("chan.new(") && text.contains("use chan as"), + "and name both ways out: {text}" + ); + + // The alias form binds the alias, not the module's own name — so the + // global stays reachable, which is exactly what the message suggests. + run("use chan as ch;\nlet c = chan(1);\nch.close(c);\n").expect("the alias leaves `chan` alone"); + } + + /// Awaiting twice says so, instead of describing the task table. + #[test] + fn awaiting_twice_says_the_result_is_already_taken() { + let error = run("use task;\nlet h = spawn(|| 5);\nlet a = task.await(h);\nlet b = task.await(h);\n") + .expect_err("the second await has nothing to take"); + let text = format!("{error:#}"); + assert!( + text.contains("already been awaited"), + "the error should speak the language: {text}" + ); + } } diff --git a/stdlib/src/datetime_test.rs b/stdlib/src/datetime_test.rs index f7ec358c..e1f7a277 100644 --- a/stdlib/src/datetime_test.rs +++ b/stdlib/src/datetime_test.rs @@ -81,6 +81,38 @@ mod tests { Ok(()) } + /// `parse` accepts **whatever `format` can write** — a date alone and a time + /// alone included. + /// + /// It used to try only `NaiveDateTime`, which needs both halves, so the pair + /// could not round-trip: `format(t, "%Y-%m-%d")` gave `1970-01-02` and + /// parsing it back with the same format string answered chrono's "input is + /// not enough for unique date and time". A format string describes the text + /// on both sides; the two directions have to agree about what it describes. + #[test] + fn parse_accepts_every_shape_format_writes() -> Result<()> { + // Date only → midnight UTC, which is the half `format` dropped. + assert_eq!( + call_datetime_strings("parse", "1970-01-02", "%Y-%m-%d")?, + RuntimeVal::Int(86400) + ); + // Time only → that time on the epoch day. + assert_eq!( + call_datetime_strings("parse", "01:01:01", "%H:%M:%S")?, + RuntimeVal::Int(3661) + ); + // Before the epoch too. + assert_eq!( + call_datetime_strings("parse", "1969-12-31", "%Y-%m-%d")?, + RuntimeVal::Int(-86400) + ); + // And the error names the format instead of describing chrono's parser. + let error = call_datetime_strings("parse", "zz", "%Y-%m-%d").expect_err("not a date"); + let text = format!("{error:#}"); + assert!(text.contains("does not match the format"), "unexpected error: {text}"); + Ok(()) + } + #[test] fn test_day_of_week_and_weekend() -> Result<()> { let saturday = Utc.with_ymd_and_hms(2024, 1, 6, 0, 0, 0).unwrap().timestamp(); @@ -137,7 +169,12 @@ mod tests { fn test_parse_invalid_string_errors() { let err = call_datetime_strings("parse", "not-a-date", "%Y-%m-%d").expect_err("invalid datetime string should error"); - assert!(err.to_string().contains("failed to parse datetime")); + // The text names the value and the format, not chrono's own parser + // requirement ("input is not enough for unique date and time") — a + // sentence about a library the program never mentioned. + let text = err.to_string(); + assert!(text.contains("not-a-date"), "unexpected error: {text}"); + assert!(text.contains("%Y-%m-%d"), "unexpected error: {text}"); } #[test] diff --git a/stdlib/src/globals_test.rs b/stdlib/src/globals_test.rs index 1b386113..ae2a785b 100644 --- a/stdlib/src/globals_test.rs +++ b/stdlib/src/globals_test.rs @@ -35,25 +35,33 @@ mod tests { Ok(()) } + /// `panic` stops the program, and `catch` does not intervene. + /// + /// It used to call Rust's `panic!` and this test caught the unwind. That + /// worked on a desktop and nowhere else — an unrecoverable trap in wasm, no + /// unwinder at all on bare metal — so the other two hosts each wrote their + /// own `panic` and each made it an ordinary catchable error, which is the + /// opposite of what `panic` means. It is one `LkPanic` now, refused by the + /// unwinder, and the test asks about that rather than about Rust's stack. #[test] - fn test_global_panic_panics_with_backtrace() -> Result<()> { - let source = "panic(\"boom\");"; - let tokens = Tokenizer::tokenize(source)?; - let mut parser = StmtParser::new(&tokens); - let program = parser.parse_program()?; - - let mut registry = module::ModuleRegistry::new(); - crate::register_stdlib_modules(&mut registry)?; - crate::register_stdlib_globals(&mut registry); - - let resolver = Arc::new(vm::ModuleResolver::with_registry(registry)); - let mut env = vm::VmContext::new().with_resolver(resolver); + fn test_global_panic_stops_the_program_and_cannot_be_caught() -> Result<()> { + let error = execute_with_stdlib_globals("panic(\"boom\");").expect_err("panic must stop the program"); + assert!(format!("{error:#}").contains("boom"), "{error:#}"); - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let _ = program.execute_with_ctx(&mut env); - })); + // The distinguishing property: a `try` around it does not swallow it. + let error = + execute_with_stdlib_globals("try { panic(\"boom\"); } catch e { return \"caught\"; } return \"ran\";") + .expect_err("a panic must cross a catch"); + assert!(format!("{error:#}").contains("boom"), "{error:#}"); - assert!(result.is_err(), "expected panic, but code did not panic"); + // …while `error(v)` — the recoverable one — is caught, which is the + // distinction the two are for. + let caught = execute_with_stdlib_globals("try { error(\"boom\"); } catch e { return e; } return \"ran\";") + .expect("error() is recoverable"); + assert_eq!( + crate::runtime_native::runtime_display_value(caught.first_return(), caught.state.heap())?, + "boom" + ); Ok(()) } @@ -99,6 +107,37 @@ mod tests { } } + /// Which channel operations are reachable **without** `use chan;`. + /// + /// `docs/concurrency.md` claimed every operation had both spellings — the + /// bare global and `chan.…` — and that was false for six of the nine: + /// `close`, `is_closed`, `len`, `capacity`, `try_send` and `try_recv` are + /// module-only. The claim survived because nothing pinned the set; a doc + /// sentence is not a gate. + /// + /// The split is deliberate rather than incidental — see the doc — so this + /// pins **both** halves: the five that must be there, and the six that must + /// not. Adding one is a language change, and this is where it gets decided. + #[test] + fn the_bare_channel_globals_are_the_go_shaped_core_and_nothing_else() { + let mut registry = module::ModuleRegistry::new(); + crate::register_stdlib_globals(&mut registry); + + for name in ["chan", "send", "recv", "spawn"] { + assert!( + registry.get_runtime_builtin(name).is_some(), + "`{name}` is part of the bare surface" + ); + } + for name in ["close", "is_closed", "len", "capacity", "try_send", "try_recv"] { + assert!( + registry.get_runtime_builtin(name).is_none(), + "`{name}` is reachable only as `chan.{name}` — adding a bare global is a language \ + change, and `docs/concurrency.md` documents why these six are module-only" + ); + } + } + #[test] fn test_global_assertions_execute_without_use() -> Result<()> { let source = r#" @@ -126,10 +165,10 @@ mod tests { "assert_eq(1, 2, \"math broke\");", "assertion failed: expected 2, got 1 - math broke", ), - ("assert_ne(1, 1);", "assertion failed: values should not be equal"), + ("assert_ne(1, 1);", "assertion failed: expected something other than 1"), ( "assert_ne(1, 1, \"duplicate\");", - "assertion failed: values should not be equal - duplicate", + "assertion failed: expected something other than 1 - duplicate", ), ] { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { @@ -153,6 +192,10 @@ mod tests { #[test] fn test_global_assertions_reject_bad_arity_and_named_args() { for (source, expected) in [ + // These are now *check-time* errors too: the arity each body + // enforces is the same constant the registration hands the type + // checker, so `lk check` catches them and the body stays as the + // guard for anything that reaches the native another way. ("assert();", "assert() expects 1 or 2 arguments"), ("assert(true, \"ok\", \"extra\");", "assert() expects 1 or 2 arguments"), ("assert_eq(1);", "assert_eq() expects 2 or 3 arguments"), @@ -161,10 +204,19 @@ mod tests { "assert_eq() expects 2 or 3 arguments", ), ("assert_ne(1);", "assert_ne() expects 2 or 3 arguments"), - ( - "assert(cond: true);", - "Compiler missing named-call signature for `assert`", - ), + // A builtin declares no named parameters — and this is now a + // *check-time* error, in the same words the native uses. + // + // It had been caught early by accident and said the wrong thing: + // the compiler bailed on any named call it had no signature for + // (`Compiler missing named-call signature for `assert``, a sentence + // about its own bookkeeping), which also rejected + // `use { f } from "m"; f(a: 1)` — a perfectly good call. With that + // bail gone the rule is stated where it belongs: the checker knows + // which names the standard library registers as globals, and none + // of them takes named arguments. + // + ("assert(cond: true);", "assert() does not accept named arguments"), ] { let err = execute_with_stdlib_globals(source).expect_err("expected assertion argument error"); assert!( diff --git a/stdlib/src/host_parity_test.rs b/stdlib/src/host_parity_test.rs new file mode 100644 index 00000000..00d3e369 --- /dev/null +++ b/stdlib/src/host_parity_test.rs @@ -0,0 +1,532 @@ +//! What every host owes a program, checked against the hosts themselves. +//! +//! LK swaps platform capabilities at the stdlib layer rather than behind a +//! capability-trait HAL: a target without an OS supplies its own +//! `ModuleRegistry` population. That is the design, and it has a failure mode +//! the design does not prevent — a name the desktop host has and another host +//! has *never heard of*. +//! +//! There are two absences and they read differently: +//! +//! * **Unavailable.** `use fs` on bare metal answers "module 'fs' is not +//! available on bare metal". The reader learns the program needs something +//! this machine has not got. +//! * **Unknown.** A module in neither the backed list nor the unavailable one +//! answers "unknown module", which is what a *typo* answers. The reader is +//! sent to look for a spelling mistake that is not there. +//! +//! The second is what these tests forbid. They ask each host what it knows by +//! building its registry, rather than comparing two hand-written lists — a +//! second list is a thing that drifts, and the drift is invisible until someone +//! runs a program on the smaller host. +//! +//! Found the hard way one level down: `error` — the global a `catch` catches — +//! was missing from both alternative hosts, so every raising program parsed, +//! type-checked, and then failed at run time with "undefined function". Nothing +//! compared the lists, because nothing could. + +use lk_core::module::ModuleRegistry; + +/// Every module the desktop host registers. +fn desktop_modules() -> Vec { + let mut registry = ModuleRegistry::new(); + crate::register_stdlib_modules(&mut registry).expect("desktop modules register"); + registry.get_module_names() +} + +/// Every global the desktop host registers, by name. +fn desktop_globals() -> Vec { + let mut registry = ModuleRegistry::new(); + crate::register_stdlib_core_globals(&mut registry); + crate::register_stdlib_concurrency_globals(&mut registry); + runtime_builtin_names(®istry) +} + +fn runtime_builtin_names(registry: &ModuleRegistry) -> Vec { + // The two-level `chan::try_send` names and the `$`-bearing internals are not + // things a program can write, so they are not part of what a host owes one. + desktop_builtin_candidates() + .into_iter() + .filter(|name| registry.get_runtime_builtin(name).is_some()) + .collect() +} + +/// The globals a program can actually write. `try$call` and `select$block` are +/// deliberately untypeable, and `chan::try_send` is reached through a method. +fn desktop_builtin_candidates() -> Vec { + [ + "print", + "println", + "panic", + "error", + "assert", + "assert_eq", + "assert_ne", + "spawn", + "chan", + "send", + "recv", + ] + .into_iter() + .map(str::to_string) + .collect() +} + +#[test] +fn the_bare_host_knows_every_module_the_desktop_host_has() { + let mut registry = ModuleRegistry::new(); + lk_stdlib_bare::register_bare_stdlib(&mut registry).expect("bare stdlib registers"); + let known = registry.get_module_names(); + let unknown: Vec = desktop_modules() + .into_iter() + .filter(|name| !known.contains(name)) + .collect(); + assert!( + unknown.is_empty(), + "bare metal has never heard of {unknown:?} — a program importing one is told it made a \ + typo. Add it to `BARE_MODULES` if it can work without an OS, or to \ + `UNSUPPORTED_MODULES` if it cannot" + ); +} + +#[test] +fn the_web_host_knows_every_module_the_desktop_host_has() { + let mut registry = ModuleRegistry::new(); + lk_stdlib_web::register_web_stdlib(&mut registry).expect("web stdlib registers"); + let known = registry.get_module_names(); + let unknown: Vec = desktop_modules() + .into_iter() + .filter(|name| !known.contains(name)) + .collect(); + assert!( + unknown.is_empty(), + "the browser host has never heard of {unknown:?} — see the bare-metal test for what the \ + two answers mean" + ); +} + +#[test] +fn every_host_has_every_global_a_program_can_write() { + for (host, registry) in [ + ("bare metal", { + let mut registry = ModuleRegistry::new(); + lk_stdlib_bare::register_bare_stdlib(&mut registry).expect("bare stdlib registers"); + registry + }), + ("the browser", { + let mut registry = ModuleRegistry::new(); + lk_stdlib_web::register_web_stdlib(&mut registry).expect("web stdlib registers"); + registry + }), + ] { + let missing: Vec = desktop_globals() + .into_iter() + .filter(|name| registry.get_runtime_builtin(name).is_none()) + .collect(); + assert!( + missing.is_empty(), + "{host} does not register {missing:?}. A global it cannot back is still owed a \ + *refusal* — `spawn` on bare metal answers \"not available on bare metal: there is \ + one task\", which a program can catch. Absent, it answers \"undefined function\", \ + which a program cannot tell from a typo" + ); + } +} + +/// The two boards outside the workspace, and the features they ask this crate +/// for. +/// +/// `bare-metal/` and `bare-metal-x86/` are `exclude`d from the workspace — they +/// build only for `thumbv7em-none-eabi` and `x86_64-unknown-none` — so +/// `cargo test --workspace` never touches them and neither does CI. Each names +/// a subset of `stdlib/bare`'s features in its own manifest, which makes the +/// list a thing written down three times. +/// +/// Removing a module is therefore three edits, and missing one is a build that +/// fails only when somebody builds that target by hand. That is not a +/// hypothetical: the `slice` module was removed and both boards kept asking for +/// its feature. The x86 kernel was found a day later; the Cortex-M demo — the +/// only thing that shows the no_std VM *running*, on a second architecture — +/// was found the day after that, by looking. +/// +/// This test cannot build those targets. What it can do is read their manifests +/// and check that every feature they name still exists here, which is exactly +/// the failure both of them had. +#[test] +fn the_out_of_workspace_boards_ask_for_features_that_exist() { + let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + let ours = std::fs::read_to_string(root.join("bare/Cargo.toml")).expect("read stdlib/bare manifest"); + let available = feature_names(&ours); + assert!( + available.contains(&"math".to_string()), + "the feature parse found nothing; it is reading the wrong file or the wrong shape" + ); + + for board in ["bare-metal", "bare-metal-x86"] { + let manifest = root.join("..").join(board).join("Cargo.toml"); + let text = std::fs::read_to_string(&manifest).unwrap_or_else(|e| panic!("read {}: {e}", manifest.display())); + let asked = requested_features(&text); + assert!( + !asked.is_empty(), + "{board} names no features for `lk-stdlib-bare`; if that dependency went away, this \ + test should go with it" + ); + let gone: Vec<&String> = asked.iter().filter(|name| !available.contains(name)).collect(); + assert!( + gone.is_empty(), + "{board}/Cargo.toml asks `lk-stdlib-bare` for {gone:?}, which it no longer has. That \ + board is outside the workspace, so nothing else here builds it — the error it gets is \ + `failed to select a version for lk-stdlib-bare`, and only when someone builds that \ + target by hand" + ); + } +} + +/// And the fourth copy of the same list: CI's own. +/// +/// `.github/workflows/check.yml` builds each computation-only module *alone* on +/// `thumbv7em-none-eabi`, because a crate that only builds when a sibling +/// happens to enable `std` for it is not actually no_std. The loop names them, +/// which makes this the fourth place the set is written down — after +/// `stdlib/bare`'s features and the two boards' manifests. +/// +/// It went stale the same way the boards did: `slice` was removed and the loop +/// kept building `lk-stdlib-slice`. Nothing noticed, because this branch had +/// never been pushed — CI does cover these targets, and would have said so on +/// the first run. +#[test] +fn ci_builds_exactly_the_modules_that_exist() { + let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); + let workflow = root.join("../.github/workflows/check.yml"); + let text = std::fs::read_to_string(&workflow).unwrap_or_else(|e| panic!("read {}: {e}", workflow.display())); + let line = text + .lines() + .find(|line| line.contains("for m in") && line.contains("cargo build")) + .or_else(|| text.lines().find(|line| line.trim_start().starts_with("for m in"))) + .expect("the per-module thumbv7em loop; if it was rewritten, so should this test be"); + let named: Vec = line + .split_once("for m in") + .expect("checked") + .1 + .split(';') + .next() + .expect("checked") + .split_whitespace() + .map(str::to_string) + .collect(); + assert!(!named.is_empty(), "the loop names no modules"); + + let ours = std::fs::read_to_string(root.join("bare/Cargo.toml")).expect("read stdlib/bare manifest"); + let available = feature_names(&ours); + let gone: Vec<&String> = named.iter().filter(|name| !available.contains(name)).collect(); + assert!( + gone.is_empty(), + "check.yml builds `lk-stdlib-{{{gone:?}}}` on thumbv7em, and no such crate exists any \ + more. CI fails on the first push with a message about the crate rather than about \ + the list it came from" + ); +} + +/// The keys of `stdlib/bare`'s `[features]` table. +fn feature_names(manifest: &str) -> Vec { + let mut names = Vec::new(); + let mut in_features = false; + for line in manifest.lines() { + let line = line.trim(); + if line.starts_with('[') { + in_features = line == "[features]"; + continue; + } + if !in_features || line.starts_with('#') || line.is_empty() { + continue; + } + if let Some((name, _)) = line.split_once('=') { + names.push(name.trim().to_string()); + } + } + names +} + +/// The feature names a board's `lk-stdlib-bare` dependency asks for. +fn requested_features(manifest: &str) -> Vec { + let Some(line) = manifest + .lines() + .find(|line| line.trim_start().starts_with("lk-stdlib-bare")) + else { + return Vec::new(); + }; + let Some(list) = line.split_once("features").and_then(|(_, rest)| rest.split_once('[')) else { + return Vec::new(); + }; + let Some((inside, _)) = list.1.split_once(']') else { + return Vec::new(); + }; + inside + .split(',') + .map(|piece| piece.trim().trim_matches('"').to_string()) + .filter(|piece| !piece.is_empty()) + .collect() +} + +/// The same program, run on every host, must answer the same thing. +/// +/// The tests above check *names*: every host knows every module and every +/// global. That is necessary and it is not enough — `assert_eq` was present on +/// all three hosts and did the wrong thing on two of them, because each host +/// had written its own: +/// +/// ```text +/// assert_eq("abcdefghij", "abcdefghij") passed on desktop, failed on web and bare +/// ``` +/// +/// A name list cannot see that. Running the program can, so this does: the +/// corpus below is answers, not spellings. +#[cfg(test)] +mod behaviour { + use lk_core::module::ModuleRegistry; + use lk_core::stmt::stmt_parser::StmtParser; + use lk_core::token::Tokenizer; + use lk_core::vm::{ModuleResolver, ProgramExec, VmContext}; + use std::sync::Arc; + + /// What a host answered: the returned value rendered, or the error text. + fn outcome(register: impl FnOnce(&mut ModuleRegistry), source: &str) -> String { + let tokens = match Tokenizer::tokenize(source) { + Ok(tokens) => tokens, + Err(error) => return format!("parse error: {error}"), + }; + let program = match StmtParser::new(&tokens).parse_program() { + Ok(program) => program, + Err(error) => return format!("parse error: {error}"), + }; + let mut registry = ModuleRegistry::new(); + register(&mut registry); + let resolver = Arc::new(ModuleResolver::with_registry(registry)); + let mut env = VmContext::new().with_resolver(resolver); + match program.execute_with_ctx(&mut env) { + // The *value*, not its kind: two hosts both answering "a String" is + // not two hosts agreeing. `show is dispatched` below returns a + // string on every host and returned a different one on two of them. + Ok(result) => match lk_stdlib_common::runtime_native::runtime_display_value( + result.first_return(), + result.state.heap(), + ) { + Ok(rendered) => format!("ok: {rendered}"), + Err(error) => format!("ok, undisplayable: {error:#}"), + }, + // The text, not the type: an error a program can `catch` is a + // value it can read, so two hosts disagreeing about the words is + // two hosts disagreeing. + Err(error) => format!("error: {error:#}"), + } + } + + fn desktop(source: &str) -> String { + outcome( + |registry| { + crate::register_stdlib_core_globals(registry); + crate::register_stdlib_modules(registry).expect("desktop modules"); + }, + source, + ) + } + + fn web(source: &str) -> String { + outcome( + |registry| { + lk_stdlib_web::register_web_stdlib(registry).expect("web host"); + }, + source, + ) + } + + fn bare(source: &str) -> String { + outcome( + |registry| { + lk_stdlib_bare::register_bare_stdlib(registry).expect("bare host"); + }, + source, + ) + } + + /// Programs whose answer must not depend on which host runs them. + /// + /// Deliberately about the *globals* — the surface every host reimplements + /// rather than shares, and therefore the surface where they can differ + /// without anything noticing. + const CORPUS: &[(&str, &str)] = &[ + // The bug that prompted this: equality across the seven-byte boundary. + ("assert_eq short", r#"assert_eq("ab", "ab"); return 1;"#), + ("assert_eq long", r#"assert_eq("abcdefghij", "abcdefghij"); return 1;"#), + ("assert_eq list", "assert_eq([1, 2], [1, 2]); return 1;"), + ("assert_eq map", r#"assert_eq({"a": 1}, {"a": 1}); return 1;"#), + ("assert_eq int", "assert_eq(1, 1); return 1;"), + // …and the failing side, whose *message* a program can catch. + ("assert_eq fails", r#"assert_eq("a", "b"); return 1;"#), + ("assert_ne holds", r#"assert_ne("abcdefghij", "abcdefghik"); return 1;"#), + ("assert_ne fails", r#"assert_ne("abcdefghij", "abcdefghij"); return 1;"#), + // Truthiness: only nil and false are falsy. + ("assert nil", "assert(nil); return 1;"), + ("assert zero", "assert(0); return 1;"), + ("assert empty string", r#"assert(""); return 1;"#), + ("assert empty list", "assert([]); return 1;"), + ("panic", r#"panic("boom"); return 1;"#), + // `error`/`catch` — the pair that was missing from both alternative + // hosts once already. + ( + "catch a raise", + r#"try { panic("boom"); } catch e { return e; } return 0;"#, + ), + // Interpolation, which the *VM* renders — so this cannot diverge + // between hosts and is here to say so: `print`'s rendering is the + // host's and is checked in `formatting` below, template interpolation + // is not. Confusing the two is how a "parity" case ends up testing + // nothing (this one did, until the deliberate-break check caught it). + // `error(v)` carries `v` itself where it can. The doc on it says a host + // without full VM state falls back to the rendered message — so this + // asks whether the two alternative hosts have it. + ( + "error carries a list", + "try { error([1, 2]); } catch e { return e; } return 0;", + ), + ( + "error carries an int", + "try { error(42); } catch e { return e; } return 0;", + ), + ( + "error carries a long string", + r#"try { error("abcdefghij"); } catch e { return e; } return 0;"#, + ), + ( + "interpolation renders in the VM", + r#"struct P { a: Int } + let p = P { a: 1 }; + return "${p}";"#, + ), + // Every container renders what is in it. `Bytes` used to answer + // `` — a count — so the only way to see a byte buffer + // was to convert it, and no test anywhere said what it should look + // like, which is why the count survived the round that unified the + // renderers. + ( + "bytes render their contents", + r#"use bytes; + let b = bytes.from_list([104, 105]); + return "${b} ${[b]} ${bytes.from_list([])}";"#, + ), + ]; + + /// The corpus is only worth its comparison if the programs actually run. + /// + /// Three hosts that all *fail* the same way agree, vacuously. This pins the + /// one answer that a reader would otherwise have to trust — and it is the + /// case with no other test in the tree, which is how `` + /// survived the round that unified the renderers. + #[test] + fn the_corpus_programs_produce_the_answers_they_claim() { + assert_eq!( + desktop( + r#"use bytes; + let b = bytes.from_list([104, 105]); + return "${b} ${[b]} ${bytes.from_list([])}";"# + ), + "ok: Bytes([104,105]) [Bytes([104,105])] Bytes([])", + "a byte buffer should render its contents, like every other container" + ); + assert_eq!(desktop("return [1, 2];"), "ok: [1,2]"); + assert_eq!(desktop(r#"return {"a": 1};"#), r#"ok: {"a":1}"#); + } + + #[test] + fn every_host_answers_the_same() { + let mut differences: Vec = Vec::new(); + for (name, source) in CORPUS { + let expected = desktop(source); + for (host, actual) in [("web", web(source)), ("bare", bare(source))] { + if actual != expected { + differences.push(format!("{name} — desktop: {expected}\n {host}: {actual}")); + } + } + } + assert!( + differences.is_empty(), + "hosts disagree about what these programs do:\n {}", + differences.join("\n ") + ); + } +} + +/// What `print` renders, checked against the one implementation that renders it. +/// +/// The formatter lived in all three hosts. They agreed on the interesting parts +/// — `{}` takes the next argument, a hole with nothing left stays a hole — and +/// disagreed on one line: the separator before arguments that run past the last +/// hole. With an empty template, bare metal pushed a space and the other two +/// did not, so `print("", 1, 2)` was `" 1 2"` there and `"1 2"` everywhere else. +/// +/// One implementation now (`lk_stdlib_common::language::format_variadic`), so +/// this checks the *rules* rather than three copies against each other. Run +/// through the web host because it is the one that can hand its output back. +#[cfg(test)] +mod formatting { + use lk_core::module::ModuleRegistry; + use lk_core::stmt::stmt_parser::StmtParser; + use lk_core::token::Tokenizer; + use lk_core::vm::{ModuleResolver, ProgramExec, VmContext}; + use std::sync::Arc; + + fn printed(call_args: &str) -> String { + let source = format!("print({call_args});"); + let tokens = Tokenizer::tokenize(&source).expect("tokenize"); + let program = StmtParser::new(&tokens).parse_program().expect("parse"); + let mut registry = ModuleRegistry::new(); + lk_stdlib_web::register_web_stdlib(&mut registry).expect("web host"); + let resolver = Arc::new(ModuleResolver::with_registry(registry)); + let mut env = VmContext::new().with_resolver(resolver); + lk_stdlib_web::clear_stdout(); + program.execute_with_ctx(&mut env).expect("run"); + lk_stdlib_web::take_stdout() + } + + #[test] + fn a_template_takes_arguments_and_says_what_is_left_over() { + assert_eq!(printed(""), ""); + assert_eq!(printed(r#""a={}", 1"#), "a=1"); + // A hole with nothing left stays a hole, rather than closing over + // nothing. + assert_eq!(printed(r#""a={}""#), "a={}"); + assert_eq!(printed(r#""a={} b={}", 1"#), "a=1 b={}"); + assert_eq!(printed(r#""{}{}", 1, 2"#), "12"); + // Arguments past the last hole are appended, space-separated… + assert_eq!(printed(r#""x", 1, 2"#), "x 1 2"); + // …and with nothing to separate them from, no leading space. The line + // the three copies disagreed on. + assert_eq!(printed(r#""", 1, 2"#), "1 2"); + // A first argument that is not a string is not a template. + assert_eq!(printed("1, 2"), "1 2"); + } + + /// `show` decides what printing a struct says. + /// + /// A language rule — `impl Show for P` is in the program, not in the host — + /// and it lived in the desktop host alone. The web and bare hosts rendered + /// the raw struct, so the same value printed `P!` on a desktop and `P{a:1}` + /// in the browser. This runs through the web host, which is one of the two + /// that could not do it. + #[test] + fn printing_a_struct_asks_its_show_impl() { + let source = r#"struct P { a: Int } + trait Display { fn show(self) -> String; } + impl Display for P { fn show(self) -> String { return "P!"; } } + print(P { a: 1 });"#; + let tokens = Tokenizer::tokenize(source).expect("tokenize"); + let program = StmtParser::new(&tokens).parse_program().expect("parse"); + let mut registry = ModuleRegistry::new(); + lk_stdlib_web::register_web_stdlib(&mut registry).expect("web host"); + let resolver = Arc::new(ModuleResolver::with_registry(registry)); + let mut env = VmContext::new().with_resolver(resolver); + lk_stdlib_web::clear_stdout(); + program.execute_with_ctx(&mut env).expect("run"); + assert_eq!(lk_stdlib_web::take_stdout(), "P!"); + } +} diff --git a/stdlib/src/lib.rs b/stdlib/src/lib.rs index ba481572..6e4fcab9 100644 --- a/stdlib/src/lib.rs +++ b/stdlib/src/lib.rs @@ -15,7 +15,6 @@ pub use lk_stdlib_path as path; pub use lk_stdlib_process as process; pub use lk_stdlib_random as random; pub use lk_stdlib_regex as regex; -pub use lk_stdlib_slice as slice; pub use lk_stdlib_stream as stream; pub use lk_stdlib_string as string; pub use lk_stdlib_task as concurrency_task; @@ -36,10 +35,14 @@ mod gc_stress_test; #[cfg(test)] mod globals_test; #[cfg(test)] +mod host_parity_test; +#[cfg(test)] mod math_test; #[cfg(test)] mod os_test; #[cfg(test)] +mod platform_surface_test; +#[cfg(test)] mod select_test; #[cfg(test)] mod spawn_test; @@ -57,13 +60,10 @@ use lk_core::{ module::ModuleRegistry, rt::{self, RuntimePayload}, val, - val::{ - CallableValue, ChannelValue, HeapRef, HeapStore, HeapValue, RuntimeMapKey, RuntimeSet, RuntimeVal, TaskValue, - Type, TypedList, TypedMap, - }, + val::{CallableValue, HeapRef, HeapStore, HeapValue, RuntimeVal, TaskValue, TypedList}, vm::{ NativeArgs, NativeEntry, NativeFunction, NativeRuntime, call_runtime_callable_runtime, - call_runtime_value_runtime, copy_runtime_value_same_module, + copy_runtime_value_same_module, }, }; pub use lk_stdlib_common::metadata::{ @@ -155,7 +155,6 @@ define_stdlib_modules!( "uuid" => uuid::register as register_stdlib_module_uuid, "http" => http::register as register_stdlib_module_http, "net" => net::register as register_stdlib_module_net, - "slice" => slice::register as register_stdlib_module_slice, "stream" => stream::register as register_stdlib_module_stream, "task" => concurrency_task::register as register_stdlib_module_task, "chan" => concurrency_chan::register as register_stdlib_module_chan, @@ -190,7 +189,7 @@ fn register_stdlib_module_by_name(registry: &mut ModuleRegistry, name: &str) -> Ok(()) } -fn stdlib_module_names() -> impl Iterator { +pub(crate) fn stdlib_module_names() -> impl Iterator { STDLIB_MODULES.iter().map(|entry| entry.name) } @@ -392,11 +391,6 @@ pub fn register_stdlib_core_globals(registry: &mut ModuleRegistry) { register_full_state_builtin!(registry, println => println / NativeEntry::VARIADIC => core.println: Nil); register_full_state_builtin!(registry, panic => panic / NativeEntry::VARIADIC => core.panic: Nil); register_full_state_builtin!(registry, error => error / NativeEntry::VARIADIC => core.error: Nil); - // `try$call` is the hidden protected-call primitive behind try/catch's - // parse-time desugar (`$` names are untokenizable, so user code can't - // reach it) — the former user-facing `pcall` global, removed in v2: - // try/catch is the only error-handling surface. - register_runtime_builtin_full_state(registry, "try$call", pcall, NativeEntry::VARIADIC, None); register_full_state_builtin!(registry, assert => assert / NativeEntry::VARIADIC => core.assert: Nil); register_full_state_builtin!(registry, assert_eq => assert_eq / NativeEntry::VARIADIC => core.assert_eq: Nil); register_full_state_builtin!(registry, assert_ne => assert_ne / NativeEntry::VARIADIC => core.assert_ne: Nil); @@ -419,6 +413,7 @@ fn register_runtime_builtin( arity: u16, metadata: Option, ) { + register_global_name(name, arity); register_global_metadata(name, metadata); registry.register_runtime_builtin(name, NativeFunction::Plain(function), arity); } @@ -430,10 +425,55 @@ fn register_runtime_builtin_full_state( arity: u16, metadata: Option, ) { + register_global_name(name, arity); register_global_metadata(name, metadata); registry.register_runtime_builtin(name, NativeFunction::FullState(function), arity); } +/// Tells the type checker this name is a builtin global, and how many +/// arguments it takes. +/// +/// The count comes from the registry arity the call sites already state, so +/// there is nothing new to keep in sync — except for the handful whose real +/// range the registry could not express (`assert` is 1 or 2, not "any"), which +/// now state it through [`ARITY_RANGES`] and use the same constant in their +/// own check. +fn register_global_name(name: &'static str, arity: u16) { + let (min, max) = match ARITY_RANGES.iter().find(|(global, _, _)| *global == name) { + Some((_, min, max)) => (*min, Some(*max)), + None if arity == NativeEntry::VARIADIC => (0, None), + None => (arity, Some(arity)), + }; + lk_core::typ::register_stdlib_global(name, min, max); +} + +/// The globals whose argument count is a *range*, which the registry's single +/// `arity` cannot say. +/// +/// Registered as `VARIADIC` because the call machinery only knows "exactly N or +/// anything", and then checked again inside each body — so the real bound lived +/// only there, and `lk check` passed `assert(true, "a", "b")`. The numbers are +/// the ones those bodies use; `assert_arity_ranges_match_the_native_checks` +/// keeps the two together. +pub(crate) const ARITY_RANGES: &[(&str, u16, u16)] = &[ + ( + "assert", + lk_stdlib_common::language::ASSERT_ARITY.0, + lk_stdlib_common::language::ASSERT_ARITY.1, + ), + ( + "assert_eq", + lk_stdlib_common::language::ASSERT_PAIR_ARITY.0, + lk_stdlib_common::language::ASSERT_PAIR_ARITY.1, + ), + ( + "assert_ne", + lk_stdlib_common::language::ASSERT_PAIR_ARITY.0, + lk_stdlib_common::language::ASSERT_PAIR_ARITY.1, + ), + ("chan", lk_stdlib_chan::CHAN_ARITY.0, lk_stdlib_chan::CHAN_ARITY.1), +]; + fn register_global_metadata(name: &'static str, metadata: Option) { let Some(metadata) = metadata else { return; @@ -443,25 +483,34 @@ fn register_global_metadata(name: &'static str, metadata: Option, runtime: &mut NativeRuntime<'_>) -> Result { - print!("{}", format_variadic_runtime(args.as_slice(), runtime)?); + print!( + "{}", + lk_stdlib_common::language::format_variadic(args.as_slice(), runtime)? + ); Ok(RuntimeVal::Nil) } fn println(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - println!("{}", format_variadic_runtime(args.as_slice(), runtime)?); + println!( + "{}", + lk_stdlib_common::language::format_variadic(args.as_slice(), runtime)? + ); Ok(RuntimeVal::Nil) } +/// `panic(msg...)` — stop, and do not let `catch` intervene. +/// +/// A `LkPanic`, not Rust's `panic!`. The old implementation unwound the *host*, +/// which works on a desktop, is an unrecoverable trap in wasm, and has no +/// unwinder at all on bare metal — so the two alternative hosts each wrote +/// their own, and each made `panic` an ordinary catchable error, which is the +/// opposite of what it means. One raise type, refused by the unwinder, means +/// the same program stops the same way everywhere. +/// +/// The Rust backtrace went with it: it named frames of the interpreter, not of +/// the program, which is the wrong stack to show whoever wrote the `panic`. fn panic(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let mut msg = if args.is_empty() { - "panic".to_string() - } else { - join_runtime_display(args.as_slice(), runtime)? - }; - let bt = std::backtrace::Backtrace::force_capture(); - msg.push_str("\nBacktrace:\n"); - msg.push_str(&format!("{}", bt)); - panic!("{}", msg); + lk_stdlib_common::language::panic(args, runtime) } /// `error(value...)` — raise a recoverable error. Unlike `panic`, it propagates @@ -471,158 +520,23 @@ fn panic(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result, runtime: &mut NativeRuntime<'_>) -> Result { - if let [value] = args.as_slice() { - let value = *value; - // Capture the display up-front: an uncaught heap error can't be rendered - // later (the heap is gone once execution unwinds out) (plan M2.2). - let rendered = join_runtime_display(args.as_slice(), runtime)?; - // A heap object must be pinned as a GC root so it survives collection at - // the native-call safepoints hit while the error unwinds to its `pcall` - // (plan M2.2). Primitives are Copy and need no pinning. If full VM state - // is unavailable we can't pin, so fall back to a stringified message. - let carry_first_class = if matches!(value, RuntimeVal::Obj(_)) { - match runtime.state_ctx_module_mut() { - Some((state, _, _)) => { - state.set_pending_raise_root(Some(value)); - true - } - None => false, - } - } else { - true - }; - if carry_first_class { - return Err(anyhow!(lk_core::vm::LkRaisedValue { - value, - rendered: Arc::::from(rendered.as_str()), - })); - } - return Err(anyhow!("{rendered}")); - } - let msg = if args.is_empty() { - "error".to_string() - } else { - join_runtime_display(args.as_slice(), runtime)? - }; - Err(anyhow!("{msg}")) -} - -/// `pcall(f, args...) -> [ok, result_or_error]` — a protected call. Invokes `f` -/// with `args`; on success returns `[true, result]`, on any raised error returns -/// `[false, message]` instead of propagating. This is the recoverable-error -/// primitive (plan M2.1); it catches both `error(...)` and other runtime errors. -fn pcall(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let values = args.as_slice(); - let Some((&callee, call_args)) = values.split_first() else { - return Err(anyhow!("pcall expects at least 1 argument: the function to call")); - }; - let call_args = call_args.to_vec(); - let outcome = { - let Some((state, ctx, module)) = runtime.state_ctx_module_mut() else { - return Err(anyhow!("pcall requires full VM state")); - }; - call_runtime_value_runtime(callee, &call_args, state, module, ctx) - }; - if outcome.is_err() - && let Some((state, ctx, _)) = runtime.state_ctx_module_mut() - { - // The error is caught here: release the GC-root pin on any first-class - // heap error value now that it's about to be handed back (plan M2.2). - // The value stays valid — the following `pcall` allocations use the raw - // heap (no collection) — but it no longer needs to survive as a stray - // root once execution resumes normally. - state.set_pending_raise_root(None); - // Discard the traceback frames the errored call accumulated — a later - // uncaught error should report a clean call stack (plan M2.2). try/catch - // desugars to pcall, so this also covers caught language errors. - if let Some(ctx) = ctx { - ctx.truncate_call_stack(0); - } - } - let (ok, value) = match outcome { - Ok(result) => (true, result), - Err(err) => { - // The call machinery wraps errors with context, so inspect the - // deepest cause. A first-class primitive error value round-trips as - // itself (M2.2); otherwise the message string is returned. - let root = err.root_cause(); - if let Some(raised) = root.downcast_ref::() { - (false, raised.value) - } else { - let message = root.to_string(); - let handle = runtime - .heap_mut() - .alloc(HeapValue::String(Arc::::from(message.as_str()))); - (false, RuntimeVal::Obj(handle)) - } - } - }; - let list = runtime - .heap_mut() - .alloc(HeapValue::List(TypedList::Mixed(vec![RuntimeVal::Bool(ok), value]))); - Ok(RuntimeVal::Obj(list)) + lk_stdlib_common::language::error(args, runtime) } +// `assert`/`assert_eq`/`assert_ne`/`panic` are the same on every host — an +// assertion is arithmetic on values, and only `print` needs to know where +// output goes. They were written out three times and had drifted three ways; +// see `lk_stdlib_common::language`. fn assert(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - expect_assert_args(args, 1, 2, "assert")?; - let values = args.as_slice(); - if assert_truthy(&values[0]) { - return Ok(RuntimeVal::Nil); - } - let message = if let Some(message) = values.get(1) { - format!("assertion failed: {}", runtime_display(message, runtime)?) - } else { - "assertion failed".to_string() - }; - Err(anyhow!("{message}")) + lk_stdlib_common::language::assert(args, runtime) } fn assert_eq(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - expect_assert_args(args, 2, 3, "assert_eq")?; - let values = args.as_slice(); - if runtime_values_equal(&values[0], &values[1], runtime.heap())? { - return Ok(RuntimeVal::Nil); - } - let actual = runtime_display(&values[0], runtime)?; - let expected = runtime_display(&values[1], runtime)?; - let mut message = format!("assertion failed: expected {expected}, got {actual}"); - if let Some(extra) = values.get(2) { - message.push_str(" - "); - message.push_str(&runtime_display(extra, runtime)?); - } - Err(anyhow!("{message}")) + lk_stdlib_common::language::assert_eq(args, runtime) } fn assert_ne(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - expect_assert_args(args, 2, 3, "assert_ne")?; - let values = args.as_slice(); - if !runtime_values_equal(&values[0], &values[1], runtime.heap())? { - return Ok(RuntimeVal::Nil); - } - let mut message = "assertion failed: values should not be equal".to_string(); - if let Some(extra) = values.get(2) { - message.push_str(" - "); - message.push_str(&runtime_display(extra, runtime)?); - } - Err(anyhow!("{message}")) -} - -fn expect_assert_args(args: NativeArgs<'_>, min: usize, max: usize, name: &str) -> Result<()> { - if args.has_named() { - return Err(anyhow!("{name}() does not accept named arguments")); - } - let len = args.len(); - if (min..=max).contains(&len) { - Ok(()) - } else if min == max { - Err(anyhow!("{name}() expects exactly {min} arguments")) - } else { - Err(anyhow!("{name}() expects {min} or {max} arguments")) - } -} - -fn assert_truthy(value: &RuntimeVal) -> bool { - !matches!(value, RuntimeVal::Nil | RuntimeVal::Bool(false)) + lk_stdlib_common::language::assert_ne(args, runtime) } /// `spawn(f) -> Task` — run `f` as a goroutine: true parallelism on the @@ -643,7 +557,12 @@ fn spawn(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result> + Send>> = Box::pin(async move { let mut heap = HeapStore::new(); - let result = call_runtime_callable_runtime(function.as_ref(), &[], &mut heap, Some(&mut ctx))?; + // The raise leaves with its payload, while this heap is still here + // to copy it out of — `heap` is dropped the moment this block + // returns, and a first-class raise carries a handle into it. See + // `RaisedPayload`. + let result = call_runtime_callable_runtime(function.as_ref(), &[], &mut heap, Some(&mut ctx)) + .map_err(|error| lk_core::rt::RaisedPayload::detach(error, &heap))?; Ok(RuntimePayload::new(result, heap)) }); @@ -659,83 +578,24 @@ fn spawn(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result, runtime: &mut NativeRuntime<'_>) -> Result { - if args.is_empty() || args.len() > 2 { - return Err(anyhow!("chan() expects 1 or 2 arguments: capacity[, type_str]")); - } - let values = args.as_slice(); - let capacity = match &values[0] { - RuntimeVal::Int(value) => *value, - RuntimeVal::Float(value) => *value as i64, - other => { - return Err(anyhow!( - "chan() capacity must be numeric, got {}", - runtime_type_name(other, runtime.heap()) - )); - } - }; - let inner_type = if values.len() == 2 { - match &values[1] { - RuntimeVal::Nil => val::Type::Nil, - value => { - let text = runtime_string(value, runtime.heap(), "chan() type")?; - val::Type::parse(text.as_ref()).unwrap_or(val::Type::Nil) - } - } - } else { - val::Type::Nil - }; - let cap_opt = if capacity <= 0 { None } else { Some(capacity as usize) }; - let channel_id = runtime - .async_runtime() - .with(|runtime| runtime.create_channel(cap_opt)) - .map_err(|error| anyhow!("Failed to create channel: {}", error))?; - Ok(RuntimeVal::Obj(runtime.heap_mut().alloc(HeapValue::Channel(Arc::new( - ChannelValue { - id: channel_id, - capacity: Some(capacity), - inner_type, - }, - ))))) + lk_stdlib_chan::create_channel_value(args, runtime) } -/// `send(c, v)` — blocking send. Returns Nil on delivery; raises a -/// catchable error once the channel is closed (v2 error model: failures -/// raise, they don't return status values — Go's panic-on-closed-send). +/// `send(c, v)` — the bare global, one implementation with `chan.send`. fn send(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { expect_runtime_arity(args, 2, "send")?; - let values = args.as_slice(); - let channel_id = channel_id_arg(&values[0], runtime.heap(), "send first argument")?; - let value = RuntimePayload::copy_from_value(&values[1], runtime.heap())?; - let sent = runtime - .async_runtime() - .with(|runtime| runtime.block_on(runtime.guard_blocking("send", runtime.send_async(channel_id, value)))) - .map_err(|error| anyhow!("Send operation failed: {}", error))?; - if !sent { - return Err(anyhow!("send on closed channel")); - } - Ok(RuntimeVal::Nil) + lk_stdlib_chan::blocking_send_value(args, runtime, "send") } -/// `recv(c)` — blocking receive. Returns the value; raises a catchable -/// error once the channel is closed and drained (v2 error model: no -/// `[ok, value]` pairs — consume-until-closed loops wrap the loop in -/// try/catch, or poll `chan.is_closed`/`chan.try_recv`). +/// `recv(c)` — the bare global, one implementation with `chan.recv`. fn recv(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { expect_runtime_arity(args, 1, "recv")?; - let channel_id = channel_id_arg( - args.get(0).expect("arity checked"), - runtime.heap(), - "recv first argument", - )?; - let (ok, value) = runtime - .async_runtime() - .with(|runtime| runtime.block_on(runtime.guard_blocking("recv", runtime.recv_async(channel_id)))) - .map_err(|error| anyhow!("Receive operation failed: {}", error))?; - if !ok { - return Err(anyhow!("receive on closed channel")); - } - value.into_value(runtime.heap_mut()) + lk_stdlib_chan::blocking_recv_value(args, runtime, "recv") } /// Non-blocking send: `true` delivered, `false` full (not an error); @@ -745,10 +605,11 @@ fn chan_try_send(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Resul let values = args.as_slice(); let channel_id = channel_id_arg(&values[0], runtime.heap(), "chan::try_send first argument")?; let value = RuntimePayload::copy_from_value(&values[1], runtime.heap())?; + // See `chan.try_send`: the closed-channel wording is the language's, so it + // is propagated rather than decorated. let sent = runtime .async_runtime() - .with(|runtime| runtime.try_send(channel_id, value)) - .map_err(|error| anyhow!("Failed to send to channel: {}", error))?; + .with(|runtime| runtime.try_send(channel_id, value))?; Ok(RuntimeVal::Bool(sent)) } @@ -837,325 +698,6 @@ fn select_block(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result ) } -fn format_variadic_runtime(args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { - if args.is_empty() { - return Ok(String::new()); - } - let Some(format) = runtime_string_maybe(&args[0], runtime.heap())? else { - return join_runtime_display(args, runtime); - }; - let rest = &args[1..]; - let mut out = String::with_capacity(format.len() + rest.len() * 8); - let mut chars = format.chars().peekable(); - let mut arg_index = 0usize; - while let Some(ch) = chars.next() { - if ch == '{' && chars.peek() == Some(&'}') { - chars.next(); - if let Some(value) = rest.get(arg_index) { - out.push_str(&runtime_display(value, runtime)?); - arg_index += 1; - } else { - out.push_str("{}"); - } - } else { - out.push(ch); - } - } - if arg_index < rest.len() { - if !out.is_empty() { - out.push(' '); - } - out.push_str(&join_runtime_display(&rest[arg_index..], runtime)?); - } - Ok(out) -} - -fn join_runtime_display(args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { - let mut out = String::new(); - for (index, value) in args.iter().enumerate() { - if index > 0 { - out.push(' '); - } - out.push_str(&runtime_display(value, runtime)?); - } - Ok(out) -} - -fn runtime_display(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result { - if let Some(value) = runtime_display_show(value, runtime)? { - return Ok(value); - } - runtime_display_value(value, runtime.heap()) -} - -fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal, heap: &HeapStore) -> Result { - Ok(match (left, right) { - (RuntimeVal::Nil, RuntimeVal::Nil) => true, - (RuntimeVal::Bool(left), RuntimeVal::Bool(right)) => left == right, - (RuntimeVal::Int(left), RuntimeVal::Int(right)) => left == right, - (RuntimeVal::Float(left), RuntimeVal::Float(right)) => left == right, - (RuntimeVal::Int(left), RuntimeVal::Float(right)) => *left as f64 == *right, - (RuntimeVal::Float(left), RuntimeVal::Int(right)) => *left == *right as f64, - (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) if left == right => true, - (RuntimeVal::Obj(left), RuntimeVal::Obj(right)) => { - let left = heap - .get(*left) - .ok_or_else(|| anyhow!("heap object {} out of bounds", left.index()))?; - let right = heap - .get(*right) - .ok_or_else(|| anyhow!("heap object {} out of bounds", right.index()))?; - heap_values_equal(left, right, heap)? - } - _ => match ( - runtime_value_to_string(left, heap)?, - runtime_value_to_string(right, heap)?, - ) { - (Some(left), Some(right)) => left == right, - _ => false, - }, - }) -} - -fn heap_values_equal(left: &HeapValue, right: &HeapValue, heap: &HeapStore) -> Result { - Ok(match (left, right) { - (HeapValue::String(left), HeapValue::String(right)) => left == right, - (HeapValue::List(left), HeapValue::List(right)) => typed_lists_equal(left, right, heap)?, - (HeapValue::Map(left), HeapValue::Map(right)) => typed_maps_equal(left, right, heap)?, - (HeapValue::Set(left), HeapValue::Set(right)) => runtime_sets_equal(left, right), - _ => false, - }) -} - -fn runtime_sets_equal(left: &RuntimeSet, right: &RuntimeSet) -> bool { - left.len() == right.len() && left.entries().all(|key| right.contains(key)) -} - -fn runtime_value_to_string(value: &RuntimeVal, heap: &HeapStore) -> Result>> { - match value { - RuntimeVal::ShortStr(value) => Ok(Some(Arc::::from(value.as_str()))), - RuntimeVal::Obj(handle) => match heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::String(value) => Ok(Some(value.clone())), - _ => Ok(None), - }, - _ => Ok(None), - } -} - -fn typed_lists_equal(left: &TypedList, right: &TypedList, heap: &HeapStore) -> Result { - if left.len() != right.len() { - return Ok(false); - } - match (left, right) { - (TypedList::Int(left), TypedList::Int(right)) => return Ok(left == right), - (TypedList::Float(left), TypedList::Float(right)) => return Ok(left == right), - (TypedList::Bool(left), TypedList::Bool(right)) => return Ok(left == right), - (TypedList::String(left), TypedList::String(right)) => return Ok(left == right), - _ => {} - } - for index in 0..left.len() { - if !typed_list_items_equal(left, index, right, index, heap)? { - return Ok(false); - } - } - Ok(true) -} - -fn typed_list_items_equal( - left: &TypedList, - left_index: usize, - right: &TypedList, - right_index: usize, - heap: &HeapStore, -) -> Result { - match (left, right) { - (TypedList::Mixed(left), TypedList::Mixed(right)) => { - runtime_values_equal(&left[left_index], &right[right_index], heap) - } - (TypedList::Mixed(left), TypedList::String(right)) => { - runtime_value_equals_string(&left[left_index], &right[right_index], heap) - } - (TypedList::String(left), TypedList::Mixed(right)) => { - runtime_value_equals_string(&right[right_index], &left[left_index], heap) - } - (TypedList::Int(left), _) => { - typed_list_runtime_item_equal(RuntimeVal::Int(left[left_index]), right, right_index, heap) - } - (TypedList::Float(left), _) => { - typed_list_runtime_item_equal(RuntimeVal::Float(left[left_index]), right, right_index, heap) - } - (TypedList::Bool(left), _) => { - typed_list_runtime_item_equal(RuntimeVal::Bool(left[left_index]), right, right_index, heap) - } - (TypedList::String(left), _) => typed_list_string_item_equal(&left[left_index], right, right_index, heap), - (TypedList::Mixed(left), _) => typed_list_runtime_item_equal(left[left_index], right, right_index, heap), - } -} - -fn typed_list_runtime_item_equal( - value: RuntimeVal, - right: &TypedList, - right_index: usize, - heap: &HeapStore, -) -> Result { - match right { - TypedList::Mixed(right) => runtime_values_equal(&value, &right[right_index], heap), - TypedList::Int(right) => runtime_values_equal(&value, &RuntimeVal::Int(right[right_index]), heap), - TypedList::Float(right) => runtime_values_equal(&value, &RuntimeVal::Float(right[right_index]), heap), - TypedList::Bool(right) => runtime_values_equal(&value, &RuntimeVal::Bool(right[right_index]), heap), - TypedList::String(right) => runtime_value_equals_string(&value, &right[right_index], heap), - } -} - -fn typed_list_string_item_equal( - left: &Arc, - right: &TypedList, - right_index: usize, - heap: &HeapStore, -) -> Result { - match right { - TypedList::Mixed(right) => runtime_value_equals_string(&right[right_index], left, heap), - TypedList::String(right) => Ok(left == &right[right_index]), - _ => Ok(false), - } -} - -fn runtime_value_equals_string(value: &RuntimeVal, expected: &str, heap: &HeapStore) -> Result { - Ok(match value { - RuntimeVal::ShortStr(value) => value.as_str() == expected, - RuntimeVal::Obj(handle) => matches!( - heap.get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))?, - HeapValue::String(value) if value.as_ref() == expected - ), - _ => false, - }) -} - -fn typed_maps_equal(left: &TypedMap, right: &TypedMap, heap: &HeapStore) -> Result { - if left.len() != right.len() { - return Ok(false); - } - match left { - TypedMap::Mixed(entries) => { - for (key, value) in entries { - if !typed_map_value_equal(right, key, value, heap)? { - return Ok(false); - } - } - } - TypedMap::StringMixed(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !typed_map_value_equal(right, &key, value, heap)? { - return Ok(false); - } - } - } - TypedMap::StringInt(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !typed_map_value_equal(right, &key, &RuntimeVal::Int(*value), heap)? { - return Ok(false); - } - } - } - TypedMap::StringFloat(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !typed_map_value_equal(right, &key, &RuntimeVal::Float(*value), heap)? { - return Ok(false); - } - } - } - TypedMap::StringBool(entries) => { - for (key, value) in entries { - let key = RuntimeMapKey::String(key.clone()); - if !typed_map_value_equal(right, &key, &RuntimeVal::Bool(*value), heap)? { - return Ok(false); - } - } - } - } - Ok(true) -} - -fn typed_map_value_equal( - right: &TypedMap, - key: &RuntimeMapKey, - left_value: &RuntimeVal, - heap: &HeapStore, -) -> Result { - let Some(right_value) = right.get(key) else { - return Ok(false); - }; - runtime_values_equal(left_value, &right_value, heap) -} - -fn runtime_display_show(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result> { - let Some(receiver_type) = runtime_display_receiver_type(value, runtime.heap()) else { - return Ok(None); - }; - // The declaring module is the other half of the receiver's type identity; - // read it before `state_ctx_module_mut` takes the heap mutably. - let receiver_scope = lk_core::vm::receiver_type_scope(value, runtime.heap()); - let Some((state, ctx, module)) = runtime.state_ctx_module_mut() else { - return Ok(None); - }; - let Some(ctx) = ctx else { - return Ok(None); - }; - let Type::Named(receiver_type_name) = &receiver_type else { - return Ok(None); - }; - let Some(impl_ref) = ctx.trait_method(&receiver_scope, receiver_type_name, "show").cloned() else { - return Ok(None); - }; - let result = lk_core::vm::call_trait_method( - &impl_ref, - lk_core::vm::TraitMethodRef { - type_name: receiver_type_name, - method: "show", - }, - value, - None, - state, - module, - Some(ctx), - )?; - runtime_string_maybe(&result, state.heap()).map(|value| value.map(|value| value.to_string())) -} - -fn runtime_display_receiver_type(value: &RuntimeVal, heap: &HeapStore) -> Option { - let RuntimeVal::Obj(handle) = value else { - return None; - }; - let Some(HeapValue::Object(object)) = heap.get(*handle) else { - return None; - }; - Some(Type::Named(object.type_name().to_string())) -} - -fn runtime_string(value: &RuntimeVal, heap: &HeapStore, context: &str) -> Result> { - runtime_string_maybe(value, heap)?.ok_or_else(|| anyhow!("{context} must be a string")) -} - -fn runtime_string_maybe(value: &RuntimeVal, heap: &HeapStore) -> Result>> { - match value { - RuntimeVal::ShortStr(value) => Ok(Some(Arc::::from(value.as_str()))), - RuntimeVal::Obj(handle) => match heap - .get(*handle) - .ok_or_else(|| anyhow!("heap object {} out of bounds", handle.index()))? - { - HeapValue::String(value) => Ok(Some(value.clone())), - _ => Ok(None), - }, - _ => Ok(None), - } -} - /// Resolve a spawn target to a self-contained `RuntimeCallable`. A /// `CallableValue::Runtime` already carries its module + state; a plain /// `Closure` gets promoted here by snapshotting: `Arc::new(module.clone())` @@ -1370,17 +912,6 @@ fn expect_runtime_arity(args: NativeArgs<'_>, expected: usize, name: &str) -> Re } } -fn runtime_type_name(value: &RuntimeVal, heap: &HeapStore) -> &'static str { - match value { - RuntimeVal::Nil => "Nil", - RuntimeVal::Bool(_) => "Bool", - RuntimeVal::Int(_) => "Int", - RuntimeVal::Float(_) => "Float", - RuntimeVal::ShortStr(_) => "String", - RuntimeVal::Obj(handle) => heap.get(*handle).map(HeapValue::type_name).unwrap_or("Obj"), - } -} - pub fn register_stdlib_globals(registry: &mut ModuleRegistry) { register_stdlib_core_globals(registry); register_stdlib_concurrency_globals(registry); @@ -1389,7 +920,10 @@ pub fn register_stdlib_globals(registry: &mut ModuleRegistry) { #[cfg(test)] mod runtime_registration_tests { use super::*; - use lk_core::{val::Type, vm::RuntimeModuleState}; + use lk_core::{ + val::{ChannelValue, Type}, + vm::RuntimeModuleState, + }; #[test] fn named_registration_includes_only_requested_modules() { diff --git a/stdlib/src/math_test.rs b/stdlib/src/math_test.rs index 2178ec9b..06876db0 100644 --- a/stdlib/src/math_test.rs +++ b/stdlib/src/math_test.rs @@ -154,27 +154,42 @@ mod tests { Ok(()) } + /// Every `math` member is a plain `RuntimeNative`, and its registered arity + /// follows from its declaration rather than from a list kept here. + /// + /// A member that declares `named(...)` registers as `VARIADIC`: a named + /// argument does not occupy a positional slot, so the VM's pre-call check + /// would reject `math.pow(2, exponent: 10)` as "expects 2, got 1". The + /// bounds are checked by the generated precheck instead, which knows the + /// names. Asserting that rule keeps this test from having to be edited — + /// and from being *wrong* — every time a member becomes nameable. #[test] fn test_math_selected_functions_use_runtime_native_abi() -> Result<()> { for name in [ "abs", "sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "log", "log10", "log2", "exp", "pow", - "floor", "ceil", "round", "min", "max", "random", + "floor", "ceil", "round", "min", "max", "random", "clamp", ] { let (arity, function) = math_native(name)?; assert!( matches!(function, NativeFunction::Plain(_)), "{name} should use plain RuntimeNative" ); - assert_ne!( - arity, - NativeEntry::VARIADIC, - "{name} should have fixed positional arity" - ); + let path = format!("math.{name}"); + let nameable = MathModule::stdlib_metadata() + .signatures + .iter() + .find(|signature| signature.path == path) + .is_some_and(|signature| signature.params.iter().any(|param| param.named)); + if nameable { + assert_eq!(arity, NativeEntry::VARIADIC, "{name} declares named(...)"); + } else { + assert_ne!( + arity, + NativeEntry::VARIADIC, + "{name} should have fixed positional arity" + ); + } } - - let (arity, function) = math_native("clamp")?; - assert!(matches!(function, NativeFunction::Plain(_))); - assert_eq!(arity, NativeEntry::VARIADIC); Ok(()) } diff --git a/stdlib/src/platform_surface_test.rs b/stdlib/src/platform_surface_test.rs new file mode 100644 index 00000000..f1a82ae7 --- /dev/null +++ b/stdlib/src/platform_surface_test.rs @@ -0,0 +1,67 @@ +//! Every stdlib module reaches every platform — provided, or explicitly absent. +//! +//! A platform surface (`stdlib/web`, `stdlib/bare`) populates its own +//! `ModuleRegistry` instead of going through a HAL trait, which is the +//! documented design. The cost of that freedom is a list: `web` names the +//! modules it cannot support so `use fs` there says "not supported on this +//! platform" rather than "unknown module". +//! +//! A list is exactly the thing that goes stale. Add a module to the umbrella +//! and neither platform notices — the module simply is not there, and the +//! diagnostic a user gets is about a *name*, not about a platform. Nothing +//! failed when that happened, which is why this asserts it instead. + +use lk_core::module::ModuleRegistry; + +/// The umbrella's module names — the definition of "every stdlib module". +fn all_module_names() -> Vec { + crate::stdlib_catalog() + .modules + .iter() + .map(|module| module.name.to_string()) + .collect() +} + +fn registered_names(register: fn(&mut ModuleRegistry) -> anyhow::Result<()>) -> Vec { + let mut registry = ModuleRegistry::new(); + register(&mut registry).expect("platform registration"); + registry.get_module_names() +} + +/// `web` either provides a module or registers the placeholder that explains +/// itself. Missing from both is the failure this catches. +#[test] +fn the_web_surface_accounts_for_every_stdlib_module() { + let available = registered_names(lk_stdlib_web::register_web_stdlib_modules); + let missing: Vec = all_module_names() + .into_iter() + .filter(|name| !available.contains(name)) + .collect(); + assert!( + missing.is_empty(), + "these stdlib modules are neither provided nor listed unsupported on web, so `use ` \ + there reports an unknown module rather than an unsupported platform: {missing:?}" + ); +} + +/// Bare metal is the opposite shape — a *subset* by design, since flash is the +/// scarce resource and every module is opt-in. So this asserts the weaker but +/// still load-bearing thing: what it does register is a subset of the real +/// stdlib, i.e. no platform surface invents a module name that the language +/// does not have. +#[test] +fn no_platform_surface_invents_a_module_name() { + let all = all_module_names(); + for (platform, names) in [ + ("web", registered_names(lk_stdlib_web::register_web_stdlib_modules)), + ("bare", registered_names(lk_stdlib_bare::register_bare_stdlib_modules)), + ] { + for name in &names { + assert!( + all.contains(name), + "{platform} registers `{name}`, which is not a stdlib module — a platform surface \ + may omit modules, never add ones the language does not define" + ); + } + } +} diff --git a/stdlib/src/stdlib_modules_test.rs b/stdlib/src/stdlib_modules_test.rs index 33c9b863..3e06f4eb 100644 --- a/stdlib/src/stdlib_modules_test.rs +++ b/stdlib/src/stdlib_modules_test.rs @@ -46,11 +46,13 @@ mod tests { .and_then(|export| export.return_kind), Some(StdlibReturnKind::Float) ); + // `string.to_float` answers `Float?` — text that is not a number is + // nil — so its return kind is the boxed one, not `Float`. assert_eq!( catalog .export_path(&["string", "to_float"]) .and_then(|export| export.return_kind), - Some(StdlibReturnKind::Float) + Some(StdlibReturnKind::RuntimeValue) ); assert_eq!( catalog @@ -114,14 +116,6 @@ mod tests { Some("Decodes bytes as UTF-8 and raises an error for invalid input.") ); - let slice_from_string = catalog - .export_path(&["slice", "from_string"]) - .expect("slice.from_string export"); - assert_eq!( - slice_from_string.signature.as_deref(), - Some("slice.from_string(text: String) -> Slice") - ); - let encoding = catalog.module("encoding").expect("encoding module"); assert_eq!(encoding.docs.as_deref(), Some("Encoding and data format helpers")); let json = encoding.export("json").expect("encoding.json namespace"); @@ -135,15 +129,17 @@ mod tests { Some("encoding.json.parse(source: String) -> Value") ); - let string_char = catalog.export_path(&["string", "char"]).expect("string.char export"); + let string_char = catalog.export_path(&["string", "get"]).expect("string.get export"); assert_eq!( string_char.signature.as_deref(), - Some("string.char(text: String, index: Int) -> String?") + Some("string.get(text: String, index: Int) -> String?") ); - let string_byte = catalog.export_path(&["string", "byte"]).expect("string.byte export"); + let string_byte = catalog + .export_path(&["string", "byte_at"]) + .expect("string.byte_at export"); assert_eq!( string_byte.signature.as_deref(), - Some("string.byte(text: String, index: Int) -> Int?") + Some("string.byte_at(text: String, index: Int) -> Int?") ); let string_pad_left = catalog .export_path(&["string", "pad_left"]) @@ -157,13 +153,20 @@ mod tests { .expect("string.replace export"); assert_eq!( string_replace.signature.as_deref(), - Some("string.replace(text: String, pattern?: String, with?: String, all?: Bool) -> String") + Some("string.replace(text: String, pattern: String, with: String, all?: Bool = true) -> String") ); let time_since = catalog.export_path(&["time", "since"]).expect("time.since export"); assert_eq!( time_since.signature.as_deref(), Some("time.since(start_ms: Int | Float, end_ms: Int | Float) -> Int") ); + let string_split = catalog.export_path(&["string", "split"]).expect("string.split export"); + assert_eq!( + string_split.signature.as_deref(), + // Angle brackets, because that is how the language spells a generic: + // a signature shown on hover has to be one the reader can write down. + Some("string.split(text: String, separator: String) -> List") + ); let stream_collect = catalog .export_path(&["stream", "collect"]) .expect("stream.collect export"); @@ -173,6 +176,41 @@ mod tests { ); } + /// A module name that is also a global builtin is a dead end, not a style + /// question: `use chan;` binds the name to the module, so the global + /// `chan(3)` stops being a call — and until `chan.new` existed there was no + /// way left to make a channel at all. + /// + /// `chan` was the only one. Checked rather than remembered, because the + /// next module to collide would fail the same silent way: its constructor + /// would keep working right up until someone imported it. + #[test] + fn no_stdlib_module_shadows_a_global_builtin() -> Result<()> { + let mut registry = ModuleRegistry::new(); + register_stdlib_modules(&mut registry)?; + crate::register_stdlib_core_globals(&mut registry); + crate::register_stdlib_concurrency_globals(&mut registry); + + for name in crate::stdlib_module_names() { + // `chan` is the known exception, and it is *complete*: the module + // carries `chan.new`, so importing it does not take the constructor + // away — it renames it. A new collision has no such answer. + if name == "chan" { + assert!( + registry.get_runtime_builtin("chan::new").is_some(), + "chan shadows the global constructor, so the module must carry `new`" + ); + continue; + } + assert!( + registry.get_runtime_builtin(name).is_none(), + "module `{name}` is also a global builtin: importing it would shadow the global \ + and leave whatever the global did unreachable" + ); + } + Ok(()) + } + #[test] fn test_stdlib_export_macro_registers_selected_runtime_builtins() -> Result<()> { let mut registry = ModuleRegistry::new(); @@ -180,7 +218,10 @@ mod tests { for (name, arity) in [ ("time::sleep", 1), - ("time::since", 2), + // `since` declares `named(end_ms)`, and a named argument does not + // occupy a positional slot — so it registers variadic and the + // generated precheck, which knows the names, checks the bounds. + ("time::since", lk_core::vm::NativeEntry::VARIADIC), ("chan::try_send", 2), ("task::join_all", lk_core::vm::NativeEntry::VARIADIC), ] { @@ -247,6 +288,88 @@ mod tests { Ok(()) } + /// `parse` had no `stringify`, so a script could read a config and change + /// it but not write it back — `base64`, `hex` and `url` next door are all + /// pairs. Object keys come out sorted (`serde_json::Map` is a `BTreeMap`), + /// which makes a generated config byte-stable and therefore diffable. + /// `..` cancels a *named* component and nothing else. + /// + /// Two bugs met here. Above a root, a `..` that could not be popped was + /// pushed back, so `/../a` normalized to `/../a` — a path that normalizes + /// to itself forever, and one no filesystem agrees with (`/..` is `/`). + /// And a `..` popped whatever was last, including another `..`, so + /// `../..` — two levels up — answered the empty string. + /// `i64::abs` panics on `Int::MIN` — there is no positive one — so + /// `math.abs` on that single value took the process down, which a script + /// cannot catch. Wrapping is the language's own rule for Int overflow, and + /// this *is* an Int overflow. + #[test] + fn test_math_abs_of_the_smallest_int_wraps_instead_of_aborting() -> Result<()> { + let out = run(r#" + use math; + let smallest = -9223372036854775807 - 1; + return [math.abs(smallest), math.abs(-5), math.abs(5)]; + "#)?; + let list = runtime_list(out.first_return(), out.state.heap()); + let TypedList::Int(values) = list else { + panic!("expected a list of ints, got {list:?}"); + }; + assert_eq!(values, &[i64::MIN, 5, 5]); + Ok(()) + } + + #[test] + fn test_path_normalize_cancels_only_named_components() -> Result<()> { + let out = run(r#" + use path; + return [ + path.normalize("/../a"), + path.normalize("/a/../.."), + path.normalize("/a/../../b"), + path.normalize(".."), + path.normalize("../.."), + path.normalize("a/../../b"), + path.normalize("./a/./b"), + path.normalize("a/b/../c"), + ]; + "#)?; + let list = runtime_list(out.first_return(), out.state.heap()); + let TypedList::String(values) = list else { + panic!("expected a list of strings, got {list:?}"); + }; + assert_eq!( + values.iter().map(|value| value.as_ref()).collect::>(), + ["/a", "/", "/b", "..", "../..", "../b", "a/b", "a/c"] + ); + Ok(()) + } + + #[test] + fn test_encoding_stringify_round_trips_and_refuses_what_json_cannot_spell() -> Result<()> { + let out = run(r#" + use encoding; + let text = encoding.json.stringify({"b": [1, 2], "a": "x"}); + let back = encoding.json.parse(text); + let refused_key = try { + let m = {}; + m[1] = 2; + encoding.json.stringify(m); + "not refused" + } catch e { e }; + let refused_set = try { encoding.json.stringify(Set([1])); "not refused" } catch e { e }; + return text == "{\"a\":\"x\",\"b\":[1,2]}" + && back.a == "x" + && back.b[1] == 2 + && encoding.json.stringify([1, "a", true, nil]) == "[1,\"a\",true,null]" + && refused_key.contains("is an Int") + && refused_set.contains("no JSON form") + && encoding.yaml.stringify({"a": 1}).contains("a: 1") + && encoding.toml.stringify({"a": 1}).contains("a = 1"); + "#)?; + assert_eq!(out.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + #[test] fn test_encoding_hash_regex_random_uuid_modules() -> Result<()> { let out = run(r#" @@ -262,8 +385,10 @@ mod tests { && encoding.hex.encode("hi") == "6869" && bytes.to_string_utf8(encoding.hex.decode("6869")) == "hi" && hash.sha256("abc") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" - && regex.is_match("[0-9]+", "a12") - && regex.find("[0-9]+", "a12").text == "12" + && regex.is_match("a12", "[0-9]+") + // `regex.find` is declared `Map?` — it finds nothing for some + // inputs — so the field access has to go through `?.`. + && regex.find("a12", "[0-9]+")?.text == "12" && random.int(1, 3) >= 1 && random.int(1, 3) <= 3 && uuid.is_valid(id) @@ -279,4 +404,187 @@ mod tests { assert!(run("use yaml; return yaml.parse(\"a: 1\");").is_err()); assert!(run("use toml; return toml.parse(\"a = 1\");").is_err()); } + + /// Type texts the checker is allowed not to understand. + /// + /// Every one of these names a runtime handle the type system has no variant + /// for, so widening it to `Any` is the honest answer. A text that reaches + /// `Any` *without* being on this list is a declaration the checker silently + /// gave up on — a typo, or a spelling the alias table has not been taught — + /// and the export it belongs to would be untyped for no stated reason. + const UNDERSTOOD_AS_ANY: &[&str] = &[ + "Any", "Bytes", "Resource", "Stream", "Cursor", "Slice", "Value", "Fn", "Task", "Channel", + ]; + + fn is_understood(text: &str) -> bool { + let text = text.trim().trim_end_matches('?').trim(); + if UNDERSTOOD_AS_ANY.contains(&text) { + return true; + } + if lk_core::typ::type_from_text(text) != lk_core::val::Type::Any { + return true; + } + // A union is understood when each arm is: `Bytes | String` resolves to + // `Any` as a whole precisely *because* one arm is opaque. + text.contains('|') && text.split('|').all(is_understood) + } + + #[test] + fn every_declared_stdlib_type_is_understood_by_the_checker() { + let mut registry = ModuleRegistry::new(); + register_stdlib_modules(&mut registry).expect("register stdlib modules"); + + let mut unknown: Vec = Vec::new(); + for name in crate::STDLIB_MODULES.iter().map(|entry| entry.name) { + let Some(metadata) = crate::registered_stdlib_module_metadata(name) else { + continue; + }; + for signature in metadata.signatures { + for param in signature.params { + if !is_understood(param.ty) { + unknown.push(format!("{}({}: {})", signature.path, param.name, param.ty)); + } + } + if !is_understood(signature.returns) { + unknown.push(format!("{} -> {}", signature.path, signature.returns)); + } + } + } + unknown.sort(); + + assert!( + unknown.is_empty(), + "stdlib declares types the checker cannot act on:\n {}", + unknown.join("\n ") + ); + } + + /// A parameter whose *position* cannot say what it means must be named. + /// + /// The mechanical half of the convention in `docs/stdlib.md`: two + /// parameters of the same type, past the first, are indistinguishable at + /// the call site, so swapping them is silent — the program keeps running + /// and answers something else. + /// + /// The evidence this is not hypothetical: `"abcdef".substring(2, 3)` is + /// `"cde"` (the third argument is a *length*) while + /// `[1,2,3,4,5,6].slice(2, 3)` is `[3]` (an *end*). Two sibling operations, + /// identical call sites, different meanings. Only the declaration knows, + /// and only a name carries the declaration to where the code is read. + /// + /// The first parameter is exempt: the subject of a call is what the call is + /// about, and its position says so. `string.len(text: s)` would be noise. + #[test] + fn every_ambiguous_parameter_is_named() { + let mut registry = ModuleRegistry::new(); + register_stdlib_modules(&mut registry).expect("register stdlib modules"); + + let mut unnamed: Vec = Vec::new(); + for name in crate::STDLIB_MODULES.iter().map(|entry| entry.name) { + let Some(metadata) = crate::registered_stdlib_module_metadata(name) else { + continue; + }; + for signature in metadata.signatures { + // Past the first: the subject is identified by being first. + let tail = &signature.params[signature.params.len().min(1)..]; + for (index, param) in tail.iter().enumerate() { + let shares_type = tail + .iter() + .enumerate() + .any(|(other, candidate)| other != index && candidate.ty == param.ty); + if shares_type && !param.named { + unnamed.push(format!("{}({}: {})", signature.path, param.name, param.ty)); + } + } + } + } + unnamed.sort(); + unnamed.dedup(); + + assert!( + unnamed.is_empty(), + "these parameters share a type with a sibling and cannot be told apart by position; \ + declare them `named(...)` (docs/stdlib.md):\n {}", + unnamed.join("\n ") + ); + } +} + +#[cfg(test)] +mod reference_conformance { + /// Every function the published reference documents under a module heading + /// is a member of that module. + /// + /// A reference page is a claim about what exists, and this one had drifted: + /// it still listed `bytes.eq(a, b)`, deleted because it is `a == b` and an + /// operator does not need a module-function double. Nothing could disagree + /// with the page until now. + /// + /// One direction only. "Documented but absent" is always a bug; "present but + /// undocumented" is an editorial choice the reference makes on purpose — + /// `math` alone has sixty members and the page groups several per row. + #[test] + fn every_documented_module_function_exists() { + let doc = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/../website/src/stdlib/STDLIB.md")) + .expect("the published stdlib reference"); + let catalog = crate::stdlib_catalog(); + let mut checked = 0usize; + // `## bytes` and `### io.std` alike: the heading is the path to the + // namespace whose members the table below it lists. + for section in doc + .split('\n') + .fold(Vec::new(), |mut sections: Vec<(String, Vec<&str>)>, line| { + match line.strip_prefix("## ").or_else(|| line.strip_prefix("### ")) { + Some(heading) => sections.push((heading.trim().to_string(), Vec::new())), + None => { + if let Some(section) = sections.last_mut() { + section.1.push(line); + } + } + } + sections + }) + { + let (heading, body) = section; + let mut path: Vec<&str> = heading.split('.').collect(); + let Some(module) = catalog.module(path.remove(0)) else { + // A heading that is not a module: `string` documents *methods*, + // which `lk-completion` checks against the method table. + continue; + }; + for line in body { + let Some(row) = line.strip_prefix("| `") else { continue }; + let Some(name) = row.split(['(', '`', ' ']).next() else { + continue; + }; + // `sin/cos/tan(x)` — the page groups members that differ only + // in name, and each half is a real member. + for name in name.split('/') { + // The member's own path, under the heading's: `json.parse` + // is written out under `## encoding`. + let mut member: Vec<&str> = path.clone(); + member.extend(name.split('.')); + let mut exports = &module.exports; + let mut found = false; + for (depth, step) in member.iter().enumerate() { + let Some(export) = exports.iter().find(|export| export.name == *step) else { + break; + }; + if depth + 1 == member.len() { + found = true; + break; + } + exports = &export.children; + } + assert!( + found, + "the reference documents `{heading}.{name}`, which {} does not export", + module.name + ); + checked += 1; + } + } + } + assert!(checked > 100, "the reference did not parse: {checked} rows checked"); + } } diff --git a/stdlib/src/stdlib_runtime_test.rs b/stdlib/src/stdlib_runtime_test.rs index 46b83737..20bd4309 100644 --- a/stdlib/src/stdlib_runtime_test.rs +++ b/stdlib/src/stdlib_runtime_test.rs @@ -9,7 +9,7 @@ mod tests { module::ModuleRegistry, stmt::stmt_parser::StmtParser, token::Tokenizer, - val::RuntimeVal, + val::{HeapValue, RuntimeVal, TypedList}, vm::{ProgramResult, VmContext}, }; @@ -78,16 +78,429 @@ mod tests { } #[test] - fn slice_module_keeps_views_until_materialization() -> Result<()> { + fn list_windows_stay_views_until_materialized() -> Result<()> { + // Was `slice_module_keeps_views_until_materialization`, against a + // `slice` module that has been removed: taking a window over a list is + // something the list does, not a module you import first. let source = r#" - use slice; let xs = [1, 2, 3, 4]; - let view = slice.sub(slice.from_list(xs), 1, 3); - let bytes = slice.sub(slice.from_string("abcd"), 1, 3); - return slice.len(view) == 2 - && slice.get(view, 0) == 2 - && slice.to_list(view) == [2, 3] - && slice.to_string(bytes) == "bc"; + let view = xs.slice(1, 3); + return view.len() == 2 + && view[0] == 2 + && view.to_list() == [2, 3] + && view.slice(1, 2).to_list() == [3]; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// `iter.f(xs, ...)` and `xs.f(...)` are the same operation, and this is + /// what says so. + /// + /// They used to be two implementations — the module's own snapshotting, + /// truthiness and result-building beside `core_methods`' — and they agreed + /// on everything checked here, which is exactly why nobody noticed that + /// `take(-1)` did not: the method form cast `-1` to `usize` and returned + /// the whole list, the module form raised. Comparing them element by + /// element is the only thing that would have found it, so it lives here + /// now rather than in whoever's memory. + #[test] + fn the_iter_module_is_a_spelling_of_the_list_methods() -> Result<()> { + let source = r#" + use iter; + let xs = [1, 2, 3, 4, 5]; + let d = [3, 1, 3, 2, 1]; + let n = [[1, 2], [3], [4, [5, 6]]]; + return iter.map(xs, |x| x * 2) == xs.map(|x| x * 2) + && iter.filter(xs, |x| x % 2 == 0) == xs.filter(|x| x % 2 == 0) + && iter.reduce(xs, 0, |a, b| a + b) == xs.reduce(0, |a, b| a + b) + && iter.enumerate(xs) == xs.enumerate() + && iter.zip(xs, d) == xs.zip(d) + && iter.take(xs, 2) == xs.take(2) + && iter.take(xs, 99) == xs.take(99) + && iter.skip(xs, 2) == xs.skip(2) + && iter.skip(xs, 99) == xs.skip(99) + && iter.chain(xs, d) == xs.chain(d) + && iter.flatten(n) == n.flatten() + && iter.unique(d) == d.unique() + && iter.chunk(xs, 2) == xs.chunk(2) + && iter.next(xs) == xs.first(); + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// A count is not an index: there is nothing for a negative one to mean. + /// + /// Both spellings raise, with the same text — the method form used to + /// answer `[1, 2, 3]` here, by way of `-1 as usize`. + #[test] + fn a_negative_take_or_skip_count_raises_in_both_spellings() { + for source in [ + "let xs = [1, 2, 3]; return xs.take(0 - 1);", + "use iter; let xs = [1, 2, 3]; return iter.take(xs, 0 - 1);", + "let xs = [1, 2, 3]; return xs.skip(0 - 1);", + "use iter; let xs = [1, 2, 3]; return iter.skip(xs, 0 - 1);", + ] { + let error = run(source).expect_err(&format!("`{source}` must raise")); + let text = format!("{error:#}"); + assert!( + text.contains("count must be non-negative, got -1"), + "`{source}` raised the wrong thing: {text}" + ); + } + } + + /// Every string operation that has both a method and a module spelling, + /// asserted equal on the same inputs. + /// + /// The module form is not a second implementation but it is a second + /// *declaration*, and the two had drifted five ways before this test + /// existed — `len` counted bytes on one side and characters on the other, + /// `find` answered -1 versus nil, `chars` built a differently-typed list, + /// `substring`'s third parameter was documented as `end` while it is a + /// length, and `byte_at` was called `byte` here and answered -1 there. + /// Every one of them was found by comparing, not by reading. + + #[test] + fn the_string_module_is_a_spelling_of_the_string_methods() -> Result<()> { + let source = r#" + use string; + // Empty, ASCII, multi-byte, padded, and one with separators — the + // shapes that told the two forms apart. + let inputs = ["", "a", "abc", "héllo wörld", " pad ", "aXbXc"]; + let mismatch = []; + for s in inputs { + if (s.len() != string.len(s)) { mismatch.push("len"); } + if (s.is_empty() != string.is_empty(s)) { mismatch.push("is_empty"); } + if (s.lower() != string.lower(s)) { mismatch.push("lower"); } + if (s.upper() != string.upper(s)) { mismatch.push("upper"); } + if (s.trim() != string.trim(s)) { mismatch.push("trim"); } + if (s.reverse() != string.reverse(s)) { mismatch.push("reverse"); } + if (s.chars() != string.chars(s)) { mismatch.push("chars"); } + if (s.split("X") != string.split(s, "X")) { mismatch.push("split"); } + if (s.contains("b") != string.contains(s, "b")) { mismatch.push("contains"); } + if (s.starts_with("a") != string.starts_with(s, "a")) { mismatch.push("starts_with"); } + if (s.ends_with("c") != string.ends_with(s, "c")) { mismatch.push("ends_with"); } + if (s.index_of("b") != string.index_of(s, "b")) { mismatch.push("index_of"); } + if (s.index_of("zz") != string.index_of(s, "zz")) { mismatch.push("index_of-miss"); } + if (s.repeat(2) != string.repeat(s, 2)) { mismatch.push("repeat"); } + if (s.slice(1, 3) != string.slice(s, 1, 3)) { mismatch.push("slice"); } + if (s.replace("X", "-") != string.replace(s, "X", "-")) { mismatch.push("replace"); } + if (s.byte_at(0) != string.byte_at(s, 0)) { mismatch.push("byte_at"); } + if (s.byte_at(99) != string.byte_at(s, 99)) { mismatch.push("byte_at-oob"); } + } + return mismatch; + "#; + let result = run(source)?; + let RuntimeVal::Obj(handle) = result.first_return() else { + panic!("expected the mismatch list"); + }; + let names: Vec = match result.state.heap().get(*handle) { + Some(HeapValue::List(TypedList::String(values))) => values.iter().map(|v| v.to_string()).collect(), + Some(HeapValue::List(TypedList::Mixed(values))) if values.is_empty() => Vec::new(), + other => panic!("expected a string list, got {other:?}"), + }; + assert!(names.is_empty(), "the two spellings disagree on: {}", names.join(", ")); + Ok(()) + } + + /// Absence is nil, including at the byte level. + /// + /// `s.byte_at(oob)` answered `-1` — a sentinel, in a language that says nil + /// everywhere else it means absent (`find`, `get`, `first`, `last`, `pop`), + /// and against the `Int?` the method itself declares. + #[test] + fn an_out_of_range_byte_is_nil_not_a_sentinel() -> Result<()> { + let result = run(r#"use string; + return "abc".byte_at(9) == nil && string.byte_at("abc", 9) == nil && "abc".byte_at(0) == 97;"#)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// The three sequence types answer the read operations the same way. + /// + /// They did not: `Bytes` had no methods at all — `b[0]` was "not + /// indexable", `for x in b` was a type error — so reading bytes meant + /// `bytes.to_list(b)`, a copy that also turns each byte into an eight-byte + /// `Int`. The only way to read bytes was to stop having bytes. `Slice` was + /// half-way: indexable and iterable, but without `first`/`last`/ + /// `contains`/`index_of`. + /// + /// What belongs here is the operations whose meaning does not depend on the + /// element type. `map` deliberately does not: it cannot answer a `Bytes`, + /// because a callback may return something that is not a byte. + #[test] + fn the_three_sequences_read_alike() -> Result<()> { + let source = r#" + let xs = [97, 98, 99]; + let w = xs.slice(0, 3); + let b = "abc".bytes(); + return xs.len() == 3 && w.len() == 3 && b.len() == 3 + && xs[0] == 97 && w[0] == 97 && b[0] == 97 + && xs[-1] == 99 && w[-1] == 99 && b[-1] == 99 + && xs[9] == nil && w[9] == nil && b[9] == nil + && xs.first() == 97 && w.first() == 97 && b.first() == 97 + && xs.last() == 99 && w.last() == 99 && b.last() == 99 + && xs.get(1) == 98 && w.get(1) == 98 && b.get(1) == 98 + && xs.contains(98) && w.contains(98) && b.contains(98) + && xs.index_of(99) == 2 && w.index_of(99) == 2 && b.index_of(99) == 2 + && xs.index_of(1) == nil && w.index_of(1) == nil && b.index_of(1) == nil + && !xs.is_empty() && !w.is_empty() && !b.is_empty() + && w.to_list() == xs && b.to_list() == xs; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// …including `for`, which is the operation the question started from. + #[test] + fn all_three_sequences_iterate() -> Result<()> { + let source = r#" + let xs = [97, 98, 99]; + let sums = []; + for source in [xs, xs.slice(0, 3), "abc".bytes()] { + let total = 0; + for value in source { total = total + value; } + sums.push(total); + } + return sums; + "#; + let result = run(source)?; + let RuntimeVal::Obj(handle) = result.first_return() else { + panic!("expected the sums list"); + }; + let sums = match result.state.heap().get(*handle) { + Some(HeapValue::List(TypedList::Int(values))) => values.clone(), + other => panic!("expected an int list, got {other:?}"), + }; + assert_eq!(sums, vec![294, 294, 294]); + Ok(()) + } + + /// Which transforms keep a sequence's type, and which cannot. + /// + /// The rule is whether the result's elements can be something the receiver + /// could not hold. `filter` keeps a subset, so a filtered `Bytes` is still + /// `Bytes` and a `take` of a window is still a window — contiguous, so it + /// costs nothing. `map` may answer anything, so it is a list whatever it + /// started from; and `filter` on a *window* is a list too, because what it + /// keeps is not contiguous. + #[test] + fn a_transform_keeps_the_sequence_type_only_when_its_elements_must_fit() -> Result<()> { + let source = r#" + use iter; + let b = "abc".bytes(); + let w = [1, 2, 3, 4].slice(1, 4); + return b.map(|x| x + 1) == [98, 99, 100] + && w.map(|x| x * 10) == [20, 30, 40] + && b.reduce(0, |a, x| a + x) == 294 + && w.reduce(0, |a, x| a + x) == 9 + // `filter` on bytes is bytes: comparing to a list would be + // comparing two different types. + && b.filter(|x| x > 97).to_list() == [98, 99] + && b.take(2).to_list() == [97, 98] + && b.skip(2).to_list() == [99] + // …and on a window it is a list, because what it keeps has + // holes in it. + && w.filter(|x| x > 2) == [3, 4] + && w.take(2).to_list() == [2, 3] + && w.skip(2).to_list() == [4] + // The module spelling reaches all three for the exports whose + // result does not depend on which sequence came in. + && iter.map(b, |x| x + 1) == [98, 99, 100] + && iter.reduce(w, 0, |a, x| a + x) == 9 + && iter.next(b) == 97; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// Searching a list compares values, not handles. + /// + /// It compared handles, and the boundary that drew was `ShortStr`'s + /// seven-byte inline limit — invisible in the source and decisive in the + /// answer: + /// + /// ```text + /// ["ab", "cd"].contains("ab") → true + /// ["abcdefghij", …].contains("abcdefghij") → false + /// ``` + /// + /// Same shape as the `TypedList::String` read bug, in a different method. + /// A list, a map or a set could never be found at all, at any length. + #[test] + fn a_list_is_searched_by_value_not_by_handle() -> Result<()> { + let source = r#" + let long = ["abcdefghij", "klmnopqrst"]; + let nested = [[1], [2]]; + let maps = [{"a": 1}, {"b": 2}]; + return long.contains("abcdefghij") + && long.index_of("klmnopqrst") == 1 + && ["ab", "cd"].contains("ab") + && nested.contains([1]) + && nested.index_of([2]) == 1 + && maps.contains({"b": 2}) + && ["a", "a", "abcdefghij", "abcdefghij"].unique().len() == 2; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// A window equals what it holds. + /// + /// It had no equality arm at all, so it fell through to `false`: a window + /// printed `[97,98,99]` and compared unequal to `[97,98,99]` — and unequal + /// to another window over the same range of the same list. + #[test] + fn a_window_equals_the_elements_it_windows() -> Result<()> { + let source = r#" + let xs = [97, 98, 99]; + let w = xs.slice(0, 3); + return w == xs + && xs == w + && w == xs.slice(0, 3) + && w != xs.slice(0, 2) + && xs.slice(1, 3) == [98, 99] + && ["abcdefghij", "x"].slice(0, 1) == ["abcdefghij"]; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// `x in xs` compares values too. + /// + /// The mixed-list arm was `value == needle` — the derived `PartialEq` on + /// `RuntimeVal`, which is handle identity for anything on the heap. Strings + /// happened to work because a `TypedList::String` has its own arm; a list, + /// a map or a set never did. + #[test] + fn the_in_operator_compares_values() -> Result<()> { + let source = r#" + return [1, 2] in [[1, 2], [3]] + && {"a": 1} in [{"a": 1}, {"b": 2}] + && "abcdefghij" in ["abcdefghij", "x"] + && !([9, 9] in [[1, 2], [3]]); + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// Searching and deduplicating read the list where it lies. + /// + /// They used to clone it and materialize every element into a `RuntimeVal` + /// first — a heap allocation per element past seven bytes — to answer a + /// question that reads each element once and often stops at the first. + /// `unique` was quadratic on top of that, and returned a `Mixed` list + /// whatever it was given, so an `Int` list came back boxed. + /// + /// Measured on twenty thousand elements: `contains` 7.15s → 0.23s, + /// `unique` 1.25s → 0.025s. This test is about the answers being the same; + /// the numbers are why the answers are computed differently. + #[test] + fn searching_a_list_reads_it_in_place() -> Result<()> { + let source = r#" + let ints = [3, 1, 3, 2, 1]; + let texts = ["abcdefghij", "abcdefghij", "x"]; + let nested = [[1], [2], [1]]; + return ints.contains(2) + && ints.index_of(2) == 3 + && ints.index_of(9) == nil + && texts.contains("abcdefghij") + && texts.index_of("x") == 2 + && nested.contains([2]) + && ints.unique() == [3, 1, 2] + && texts.unique() == ["abcdefghij", "x"] + && nested.unique() == [[1], [2]] + && [1.5, 1.5, 2.5].unique() == [1.5, 2.5] + && [true, false, true].unique() == [true, false]; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// Sorting orders strings wherever they live. + /// + /// `compare_runtime_values` handled `ShortStr` against `ShortStr` and let + /// everything else fall to a by-kind ranking — so two heap strings, both + /// `Obj` and therefore the same kind, compared *equal*. Sorting short + /// strings worked and sorting long ones did nothing: + /// + /// ```text + /// ["zzz", "aaa", "mmm"].sort() → ["aaa", "mmm", "zzz"] + /// ["zzzzzzzzzz", "aaaaaaaaaa", "mmmmmmmmmm"] → unchanged + /// ``` + #[test] + fn sorting_orders_long_strings_too() -> Result<()> { + let source = r#" + fn mixed(values) { return values.sort(); } + return ["zzz", "aaa", "mmm"].sort() == ["aaa", "mmm", "zzz"] + && ["zzzzzzzzzz", "aaaaaaaaaa", "mmmmmmmmmm"].sort() + == ["aaaaaaaaaa", "mmmmmmmmmm", "zzzzzzzzzz"] + // …including down the mixed path, where the elements are + // arbitrary values rather than a typed run. + && mixed(["zzzzzzzzzz", 1, "aaaaaaaaaa"]) == [1, "aaaaaaaaaa", "zzzzzzzzzz"]; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// `reverse`/`sort`/`concat` keep the representation they were given. + /// + /// They materialized every element into a `RuntimeVal` — a heap allocation + /// per element past seven bytes — and boxed the result as `Mixed`, so an + /// `Int` list came back boxed and every later read of it took the slow + /// path. Measured on twenty thousand elements: `reverse` 0.42s → 0.07s, + /// `concat` 0.81s → 0.19s. + #[test] + fn rebuilding_a_list_keeps_its_representation() -> Result<()> { + let source = r#" + fn joined(a, b) { return a.concat(b); } + return [3, 1, 2].reverse() == [2, 1, 3] + && ["abcdefghij", "b"].reverse() == ["b", "abcdefghij"] + && [].reverse() == [] + && [1, 2].concat([3, 4]) == [1, 2, 3, 4] + && [1, 2].chain([3, 4]) == [1, 2, 3, 4] + // Two representations that do not match still join — through + // the one path that can. + && joined([1, 2], ["a"]) == [1, 2, "a"] + && joined(["a"], [1, 2]) == ["a", 1, 2]; + "#; + let result = run(source)?; + assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); + Ok(()) + } + + /// Reading one element reads one element. + /// + /// `first`/`last`/`get`/`pop` called `list_runtime_items`, which + /// materializes *every* element. Two thousand `pop`s on a + /// twenty-thousand-element string list did forty million allocations to + /// return two thousand values: 8.67s, now 0.023s. + #[test] + fn a_single_element_read_touches_one_element() -> Result<()> { + let source = r#" + let ints = [10, 20, 30]; + let texts = ["abcdefghij", "k"]; + let reads = ints.first() == 10 && ints.last() == 30 && ints.get(1) == 20 + && texts.first() == "abcdefghij" && texts.last() == "k" + && texts.get(0) == "abcdefghij" + && [].first() == nil && [].last() == nil && [].pop() == nil + && ints.get(9) == nil && ints.get(0 - 1) == 30; + // `pop` *removes*; `last` is the read. They were the same function + // under two names, so this used to be written as another read. + let popped = ints.pop() == 30 && ints.len() == 2 && ints.last() == 20 + && texts.pop() == "k" && texts.len() == 1; + return reads && popped; "#; let result = run(source)?; assert_eq!(result.first_return(), &RuntimeVal::Bool(true)); diff --git a/stdlib/src/string_test.rs b/stdlib/src/string_test.rs index 99aacf95..d4dbd6a7 100644 --- a/stdlib/src/string_test.rs +++ b/stdlib/src/string_test.rs @@ -4,7 +4,9 @@ mod tests { use lk_core::vm::ProgramExec; use std::sync::Arc; - use crate::{register_stdlib_modules, runtime_native::runtime_string_value, string::StringModule}; + use crate::{ + register_stdlib_globals, register_stdlib_modules, runtime_native::runtime_string_value, string::StringModule, + }; use anyhow::Result; use lk_core::{ module::ModuleRegistry, @@ -20,6 +22,10 @@ mod tests { let program = parser.parse_program()?; let mut registry = ModuleRegistry::new(); + // The globals too, not just the modules: `!` desugars to a nil check + // that raises through `error`, so without them core syntax fails here + // with "undefined callable" — a harness gap, not a language one. + register_stdlib_globals(&mut registry); register_stdlib_modules(&mut registry)?; let resolver = Arc::new(ModuleResolver::with_registry(registry)); let mut env = VmContext::new().with_resolver(resolver); @@ -84,13 +90,12 @@ mod tests { "starts_with", "ends_with", "contains", - "substring", "split", "join", "reverse", "repeat", - "char", - "byte", + "get", + "byte_at", "chars", "is_empty", ] { @@ -105,7 +110,11 @@ mod tests { "{name} should have fixed positional arity" ); } - for name in ["replace", "find", "format"] { + // `slice` joins these: a named parameter does not occupy a + // positional slot, so a call using one supplies fewer arguments than + // the declaration lists, and a fixed arity would reject it before the + // export ran (`slice(s, start: 2, end: 5)`). + for name in ["replace", "index_of", "format", "slice"] { let (arity, function) = string_native(name)?; assert!(matches!(function, NativeFunction::Plain(_))); assert_eq!(arity, NativeEntry::VARIADIC); @@ -113,6 +122,49 @@ mod tests { Ok(()) } + /// A `named(...)` parameter can be given positionally *or* by name, in any + /// mixture — and never both. + /// + /// The checker used to build a stdlib function's positional list by + /// *removing* every named-eligible parameter, so a call that mixed the two + /// spellings was rejected: `bytes.slice(b, 0, end: 2)` was told the + /// function "expects 1 positional arguments" while `bytes.slice(b, 0, 2)` + /// was fine. `math.clamp` was the sole exception, by way of a rule in the + /// checker naming it — which is why it alone behaved. + #[test] + fn named_and_positional_spellings_mix_freely() -> Result<()> { + let source = r#" + use string; + use bytes; + let all_positional = string.slice("hello", 1, 3); + let all_named = string.slice("hello", start: 1, end: 3); + let mixed = string.slice("hello", 1, end: 3); + let sliced = bytes.slice(bytes.from_list([1, 2, 3]), 0, end: 2); + let window = if sliced.len() == 2 { "two" } else { "wrong" }; + return [all_positional, all_named, mixed, window]; + "#; + let result = execute_string(source)?; + let TypedList::String(values) = runtime_list(result.first_return(), result.state.heap()) else { + panic!("expected typed string list"); + }; + for (index, value) in values[..3].iter().enumerate() { + assert_eq!(value.as_ref(), "el", "spelling {index} should agree with the others"); + } + assert_eq!( + values[3].as_ref(), + "two", + "the mixed-spelling byte window should hold two bytes" + ); + Ok(()) + } + + /// Naming an argument means what passing it positionally means. + /// + /// `replace`'s `all` flag used to default to whether the call *spelled* + /// its arguments by name: `replace("aaa", "a", "b")` replaced every + /// occurrence and `replace("aaa", pattern: "a", with: "b")` replaced one. + /// Same arguments, different answer, decided by punctuation. All three + /// spellings below now agree. #[test] fn test_string_replace_named_arguments() -> Result<()> { let source = r#" @@ -129,7 +181,7 @@ mod tests { assert_eq!( values.as_slice(), &[ - Arc::::from("xollipop"), + Arc::::from("xoxxipop"), Arc::::from("xoxxipop"), Arc::::from("xoxxipop") ] @@ -164,10 +216,80 @@ mod tests { } #[test] - fn test_string_substring_out_of_bounds_error() { - let source = "use string; return string.substring(\"abc\", 10, 1);"; - let err = execute_string(source).expect_err("out-of-bounds substring should error"); - assert!(err.to_string().contains("start index out of bounds")); + fn test_string_slice_out_of_range_is_empty() -> Result<()> { + // Clamped, not an error — the same as everywhere else a position runs + // past the end in this language: `s[1..99]` answers `"bc"`, + // `xs[0..99]` answers the whole list, `xs.get(99)` answers nil. This + // was the module form's own convention (it raised) while the method + // form clamped, so the two disagreed about the same call. + let result = execute_string("use string; return string.slice(\"abc\", 10, 11);")?; + assert_eq!( + result.first_return(), + &RuntimeVal::ShortStr(ShortStr::new("").expect("empty")) + ); + + Ok(()) + } + + #[test] + fn test_method_and_module_forms_agree_on_multibyte_text() -> Result<()> { + // The two spellings of every string operation had drifted apart: + // `"héllo wörld".len()` answered 11 (characters) while + // `string.len(…)` answered 13 (bytes), `find` answered `-1` on one + // side and `nil` on the other, and `substring` panicked on both when a + // position landed inside a multi-byte character. They share one + // implementation now; this is what keeps them sharing it. + let source = r#" + use string; + let s = "héllo wörld"; + return [ + s.len() == string.len(s), + s.index_of("wörld") == string.index_of(s, "wörld"), + s.index_of("zz") == string.index_of(s, "zz"), + s.slice(2, 5) == string.slice(s, 2, 5), + s.slice(0, s.len()) == s, + s.slice(s.index_of("wörld")!, s.index_of("wörld")! + 5) == "wörld", + s.len() == 11, + s.index_of("zz") == nil, + ]; + "#; + let result = execute_string(source)?; + let TypedList::Bool(values) = runtime_list(result.first_return(), result.state.heap()) else { + panic!("expected a list of booleans"); + }; + assert!( + values.iter().all(|holds| *holds), + "method and module forms disagree: {values:?}" + ); + Ok(()) + } + + #[test] + fn test_bytes_is_the_explicit_way_to_byte_positions() -> Result<()> { + // Characters are the default; bytes are asked for. `s.len()` and + // `s.bytes().len()` disagree on purpose, and which one you get is now + // the reader's choice rather than a property of which spelling of the + // operation they happened to reach for. + let source = r#" + use bytes; + let s = "héllo"; + return [ + s.len() == 5, + s.bytes().len() == 6, + s.bytes() == bytes.from_string(s), + bytes.to_string_utf8(s.bytes()) == s, + s.chars() == ["h", "é", "l", "l", "o"], + ]; + "#; + let result = execute_string(source)?; + let TypedList::Bool(values) = runtime_list(result.first_return(), result.state.heap()) else { + panic!("expected a list of booleans"); + }; + assert!( + values.iter().all(|holds| *holds), + "byte/character split broke: {values:?}" + ); + Ok(()) } #[test] @@ -192,4 +314,260 @@ mod tests { assert_eq!(result, RuntimeVal::Bool(true)); Ok(()) } + + /// Both pad functions measured the width in *bytes* and then sliced the + /// repeated fill by byte offset, so a multi-byte fill cut inside a + /// character and **panicked the process** — which a script cannot catch. + /// Characters is also the unit everything else counts: `s.len()`, `s[i]`, + /// `s.slice(a, b)`. + #[test] + fn pad_counts_characters_and_survives_a_multibyte_fill() -> Result<()> { + let out = execute_string( + r#" + use string; + return [ + string.pad_left("a", 5, "中"), + string.pad_right("a", 5, "中"), + string.pad_left("中文", 4, "-"), + string.pad_left("a", 5, "xy"), + string.pad_left("abcdef", 3, "-"), + ]; + "#, + )?; + let TypedList::String(values) = runtime_list(out.first_return(), out.state.heap()) else { + panic!("expected a list of strings"); + }; + assert_eq!( + values.iter().map(|value| value.as_ref()).collect::>(), + ["中中中中a", "a中中中中", "--中文", "xyxya", "abcdef"] + ); + Ok(()) + } + + /// `strip`'s parameter has always been named `chars` — a *set* — but the + /// body stripped the whole string as a prefix, and only if that failed as a + /// suffix, once: `strip("--a--", "-")` answered `"-a--"`. `strip_prefix` + /// and `strip_suffix` next door are the once-each operations. + #[test] + fn strip_removes_every_leading_and_trailing_character_in_the_set() -> Result<()> { + let out = execute_string( + r#" + use string; + return [ + string.strip("--a--", "-"), + string.strip("xxaybyxx", "xy"), + string.strip("abc", "-"), + string.strip("---", "-"), + ]; + "#, + )?; + let TypedList::String(values) = runtime_list(out.first_return(), out.state.heap()) else { + panic!("expected a list of strings"); + }; + assert_eq!( + values.iter().map(|value| value.as_ref()).collect::>(), + ["a", "ayb", "abc", ""] + ); + Ok(()) + } + + /// Reading a number out of text is the operation LK did not have. + /// + /// `string.to_int` looked like the answer and refused a `String` outright, + /// so a program could split a CSV, read a config or take an argument and + /// had nowhere to go. Text that is not a number answers `nil` (a question + /// about input, not a program error); a Float with no Int raises. + #[test] + fn to_int_reads_text_and_refuses_a_float_with_no_int() -> Result<()> { + let out = execute_string( + r#" + use string; + return [ + string.to_int("42") ?? -1, + string.to_int(" 42\n") ?? -1, + string.to_int("-42") ?? -1, + string.to_int("42abc") ?? -1, + string.to_int("") ?? -1, + string.to_int("42.0") ?? -1, + string.to_int("9223372036854775808") ?? -1, + string.to_int("ff", 16) ?? -1, + string.to_int("-101", 2) ?? -1, + string.to_int("9", 8) ?? -1, + string.to_int(3.99) ?? -1, + string.to_int(-3.99) ?? -1, + string.to_int(true) ?? -1, + ]; + "#, + )?; + let TypedList::Int(values) = runtime_list(out.first_return(), out.state.heap()) else { + panic!("expected a list of ints"); + }; + assert_eq!(values, &[42, 42, -42, -1, -1, -1, -1, 255, -5, -1, 3, -3, 1]); + + for (source, expected) in [ + ("string.to_int(0.0 / 0.0);", "NaN"), + ("string.to_int(1e30);", "outside the Int range"), + ("string.to_int(\"7\", 1);", "base must be between 2 and 36"), + ] { + let error = execute_string(&format!("use string;\n{source}")).expect_err(source); + assert!( + format!("{error:#}").contains(expected), + "`{source}` should mention `{expected}`: {error:#}" + ); + } + Ok(()) + } + + /// The `Float` half, including the values only a Float has. + #[test] + fn to_float_reads_text_including_nan_and_the_infinities() -> Result<()> { + let out = execute_string( + r#" + use string; + return [ + string.to_float("3.5") ?? -1.0, + string.to_float(" -2e3 ") ?? -1.0, + string.to_float("abc") ?? -1.0, + string.to_float("") ?? -1.0, + string.to_float(7) ?? -1.0, + string.to_float(true) ?? -1.0, + ]; + "#, + )?; + let TypedList::Float(values) = runtime_list(out.first_return(), out.state.heap()) else { + panic!("expected a list of floats"); + }; + assert_eq!(values, &[3.5, -2000.0, -1.0, -1.0, 7.0, 1.0]); + + let out = execute_string("use string;\nreturn string.to_float(\"inf\");")?; + assert_eq!(out.first_return(), &RuntimeVal::Float(f64::INFINITY)); + let out = execute_string("use string;\nreturn string.to_float(\"nan\");")?; + let RuntimeVal::Float(value) = out.first_return() else { + panic!("expected a float"); + }; + assert!(value.is_nan(), "`nan` parses to NaN, got {value}"); + Ok(()) + } + /// One convention for a negative position, across every sequence. + /// + /// `xs[-1]` and `xs.get(-1)` have always counted from the end. `slice` had + /// four implementations and three answers: List and Bytes raised, String + /// and Slice clamped to 0 and returned a window nobody asked for — and the + /// *native* string slice already counted from the end, so + /// `"abcde".slice(1, -1)` was `""` interpreted and `"bcd"` compiled. + #[test] + fn a_negative_slice_bound_counts_from_the_end_on_every_sequence() -> Result<()> { + let out = execute_string( + r#" + use bytes; + let s = "abcde"; + let xs = [1, 2, 3, 4, 5]; + let b = bytes.from_string("abcde"); + return [ + s.slice(-2, 5), + s.slice(1, -1), + s.slice(-99, 99), + s.slice(-1, -3), + "${xs.slice(-2, 5).to_list()}", + "${xs.slice(1, -1).to_list()}", + "${xs.slice(-99, 99).to_list()}", + "${xs.slice(-1, -3).to_list()}", + "${b.slice(-2, 5)}", + "${b.slice(1, -1)}", + ]; + "#, + )?; + let TypedList::String(values) = runtime_list(out.first_return(), out.state.heap()) else { + panic!("expected a list of strings"); + }; + assert_eq!( + values.iter().map(|value| value.as_ref()).collect::>(), + [ + "de", + "bcd", + "abcde", + "", + "[4,5]", + "[2,3,4]", + "[1,2,3,4,5]", + "[]", + "Bytes([100,101])", + "Bytes([98,99,100])", + ] + ); + Ok(()) + } + + /// Every `string` module member answers exactly what its method spelling + /// answers — by construction, because the module forwards. + /// + /// It did not, and the divergences were live: `split(s, "")` was + /// `["a","b","c"]` through the module and `["","a","b","c",""]` through the + /// method, `slice(s, -1, 3)` raised through the module while the method + /// counted from the end (the language's own rule for a negative position), + /// and `byte_at(s, -1)` raised on one side and answered nil on the other. + /// Fifteen operations had two bodies; three of them had already drifted. + /// + /// The comparison is the language's own `==`, on the inputs where the two + /// used to differ — reading the two answers back out of rendered text was a + /// second parser to get wrong, and I got it wrong first. + #[test] + fn every_module_spelling_answers_what_the_method_answers() -> Result<()> { + let source = r#" + use string; + let s = "abc"; + return [ + string.split(s, "") == s.split(""), + string.slice(s, -1, 3) == s.slice(-1, 3), + string.byte_at(s, -1) == s.byte_at(-1), + string.upper(s) == s.upper(), + string.len("héllo") == "héllo".len(), + string.replace("aa", "a", "b", false) == "aa".replace("a", "b", false), + string.index_of(s, "z") == s.index_of("z"), + string.repeat(s, 0) == s.repeat(0), + string.chars(s) == s.chars(), + string.trim(" a ") == " a ".trim(), + ]; + "#; + let result = execute_string(source)?; + let rendered = lk_core::vm::display_runtime_value(result.first_return(), result.state.heap()); + assert!( + !rendered.contains("false"), + "a module spelling and its method disagree: {rendered}" + ); + + // The members that used to have only one of the two spellings, in a + // second program: one list of thirty comparisons is a single expression, + // and a single expression has 256 registers to live in. + let source = r#" + use string; + let s = "abc"; + return [ + string.get(s, -1) == s.get(-1), + string.first(s) == s.first(), + string.last(s) == s.last(), + string.take(s, 2) == s.take(2), + string.skip(s, 2) == s.skip(2), + string.bytes(s) == s.bytes(), + string.capitalize("aBC") == "aBC".capitalize(), + string.title("aB cD") == "aB cD".title(), + string.count("中中", "中") == "中中".count("中"), + string.count("中中", "") == "中中".count(""), + string.strip("--a--", "-") == "--a--".strip("-"), + string.strip_prefix(s, "z") == s.strip_prefix("z"), + string.strip_suffix(s, "c") == s.strip_suffix("c"), + string.pad_left("a", 5, "中") == "a".pad_left(5, "中"), + string.pad_right("a", 5) == "a".pad_right(5), + string.format("{}-{}", 1, 2) == "{}-{}".format(1, 2), + string.format("{}", 1, 2) == "{}".format(1, 2), + ]; + "#; + let result = execute_string(source)?; + let rendered = lk_core::vm::display_runtime_value(result.first_return(), result.state.heap()); + assert!( + !rendered.contains("false"), + "a module spelling and its method disagree: {rendered}" + ); + Ok(()) + } } diff --git a/stdlib/web/Cargo.toml b/stdlib/web/Cargo.toml index d57fbfe4..efcd4c11 100644 --- a/stdlib/web/Cargo.toml +++ b/stdlib/web/Cargo.toml @@ -21,5 +21,4 @@ lk-stdlib-iter = { path = "../crates/iter" } lk-stdlib-math = { path = "../crates/math" } lk-stdlib-path = { path = "../crates/path" } lk-stdlib-regex = { path = "../crates/regex" } -lk-stdlib-slice = { path = "../crates/slice" } lk-stdlib-string = { path = "../crates/string" } diff --git a/stdlib/web/src/lib.rs b/stdlib/web/src/lib.rs index 66e67cd6..2109aac2 100644 --- a/stdlib/web/src/lib.rs +++ b/stdlib/web/src/lib.rs @@ -6,13 +6,40 @@ use lk_core::{ val::RuntimeVal, vm::{NativeArgs, NativeEntry, NativeRuntime, RuntimeExport}, }; -use lk_stdlib_common::runtime_native::runtime_display_value; thread_local! { static STDOUT: RefCell = const { RefCell::new(String::new()) }; } -const UNSUPPORTED_MODULES: &[&str] = &[ +/// The concurrency globals, present and refusing by name. +/// +/// `chan` is already an unsupported *module* here, so `use chan` says so. +/// `spawn(f)` said "undefined function `spawn`" — the same absence, reported as +/// if the program had a typo. The playground runs on one thread, so these cannot +/// work; what they can do is say which of the two problems the reader has. +fn unavailable(name: &str) -> Result { + Err(anyhow!( + "`{name}` is not available in the browser: the playground is single-threaded" + )) +} + +fn spawn(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("spawn") +} + +fn chan(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("chan") +} + +fn send(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("send") +} + +fn recv(_args: NativeArgs<'_>, _runtime: &mut NativeRuntime<'_>) -> Result { + unavailable("recv") +} + +pub const UNSUPPORTED_MODULES: &[&str] = &[ "chan", "datetime", "env", "fs", "http", "io", "net", "os", "process", "random", "stream", "task", "time", "uuid", ]; @@ -40,7 +67,6 @@ define_web_modules!( lk_stdlib_math::register, lk_stdlib_path::register, lk_stdlib_regex::register, - lk_stdlib_slice::register, lk_stdlib_string::register, ); @@ -62,6 +88,15 @@ pub fn register_web_stdlib_globals(registry: &mut ModuleRegistry) { full_state "assert" => assert, NativeEntry::VARIADIC, full_state "assert_eq" => assert_eq, NativeEntry::VARIADIC, full_state "assert_ne" => assert_ne, NativeEntry::VARIADIC, + // `error`, which is what a `catch` catches. Not a module: a host + // may leave `fs` out and a program is told so, but a program that + // raises on this host was told "undefined function" instead. + full_state "error" => lk_stdlib_common::language::error, NativeEntry::VARIADIC, + // Present and refusing, rather than absent — see `unavailable`. + full_state "spawn" => spawn, 1, + full_state "chan" => chan, NativeEntry::VARIADIC, + full_state "send" => send, 2, + full_state "recv" => recv, 1, ], ); } @@ -82,13 +117,13 @@ pub fn register_web_stdlib(registry: &mut ModuleRegistry) -> Result<()> { } fn print(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let text = format_variadic_runtime(args.as_slice(), runtime)?; + let text = lk_stdlib_common::language::format_variadic(args.as_slice(), runtime)?; STDOUT.with(|stdout| stdout.borrow_mut().push_str(&text)); Ok(RuntimeVal::Nil) } fn println(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - let text = format_variadic_runtime(args.as_slice(), runtime)?; + let text = lk_stdlib_common::language::format_variadic(args.as_slice(), runtime)?; STDOUT.with(|stdout| { let mut stdout = stdout.borrow_mut(); stdout.push_str(&text); @@ -98,138 +133,23 @@ fn println(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result, runtime: &mut NativeRuntime<'_>) -> Result { - let message = if args.is_empty() { - "panic".to_string() - } else { - join_runtime_display(args.as_slice(), runtime)? - }; - Err(anyhow!("{message}")) + lk_stdlib_common::language::panic(args, runtime) } +// `assert`/`assert_eq`/`assert_ne`/`panic` are the same on every host — an +// assertion is arithmetic on values, and only `print` needs to know where +// output goes. They were written out three times and had drifted three ways; +// see `lk_stdlib_common::language`. fn assert(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - expect_assert_args(args, 1, 2, "assert")?; - let values = args.as_slice(); - if assert_truthy(&values[0]) { - return Ok(RuntimeVal::Nil); - } - let message = if let Some(message) = values.get(1) { - format!("assertion failed: {}", runtime_display(message, runtime)?) - } else { - "assertion failed".to_string() - }; - Err(anyhow!("{message}")) + lk_stdlib_common::language::assert(args, runtime) } fn assert_eq(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - expect_assert_args(args, 2, 3, "assert_eq")?; - let values = args.as_slice(); - if runtime_values_equal(&values[0], &values[1]) { - return Ok(RuntimeVal::Nil); - } - let actual = runtime_display(&values[0], runtime)?; - let expected = runtime_display(&values[1], runtime)?; - let mut message = format!("assertion failed: expected {expected}, got {actual}"); - if let Some(extra) = values.get(2) { - message.push_str(" - "); - message.push_str(&runtime_display(extra, runtime)?); - } - Err(anyhow!("{message}")) + lk_stdlib_common::language::assert_eq(args, runtime) } fn assert_ne(args: NativeArgs<'_>, runtime: &mut NativeRuntime<'_>) -> Result { - expect_assert_args(args, 2, 3, "assert_ne")?; - let values = args.as_slice(); - if !runtime_values_equal(&values[0], &values[1]) { - return Ok(RuntimeVal::Nil); - } - let mut message = "assertion failed: values should not be equal".to_string(); - if let Some(extra) = values.get(2) { - message.push_str(" - "); - message.push_str(&runtime_display(extra, runtime)?); - } - Err(anyhow!("{message}")) -} - -fn format_variadic_runtime(args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { - if args.is_empty() { - return Ok(String::new()); - } - let Some(format) = runtime_string_maybe(&args[0], runtime)? else { - return join_runtime_display(args, runtime); - }; - let rest = &args[1..]; - let mut out = String::with_capacity(format.len() + rest.len() * 8); - let mut chars = format.chars().peekable(); - let mut arg_index = 0usize; - while let Some(ch) = chars.next() { - if ch == '{' && chars.peek() == Some(&'}') { - chars.next(); - if let Some(value) = rest.get(arg_index) { - out.push_str(&runtime_display(value, runtime)?); - arg_index += 1; - } else { - out.push_str("{}"); - } - } else { - out.push(ch); - } - } - if arg_index < rest.len() { - if !out.is_empty() { - out.push(' '); - } - out.push_str(&join_runtime_display(&rest[arg_index..], runtime)?); - } - Ok(out) -} - -fn join_runtime_display(args: &[RuntimeVal], runtime: &mut NativeRuntime<'_>) -> Result { - let mut out = String::new(); - for (index, value) in args.iter().enumerate() { - if index > 0 { - out.push(' '); - } - out.push_str(&runtime_display(value, runtime)?); - } - Ok(out) -} - -fn runtime_display(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result { - runtime_display_value(value, runtime.heap()) -} - -fn runtime_string_maybe(value: &RuntimeVal, runtime: &mut NativeRuntime<'_>) -> Result> { - Ok(match value { - RuntimeVal::ShortStr(value) => Some(value.as_str().to_string()), - RuntimeVal::Obj(handle) => match runtime.heap().get(*handle) { - Some(lk_core::val::HeapValue::String(value)) => Some(value.to_string()), - Some(_) => None, - None => return Err(anyhow!("heap object {} out of bounds", handle.index())), - }, - _ => None, - }) -} - -fn runtime_values_equal(left: &RuntimeVal, right: &RuntimeVal) -> bool { - left == right -} - -fn expect_assert_args(args: NativeArgs<'_>, min: usize, max: usize, name: &str) -> Result<()> { - if args.has_named() { - return Err(anyhow!("{name}() does not accept named arguments")); - } - let len = args.len(); - if (min..=max).contains(&len) { - Ok(()) - } else if min == max { - Err(anyhow!("{name}() expects exactly {min} arguments")) - } else { - Err(anyhow!("{name}() expects {min} or {max} arguments")) - } -} - -fn assert_truthy(value: &RuntimeVal) -> bool { - !matches!(value, RuntimeVal::Nil | RuntimeVal::Bool(false)) + lk_stdlib_common::language::assert_ne(args, runtime) } #[derive(Debug)] @@ -257,3 +177,43 @@ impl ModuleProvider for UnsupportedWebModule { )) } } + +#[cfg(test)] +mod tests { + use super::*; + use lk_core::stmt::stmt_parser::StmtParser; + use lk_core::token::Tokenizer; + use lk_core::vm::{ModuleResolver, ProgramExec, VmContext}; + use std::sync::Arc; + + fn run(source: &str) -> Result<()> { + let tokens = Tokenizer::tokenize(source)?; + let program = StmtParser::new(&tokens).parse_program()?; + let mut registry = ModuleRegistry::new(); + register_web_stdlib(&mut registry)?; + let resolver = Arc::new(ModuleResolver::with_registry(registry)); + let mut env = VmContext::new().with_resolver(resolver); + program.execute_with_ctx(&mut env)?; + Ok(()) + } + + /// `assert_eq` in the playground compared values, not handles. + /// + /// It was `left == right` — the *derived* `PartialEq` on `RuntimeVal`, + /// which is structural for a `ShortStr` and handle identity for an `Obj`. + /// So an assertion held or failed depending on whether its strings fitted + /// in seven bytes, and the same program passed in the CLI and failed in the + /// browser. + #[test] + fn assert_eq_compares_values_not_handles() { + // Seven bytes or fewer: inline, and this always worked. + run(r#"assert_eq("ab", "ab");"#).expect("short strings"); + // Eight or more: a heap object each, and this did not. + run(r#"assert_eq("abcdefghij", "abcdefghij");"#).expect("long strings"); + run("assert_eq([1, 2], [1, 2]);").expect("lists"); + run(r#"assert_eq({"a": 1}, {"a": 1});"#).expect("maps"); + // And it still tells unequal values apart. + run(r#"assert_eq("abcdefghij", "abcdefghik");"#).expect_err("different strings must fail"); + run("assert_eq([1, 2], [1, 3]);").expect_err("different lists must fail"); + } +} diff --git a/values/src/lib.rs b/values/src/lib.rs index dba298d4..7ce931e4 100644 --- a/values/src/lib.rs +++ b/values/src/lib.rs @@ -17,7 +17,10 @@ mod strings; mod types; pub use numeric::{NumericClass, NumericHierarchy}; -pub use types::{FunctionNamedParamType, IntKind, ShortStr, ShortStrOrStr, Type}; +pub use types::{ + CONTAINER_TYPE_NAMES, FunctionNamedParamType, IntKind, NUMBER_TYPE_NAME, NoTraits, PRIMITIVE_TYPES, ShortStr, + ShortStrOrStr, TYPE_SPELLINGS, TraitOracle, Type, +}; // NOTE: runtime resource-handle values (TaskValue/ChannelValue/StreamValue/ // StreamCursorValue/SliceValue/ResourceValue/ResourceHandle) live in diff --git a/values/src/types.rs b/values/src/types.rs index f714bfe1..8e3020ee 100644 --- a/values/src/types.rs +++ b/values/src/types.rs @@ -11,8 +11,15 @@ use serde::{Deserialize, Serialize, Serializer}; use crate::{NumericClass, NumericHierarchy}; -/// 内联短字符串:0–7 字节 UTF-8,完全存储在 LiteralVal 内(零堆分配)。 -/// 实现了 Copy,克隆无需原子操作。 +/// An inline short string: 0–7 UTF-8 bytes, held entirely inside a +/// `LiteralVal` with no heap allocation. `Copy`, so a clone costs no atomic. +/// +/// **Invariant: `data[..len]` is valid UTF-8.** Both fields are private, so +/// nothing outside this module can build one; every construction site inside it +/// either copies a `&str`'s bytes whole, is `char::encode_utf8`'s output, or +/// appends ASCII digits to a valid prefix, and `Deserialize` goes through +/// [`ShortStr::new`]. A new construction site has to keep it — +/// `every_short_str_constructor_keeps_the_utf8_invariant` checks each one. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ShortStr { len: u8, @@ -20,7 +27,7 @@ pub struct ShortStr { } impl ShortStr { - /// 从 str 创建。若 s.len() > 7 返回 None。 + /// From a `str`; `None` when it is longer than 7 bytes. #[inline] pub fn new(s: &str) -> Option { let bytes = s.as_bytes(); @@ -47,7 +54,16 @@ impl ShortStr { #[inline] pub fn as_str(&self) -> &str { - // SAFETY: data 在构造时已验证为合法 UTF-8。 + // The checked `from_utf8`, although the invariant above says it cannot + // fail: swapping in `from_utf8_unchecked` was measured and **bought + // nothing** (min-of-9 over a two-million-iteration map-string-key plus + // method-call workload: 0.87s vs 0.89s). The 5% that + // `core::str::converts::from_utf8` takes in a profile is misleading — + // the length is capped at 7 bytes, so for ASCII the check is one byte + // scan the compiler has already flattened. + // + // So no `unsafe` here: trading safety for a gain that does not measure + // is a loss. Re-run those numbers before changing it back. core::str::from_utf8(&self.data[..self.len as usize]).expect("ShortStr contains valid UTF-8") } @@ -262,30 +278,75 @@ impl IntKind { /// the real width is the target's and the narrower target is the binding /// one — a literal that fits everywhere is the only one that is portably /// safe to accept without a cast. + /// Whether a literal fits. + /// + /// A radix literal past `i64::MAX` arrives here as its unsigned value, not as + /// the negative carrier the `i64` would show: `let bit: u64 = + /// 0x8000000000000000;` is a perfectly good u64 — the NX bit in a page-table + /// entry, the high half of a 64-bit BAR — and the lexer keeps it as a `u64` + /// (`Token::UInt`) precisely so this check sees what was written. + /// + /// Reinterpreting a negative value as its unsigned bit pattern *here* would + /// not have worked: `let y: u8 = -1;` reaches this function too and is + /// rightly refused, and the two are indistinguishable once the sign is the + /// only evidence left. That is why the distinction is made in the lexer, + /// where the source text still says which one it was. pub fn accepts_literal(self, value: i128) -> bool { match self.range() { Some((lo, hi)) => value >= lo && value <= hi, + // Pointer width, measured at the width this compiles for. + // + // It used to probe `u32`/`i32` — "assume the smaller, be safe" — + // which on a 64-bit target refuses a legal value: `let a: usize = + // 0xFFFF_FFFF_FFFF_FFFF` was rejected while the identical `u64` was + // accepted. Nothing else in the compiler hedges this way: the + // unsigned-operator rewrites treat `usize` as carrier-filling + // alongside `u64`, and pointer casts are lowered as 64-bit. The + // range check was the only place still guessing, and it guessed + // differently from the code it guards. + // + // TODO(32-bit targets): a 32-bit deployment target needs this — and + // the pointer-width cast in `lower_cast` — to follow the target + // rather than the host. Same TODO, one decision. + // + // Not reachable today, and `no_32_bit_target_is_reachable_yet` + // (lk-aot-codegen) is what says so: every 32-bit triple is refused + // at `isa::lookup`, so there is no target on which this range check + // is wrong. That test fails when one arrives, and names this site. + // + // The checker cannot answer it by threading a target through + // either: `lk check` has none, and bytecode is target-agnostic — + // the target only exists at `lk compile object:`. Whatever + // the decision turns out to be, it is a decision about *where* the + // width comes from, not just what it is. None => { - let probe = if self.is_signed() { Self::I32 } else { Self::U32 }; + let probe = if self.is_signed() { Self::I64 } else { Self::U64 }; probe.accepts_literal(value) } } } + /// Every machine-int kind. + /// + /// Enumerated because the names are read from outside — the editor grammars + /// keep their own copy, and `type_name_lists_agree` checks those against + /// this. The *names* are not repeated here: they stay in `name()`, whose + /// `match` the compiler keeps exhaustive. + pub const ALL: &'static [IntKind] = &[ + Self::I8, + Self::I16, + Self::I32, + Self::I64, + Self::U8, + Self::U16, + Self::U32, + Self::U64, + Self::Isize, + Self::Usize, + ]; + pub fn parse(name: &str) -> Option { - Some(match name { - "i8" => Self::I8, - "i16" => Self::I16, - "i32" => Self::I32, - "i64" => Self::I64, - "u8" => Self::U8, - "u16" => Self::U16, - "u32" => Self::U32, - "u64" => Self::U64, - "isize" => Self::Isize, - "usize" => Self::Usize, - _ => return None, - }) + Self::ALL.iter().copied().find(|kind| kind.name() == name) } pub fn name(self) -> &'static str { @@ -398,21 +459,142 @@ pub enum Type { /// Any type (top type) Any, + + /// An element type that is not known: `List<_>`, `Map`. + /// + /// Written `_`, the same "unnamed anything" it means in a pattern, and only + /// valid inside a type's parameter list. **Nothing is assignable to it**, + /// which is what makes a container parameterised by it readable but not + /// writable — the read-only view falls out of the type rather than being a + /// second rule about containers. + /// + /// It exists because containers are invariant (see `is_assignable_to`): + /// without it, a signature could not say "a list of anything", and this + /// language has no generic functions to say it with. Covariance said it + /// instead, and covariance over a *mutable* container is unsound — + /// `List` widened to `List` accepted a `String` through the alias + /// and `let b: Int = a[2]` then type-checked and held one. + Unknown, +} + +/// The parameterless builtin types, with the name the language spells each. +/// +/// A list rather than a `match` arm because three other places keep their own +/// copy of these names — the tree-sitter grammar, the TextMate grammar, and +/// completion's receiver table — and a copy nobody can read is a copy that +/// drifts. `type_name_lists_agree` checks the editor grammars against this. +pub const PRIMITIVE_TYPES: &[(&str, Type)] = &[ + ("Int", Type::Int), + ("Float", Type::Float), + ("String", Type::String), + ("Bool", Type::Bool), + ("Nil", Type::Nil), + ("Any", Type::Any), +]; + +/// Second spellings of types that already exist. +/// +/// A language that wants to be written down to machine code without ambiguity +/// needs a name that says the width — and one that does not, for the code that +/// is not about widths. `Int` and `i64` are that pair: one type, two spellings, +/// so a driver's `i64` and a front end's `Int` are the same value and pass +/// through each other's functions without a cast. +/// +/// They are *aliases*, not two types that happen to convert. Two convertible +/// types would be more ambiguity, not less: the reader would have to know which +/// one a value is to know what it does. +/// +/// `isize`/`usize` are deliberately not here. Pointer width is the whole reason +/// those names exist, and equating either with a fixed width is the mistake +/// this table exists to avoid. +pub const TYPE_SPELLINGS: &[(&str, Type)] = &[("i64", Type::Int), ("f64", Type::Float)]; + +/// `Number` — `Int | Float`, and the one spelling that cannot live in +/// [`TYPE_SPELLINGS`] because a `const` cannot build the `Vec` a union needs. +/// [`Type::parse`] resolves it; this is here so the name has one home. +pub const NUMBER_TYPE_NAME: &str = "Number"; + +/// Builtin types that take parameters: `List`, `Map`, `Set`, … +/// +/// Names only. What each does with its parameters is `Type::parse`'s business, +/// and the arities differ; this is the list of *names* the editors have to know +/// about, which is the part that drifts. +pub const CONTAINER_TYPE_NAMES: &[&str] = &["List", "Map", "Set", "Tuple", "Task", "Channel", "Box", "Boxed"]; + +/// Answers "does this type implement this trait", for the assignability walk. +/// +/// This crate has the `Type` and none of the declarations: a trait's name is +/// just a `Type::Named` here. The type checker holds the trait and impl tables +/// and implements this; [`NoTraits`] is the answer everywhere else, and is what +/// every existing caller of [`Type::is_assignable_to`] gets. +pub trait TraitOracle { + fn implements(&self, ty: &Type, trait_name: &str) -> bool; +} + +/// The oracle for a caller with no trait tables: nothing implements anything. +pub struct NoTraits; + +impl TraitOracle for NoTraits { + fn implements(&self, _ty: &Type, _trait_name: &str) -> bool { + false + } } impl Type { pub fn parse(s: &str) -> Option { + Type::parse_at(s, 0) + } + + /// [`Type::parse`], counting how deep it has gone. + /// + /// A type spelling nests without bound — `List…>>` — and this + /// is a recursive descent over it, so a deep enough annotation overflowed + /// the stack: `SIGABRT` and a core dump past about 1700 levels, on a + /// program the tokenizer had accepted. The expression parser has had a + /// bound for this reason; the type parser is the other half of the same + /// surface, and the LSP and the browser playground read both from text they + /// did not write. + /// + /// Past the bound is `None`, which every caller already words as "not a + /// type" — the same answer a misspelling gets, and the reason this needs no + /// new error path. + fn parse_at(s: &str, depth: usize) -> Option { + /// Deep enough that nothing written by hand comes close — the deepest + /// annotation in this repository is four — and far enough under the + /// measured overflow (between 1500 and 2000 levels) to stay there when + /// a later walk over the type gets hungrier. + const MAX_TYPE_DEPTH: usize = 128; + if depth >= MAX_TYPE_DEPTH { + return None; + } + let depth = depth + 1; let s = s.trim(); + // `_` — an element type that is not known. No positional rule keeps it + // out of the top level: `let x: _ = 1;` parses and then fails to + // type-check, because nothing is assignable to `_`. That is the same + // answer a positional rule would give, from the type itself. + if s == "_" { + return Some(Type::Unknown); + } + // Handle primitive types - match s { - "Int" => return Some(Type::Int), - "Float" => return Some(Type::Float), - "String" => return Some(Type::String), - "Bool" => return Some(Type::Bool), - "Nil" => return Some(Type::Nil), - "Any" => return Some(Type::Any), - _ => {} + if let Some((_, ty)) = PRIMITIVE_TYPES.iter().find(|(name, _)| *name == s) { + return Some(ty.clone()); + } + + // A second spelling of one of them (`i64` is `Int`), checked before + // `IntKind` so that `i64` does not become a *machine* int distinct from + // the `Int` it is a spelling of. + if let Some((_, ty)) = TYPE_SPELLINGS.iter().find(|(name, _)| *name == s) { + return Some(ty.clone()); + } + + // `Number` is what the standard library's declarations have always + // called `Int | Float`; until now it was a name the documentation could + // write and the language could not. + if s == NUMBER_TYPE_NAME { + return Some(Type::Union(vec![Type::Int, Type::Float])); } if let Some(kind) = IntKind::parse(s) { @@ -427,14 +609,14 @@ impl Type { // `*mut T` before `*T`: the former's prefix is a superset. if let Some(rest) = s.strip_prefix("*mut ").or_else(|| s.strip_prefix("*mut")) { - let pointee = Type::parse(rest.trim())?; + let pointee = Type::parse_at(rest.trim(), depth)?; return Some(Type::Ptr { pointee: Box::new(pointee), mutable: true, }); } if let Some(rest) = s.strip_prefix('*') { - let pointee = Type::parse(rest.trim())?; + let pointee = Type::parse_at(rest.trim(), depth)?; return Some(Type::Ptr { pointee: Box::new(pointee), mutable: false, @@ -458,7 +640,7 @@ impl Type { if let Some(inner) = s_no_ws.strip_suffix('?') { let inner = inner.trim_end(); if !inner.is_empty() { - return Type::parse(inner).map(|t| Type::Optional(Box::new(t))); + return Type::parse_at(inner, depth).map(|t| Type::Optional(Box::new(t))); } } @@ -471,7 +653,7 @@ impl Type { } else { let mut types = Vec::new(); for part in parts { - if let Some(ty) = Type::parse(part) { + if let Some(ty) = Type::parse_at(part, depth) { types.push(ty); } } @@ -497,7 +679,7 @@ impl Type { } else { let mut params = Vec::new(); for param in split_top_level(params_str, ',') { - params.push(Type::parse(param)?); + params.push(Type::parse_at(param, depth)?); } params }; @@ -556,6 +738,23 @@ impl Type { "List" => Some(Type::List(Box::new(Type::Any))), "Map" => Some(Type::Map(Box::new(Type::Any), Box::new(Type::Any))), "Set" => Some(Type::Set(Box::new(Type::Any))), + // A window is `Slice`, and a bare `Slice` is the same + // "whatever it holds" the three above mean. Without it `impl Slice` + // typed `self` as a `Slice` with no element at all, which unified + // with no receiver — so the block's methods could not be called, + // and the diagnostic said the window had no such method. + "Slice" => Some(Type::Generic { + name: "Slice".to_string(), + params: vec![Type::Any], + }), + // The rest of the parameterized built-ins, for the same reason: + // `impl Task { … }` typed `self` as a `Task` of nothing. + "Task" => Some(Type::Task(Box::new(Type::Any))), + "Channel" => Some(Type::Channel(Box::new(Type::Any))), + "Stream" => Some(Type::Generic { + name: "Stream".to_string(), + params: vec![Type::Any], + }), _ => { // Assume it's a named custom type if is_type_name(s) { @@ -571,6 +770,7 @@ impl Type { pub fn display(&self) -> String { match self { Type::Int => "Int".to_string(), + Type::Unknown => "_".to_string(), Type::MachineInt(kind) => kind.name().to_string(), Type::Ptr { pointee, mutable } => { if *mutable { @@ -649,26 +849,86 @@ impl Type { } } + /// Whether a container whose element type is `source` may be used where + /// one whose element type is `target` is expected. + /// + /// Invariant, with two exceptions that are not variance: + /// + /// - `target` is `_` — the container is being read, never written, so any + /// element type is fine. This is the whole reason `_` exists. + /// - either side is still a free type variable — `let xs: List = [];` + /// gives the empty literal `List<'T>`, and binding `'T` to `Int` is + /// inference, not a widening of one container into another. + fn element_assignable_with(source: &Type, target: &Type, oracle: &dyn TraitOracle) -> bool { + match (source, target) { + (_, Type::Unknown) => true, + (Type::Variable(_), _) | (_, Type::Variable(_)) => source.is_assignable_to_with(target, oracle), + _ => source == target, + } + } + /// Check if this type can be assigned to another type (subtyping) pub fn is_assignable_to(&self, other: &Type) -> bool { + self.is_assignable_to_with(other, &NoTraits) + } + + /// [`Self::is_assignable_to`] with a [`TraitOracle`] for the one question + /// this crate cannot answer on its own: whether a type implements a named + /// trait. The rule belongs in this walk — a trait may be the target + /// anywhere a type may — and the tables that answer it live in the type + /// checker, so it arrives as a parameter rather than as a second, partial + /// copy of the walk over there. + pub fn is_assignable_to_with(&self, other: &Type, oracle: &dyn TraitOracle) -> bool { match (self, other) { // Any type is assignable to Any (_, Type::Any) => true, // Any can flow into any type (dynamic fallback) (Type::Any, _) => true, + // A value whose type is still a free variable can become what is + // expected of it. `let xs: List = [];` is the case that + // matters: an empty literal has no element to infer from, so its + // type is `List<'T>`, and recursing into the element compared `'T` + // against `Int` and fell through to "no rule" — an annotation being + // *rejected* by the very absence of information it was written to + // supply. + // + // Source side only. A variable here means the value has not been + // decided yet, which is a thing an annotation may decide; a + // variable on the *target* side would mean the annotation itself is + // undetermined, and accepting anything into it would make a generic + // parameter a hole rather than a constraint. This is not where + // unification happens either way — the constraint solver runs after + // and is what rejects a variable that two uses pull apart. + (Type::Variable(_), _) => true, // Same types are assignable (a, b) if a == b => true, // Boxed types act as transparent wrappers — must come before numeric hierarchy // so that Box unwraps to Any before numeric ordering is applied. - (Type::Boxed(inner), Type::Boxed(expected)) => inner.is_assignable_to(expected), - (Type::Boxed(inner), expected) => inner.is_assignable_to(expected), - (actual, Type::Boxed(expected)) => actual.is_assignable_to(expected), + (Type::Boxed(inner), Type::Boxed(expected)) => inner.is_assignable_to_with(expected, oracle), + (Type::Boxed(inner), expected) => inner.is_assignable_to_with(expected, oracle), + (actual, Type::Boxed(expected)) => actual.is_assignable_to_with(expected, oracle), // Machine integers convert only explicitly, in either direction and // even between two machine widths. Systems code is exactly where an // implicit narrowing or sign change is a bug rather than a // convenience, and `u8 -> Int` silently promoting would defeat the // point of asking for a fixed width. `as` is the way across. (Type::MachineInt(_), _) | (_, Type::MachineInt(_)) => false, + // Nullability is not a numeric property. The hierarchy rule below + // asks `numeric_class`, which looks *through* `Optional` (it must — + // it answers "what does arithmetic on this produce"), so `Int?` and + // `Int` both classified as Int and `let n: Int = xs.index_of(x);` + // was accepted. The nil then travelled to whatever used `n` and + // failed there instead, which is exactly what `?` exists to + // prevent — and `String?` was already rejected in the same + // position, so the rule only had a hole for numbers. + // + // A *trait* is not a slot in the sense this rule guards: `impl D + // for Nil` makes nil a `D`, so whether it fits is the oracle's + // answer and not this one's. Deciding it here refused `t(nil)` for + // a program that had written that impl, and refused it before the + // oracle was ever asked. + (lhs, Type::Named(trait_name)) if lhs.may_be_nil() => oracle.implements(lhs, trait_name), + (lhs, rhs) if lhs.may_be_nil() && !rhs.may_be_nil() => false, // Numeric hierarchy: allow Int -> Float, Float -> Boxed, etc. (lhs, rhs) if lhs.numeric_class().is_some() && rhs.numeric_class().is_some() => { let lhs_class = lhs.numeric_class().unwrap(); @@ -677,18 +937,83 @@ impl Type { } (Type::Nil, Type::Optional(_)) => true, // Optional types: T is assignable to ?T - (inner, Type::Optional(expected_inner)) => inner.is_assignable_to(expected_inner), + (inner, Type::Optional(expected_inner)) => inner.is_assignable_to_with(expected_inner, oracle), // Union types: T is assignable to Union if T is assignable to any member - (t, Type::Union(union_types)) => union_types.iter().any(|ut| t.is_assignable_to(ut)), + (t, Type::Union(union_types)) => union_types.iter().any(|ut| t.is_assignable_to_with(ut, oracle)), // Union member is assignable to union - (Type::Union(union_types), target) => union_types.iter().all(|ut| ut.is_assignable_to(target)), - // Generic containers with covariant element types - (Type::List(a), Type::List(b)) => a.is_assignable_to(b), - (Type::Map(ak, av), Type::Map(bk, bv)) => ak.is_assignable_to(bk) && av.is_assignable_to(bv), - (Type::Set(a), Type::Set(b)) => a.is_assignable_to(b), + (Type::Union(union_types), target) => union_types.iter().all(|ut| ut.is_assignable_to_with(target, oracle)), + // Containers are **invariant** in their element types, and the + // read-only view `List<_>` is how a signature says "a list of + // anything" without them. + // + // Covariance here was unsound, because these containers are mutable + // and a widening is an *alias*: `let b: List = a;` then + // `b.push("s")` put a String into an `List`, and + // `let c: Int = a[2]` type-checked and held it. Five widening + // positions did it — a `let`, a parameter, a struct field, a + // container element, and a return type — so restricting any one of + // them would not have been enough. + (Type::List(a), Type::List(b)) => Self::element_assignable_with(a, b, oracle), + (Type::Map(ak, av), Type::Map(bk, bv)) => { + Self::element_assignable_with(ak, bk, oracle) && Self::element_assignable_with(av, bv, oracle) + } + (Type::Set(a), Type::Set(b)) => Self::element_assignable_with(a, b, oracle), + // The same rule for a parameterised named type — `Slice` is the + // only one today. Without an element rule at all, `Slice` was + // assignable to nothing but itself, so a declaration could not + // accept "a window over anything". + ( + Type::Generic { + name: a_name, + params: a_params, + }, + Type::Generic { + name: b_name, + params: b_params, + }, + ) => { + a_name == b_name + && a_params.len() == b_params.len() + && a_params + .iter() + .zip(b_params) + .all(|(a, b)| Self::element_assignable_with(a, b, oracle)) + } (Type::Tuple(as_), Type::Tuple(bs)) => { - as_.len() == bs.len() && as_.iter().zip(bs.iter()).all(|(a, b)| a.is_assignable_to(b)) + as_.len() == bs.len() + && as_ + .iter() + .zip(bs.iter()) + .all(|(a, b)| a.is_assignable_to_with(b, oracle)) } + // A tuple *is* a list. `Tuple` is not a runtime thing — `HeapValue` + // has `List` and no tuple at all; the variant exists so a + // heterogeneous literal can keep each element's type instead of + // collapsing to `List`. Without this rule that extra precision + // reads as a different type, and `let xs: List = [1, "a"];` — an + // ordinary list in a language whose lists are heterogeneous — was + // rejected by the annotation written to describe it. + (Type::Tuple(elems), Type::List(target)) => elems + .iter() + .all(|elem| Self::element_assignable_with(elem, target, oracle)), + // And the way back, which was missing — so `Tuple` was a + // type nothing could satisfy: `[1, 2]` is `List` (its elements + // do not differ, so no tuple is inferred), and without this rule it + // was not assignable to the annotation written to describe it. + // `Tuple` looked fine only because a *heterogeneous* + // literal infers `Tuple` directly and never needed the conversion. + // + // The unifier has had both directions all along, in one arm with + // both orders — so this was also the two of them disagreeing, which + // is the thing the note over there says must not happen. + // + // Length is deliberately not part of it: a `List` type carries no + // length, so there is nothing to compare against the tuple's arity. + // The precision a tuple adds is *per-position element types*, and + // that is what this checks. + (Type::List(source), Type::Tuple(elems)) => elems + .iter() + .all(|elem| Self::element_assignable_with(source, elem, oracle)), // Function types (contravariant parameters, covariant return) ( Type::Function { @@ -709,7 +1034,7 @@ impl Type { let params_compatible = b_params .iter() .zip(a_params.iter()) - .all(|(b_param, a_param)| b_param.is_assignable_to(a_param)); + .all(|(b_param, a_param)| b_param.is_assignable_to_with(a_param, oracle)); if !params_compatible { return false; } @@ -723,7 +1048,7 @@ impl Type { } let named_compatible = b_named.iter().all(|b_np| { if let Some(a_np) = a_map.get(b_np.name.as_str()) { - b_np.has_default == a_np.has_default && b_np.ty.is_assignable_to(&a_np.ty) + b_np.has_default == a_np.has_default && b_np.ty.is_assignable_to_with(&a_np.ty, oracle) } else { false } @@ -732,13 +1057,18 @@ impl Type { return false; } // Return type is covariant - let return_compatible = a_ret.is_assignable_to(b_ret); + let return_compatible = a_ret.is_assignable_to_with(b_ret, oracle); params_compatible && named_compatible && return_compatible } } // Concurrency types - (Type::Task(a), Type::Task(b)) => a.is_assignable_to(b), - (Type::Channel(a), Type::Channel(b)) => a.is_assignable_to(b), + (Type::Task(a), Type::Task(b)) => a.is_assignable_to_with(b, oracle), + (Type::Channel(a), Type::Channel(b)) => a.is_assignable_to_with(b, oracle), + // A trait names a type, and whatever implements it may stand where + // it is expected. Last, so it costs nothing until every structural + // rule has already declined — and only the *oracle* decides, so a + // build with no trait tables behaves exactly as before. + (from, Type::Named(trait_name)) => oracle.implements(from, trait_name), // No other assignability rules _ => false, } @@ -749,6 +1079,19 @@ impl Type { NumericHierarchy::classify(self) } + /// Whether a value of this type can be `nil`. + /// + /// `Any` says no: it is *unknown*, not nullable, and assignability already + /// lets it flow both ways before this is consulted. + pub fn may_be_nil(&self) -> bool { + match self { + Type::Nil | Type::Optional(_) => true, + Type::Union(items) => items.iter().any(Type::may_be_nil), + Type::Boxed(inner) => inner.may_be_nil(), + _ => false, + } + } + /// Check if this type contains any type variables pub fn contains_variables(&self) -> bool { match self { @@ -774,6 +1117,61 @@ impl Type { } } + /// Every type variable name occurring in this type, in order, without + /// duplicates. + /// + /// [`contains_variables`] answers whether there are any; this answers + /// *which*, which is what instantiating a generic signature needs — each + /// one gets a fresh copy, consistently across the whole signature so that + /// `fn first(xs) { return xs[0]; }`'s `List<'a> -> 'a` stays one relation + /// rather than two unrelated holes. + /// + /// [`contains_variables`]: Type::contains_variables + pub fn collect_variables(&self, out: &mut Vec) { + match self { + Type::Variable(name) => { + if !out.iter().any(|seen| seen == name) { + out.push(name.clone()); + } + } + Type::List(inner) + | Type::Set(inner) + | Type::Optional(inner) + | Type::Task(inner) + | Type::Channel(inner) + | Type::Boxed(inner) => inner.collect_variables(out), + Type::Ptr { pointee, .. } => pointee.collect_variables(out), + Type::Map(k, v) => { + k.collect_variables(out); + v.collect_variables(out); + } + Type::Function { + params, + named_params, + return_type, + } => { + for param in params { + param.collect_variables(out); + } + for named in named_params { + named.ty.collect_variables(out); + } + return_type.collect_variables(out); + } + Type::Union(types) | Type::Tuple(types) => { + for ty in types { + ty.collect_variables(out); + } + } + Type::Generic { params, .. } => { + for param in params { + param.collect_variables(out); + } + } + _ => {} + } + } + /// Substitute type variables with concrete types pub fn substitute(&self, substitutions: &HashMap) -> Type { match self { @@ -1001,8 +1399,37 @@ fn split_top_level(s: &str, delimiter: char) -> Vec<&str> { #[cfg(test)] mod tests { + /// A type spelling too deep to walk is refused, not a core dump. + /// + /// `Type::parse` is a recursive descent over the spelling, so + /// `List…>>` overflowed the stack past about 1700 levels — + /// `SIGABRT`, on a program the tokenizer had accepted. The expression + /// parser has had a bound for this reason and this is the other half of the + /// same surface: the LSP and the browser playground read both from text + /// they did not write. + #[test] + fn a_type_too_deep_is_refused_not_aborted() { + // Four is the deepest annotation this repository writes; a hundred is + // past anything and still parses. + let ok = format!("{}Int{}", "List<".repeat(100), ">".repeat(100)); + assert!(Type::parse(&ok).is_some(), "a hundred levels still parses"); + + // Past the bound is `None` — "not a type", the answer a misspelling + // gets — at any size. + for depth in [200, 3000, 20_000] { + let deep = format!("{}Int{}", "List<".repeat(depth), ">".repeat(depth)); + assert!( + Type::parse(&deep).is_none(), + "{depth} levels must be refused, not walked" + ); + } + } + use super::{IntKind, ShortStr, ShortStrOrStr, Type}; use alloc::boxed::Box; + use alloc::format; + use alloc::string::ToString; + use alloc::vec; #[test] fn short_str_concat_int_falls_back_when_prefix_fills_inline_buffer() { @@ -1031,12 +1458,16 @@ mod tests { IntKind::Usize, ] { assert_eq!(IntKind::parse(kind.name()), Some(kind), "{}", kind.name()); - assert_eq!( - Type::parse(kind.name()), - Some(Type::MachineInt(kind)), - "{}", - kind.name() - ); + // `i64` is the exception, and deliberately: it is a second spelling + // of `Int` rather than a machine int of its own, so that a driver's + // `i64` and a front end's `Int` are one type instead of two that + // need a cast between them (see `TYPE_SPELLINGS`). + let expected = if kind == IntKind::I64 { + Type::Int + } else { + Type::MachineInt(kind) + }; + assert_eq!(Type::parse(kind.name()), Some(expected), "{}", kind.name()); assert_eq!(Type::MachineInt(kind).display(), kind.name()); } } @@ -1123,4 +1554,83 @@ mod tests { assert!(u8_.is_assignable_to(&Type::Any)); assert!(Type::Any.is_assignable_to(&u8_)); } + + /// One type, two spellings — so a driver's `i64` and a front end's `Int` + /// are the same value and pass through each other's functions. + /// + /// Two *convertible* types would be more ambiguity, not less: a reader + /// would have to know which one a value is to know what it does. + #[test] + fn a_widthed_spelling_and_a_plain_one_name_the_same_type() { + assert_eq!(Type::parse("i64"), Some(Type::Int)); + assert_eq!(Type::parse("f64"), Some(Type::Float)); + assert!(Type::Int.is_assignable_to(&Type::parse("i64").unwrap())); + assert!(Type::parse("i64").unwrap().is_assignable_to(&Type::Int)); + } + + /// `isize` is deliberately *not* one of them. + /// + /// Pointer width is the entire reason that name exists, and equating it + /// with a fixed width would put the language's plain integer at the mercy + /// of the target: on `thumbv7em-none-eabi` it is 32 bits while the VM's + /// `RuntimeVal::Int` is still an `i64`. One name, two widths. + #[test] + fn pointer_width_is_its_own_type() { + assert_eq!(Type::parse("isize"), Some(Type::MachineInt(IntKind::Isize))); + assert_eq!(Type::parse("usize"), Some(Type::MachineInt(IntKind::Usize))); + assert_ne!(Type::parse("isize"), Some(Type::Int)); + } + + #[test] + fn number_is_int_or_float() { + assert_eq!(Type::parse("Number"), Some(Type::Union(vec![Type::Int, Type::Float]))); + assert!(Type::Int.is_assignable_to(&Type::parse("Number").unwrap())); + assert!(Type::Float.is_assignable_to(&Type::parse("Number").unwrap())); + assert!(!Type::String.is_assignable_to(&Type::parse("Number").unwrap())); + } + + /// Every way a `ShortStr` can come into existence produces valid UTF-8. + /// + /// `as_str` skips the check and reads the bytes directly, so this is the + /// thing that has to stay true. The `debug_assert!` inside `as_str` does + /// the actual verifying — this test's job is to *reach* it from each + /// constructor, including the multi-byte cases a byte-length limit is most + /// likely to cut in half. + #[test] + fn every_short_str_constructor_keeps_the_utf8_invariant() { + for text in ["", "a", "abc", "1234567", "中", "中中", "é", "aé", "\u{7f}", "\u{80}"] { + match ShortStr::new(text) { + Some(short) => assert_eq!(short.as_str(), text), + // Over seven bytes: refused, which is the other half of the + // invariant (a truncating constructor could split a character). + None => assert!(text.len() > 7, "{text:?} fits but was refused"), + } + } + for ch in ['a', '中', 'é', '\u{10FFFF}', '\u{0}'] { + assert_eq!(ShortStr::from_char(ch).as_str().chars().next(), Some(ch)); + } + let base = ShortStr::new("ab").expect("fits"); + for n in [0i64, 7, 9999, 10_000, -1, i64::MIN] { + let joined = match base.concat_int(n) { + ShortStrOrStr::Short(short) => short.as_str().to_string(), + ShortStrOrStr::Str(text) => text, + }; + assert_eq!(joined, format!("ab{n}")); + let prefixed = match ShortStr::concat_int_prefix(n, base) { + ShortStrOrStr::Short(short) => short.as_str().to_string(), + ShortStrOrStr::Str(text) => text, + }; + assert_eq!(prefixed, format!("{n}ab")); + } + // Concatenation across the seven-byte edge, with a multi-byte operand + // on each side. + let multi = ShortStr::new("中").expect("three bytes fit"); + for (left, right) in [(base, multi), (multi, base), (multi, multi)] { + let joined = match left.concat(right) { + ShortStrOrStr::Short(short) => short.as_str().to_string(), + ShortStrOrStr::Str(text) => text, + }; + assert_eq!(joined, format!("{}{}", left.as_str(), right.as_str())); + } + } } diff --git a/website/src/learn/LEARN.md b/website/src/learn/LEARN.md index 0193ca3a..c5c54fa4 100644 --- a/website/src/learn/LEARN.md +++ b/website/src/learn/LEARN.md @@ -24,7 +24,7 @@ The REPL and CLI only print a result when it is not `nil`. Functions return `nil LK has six primitive types and several collection types. Use `typeof(value)` to check the runtime type name. -```lk +```lk,fragment typeof(42) // "Int" typeof(3.14) // "Float" typeof("hello") // "String" @@ -60,7 +60,7 @@ count := 0; // equivalent to let count = 0; `const` cannot be reassigned; `let` can: -```lk +```lk,fragment let x = 1; x = 2; // OK @@ -90,7 +90,7 @@ let { "name": n, "age": age } = { "name": "LK", "age": 1 }; ### Arithmetic & Comparison -```lk +```lk,fragment 1 + 2 // 3 10 % 3 // 1 3 == 3 // true @@ -100,7 +100,7 @@ let { "name": n, "age": age } = { "name": "LK", "age": 1 }; ### Logic & Bitwise -```lk +```lk,fragment true && false // false !true // false 0xA & 0xF // bitwise AND @@ -129,7 +129,7 @@ let label = status ? "active" : "inactive"; // "active" ### Optional Chaining -```lk +```lk,fragment let user = { "name": "LK" }; user?.name // "LK" nil?.name // nil @@ -147,9 +147,8 @@ let even = 0..10..2; // [0, 2, 4, 6, 8] ### String & Collection Operators -```lk -"ha" * 3 // "hahaha" -3 * "ab" // "ababab" +```lk,fragment +"ha".repeat(3) // "hahaha" (`*` does not repeat a string) [1, 2] + [3, 4] // [1, 2, 3, 4] [1, 2, 3] - [2] // [1, 3] { "a": 1 } + { "b": 2 } // { "a": 1, "b": 2 } @@ -161,7 +160,7 @@ let even = 0..10..2; // [0, 2, 4, 6, 8] ### Lists -```lk +```lk,fragment let fruits = ["apple", "banana", "cherry"]; fruits[0] // "apple" fruits[-1] // "cherry" @@ -170,7 +169,7 @@ fruits[1..3] // ["banana", "cherry"] List meta-methods (no import needed): -```lk +```lk,fragment fruits.len() // 3 fruits.push("date"); fruits.contains("apple") // true @@ -182,7 +181,7 @@ fruits.filter(|f| f.starts_with("a")) Spread syntax: -```lk +```lk,fragment let more = ["date", "elderberry"]; let all = [..fruits, ..more, "fig"]; ``` @@ -191,7 +190,7 @@ let all = [..fruits, ..more, "fig"]; Bare keys are string keys: -```lk +```lk,fragment let profile = { name: "LK", version: 1 }; // equivalent to { "name": "LK", "version": 1 } profile.name // "LK" @@ -204,7 +203,7 @@ Map methods: `len`, `is_empty`, `keys`, `values`, `has`, `get`, `set`, `delete`, ### Sets -```lk +```lk,fragment let s = Set([1, 2, 3, 2]); // {1, 2, 3} s.has(2) // true s.add(4) @@ -218,7 +217,7 @@ s.values() // [2, 3, 4] (order not guaranteed) Parentheses are optional. `false` and `nil` are falsy; everything else (including `0`, `""`) is truthy: -```lk +```lk,fragment if score > 90 { println("A"); } else if score > 80 { @@ -228,6 +227,19 @@ if score > 90 { } ``` +`if` is an expression, and so is a block — `{ … }` in value position evaluates +to its last expression: + +```lk,fragment +let grade = if score > 90 { "A" } else { "B" }; +let area = { let w = 3; let h = 4; w * h }; // 12 +``` + +`{` still opens a **map** wherever a map is possible: `{}` is the empty map, and +`{"a": 1}` is a map. The brace is a block when what follows it cannot be a map — +a statement keyword (`let`, `return`, `for`, …), or no `:` before the first `;` +or `}`. + ### Loops ```lk @@ -244,7 +256,8 @@ for ch in "hello" { println(ch); } -for entry in { "a": 1, "b": 2 } { +let pairs = { "a": 1, "b": 2 }; +for entry in pairs { println(entry); // ["a", 1] } ``` @@ -291,7 +304,7 @@ let { "name": n, "age": a, ..other } = { "name": "LK", "age": 1, "lang": "script ### if let / while let -```lk +```lk,fragment if let { "user": { "id": uid } } = payload { println("User ID: {}", uid); } @@ -304,7 +317,7 @@ while let [item, ..tail] = remaining { ### Guards & Ranges -```lk +```lk,fragment match score { n if n >= 90 => "A", n if n >= 80 => "B", @@ -317,7 +330,7 @@ match score { ### Definition -```lk +```lk,fragment fn add(a, b) { return a + b; } @@ -345,7 +358,7 @@ draw_rect(0, 0, width: 50, height: 200); ### Closures -```lk +```lk,fragment let double = |x| x * 2; let add = |a, b| { let sum = a + b; sum }; @@ -356,7 +369,7 @@ add(3, 4) // 7 Closures capture and mutate enclosing variables: ```lk -let count := 0; +let count = 0; let inc = || { count += 1; }; inc(); inc(); @@ -367,7 +380,7 @@ Function-literal form: `fn(a, b) => a + b` ### First-class Functions -```lk +```lk,fragment fn apply(f, x) { return f(x); } @@ -379,7 +392,7 @@ apply(|n| n * 3, 7) // 21 ### Definition & Instantiation -```lk +```lk,fragment struct Rect { w: Int, h: Int } let shape = Rect { w: 8, h: 5 }; @@ -388,13 +401,13 @@ shape.w // 8 Call sugar (equivalent to `Rect(w: 8, h: 5)`) and update syntax: -```lk +```lk,fragment let bigger = Rect { ..shape, h: 10 }; ``` ### Traits & Impl -```lk +```lk,fragment trait Area { fn area(self) -> Int; } @@ -408,9 +421,11 @@ impl Area for Rect { shape.area() // 40 ``` -Auto-display: implement `show`, `display`, or `to_string` and `println("{}")` and `${value}` will use it: +Auto-display: implement a method named `show` and `println("{}")` and +`${value}` will use it. One name, not three — `display` and `to_string` are not +looked up, and a method by either of those names changes nothing. -```lk +```lk,fragment impl Area for Rect { fn area(self) -> Int { return self.w * self.h; } fn show(self) -> String { return "Rect(${self.w}x${self.h})"; } @@ -433,9 +448,9 @@ println("{}", p); // Point { x: 1, y: 2 } ## Strings & Bytes -String meta-methods (no import needed): `len`, `lower`, `upper`, `trim`, `starts_with`, `ends_with`, `contains`, `replace`, `substring`, `split`, `join`, `reverse`, `repeat`, `chars`, `char_at`, `byte_at`, `find`, `is_empty`, `format` +String meta-methods (no import needed): `len`, `is_empty`, `lower`, `upper`, `trim`, `reverse`, `repeat`, `starts_with`, `ends_with`, `contains`, `count`, `index_of`, `slice`, `get`, `first`, `last`, `take`, `skip`, `replace`, `split`, `chars`, `bytes`, `byte_at`, `capitalize`, `title`, `strip`, `strip_prefix`, `strip_suffix`, `pad_left`, `pad_right`, `format` -```lk +```lk,fragment "Hello".len() // 5 "hello".upper() // "HELLO" " hi ".trim() // "hi" @@ -448,7 +463,7 @@ String meta-methods (no import needed): `len`, `lower`, `upper`, `trim`, `starts The `bytes` module handles binary data (requires `use bytes`): -```lk +```lk,fragment use bytes; let raw = bytes.from_string("hello"); @@ -475,7 +490,7 @@ Also: `enumerate`, `zip`, `take`, `skip`, `chain`, `flatten`, `unique`, `chunk` The `stream` module provides lazy evaluation pipelines (requires `use stream`): -```lk +```lk,fragment use stream; let s = stream.from_list([1, 2, 3, 4, 5]); @@ -492,7 +507,7 @@ stream.collect(cursor) // [30, 40, 50] ### use Imports -```lk +```lk,fragment use math; // entire module as namespace use { abs, sqrt } from math; // selective import use math as m; // alias @@ -541,7 +556,7 @@ Built-in macros: `vec!`, `assert!`, `assert_eq!`, `assert_ne!`, `matches!`, `pan ```lk #[derive(Show)] -struct Point { x: Int, y: y: Int } +struct Point { x: Int, y: Int } #[cfg(feature = "debug")] fn debug_log(msg) { println(msg); } @@ -576,9 +591,9 @@ let [ok, val] = recv(ch); // select chooses select { - case value <- recv(ch) => println("got {}", value), - case send(ch, 42) => println("sent"), - default => println("none ready"), + case value <- recv(ch) => println("got {}", value); + case send(ch, 42) => println("sent"); + default => println("none ready"); } ``` diff --git a/website/src/learn/LEARN_zh.md b/website/src/learn/LEARN_zh.md index 26e07b0a..a84c6f5d 100644 --- a/website/src/learn/LEARN_zh.md +++ b/website/src/learn/LEARN_zh.md @@ -24,7 +24,7 @@ REPL 和 CLI 只在结果不为 `nil` 时打印返回值。如果函数没有 `r LK 有六种原始类型和几种复合类型。用 `typeof(value)` 查看运行时类型名。 -```lk +```lk,fragment typeof(42) // "Int" typeof(3.14) // "Float" typeof("hello") // "String" @@ -60,7 +60,7 @@ count := 0; // 等价于 let count = 0; `const` 不可重新赋值,`let` 可以: -```lk +```lk,fragment let x = 1; x = 2; // OK @@ -90,7 +90,7 @@ let { "name": n, "age": age } = { "name": "LK", "age": 1 }; ### 算术与比较 -```lk +```lk,fragment 1 + 2 // 3 10 % 3 // 1 3 == 3 // true @@ -100,7 +100,7 @@ let { "name": n, "age": age } = { "name": "LK", "age": 1 }; ### 逻辑与位运算 -```lk +```lk,fragment true && false // false !true // false 0xA & 0xF // 按位与 @@ -129,7 +129,7 @@ let label = status ? "active" : "inactive"; // "active" ### 可选链 -```lk +```lk,fragment let user = { "name": "LK" }; user?.name // "LK" nil?.name // nil @@ -147,9 +147,8 @@ let even = 0..10..2; // [0, 2, 4, 6, 8] ### 字符串与集合运算 -```lk -"ha" * 3 // "hahaha" -3 * "ab" // "ababab" +```lk,fragment +"ha".repeat(3) // "hahaha" (`*` does not repeat a string) [1, 2] + [3, 4] // [1, 2, 3, 4] [1, 2, 3] - [2] // [1, 3] { "a": 1 } + { "b": 2 } // { "a": 1, "b": 2 } @@ -161,7 +160,7 @@ let even = 0..10..2; // [0, 2, 4, 6, 8] ### 列表 -```lk +```lk,fragment let fruits = ["apple", "banana", "cherry"]; fruits[0] // "apple" fruits[-1] // "cherry" @@ -170,7 +169,7 @@ fruits[1..3] // ["banana", "cherry"] 列表方法(无需导入): -```lk +```lk,fragment fruits.len() // 3 fruits.push("date"); fruits.contains("apple") // true @@ -182,7 +181,7 @@ fruits.filter(|f| f.starts_with("a")) 展开语法: -```lk +```lk,fragment let more = ["date", "elderberry"]; let all = [..fruits, ..more, "fig"]; ``` @@ -191,7 +190,7 @@ let all = [..fruits, ..more, "fig"]; 裸键为字符串键: -```lk +```lk,fragment let profile = { name: "LK", version: 1 }; // 等价于 { "name": "LK", "version": 1 } profile.name // "LK" @@ -204,7 +203,7 @@ Map 方法:`len`、`is_empty`、`keys`、`values`、`has`、`get`、`set`、`d ### 集合 -```lk +```lk,fragment let s = Set([1, 2, 3, 2]); // {1, 2, 3} s.has(2) // true s.add(4) @@ -218,7 +217,7 @@ s.values() // [2, 3, 4](顺序不保证) 括号可选。`false` 和 `nil` 为假,其余(包括 `0`、`""`)为真: -```lk +```lk,fragment if score > 90 { println("A"); } else if score > 80 { @@ -228,6 +227,17 @@ if score > 90 { } ``` +`if` 是表达式,块也是 —— `{ … }` 出现在取值位置时,求值为它最后一条表达式: + +```lk,fragment +let grade = if score > 90 { "A" } else { "B" }; +let area = { let w = 3; let h = 4; w * h }; // 12 +``` + +能是 map 的地方 `{` 仍然是 **map**:`{}` 是空 map,`{"a": 1}` 是 map。只有在 +后面不可能是 map 时才当块 —— 开头是语句关键字(`let`、`return`、`for`……), +或者在第一个 `;` / `}` 之前没有 `:`。 + ### 循环 ```lk @@ -244,7 +254,8 @@ for ch in "hello" { println(ch); } -for entry in { "a": 1, "b": 2 } { +let pairs = { "a": 1, "b": 2 }; +for entry in pairs { println(entry); // ["a", 1] } ``` @@ -291,7 +302,7 @@ let { "name": n, "age": a, ..other } = { "name": "LK", "age": 1, "lang": "script ### if let / while let -```lk +```lk,fragment if let { "user": { "id": uid } } = payload { println("User ID: {}", uid); } @@ -304,7 +315,7 @@ while let [item, ..tail] = remaining { ### 守卫与范围 -```lk +```lk,fragment match score { n if n >= 90 => "A", n if n >= 80 => "B", @@ -317,7 +328,7 @@ match score { ### 定义 -```lk +```lk,fragment fn add(a, b) { return a + b; } @@ -345,7 +356,7 @@ draw_rect(0, 0, width: 50, height: 200); ### 闭包 -```lk +```lk,fragment let double = |x| x * 2; let add = |a, b| { let sum = a + b; sum }; @@ -356,7 +367,7 @@ add(3, 4) // 7 闭包捕获并修改外层变量: ```lk -let count := 0; +let count = 0; let inc = || { count += 1; }; inc(); inc(); @@ -367,7 +378,7 @@ println(count); // 2 ### 一等函数 -```lk +```lk,fragment fn apply(f, x) { return f(x); } @@ -379,7 +390,7 @@ apply(|n| n * 3, 7) // 21 ### 定义与实例化 -```lk +```lk,fragment struct Rect { w: Int, h: Int } let shape = Rect { w: 8, h: 5 }; @@ -388,13 +399,13 @@ shape.w // 8 调用糖(等价于 `Rect(w: 8, h: 5)`)和更新语法: -```lk +```lk,fragment let bigger = Rect { ..shape, h: 10 }; ``` ### Trait 与 Impl -```lk +```lk,fragment trait Area { fn area(self) -> Int; } @@ -408,9 +419,10 @@ impl Area for Rect { shape.area() // 40 ``` -自动展示:实现 `show`、`display` 或 `to_string` 方法后,`println("{}")` 和 `${value}` 自动使用它: +自动展示:实现名为 `show` 的方法后,`println("{}")` 和 `${value}` 自动使用它。 +只有这一个名字 —— `display` 和 `to_string` 不会被查找,用这两个名字写的方法不起作用。 -```lk +```lk,fragment impl Area for Rect { fn area(self) -> Int { return self.w * self.h; } fn show(self) -> String { return "Rect(${self.w}x${self.h})"; } @@ -433,9 +445,9 @@ println("{}", p); // Point { x: 1, y: 2 } ## 字符串与字节 -String 元方法(无需导入):`len`、`lower`、`upper`、`trim`、`starts_with`、`ends_with`、`contains`、`replace`、`substring`、`split`、`join`、`reverse`、`repeat`、`chars`、`char_at`、`byte_at`、`find`、`is_empty`、`format` +String 元方法(无需导入):`len`、`is_empty`、`lower`、`upper`、`trim`、`reverse`、`repeat`、`starts_with`、`ends_with`、`contains`、`count`、`index_of`、`slice`、`get`、`first`、`last`、`take`、`skip`、`replace`、`split`、`chars`、`bytes`、`byte_at`、`capitalize`、`title`、`strip`、`strip_prefix`、`strip_suffix`、`pad_left`、`pad_right`、`format` -```lk +```lk,fragment "Hello".len() // 5 "hello".upper() // "HELLO" " hi ".trim() // "hi" @@ -448,7 +460,7 @@ String 元方法(无需导入):`len`、`lower`、`upper`、`trim`、`start `bytes` 模块处理二进制数据(需要 `use bytes`): -```lk +```lk,fragment use bytes; let raw = bytes.from_string("hello"); @@ -475,7 +487,7 @@ let total = iter.reduce(evens, 0, |acc, n| acc + n); `stream` 模块提供懒执行管道(需要 `use stream`): -```lk +```lk,fragment use stream; let s = stream.from_list([1, 2, 3, 4, 5]); @@ -492,7 +504,7 @@ stream.collect(cursor) // [30, 40, 50] ### use 导入 -```lk +```lk,fragment use math; // 整个模块作为命名空间 use { abs, sqrt } from math; // 选择性导入 use math as m; // 别名 @@ -576,9 +588,9 @@ let [ok, val] = recv(ch); // select 选择 select { - case value <- recv(ch) => println("got {}", value), - case send(ch, 42) => println("sent"), - default => println("none ready"), + case value <- recv(ch) => println("got {}", value); + case send(ch, 42) => println("sent"); + default => println("none ready"); } ``` diff --git a/website/src/stdlib/STDLIB.md b/website/src/stdlib/STDLIB.md index c737b749..7e74b560 100644 --- a/website/src/stdlib/STDLIB.md +++ b/website/src/stdlib/STDLIB.md @@ -62,24 +62,36 @@ String meta-methods — no import needed, call via `value.method()`. | Method | Description | |--------|-------------| | `len()` | Character count | -| `lower()` | Lowercase | -| `upper()` | Uppercase | -| `trim()` | Trim whitespace | +| `is_empty()` | Whether it has no characters | +| `lower()` | Lowercased | +| `upper()` | Uppercased | +| `trim()` | Without leading or trailing whitespace | +| `reverse()` | Characters in reverse order | +| `repeat(count)` | Repeated `count` times | | `starts_with(prefix)` | Prefix check | | `ends_with(suffix)` | Suffix check | -| `contains(sub)` | Contains substring | -| `replace(old, new)` | Replace substring | -| `substring(start[, end])` | Extract substring | -| `split(sep)` | Split to list | -| `join(list)` | Join list with this string | -| `reverse()` | Reverse string | -| `repeat(n)` | Repeat n times | -| `chars()` | Split to character list | -| `char_at(index)` | Character at index | -| `byte_at(index)` | Byte at index | -| `find(sub)` | Find substring position, nil if not found | -| `is_empty()` | Is empty | -| `format(args...)` | Format string | +| `contains(needle)` | Substring check | +| `count(needle)` | How many non-overlapping occurrences | +| `index_of(needle)` | Character position of the first occurrence, or nil | +| `slice(start, end)` | Characters in `[start, end)`, clamped; `end` optional | +| `get(index)` | Character at `index`, or nil; negative counts from the end | +| `first()` | First character, or nil | +| `last()` | Last character, or nil | +| `take(count)` | The first `count` characters | +| `skip(count)` | Everything after the first `count` characters | +| `replace(from, to, all)` | Occurrences replaced; `all: false` replaces only the first | +| `split(delimiter)` | Split to a list | +| `chars()` | One string per character | +| `bytes()` | The UTF-8 bytes | +| `byte_at(index)` | Byte at a *byte* offset, or nil | +| `capitalize()` | First character upper, the rest lower | +| `title()` | First character of each word upper, the rest lower | +| `strip(chars)` | Without leading/trailing characters that are in `chars` | +| `strip_prefix(prefix)` | Without `prefix`, or nil | +| `strip_suffix(suffix)` | Without `suffix`, or nil | +| `pad_left(width, fill)` | Widened to `width` characters on the left; `fill` optional | +| `pad_right(width, fill)` | Widened to `width` characters on the right; `fill` optional | +| `format(values...)` | The receiver as a template: each `{}` takes the next value | ```lk "Hello, {}!".format("LK") // "Hello, LK!" @@ -93,17 +105,25 @@ Binary data operations. | Function | Description | |----------|-------------| -| `from_list(list)` | Create from integer list | -| `from_string(str)` | Create from UTF-8 string | +| `from_list(list)` | Create from an integer list — the method spelling is `list.to_bytes()` | +| `from_string(str)` | Create from a UTF-8 string — the method spelling is `str.bytes()` | | `len(bytes)` | Byte length | | `is_empty(bytes)` | Is empty | -| `get(bytes, index)` | Byte at index | -| `slice(bytes, start[, end])` | Slice | -| `to_list(bytes)` | Convert to integer list | -| `to_string_utf8(bytes)` | Convert to UTF-8 string | -| `to_string_lossy(bytes)` | Convert to UTF-8 (replace invalid bytes) | +| `get(bytes, index)` | Byte at index, or nil; negative counts from the end | +| `first(bytes)` | First byte, or nil | +| `last(bytes)` | Last byte, or nil | +| `contains(bytes, byte)` | Whether the byte occurs | +| `index_of(bytes, byte)` | Position of the first occurrence, or nil | +| `sum(bytes)` | Sum of the bytes | +| `min(bytes)` | Smallest byte, or nil | +| `max(bytes)` | Largest byte, or nil | +| `take(bytes, count)` | The first `count` bytes | +| `skip(bytes, count)` | Everything after the first `count` bytes | +| `slice(bytes, start, end)` | Bytes in `[start, end)`, clamped; `end` optional | +| `to_list(bytes)` | Convert to an integer list | +| `to_string_utf8(bytes)` | Decode as UTF-8; raises when invalid | +| `to_string_lossy(bytes)` | Decode as UTF-8, replacing invalid sequences | | `concat(a, b)` | Concatenate | -| `eq(a, b)` | Equality check | ```lk use bytes; @@ -477,18 +497,18 @@ Regular expressions. | Function | Description | |----------|-------------| -| `is_match(pattern, text)` | Match check | -| `find(pattern, text)` | Find first match | -| `find_all(pattern, text)` | Find all matches | -| `captures(pattern, text)` | Capture groups | -| `replace(pattern, text, replacement)` | Replace | -| `split(pattern, text)` | Split by regex | +| `is_match(text, pattern)` | Match check | +| `find(text, pattern)` | Find first match | +| `find_all(text, pattern)` | Find all matches | +| `captures(text, pattern)` | Capture groups | +| `replace(text, pattern, replacement)` | Replace | +| `split(text, pattern)` | Split by regex | ```lk use regex; -regex.is_match(r"\d+", "abc123") // true -regex.find(r"\d+", "abc123") // "123" -regex.split(r"[,;]", "a,b;c") // ["a", "b", "c"] +regex.is_match("abc123", r"\d+") // true +regex.find("abc123", r"\d+") // "123" +regex.split("a,b;c", r"[,;]") // ["a", "b", "c"] ``` ## random diff --git a/website/src/stdlib/STDLIB_zh.md b/website/src/stdlib/STDLIB_zh.md index 26953540..c2f9bdb3 100644 --- a/website/src/stdlib/STDLIB_zh.md +++ b/website/src/stdlib/STDLIB_zh.md @@ -62,24 +62,36 @@ String 元方法,无需导入,直接通过 `value.method()` 调用。 | 方法 | 说明 | |------|------| | `len()` | 字符数 | +| `is_empty()` | 是否为空 | | `lower()` | 转小写 | | `upper()` | 转大写 | | `trim()` | 去除首尾空白 | +| `reverse()` | 反转字符串 | +| `repeat(count)` | 重复 `count` 次 | | `starts_with(prefix)` | 前缀匹配 | | `ends_with(suffix)` | 后缀匹配 | -| `contains(sub)` | 包含子串 | -| `replace(old, new)` | 替换子串 | -| `substring(start[, end])` | 截取子串 | -| `split(sep)` | 按分隔符拆分为列表 | -| `join(list)` | 用此字符串连接列表元素 | -| `reverse()` | 反转字符串 | -| `repeat(n)` | 重复 n 次 | +| `contains(needle)` | 包含子串 | +| `count(needle)` | 不重叠出现次数 | +| `index_of(needle)` | 首次出现的字符位置,未找到返回 nil | +| `slice(start, end)` | `[start, end)` 区间的字符,越界截断;`end` 可省略 | +| `get(index)` | `index` 处的字符,越界 nil;负数从末尾数 | +| `first()` | 首字符,空串为 nil | +| `last()` | 末字符,空串为 nil | +| `take(count)` | 前 `count` 个字符 | +| `skip(count)` | 跳过前 `count` 个字符 | +| `replace(from, to, all)` | 替换出现处;`all: false` 只替换第一处 | +| `split(delimiter)` | 按分隔符拆分为列表 | | `chars()` | 拆分为字符列表 | -| `char_at(index)` | 指定位置字符 | -| `byte_at(index)` | 指定位置字节 | -| `find(sub)` | 查找子串位置,未找到返回 nil | -| `is_empty()` | 是否为空 | -| `format(args...)` | 格式化 | +| `bytes()` | UTF-8 字节 | +| `byte_at(index)` | 按*字节*偏移取字节,越界 nil | +| `capitalize()` | 首字母大写,其余小写 | +| `title()` | 每个单词首字母大写,其余小写 | +| `strip(chars)` | 去掉首尾在 `chars` 里的字符 | +| `strip_prefix(prefix)` | 去掉 `prefix`,没有则 nil | +| `strip_suffix(suffix)` | 去掉 `suffix`,没有则 nil | +| `pad_left(width, fill)` | 左侧补到 `width` 个字符;`fill` 可省略 | +| `pad_right(width, fill)` | 右侧补到 `width` 个字符;`fill` 可省略 | +| `format(values...)` | receiver 是模板:每个 `{}` 取下一个值 | ```lk "Hello, {}!".format("LK") // "Hello, LK!" @@ -93,17 +105,25 @@ String 元方法,无需导入,直接通过 `value.method()` 调用。 | 函数 | 说明 | |------|------| -| `from_list(list)` | 从整数列表创建 | -| `from_string(str)` | 从 UTF-8 字符串创建 | +| `from_list(list)` | 从整数列表创建 —— 方法拼写是 `list.to_bytes()` | +| `from_string(str)` | 从 UTF-8 字符串创建 —— 方法拼写是 `str.bytes()` | | `len(bytes)` | 字节长度 | | `is_empty(bytes)` | 是否为空 | -| `get(bytes, index)` | 指定位置字节 | -| `slice(bytes, start[, end])` | 截取子段 | -| `to_list(bytes)` | 转整数列表 | -| `to_string_utf8(bytes)` | 转 UTF-8 字符串 | -| `to_string_lossy(bytes)` | 转 UTF-8(替换非法字节) | +| `get(bytes, index)` | 指定位置字节,越界 nil;负数从末尾数 | +| `first(bytes)` | 首字节,空则 nil | +| `last(bytes)` | 末字节,空则 nil | +| `contains(bytes, byte)` | 是否包含该字节 | +| `index_of(bytes, byte)` | 首次出现的位置,未找到返回 nil | +| `sum(bytes)` | 各字节之和 | +| `min(bytes)` | 最小字节,空则 nil | +| `max(bytes)` | 最大字节,空则 nil | +| `take(bytes, count)` | 前 `count` 个字节 | +| `skip(bytes, count)` | 跳过前 `count` 个字节 | +| `slice(bytes, start, end)` | `[start, end)` 区间的字节,越界截断;`end` 可省略 | +| `to_list(bytes)` | 转成整数列表 | +| `to_string_utf8(bytes)` | 按 UTF-8 解码,非法则报错 | +| `to_string_lossy(bytes)` | 按 UTF-8 解码,非法序列替换 | | `concat(a, b)` | 拼接 | -| `eq(a, b)` | 比较相等 | ```lk use bytes; @@ -477,18 +497,18 @@ hash.fnv64("hello") // FNV-64 哈希 | 函数 | 说明 | |------|------| -| `is_match(pattern, text)` | 是否匹配 | -| `find(pattern, text)` | 查找第一个 | -| `find_all(pattern, text)` | 查找所有 | -| `captures(pattern, text)` | 捕获分组 | -| `replace(pattern, text, replacement)` | 替换 | -| `split(pattern, text)` | 按正则拆分 | +| `is_match(text, pattern)` | 是否匹配 | +| `find(text, pattern)` | 查找第一个 | +| `find_all(text, pattern)` | 查找所有 | +| `captures(text, pattern)` | 捕获分组 | +| `replace(text, pattern, replacement)` | 替换 | +| `split(text, pattern)` | 按正则拆分 | ```lk use regex; -regex.is_match(r"\d+", "abc123") // true -regex.find(r"\d+", "abc123") // "123" -regex.split(r"[,;]", "a,b;c") // ["a", "b", "c"] +regex.is_match("abc123", r"\d+") // true +regex.find("abc123", r"\d+") // "123" +regex.split("a,b;c", r"[,;]") // ["a", "b", "c"] ``` ## random